From 81581f9719bc56f01d5aa08952671d65fda9867a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 8 May 2023 18:27:08 +0200 Subject: Merging upstream version 1.39.0. Signed-off-by: Daniel Baumann --- .flake8 | 2 + .gitattributes | 1 + .github/CODEOWNERS | 5 +- .github/ISSUE_TEMPLATE/BUG_REPORT.yml | 7 +- .github/data/distros.yml | 82 +- .github/scripts/ci-support-pkgs.sh | 17 +- .github/scripts/gen-matrix-build.py | 2 +- .github/scripts/gen-matrix-eol-check.py | 29 + .github/scripts/gen-matrix-packaging.py | 2 +- .github/scripts/gen-matrix-repoconfig.py | 2 +- .github/scripts/pkg-test.sh | 36 +- .github/scripts/platform-impending-eol.py | 58 + .github/scripts/run-updater-check.sh | 1 + .github/workflows/add-to-project.yml | 4 +- .github/workflows/build.yml | 16 +- .github/workflows/coverity.yml | 2 +- .github/workflows/dashboard-pr.yml | 2 +- .github/workflows/docker.yml | 17 +- .github/workflows/labeler.yml | 2 +- .github/workflows/packaging.yml | 35 +- .github/workflows/platform-eol-check.yml | 153 + .github/workflows/release.yml | 8 +- .github/workflows/repoconfig-packages.yml | 47 +- .github/workflows/review.yml | 85 +- .github/workflows/tests.yml | 2 +- .gitignore | 29 +- .gitmodules | 4 - .shellcheckrc | 3 + CHANGELOG.md | 604 +-- CMakeLists.txt | 57 +- Makefile.am | 92 +- README.md | 4 +- aclk/README.md | 34 +- aclk/aclk.c | 31 +- aclk/aclk.h | 4 +- aclk/aclk_alarm_api.c | 10 +- aclk/aclk_alarm_api.h | 2 +- aclk/aclk_capas.c | 42 +- aclk/aclk_query.c | 251 +- aclk/aclk_query.h | 2 + aclk/aclk_query_queue.c | 2 +- aclk/aclk_query_queue.h | 2 +- aclk/aclk_rx_msgs.c | 55 +- aclk/aclk_util.c | 8 +- aclk/aclk_util.h | 2 +- aclk/schema-wrappers/agent_cmds.cc | 38 + aclk/schema-wrappers/agent_cmds.h | 27 + aclk/schema-wrappers/alarm_stream.cc | 80 +- aclk/schema-wrappers/alarm_stream.h | 47 +- aclk/schema-wrappers/proto_2_json.cc | 11 +- aclk/schema-wrappers/schema_wrappers.h | 1 + build/m4/ax_compiler_vendor.m4 | 119 + build/m4/ax_cxx_compile_stdcxx.m4 | 1018 +++++ build_external/README.md | 5 +- .../scenarios/children-to-localhost/README.md | 10 + .../children-to-localhost/child_stream.conf | 10 + .../children-to-localhost/docker-compose.yml | 9 + .../children-to-localhost/parent_stream.conf | 7 + claim/README.md | 205 +- cli/README.md | 16 +- cli/cli.c | 49 +- collectors/COLLECTORS.md | 94 +- collectors/README.md | 88 +- collectors/REFERENCE.md | 209 +- collectors/all.h | 14 + collectors/apps.plugin/README.md | 7 +- collectors/apps.plugin/apps_groups.conf | 1 + collectors/apps.plugin/apps_plugin.c | 578 ++- collectors/apps.plugin/metrics.csv | 81 + collectors/cgroups.plugin/README.md | 56 +- collectors/cgroups.plugin/cgroup-name.sh | 55 +- collectors/cgroups.plugin/metrics.csv | 109 + collectors/cgroups.plugin/sys_fs_cgroup.c | 270 +- collectors/cgroups.plugin/sys_fs_cgroup.h | 2 +- .../cgroups.plugin/tests/test_cgroups_plugin.c | 8 +- collectors/charts.d.plugin/README.md | 64 +- collectors/charts.d.plugin/ap/README.md | 4 +- collectors/charts.d.plugin/ap/metrics.csv | 7 + collectors/charts.d.plugin/apcupsd/README.md | 4 +- collectors/charts.d.plugin/apcupsd/metrics.csv | 11 + collectors/charts.d.plugin/charts.d.plugin.in | 1 + collectors/charts.d.plugin/example/README.md | 2 +- collectors/charts.d.plugin/libreswan/README.md | 4 +- collectors/charts.d.plugin/libreswan/metrics.csv | 3 + collectors/charts.d.plugin/nut/README.md | 4 +- collectors/charts.d.plugin/nut/metrics.csv | 12 + collectors/charts.d.plugin/opensips/README.md | 4 +- collectors/charts.d.plugin/opensips/metrics.csv | 20 + collectors/charts.d.plugin/sensors/README.md | 36 +- collectors/charts.d.plugin/sensors/metrics.csv | 8 + .../charts.d.plugin/sensors/sensors.chart.sh | 2 +- collectors/cups.plugin/README.md | 4 +- collectors/cups.plugin/cups_plugin.c | 8 +- collectors/cups.plugin/metrics.csv | 7 + collectors/diskspace.plugin/README.md | 42 +- collectors/diskspace.plugin/metrics.csv | 3 + collectors/diskspace.plugin/plugin_diskspace.c | 27 +- collectors/ebpf.plugin/README.md | 4 +- collectors/ebpf.plugin/ebpf.c | 430 +- collectors/ebpf.plugin/ebpf.d.conf | 2 +- collectors/ebpf.plugin/ebpf.h | 40 +- collectors/ebpf.plugin/ebpf_apps.c | 510 ++- collectors/ebpf.plugin/ebpf_apps.h | 345 +- collectors/ebpf.plugin/ebpf_cachestat.c | 54 +- collectors/ebpf.plugin/ebpf_cachestat.h | 4 + collectors/ebpf.plugin/ebpf_cgroup.c | 145 +- collectors/ebpf.plugin/ebpf_cgroup.h | 2 + collectors/ebpf.plugin/ebpf_dcstat.c | 52 +- collectors/ebpf.plugin/ebpf_dcstat.h | 4 + collectors/ebpf.plugin/ebpf_disk.c | 9 +- collectors/ebpf.plugin/ebpf_fd.c | 77 +- collectors/ebpf.plugin/ebpf_fd.h | 5 +- collectors/ebpf.plugin/ebpf_filesystem.c | 21 +- collectors/ebpf.plugin/ebpf_hardirq.c | 195 +- collectors/ebpf.plugin/ebpf_hardirq.h | 12 +- collectors/ebpf.plugin/ebpf_mdflush.c | 10 +- collectors/ebpf.plugin/ebpf_mount.c | 33 +- collectors/ebpf.plugin/ebpf_oomkill.c | 44 +- collectors/ebpf.plugin/ebpf_process.c | 237 +- collectors/ebpf.plugin/ebpf_process.h | 11 - collectors/ebpf.plugin/ebpf_shm.c | 57 +- collectors/ebpf.plugin/ebpf_shm.h | 6 +- collectors/ebpf.plugin/ebpf_socket.c | 103 +- collectors/ebpf.plugin/ebpf_socket.h | 4 +- collectors/ebpf.plugin/ebpf_softirq.c | 7 +- collectors/ebpf.plugin/ebpf_swap.c | 32 +- collectors/ebpf.plugin/ebpf_swap.h | 2 - collectors/ebpf.plugin/ebpf_sync.c | 7 +- collectors/ebpf.plugin/ebpf_vfs.c | 82 +- collectors/ebpf.plugin/ebpf_vfs.h | 6 +- collectors/ebpf.plugin/metrics.csv | 197 + collectors/freebsd.plugin/README.md | 4 +- collectors/freebsd.plugin/freebsd_devstat.c | 8 +- collectors/freebsd.plugin/freebsd_getifaddrs.c | 16 +- collectors/freebsd.plugin/freebsd_getmntinfo.c | 18 +- collectors/freebsd.plugin/freebsd_sysctl.c | 2 +- collectors/freebsd.plugin/metrics.csv | 112 + collectors/freeipmi.plugin/README.md | 2 +- collectors/freeipmi.plugin/metrics.csv | 10 + collectors/idlejitter.plugin/README.md | 2 +- collectors/idlejitter.plugin/metrics.csv | 2 + collectors/ioping.plugin/README.md | 16 +- collectors/ioping.plugin/metrics.csv | 2 + collectors/macos.plugin/README.md | 2 +- collectors/macos.plugin/metrics.csv | 51 + collectors/nfacct.plugin/README.md | 4 +- collectors/nfacct.plugin/metrics.csv | 8 + collectors/perf.plugin/README.md | 4 +- collectors/perf.plugin/metrics.csv | 18 + collectors/plugins.d/README.md | 89 +- collectors/plugins.d/plugins_d.c | 93 +- collectors/plugins.d/plugins_d.h | 15 +- collectors/plugins.d/pluginsd_parser.c | 994 ++++- collectors/plugins.d/pluginsd_parser.h | 33 +- collectors/proc.plugin/README.md | 17 +- collectors/proc.plugin/ipc.c | 8 +- collectors/proc.plugin/metrics.csv | 271 ++ collectors/proc.plugin/proc_diskstats.c | 46 +- collectors/proc.plugin/proc_interrupts.c | 2 +- collectors/proc.plugin/proc_loadavg.c | 2 +- collectors/proc.plugin/proc_mdstat.c | 6 +- collectors/proc.plugin/proc_net_dev.c | 4 +- collectors/proc.plugin/proc_net_rpc_nfs.c | 20 +- collectors/proc.plugin/proc_net_rpc_nfsd.c | 38 +- collectors/proc.plugin/proc_pagetypeinfo.c | 10 +- collectors/proc.plugin/proc_pressure.c | 15 +- collectors/proc.plugin/proc_softirqs.c | 2 +- collectors/proc.plugin/proc_spl_kstat_zfs.c | 11 +- collectors/proc.plugin/proc_stat.c | 38 +- .../proc_sys_kernel_random_entropy_avail.c | 2 +- collectors/proc.plugin/proc_vmstat.c | 503 ++- collectors/proc.plugin/sys_block_zram.c | 32 +- collectors/proc.plugin/sys_class_infiniband.c | 9 +- collectors/proc.plugin/sys_class_power_supply.c | 4 +- .../proc.plugin/sys_devices_system_edac_mc.c | 4 +- collectors/proc.plugin/sys_devices_system_node.c | 2 +- collectors/proc.plugin/sys_fs_btrfs.c | 542 ++- collectors/proc.plugin/sys_kernel_mm_ksm.c | 8 +- collectors/python.d.plugin/Makefile.am | 3 - collectors/python.d.plugin/README.md | 201 +- collectors/python.d.plugin/adaptec_raid/README.md | 26 +- .../python.d.plugin/adaptec_raid/metrics.csv | 5 + collectors/python.d.plugin/alarms/README.md | 30 +- collectors/python.d.plugin/alarms/metrics.csv | 3 + collectors/python.d.plugin/am2320/README.md | 22 +- collectors/python.d.plugin/am2320/metrics.csv | 3 + collectors/python.d.plugin/anomalies/README.md | 9 +- .../python.d.plugin/anomalies/anomalies.chart.py | 3 +- collectors/python.d.plugin/anomalies/metrics.csv | 3 + collectors/python.d.plugin/beanstalk/README.md | 26 +- collectors/python.d.plugin/beanstalk/metrics.csv | 15 + collectors/python.d.plugin/bind_rndc/README.md | 26 +- collectors/python.d.plugin/bind_rndc/metrics.csv | 5 + collectors/python.d.plugin/boinc/README.md | 26 +- collectors/python.d.plugin/boinc/metrics.csv | 5 + collectors/python.d.plugin/ceph/README.md | 26 +- collectors/python.d.plugin/ceph/ceph.chart.py | 8 +- collectors/python.d.plugin/ceph/metrics.csv | 16 + collectors/python.d.plugin/changefinder/README.md | 26 +- .../changefinder/changefinder.chart.py | 4 +- .../python.d.plugin/changefinder/metrics.csv | 3 + collectors/python.d.plugin/dovecot/README.md | 26 +- collectors/python.d.plugin/dovecot/metrics.csv | 13 + collectors/python.d.plugin/example/README.md | 24 +- collectors/python.d.plugin/exim/README.md | 26 +- collectors/python.d.plugin/exim/metrics.csv | 2 + collectors/python.d.plugin/fail2ban/README.md | 26 +- collectors/python.d.plugin/fail2ban/metrics.csv | 4 + collectors/python.d.plugin/gearman/README.md | 24 +- collectors/python.d.plugin/gearman/metrics.csv | 3 + collectors/python.d.plugin/go_expvar/README.md | 24 +- collectors/python.d.plugin/go_expvar/metrics.csv | 8 + collectors/python.d.plugin/haproxy/README.md | 26 +- collectors/python.d.plugin/haproxy/metrics.csv | 31 + collectors/python.d.plugin/hddtemp/README.md | 26 +- collectors/python.d.plugin/hddtemp/metrics.csv | 2 + collectors/python.d.plugin/hpssa/README.md | 24 +- collectors/python.d.plugin/hpssa/metrics.csv | 6 + collectors/python.d.plugin/icecast/README.md | 26 +- collectors/python.d.plugin/icecast/metrics.csv | 2 + collectors/python.d.plugin/ipfs/README.md | 28 +- collectors/python.d.plugin/ipfs/metrics.csv | 5 + collectors/python.d.plugin/litespeed/README.md | 26 +- collectors/python.d.plugin/litespeed/metrics.csv | 10 + collectors/python.d.plugin/megacli/README.md | 24 +- collectors/python.d.plugin/megacli/metrics.csv | 6 + collectors/python.d.plugin/memcached/README.md | 26 +- .../python.d.plugin/memcached/memcached.chart.py | 20 +- collectors/python.d.plugin/memcached/metrics.csv | 15 + collectors/python.d.plugin/monit/README.md | 26 +- collectors/python.d.plugin/monit/metrics.csv | 13 + collectors/python.d.plugin/monit/monit.chart.py | 2 +- collectors/python.d.plugin/nsd/README.md | 26 +- collectors/python.d.plugin/nsd/metrics.csv | 7 + collectors/python.d.plugin/ntpd/Makefile.inc | 13 - collectors/python.d.plugin/ntpd/README.md | 14 - collectors/python.d.plugin/ntpd/ntpd.chart.py | 387 -- collectors/python.d.plugin/ntpd/ntpd.conf | 89 - collectors/python.d.plugin/nvidia_smi/README.md | 98 +- collectors/python.d.plugin/nvidia_smi/metrics.csv | 16 + collectors/python.d.plugin/openldap/README.md | 26 +- collectors/python.d.plugin/openldap/metrics.csv | 8 + collectors/python.d.plugin/oracledb/README.md | 24 +- collectors/python.d.plugin/oracledb/metrics.csv | 23 + collectors/python.d.plugin/pandas/README.md | 26 +- collectors/python.d.plugin/pandas/pandas.chart.py | 12 +- collectors/python.d.plugin/pandas/pandas.conf | 24 +- collectors/python.d.plugin/postfix/README.md | 24 +- collectors/python.d.plugin/postfix/metrics.csv | 3 + collectors/python.d.plugin/proxysql/Makefile.inc | 13 - collectors/python.d.plugin/proxysql/README.md | 14 - .../python.d.plugin/proxysql/proxysql.chart.py | 354 -- collectors/python.d.plugin/proxysql/proxysql.conf | 116 - collectors/python.d.plugin/puppet/README.md | 26 +- collectors/python.d.plugin/puppet/metrics.csv | 5 + collectors/python.d.plugin/python.d.conf | 3 - collectors/python.d.plugin/rabbitmq/Makefile.inc | 13 - collectors/python.d.plugin/rabbitmq/README.md | 141 - .../python.d.plugin/rabbitmq/rabbitmq.chart.py | 443 -- collectors/python.d.plugin/rabbitmq/rabbitmq.conf | 86 - collectors/python.d.plugin/rethinkdbs/README.md | 26 +- collectors/python.d.plugin/rethinkdbs/metrics.csv | 9 + collectors/python.d.plugin/retroshare/README.md | 26 +- collectors/python.d.plugin/retroshare/metrics.csv | 4 + collectors/python.d.plugin/riakkv/README.md | 24 +- collectors/python.d.plugin/riakkv/metrics.csv | 26 + collectors/python.d.plugin/samba/README.md | 26 +- collectors/python.d.plugin/samba/metrics.csv | 8 + collectors/python.d.plugin/sensors/README.md | 33 +- collectors/python.d.plugin/sensors/metrics.csv | 8 + collectors/python.d.plugin/smartd_log/README.md | 26 +- collectors/python.d.plugin/smartd_log/metrics.csv | 36 + collectors/python.d.plugin/spigotmc/README.md | 26 +- collectors/python.d.plugin/spigotmc/metrics.csv | 4 + collectors/python.d.plugin/squid/README.md | 26 +- collectors/python.d.plugin/squid/metrics.csv | 5 + collectors/python.d.plugin/tomcat/README.md | 26 +- collectors/python.d.plugin/tomcat/metrics.csv | 9 + collectors/python.d.plugin/tor/README.md | 26 +- collectors/python.d.plugin/tor/metrics.csv | 2 + collectors/python.d.plugin/traefik/README.md | 26 +- collectors/python.d.plugin/traefik/metrics.csv | 9 + collectors/python.d.plugin/uwsgi/README.md | 24 +- collectors/python.d.plugin/uwsgi/metrics.csv | 9 + collectors/python.d.plugin/varnish/README.md | 26 +- collectors/python.d.plugin/varnish/metrics.csv | 18 + collectors/python.d.plugin/w1sensor/README.md | 25 +- collectors/python.d.plugin/w1sensor/metrics.csv | 2 + collectors/python.d.plugin/zscores/README.md | 36 +- collectors/python.d.plugin/zscores/metrics.csv | 3 + collectors/slabinfo.plugin/README.md | 2 +- collectors/slabinfo.plugin/metrics.csv | 4 + collectors/slabinfo.plugin/slabinfo.c | 26 +- collectors/statsd.plugin/README.md | 379 +- collectors/statsd.plugin/asterisk.md | 5 +- collectors/statsd.plugin/k6.md | 5 +- collectors/statsd.plugin/statsd.c | 12 +- collectors/tc.plugin/README.md | 2 +- collectors/tc.plugin/metrics.csv | 6 + collectors/tc.plugin/plugin_tc.c | 50 +- collectors/timex.plugin/README.md | 2 +- collectors/timex.plugin/metrics.csv | 4 + collectors/xenstat.plugin/README.md | 2 +- collectors/xenstat.plugin/metrics.csv | 16 + configure.ac | 167 +- contrib/README.md | 4 + contrib/debian/conffiles | 4 + contrib/debian/control | 1 + contrib/debian/rules | 11 +- contribution-guidelines.md | 817 ---- coverity-scan.sh | 3 +- daemon/README.md | 112 +- daemon/analytics.c | 102 +- daemon/analytics.h | 16 +- daemon/anonymous-statistics.sh.in | 5 +- daemon/commands.c | 38 +- daemon/commands.h | 1 + daemon/common.h | 3 +- daemon/config/README.md | 12 +- daemon/event_loop.c | 1 + daemon/event_loop.h | 1 + daemon/global_statistics.c | 28 +- daemon/main.c | 148 +- daemon/main.h | 19 +- daemon/service.c | 2 +- daemon/system-info.sh | 26 +- daemon/unit_test.c | 87 +- database/Makefile.am | 1 + database/README.md | 42 +- database/contexts/Makefile.am | 11 + database/contexts/README.md | 0 database/contexts/api_v1.c | 439 ++ database/contexts/api_v2.c | 566 +++ database/contexts/context.c | 286 ++ database/contexts/instance.c | 524 +++ database/contexts/internal.h | 380 ++ database/contexts/metric.c | 319 ++ database/contexts/query_scope.c | 126 + database/contexts/query_target.c | 1219 ++++++ database/contexts/rrdcontext.c | 324 ++ database/contexts/rrdcontext.h | 553 +++ database/contexts/worker.c | 1094 +++++ database/engine/README.md | 305 +- database/engine/cache.c | 15 +- database/engine/datafile.c | 15 +- database/engine/datafile.h | 1 - database/engine/journalfile.c | 27 +- database/engine/journalfile.h | 13 +- database/engine/journalfile.ksy | 144 - database/engine/journalfile_v2.ksy.in | 150 + database/engine/metric.c | 62 +- database/engine/pagecache.c | 40 +- database/engine/pagecache.h | 4 +- database/engine/pdc.c | 17 +- database/engine/rrdengine.c | 202 +- database/engine/rrdengine.h | 19 +- database/engine/rrdengineapi.c | 267 +- database/engine/rrdengineapi.h | 8 +- database/engine/rrdenginelib.c | 66 - database/ram/README.md | 6 +- database/ram/rrddim_mem.c | 6 +- database/ram/rrddim_mem.h | 4 +- database/rrd.h | 432 +- database/rrdcalc.c | 22 +- database/rrdcalc.h | 4 +- database/rrdcalctemplate.c | 15 +- database/rrdcalctemplate.h | 2 +- database/rrdcontext.c | 3993 ----------------- database/rrdcontext.h | 273 -- database/rrddim.c | 16 +- database/rrdfunctions.c | 35 +- database/rrdfunctions.h | 4 +- database/rrdhost.c | 232 +- database/rrdlabels.c | 83 +- database/rrdset.c | 108 +- database/rrdvar.c | 68 + database/rrdvar.h | 6 + database/sqlite/sqlite_aclk.c | 908 ++-- database/sqlite/sqlite_aclk.h | 125 +- database/sqlite/sqlite_aclk_alert.c | 719 ++-- database/sqlite/sqlite_aclk_alert.h | 26 +- database/sqlite/sqlite_aclk_node.c | 100 +- database/sqlite/sqlite_aclk_node.h | 3 +- database/sqlite/sqlite_context.c | 24 +- database/sqlite/sqlite_db_migration.c | 4 +- database/sqlite/sqlite_functions.c | 92 +- database/sqlite/sqlite_functions.h | 3 +- database/sqlite/sqlite_health.c | 163 +- database/sqlite/sqlite_metadata.c | 549 ++- database/sqlite/sqlite_metadata.h | 2 + database/storage_engine.c | 54 +- diagrams/ephemeral-nodes-two-parents.xml | 133 + diagrams/simple-parent-child-no-cloud.xml | 125 + diagrams/simple-parent-child.xml | 132 + diagrams/windows.xml | 207 + docs/Add-more-charts-to-netdata.md | 13 - docs/Demo-Sites.md | 11 +- docs/Donations-netdata-has-received.md | 29 - docs/README.md | 17 - docs/Running-behind-apache.md | 19 +- docs/Running-behind-caddy.md | 2 +- docs/Running-behind-h2o.md | 2 +- docs/Running-behind-haproxy.md | 2 +- docs/Running-behind-lighttpd.md | 2 +- docs/Running-behind-nginx.md | 33 +- docs/a-github-star-is-important.md | 24 - docs/agent-cloud.md | 78 - docs/anonymous-statistics.md | 12 +- .../deployment-strategies.md | 66 + .../installation-overview.md | 10 + .../integrations-overview.md | 31 + docs/category-overview-pages/misc-overview.md | 19 + docs/category-overview-pages/reverse-proxies.md | 34 + docs/category-overview-pages/secure-nodes.md | 177 + .../troubleshooting-overview.md | 5 + .../visualizations-overview.md | 4 + .../add-discord-notification.md | 25 +- .../add-opsgenie-notification-configuration.md | 37 + .../add-pagerduty-notification-configuration.md | 31 +- .../add-slack-notification-configuration.md | 28 +- .../add-webhook-notification-configuration.md | 187 +- .../manage-notification-methods.md | 34 +- docs/cloud/alerts-notifications/notifications.md | 121 + docs/cloud/alerts-notifications/notifications.mdx | 155 - docs/cloud/alerts-notifications/smartboard.mdx | 46 - .../alerts-notifications/view-active-alerts.mdx | 76 - docs/cloud/beta-architecture/new-architecture.md | 36 - docs/cloud/cheatsheet.md | 215 + docs/cloud/cheatsheet.mdx | 231 - docs/cloud/cloud.mdx | 74 - docs/cloud/data-privacy.mdx | 39 - docs/cloud/get-started.mdx | 133 - docs/cloud/insights/anomaly-advisor.md | 87 + docs/cloud/insights/anomaly-advisor.mdx | 86 - docs/cloud/insights/events-feed.md | 79 + docs/cloud/insights/metric-correlations.md | 16 +- docs/cloud/manage/invite-your-team.md | 33 +- docs/cloud/manage/plans.md | 120 + docs/cloud/manage/role-based-access.md | 136 + docs/cloud/manage/sign-in.md | 81 + docs/cloud/manage/sign-in.mdx | 88 - docs/cloud/manage/themes.md | 10 +- docs/cloud/manage/view-plan-billing.md | 92 + docs/cloud/netdata-functions.md | 2 + .../runtime-troubleshooting-with-functions.md | 15 +- docs/cloud/spaces.md | 24 +- docs/cloud/visualize/dashboards.md | 21 +- docs/cloud/visualize/interact-new-charts.md | 243 +- docs/cloud/visualize/kubernetes.md | 28 +- docs/cloud/visualize/node-filter.md | 21 + docs/cloud/visualize/nodes.md | 30 +- docs/cloud/visualize/overview.md | 246 +- docs/cloud/war-rooms.md | 168 +- docs/collect/application-metrics.md | 5 +- docs/collect/container-metrics.md | 3 +- docs/collect/enable-configure.md | 72 - docs/collect/how-collectors-work.md | 82 - docs/collect/system-metrics.md | 13 +- docs/configure/common-changes.md | 100 +- docs/configure/nodes.md | 73 +- docs/configure/secure-nodes.md | 127 - docs/configure/start-stop-restart.md | 104 +- docs/contributing/contributing-documentation.md | 109 - docs/contributing/style-guide.md | 128 +- docs/dashboard/customize.md | 72 + docs/dashboard/customize.mdx | 99 - docs/dashboard/dimensions-contexts-families.md | 69 + docs/dashboard/dimensions-contexts-families.mdx | 102 - docs/dashboard/how-dashboard-works.mdx | 118 - docs/dashboard/import-export-print-snapshot.md | 74 + docs/dashboard/import-export-print-snapshot.mdx | 90 - docs/dashboard/interact-charts.mdx | 201 - docs/dashboard/reference-web-server.mdx | 278 -- .../visualization-date-and-time-controls.md | 92 + .../visualization-date-and-time-controls.mdx | 125 - docs/export/enable-connector.md | 6 +- docs/export/external-databases.md | 18 +- docs/get-started.mdx | 129 - docs/getting-started/integrations.md | 12 - docs/getting-started/introduction.md | 113 +- docs/glossary.md | 180 + docs/guidelines.md | 773 +--- docs/guides/collect-apache-nginx-web-logs.md | 25 +- docs/guides/collect-unbound-metrics.md | 4 + docs/guides/configure/performance.md | 269 +- docs/guides/deploy/ansible.md | 180 - .../export/export-netdata-metrics-graphite.md | 181 - docs/guides/longer-metrics-storage.md | 158 - docs/guides/monitor-cockroachdb.md | 33 +- docs/guides/monitor-hadoop-cluster.md | 23 +- docs/guides/monitor/anomaly-detection-python.md | 189 - docs/guides/monitor/anomaly-detection.md | 19 +- docs/guides/monitor/dimension-templates.md | 181 - docs/guides/monitor/kubernetes-k8s-netdata.md | 16 +- docs/guides/monitor/lamp-stack.md | 24 +- docs/guides/monitor/pi-hole-raspberry-pi.md | 36 +- docs/guides/monitor/process.md | 50 +- .../monitor/raspberry-pi-anomaly-detection.md | 37 +- docs/guides/monitor/statsd.md | 298 -- docs/guides/monitor/stop-notifications-alarms.md | 92 - docs/guides/monitor/visualize-monitor-anomalies.md | 142 - docs/guides/python-collector.md | 234 +- docs/guides/step-by-step/step-00.md | 120 - docs/guides/step-by-step/step-01.md | 156 - docs/guides/step-by-step/step-02.md | 208 - docs/guides/step-by-step/step-03.md | 94 - docs/guides/step-by-step/step-04.md | 144 - docs/guides/step-by-step/step-05.md | 349 -- docs/guides/step-by-step/step-06.md | 122 - docs/guides/step-by-step/step-07.md | 114 - docs/guides/step-by-step/step-08.md | 395 -- docs/guides/step-by-step/step-09.md | 162 - docs/guides/step-by-step/step-10.md | 232 - docs/guides/step-by-step/step-99.md | 51 - .../monitor-debug-applications-ebpf.md | 28 +- .../troubleshooting-agent-with-cloud-connection.md | 40 +- docs/guides/using-host-labels.md | 151 +- .../metrics-storage-management/enable-streaming.md | 228 + .../enable-streaming.mdx | 158 - .../how-streaming-works.mdx | 99 - .../reference-streaming.mdx | 490 --- docs/monitor/configure-alarms.md | 152 - docs/monitor/enable-notifications.md | 92 +- docs/monitor/view-active-alarms.md | 77 +- docs/netdata-for-IoT.md | 3 + docs/netdata-security.md | 293 +- docs/overview/netdata-monitoring-stack.md | 62 - docs/overview/what-is-netdata.md | 76 - docs/overview/why-netdata.md | 63 - docs/quickstart/infrastructure.md | 169 +- docs/quickstart/single-node.md | 92 - docs/store/change-metrics-storage.md | 247 +- docs/store/distributed-data-architecture.md | 27 +- docs/visualize/create-dashboards.md | 69 - docs/visualize/interact-dashboards-charts.md | 131 - docs/visualize/overview-infrastructure.md | 36 +- docs/why-netdata/1s-granularity.md | 59 - docs/why-netdata/README.md | 35 - docs/why-netdata/immediate-results.md | 46 - docs/why-netdata/meaningful-presentation.md | 68 - docs/why-netdata/unlimited-metrics.md | 49 - exporting/README.md | 7 +- exporting/TIMESCALE.md | 4 +- exporting/WALKTHROUGH.md | 10 - exporting/aws_kinesis/README.md | 4 +- exporting/check_filters.c | 2 +- exporting/graphite/README.md | 119 +- exporting/json/README.md | 3 +- exporting/mongodb/README.md | 4 +- exporting/opentsdb/README.md | 4 +- exporting/process_data.c | 10 +- exporting/prometheus/README.md | 323 +- exporting/prometheus/prometheus.c | 6 +- exporting/prometheus/remote_write/README.md | 5 +- exporting/pubsub/README.md | 4 +- exporting/read_config.c | 14 +- health/Makefile.am | 4 +- health/README.md | 34 +- health/REFERENCE.md | 550 ++- health/health.c | 145 +- health/health.d/btrfs.conf | 75 + health/health.d/docker.conf | 11 + health/health.d/dockerd.conf | 11 - health/health.d/windows.conf | 139 + health/health.d/wmi.conf | 139 - health/health.h | 1 + health/health_config.c | 94 +- health/health_json.c | 24 +- health/health_log.c | 2 + health/notifications/Makefile.am | 1 + health/notifications/README.md | 215 +- health/notifications/alarm-notify.sh.in | 77 +- health/notifications/alerta/README.md | 118 +- health/notifications/awssns/README.md | 165 +- health/notifications/custom/README.md | 208 +- health/notifications/discord/README.md | 86 +- health/notifications/dynatrace/README.md | 99 +- health/notifications/email/README.md | 139 +- health/notifications/flock/README.md | 74 +- health/notifications/gotify/README.md | 83 +- health/notifications/hangouts/README.md | 6 +- health/notifications/health_alarm_notify.conf | 25 + health/notifications/irc/README.md | 127 +- health/notifications/kavenegar/README.md | 86 +- health/notifications/matrix/README.md | 94 +- health/notifications/messagebird/README.md | 79 +- health/notifications/msteams/README.md | 77 +- health/notifications/ntfy/Makefile.inc | 12 + health/notifications/ntfy/README.md | 66 + health/notifications/opsgenie/README.md | 91 +- health/notifications/pagerduty/README.md | 90 +- health/notifications/prowl/README.md | 74 +- health/notifications/pushbullet/README.md | 91 +- health/notifications/pushover/README.md | 75 +- health/notifications/rocketchat/README.md | 85 +- health/notifications/slack/README.md | 77 +- health/notifications/smstools3/README.md | 88 +- health/notifications/stackpulse/README.md | 6 +- health/notifications/syslog/README.md | 80 +- health/notifications/telegram/README.md | 87 +- health/notifications/twilio/README.md | 84 +- health/notifications/web/README.md | 8 +- libnetdata/Makefile.am | 1 + libnetdata/adaptive_resortable_list/README.md | 2 +- .../adaptive_resortable_list.c | 2 +- libnetdata/aral/README.md | 2 +- libnetdata/aral/aral.c | 16 +- libnetdata/avl/README.md | 2 +- libnetdata/buffer/README.md | 2 +- libnetdata/buffer/buffer.c | 466 +- libnetdata/buffer/buffer.h | 926 +++- libnetdata/circular_buffer/README.md | 2 +- libnetdata/circular_buffer/circular_buffer.c | 5 +- libnetdata/clocks/README.md | 9 +- libnetdata/config/README.md | 2 +- libnetdata/config/appconfig.c | 17 +- libnetdata/config/appconfig.h | 1 + libnetdata/dictionary/README.md | 2 +- libnetdata/dictionary/dictionary.c | 282 +- libnetdata/dictionary/dictionary.h | 12 +- libnetdata/ebpf/README.md | 4 +- libnetdata/ebpf/ebpf.c | 99 +- libnetdata/ebpf/ebpf.h | 32 +- libnetdata/health/health.c | 10 +- libnetdata/inlined.h | 460 +- libnetdata/json/README.md | 2 +- libnetdata/july/README.md | 2 +- libnetdata/libjudy/src/JudyL/JudyLTablesGen.c | 11 +- libnetdata/libnetdata.c | 336 +- libnetdata/libnetdata.h | 244 +- libnetdata/locks/README.md | 4 +- libnetdata/log/README.md | 4 +- libnetdata/log/log.c | 25 +- libnetdata/onewayalloc/README.md | 4 +- libnetdata/onewayalloc/onewayalloc.c | 9 + libnetdata/parser/Makefile.am | 9 + libnetdata/parser/README.md | 28 + libnetdata/parser/parser.c | 225 + libnetdata/parser/parser.h | 101 + libnetdata/popen/README.md | 2 +- libnetdata/procfile/README.md | 2 +- libnetdata/simple_pattern/README.md | 2 +- libnetdata/simple_pattern/simple_pattern.c | 146 +- libnetdata/simple_pattern/simple_pattern.h | 26 +- libnetdata/socket/README.md | 7 +- libnetdata/socket/socket.c | 79 +- libnetdata/socket/socket.h | 6 + libnetdata/statistical/README.md | 2 +- libnetdata/storage_number/README.md | 2 +- libnetdata/storage_number/storage_number.c | 142 +- libnetdata/storage_number/storage_number.h | 90 +- libnetdata/string/README.md | 2 +- libnetdata/string/string.c | 8 +- libnetdata/string/string.h | 1 + libnetdata/string/utf8.h | 4 +- libnetdata/threads/README.md | 2 +- libnetdata/threads/threads.c | 74 +- libnetdata/threads/threads.h | 2 + libnetdata/url/README.md | 2 +- libnetdata/url/url.c | 180 +- libnetdata/url/url.h | 8 +- libnetdata/worker_utilization/README.md | 6 +- libnetdata/worker_utilization/worker_utilization.c | 16 +- ml/ADCharts.cc | 518 --- ml/ADCharts.h | 21 - ml/Chart.cc | 0 ml/Chart.h | 128 - ml/Config.cc | 123 +- ml/Config.h | 52 - ml/Dimension.cc | 346 -- ml/Dimension.h | 198 - ml/Host.cc | 387 -- ml/Host.h | 70 - ml/KMeans.cc | 43 - ml/KMeans.h | 41 - ml/Mutex.h | 36 - ml/Query.h | 57 - ml/Queue.h | 66 - ml/README.md | 13 +- ml/SamplesBuffer.cc | 183 - ml/SamplesBuffer.h | 149 - ml/Stats.h | 46 - ml/ad_charts.cc | 475 ++ ml/ad_charts.h | 14 + ml/ml-dummy.c | 95 +- ml/ml-private.h | 336 +- ml/ml.cc | 1657 ++++++- ml/ml.h | 42 +- netdata-installer.sh | 103 +- netdata.spec.in | 158 +- packaging/PLATFORM_SUPPORT.md | 34 +- packaging/building-native-packages-locally.md | 8 +- packaging/check-kernel-config.sh | 7 +- packaging/docker/Dockerfile | 9 +- packaging/docker/README.md | 209 +- packaging/docker/run.sh | 15 + packaging/ebpf-co-re.checksums | 2 +- packaging/ebpf-co-re.version | 2 +- packaging/ebpf.checksums | 6 +- packaging/ebpf.version | 2 +- packaging/go.d.checksums | 34 +- packaging/go.d.version | 2 +- packaging/installer/README.md | 212 +- packaging/installer/REINSTALL.md | 12 +- packaging/installer/UNINSTALL.md | 21 +- packaging/installer/UPDATE.md | 12 +- packaging/installer/dependencies/alpine.sh | 1 + packaging/installer/dependencies/arch.sh | 1 + packaging/installer/dependencies/centos.sh | 3 +- packaging/installer/dependencies/clearlinux.sh | 1 + packaging/installer/dependencies/debian.sh | 1 + packaging/installer/dependencies/fedora.sh | 1 + packaging/installer/dependencies/freebsd.sh | 1 + packaging/installer/dependencies/gentoo.sh | 1 + packaging/installer/dependencies/ol.sh | 1 + packaging/installer/dependencies/opensuse.sh | 1 + packaging/installer/dependencies/rockylinux.sh | 1 + packaging/installer/dependencies/ubuntu.sh | 1 + packaging/installer/functions.sh | 108 +- packaging/installer/install-required-packages.sh | 27 +- packaging/installer/kickstart.sh | 730 ++-- packaging/installer/methods/ansible.md | 156 + packaging/installer/methods/aws.md | 67 + packaging/installer/methods/azure.md | 68 + packaging/installer/methods/cloud-providers.md | 126 - packaging/installer/methods/freebsd.md | 113 +- packaging/installer/methods/freenas.md | 24 - packaging/installer/methods/gcp.md | 70 + packaging/installer/methods/kickstart.md | 101 +- packaging/installer/methods/kubernetes.md | 193 +- packaging/installer/methods/macos.md | 21 +- packaging/installer/methods/manual.md | 19 +- packaging/installer/methods/methods.md | 26 + packaging/installer/methods/offline.md | 19 +- packaging/installer/methods/packages.md | 10 +- packaging/installer/methods/pfsense.md | 3 + packaging/installer/methods/source.md | 6 +- packaging/installer/methods/synology.md | 11 +- packaging/installer/methods/systems.md | 18 + packaging/installer/netdata-uninstaller.sh | 12 + packaging/installer/netdata-updater.sh | 84 +- packaging/maintainers/README.md | 8 +- packaging/makeself/README.md | 24 +- packaging/makeself/functions.sh | 7 +- packaging/makeself/install-or-update.sh | 29 +- .../jobs/50-libnetfilter_acct-1.0.3.install.sh | 39 + packaging/makeself/jobs/70-netdata-git.install.sh | 9 +- packaging/makeself/jobs/99-makeself.install.sh | 13 +- packaging/makeself/makeself-header.sh | 269 +- packaging/makeself/makeself-license.txt | 13 +- packaging/makeself/makeself.lsm | 2 +- packaging/makeself/makeself.sh | 489 ++- packaging/repoconfig/netdata-edge.repo.al | 21 + packaging/repoconfig/netdata-repo.spec | 8 + packaging/repoconfig/netdata.repo.al | 21 + packaging/version | 2 +- packaging/yaml.checksums | 1 + packaging/yaml.version | 1 + parser/Makefile.am | 9 - parser/README.md | 152 - parser/parser.c | 391 -- parser/parser.h | 123 - registry/README.md | 3 +- registry/registry.c | 149 +- streaming/README.md | 656 ++- streaming/receiver.c | 26 +- streaming/replication.c | 340 +- streaming/rrdpush.c | 142 +- streaming/rrdpush.h | 33 +- streaming/sender.c | 31 +- system/Makefile.am | 131 +- system/cron/netdata-updater-daily.in | 1 + system/edit-config | 16 +- system/freebsd/rc.d/netdata.in | 53 + system/initd/init.d/netdata.in | 95 + system/install-service.sh.in | 107 +- system/launchd/netdata.plist.in | 15 + system/logrotate/netdata.in | 12 + system/lsb/init.d/netdata.in | 116 + system/netdata-freebsd.in | 53 - system/netdata-init-d.in | 95 - system/netdata-lsb.in | 116 - system/netdata-openrc.in | 88 - system/netdata-updater.service.in | 8 - system/netdata-updater.timer | 12 - system/netdata.conf | 6 +- system/netdata.crontab.in | 1 - system/netdata.logrotate.in | 12 - system/netdata.plist.in | 15 - system/netdata.service.in | 80 - system/netdata.service.v235.in | 42 - system/openrc/conf.d/netdata.in | 24 + system/openrc/init.d/netdata.in | 80 + system/runit/run.in | 16 + system/systemd/50-netdata.preset | 1 + system/systemd/netdata-updater.service.in | 8 + system/systemd/netdata-updater.timer | 12 + system/systemd/netdata.service.in | 80 + system/systemd/netdata.service.v235.in | 42 + system/vnodes/vnodes.conf | 23 + tests/health_mgmtapi/README.md | 4 + web/Makefile.am | 1 + web/README.md | 192 +- web/api/README.md | 20 +- web/api/badges/README.md | 4 + web/api/badges/web_buffer_svg.c | 8 +- web/api/exporters/README.md | 4 + web/api/exporters/allmetrics.c | 14 +- web/api/exporters/prometheus/README.md | 4 + web/api/exporters/shell/README.md | 8 +- web/api/exporters/shell/allmetrics_shell.c | 8 +- web/api/formatters/README.md | 4 + web/api/formatters/charts2json.c | 52 - web/api/formatters/charts2json.h | 1 - web/api/formatters/csv/README.md | 4 + web/api/formatters/csv/csv.c | 61 +- web/api/formatters/json/README.md | 4 + web/api/formatters/json/json.c | 170 +- web/api/formatters/json/json.h | 1 + web/api/formatters/json_wrapper.c | 1806 ++++++-- web/api/formatters/json_wrapper.h | 14 +- web/api/formatters/rrd2json.c | 231 +- web/api/formatters/rrd2json.h | 53 +- web/api/formatters/rrdset2json.c | 8 +- web/api/formatters/ssv/README.md | 4 + web/api/formatters/ssv/ssv.c | 10 +- web/api/formatters/value/README.md | 4 + web/api/formatters/value/value.c | 86 +- web/api/formatters/value/value.h | 4 +- web/api/health/README.md | 4 + web/api/health/health_cmdapi.c | 8 +- web/api/netdata-swagger.json | 4519 ++++++++++++-------- web/api/netdata-swagger.yaml | 3221 ++++++++------ web/api/queries/README.md | 41 +- web/api/queries/average/README.md | 4 + web/api/queries/average/average.c | 55 - web/api/queries/average/average.h | 57 +- web/api/queries/countif/README.md | 4 + web/api/queries/countif/countif.c | 129 - web/api/queries/countif/countif.h | 143 +- web/api/queries/des/README.md | 4 + web/api/queries/des/des.c | 129 - web/api/queries/des/des.h | 133 +- web/api/queries/incremental_sum/README.md | 4 + web/api/queries/incremental_sum/incremental_sum.c | 59 - web/api/queries/incremental_sum/incremental_sum.h | 64 +- web/api/queries/max/README.md | 4 + web/api/queries/max/max.c | 50 - web/api/queries/max/max.h | 54 +- web/api/queries/median/README.md | 4 + web/api/queries/median/median.c | 134 - web/api/queries/median/median.h | 146 +- web/api/queries/min/README.md | 4 + web/api/queries/min/min.c | 50 - web/api/queries/min/min.h | 54 +- web/api/queries/percentile/README.md | 4 + web/api/queries/percentile/percentile.c | 163 - web/api/queries/percentile/percentile.h | 175 +- web/api/queries/query.c | 2676 +++++++++--- web/api/queries/query.h | 54 +- web/api/queries/rrdr.c | 77 +- web/api/queries/rrdr.h | 189 +- web/api/queries/ses/README.md | 4 + web/api/queries/ses/ses.c | 82 - web/api/queries/ses/ses.h | 87 +- web/api/queries/stddev/README.md | 4 + web/api/queries/stddev/stddev.c | 116 +- web/api/queries/stddev/stddev.h | 118 +- web/api/queries/sum/README.md | 4 + web/api/queries/sum/sum.c | 46 - web/api/queries/sum/sum.h | 51 +- web/api/queries/trimmed_mean/README.md | 4 + web/api/queries/trimmed_mean/trimmed_mean.c | 159 - web/api/queries/trimmed_mean/trimmed_mean.h | 171 +- web/api/queries/weights.c | 1549 +++++-- web/api/queries/weights.h | 45 +- web/api/web_api.c | 216 + web/api/web_api.h | 39 + web/api/web_api_v1.c | 764 ++-- web/api/web_api_v1.h | 17 +- web/api/web_api_v2.c | 372 ++ web/api/web_api_v2.h | 12 + web/gui/README.md | 18 +- web/gui/confluence/README.md | 4 + web/gui/custom/README.md | 25 +- web/gui/dashboard/Makefile.am | 14 +- web/gui/dashboard/asset-manifest.json | 18 +- web/gui/dashboard/dashboard-react.js | 4 +- web/gui/dashboard/dashboard.js | 2 +- web/gui/dashboard/index.html | 2 +- ...he-manifest.21dcd7c609bff6504512face054c360f.js | 190 + ...he-manifest.5fec6109084644adf7bf854243e1a044.js | 190 - web/gui/dashboard/service-worker.js | 2 +- web/gui/dashboard/static/js/2.92ca8446.chunk.js | 3 - .../static/js/2.92ca8446.chunk.js.LICENSE | 278 -- .../dashboard/static/js/2.92ca8446.chunk.js.map | 1 - web/gui/dashboard/static/js/2.ae48988e.chunk.js | 3 + .../static/js/2.ae48988e.chunk.js.LICENSE | 278 ++ .../dashboard/static/js/2.ae48988e.chunk.js.map | 1 + web/gui/dashboard/static/js/main.76dfe4de.chunk.js | 3 + .../static/js/main.76dfe4de.chunk.js.LICENSE | 8 + .../dashboard/static/js/main.76dfe4de.chunk.js.map | 1 + web/gui/dashboard/static/js/main.7d1bdca1.chunk.js | 3 - .../static/js/main.7d1bdca1.chunk.js.LICENSE | 8 - .../dashboard/static/js/main.7d1bdca1.chunk.js.map | 1 - web/gui/dashboard_info.js | 188 +- web/rtc/Makefile.am | 11 + web/rtc/README.md | 0 web/rtc/webrtc.c | 740 ++++ web/rtc/webrtc.h | 12 + web/server/README.md | 104 +- web/server/static/README.md | 4 + web/server/static/static-threaded.c | 132 +- web/server/web_client.c | 815 ++-- web/server/web_client.h | 172 +- web/server/web_client_cache.c | 309 +- web/server/web_client_cache.h | 20 +- web/server/web_server.c | 25 - web/server/web_server.h | 1 - 919 files changed, 50270 insertions(+), 40694 deletions(-) create mode 100644 .flake8 create mode 100755 .github/scripts/gen-matrix-eol-check.py create mode 100755 .github/scripts/platform-impending-eol.py create mode 100644 .github/workflows/platform-eol-check.yml create mode 100644 .shellcheckrc create mode 100644 aclk/schema-wrappers/agent_cmds.cc create mode 100644 aclk/schema-wrappers/agent_cmds.h create mode 100644 build/m4/ax_compiler_vendor.m4 create mode 100644 build/m4/ax_cxx_compile_stdcxx.m4 create mode 100644 build_external/scenarios/children-to-localhost/README.md create mode 100644 build_external/scenarios/children-to-localhost/child_stream.conf create mode 100644 build_external/scenarios/children-to-localhost/docker-compose.yml create mode 100644 build_external/scenarios/children-to-localhost/parent_stream.conf create mode 100644 collectors/apps.plugin/metrics.csv create mode 100644 collectors/cgroups.plugin/metrics.csv create mode 100644 collectors/charts.d.plugin/ap/metrics.csv create mode 100644 collectors/charts.d.plugin/apcupsd/metrics.csv create mode 100644 collectors/charts.d.plugin/libreswan/metrics.csv create mode 100644 collectors/charts.d.plugin/nut/metrics.csv create mode 100644 collectors/charts.d.plugin/opensips/metrics.csv create mode 100644 collectors/charts.d.plugin/sensors/metrics.csv create mode 100644 collectors/cups.plugin/metrics.csv create mode 100644 collectors/diskspace.plugin/metrics.csv create mode 100644 collectors/ebpf.plugin/metrics.csv create mode 100644 collectors/freebsd.plugin/metrics.csv create mode 100644 collectors/freeipmi.plugin/metrics.csv create mode 100644 collectors/idlejitter.plugin/metrics.csv create mode 100644 collectors/ioping.plugin/metrics.csv create mode 100644 collectors/macos.plugin/metrics.csv create mode 100644 collectors/nfacct.plugin/metrics.csv create mode 100644 collectors/perf.plugin/metrics.csv create mode 100644 collectors/proc.plugin/metrics.csv create mode 100644 collectors/python.d.plugin/adaptec_raid/metrics.csv create mode 100644 collectors/python.d.plugin/alarms/metrics.csv create mode 100644 collectors/python.d.plugin/am2320/metrics.csv create mode 100644 collectors/python.d.plugin/anomalies/metrics.csv create mode 100644 collectors/python.d.plugin/beanstalk/metrics.csv create mode 100644 collectors/python.d.plugin/bind_rndc/metrics.csv create mode 100644 collectors/python.d.plugin/boinc/metrics.csv create mode 100644 collectors/python.d.plugin/ceph/metrics.csv create mode 100644 collectors/python.d.plugin/changefinder/metrics.csv create mode 100644 collectors/python.d.plugin/dovecot/metrics.csv create mode 100644 collectors/python.d.plugin/exim/metrics.csv create mode 100644 collectors/python.d.plugin/fail2ban/metrics.csv create mode 100644 collectors/python.d.plugin/gearman/metrics.csv create mode 100644 collectors/python.d.plugin/go_expvar/metrics.csv create mode 100644 collectors/python.d.plugin/haproxy/metrics.csv create mode 100644 collectors/python.d.plugin/hddtemp/metrics.csv create mode 100644 collectors/python.d.plugin/hpssa/metrics.csv create mode 100644 collectors/python.d.plugin/icecast/metrics.csv create mode 100644 collectors/python.d.plugin/ipfs/metrics.csv create mode 100644 collectors/python.d.plugin/litespeed/metrics.csv create mode 100644 collectors/python.d.plugin/megacli/metrics.csv create mode 100644 collectors/python.d.plugin/memcached/metrics.csv create mode 100644 collectors/python.d.plugin/monit/metrics.csv create mode 100644 collectors/python.d.plugin/nsd/metrics.csv delete mode 100644 collectors/python.d.plugin/ntpd/Makefile.inc delete mode 100644 collectors/python.d.plugin/ntpd/README.md delete mode 100644 collectors/python.d.plugin/ntpd/ntpd.chart.py delete mode 100644 collectors/python.d.plugin/ntpd/ntpd.conf create mode 100644 collectors/python.d.plugin/nvidia_smi/metrics.csv create mode 100644 collectors/python.d.plugin/openldap/metrics.csv create mode 100644 collectors/python.d.plugin/oracledb/metrics.csv create mode 100644 collectors/python.d.plugin/postfix/metrics.csv delete mode 100644 collectors/python.d.plugin/proxysql/Makefile.inc delete mode 100644 collectors/python.d.plugin/proxysql/README.md delete mode 100644 collectors/python.d.plugin/proxysql/proxysql.chart.py delete mode 100644 collectors/python.d.plugin/proxysql/proxysql.conf create mode 100644 collectors/python.d.plugin/puppet/metrics.csv delete mode 100644 collectors/python.d.plugin/rabbitmq/Makefile.inc delete mode 100644 collectors/python.d.plugin/rabbitmq/README.md delete mode 100644 collectors/python.d.plugin/rabbitmq/rabbitmq.chart.py delete mode 100644 collectors/python.d.plugin/rabbitmq/rabbitmq.conf create mode 100644 collectors/python.d.plugin/rethinkdbs/metrics.csv create mode 100644 collectors/python.d.plugin/retroshare/metrics.csv create mode 100644 collectors/python.d.plugin/riakkv/metrics.csv create mode 100644 collectors/python.d.plugin/samba/metrics.csv create mode 100644 collectors/python.d.plugin/sensors/metrics.csv create mode 100644 collectors/python.d.plugin/smartd_log/metrics.csv create mode 100644 collectors/python.d.plugin/spigotmc/metrics.csv create mode 100644 collectors/python.d.plugin/squid/metrics.csv create mode 100644 collectors/python.d.plugin/tomcat/metrics.csv create mode 100644 collectors/python.d.plugin/tor/metrics.csv create mode 100644 collectors/python.d.plugin/traefik/metrics.csv create mode 100644 collectors/python.d.plugin/uwsgi/metrics.csv create mode 100644 collectors/python.d.plugin/varnish/metrics.csv create mode 100644 collectors/python.d.plugin/w1sensor/metrics.csv create mode 100644 collectors/python.d.plugin/zscores/metrics.csv create mode 100644 collectors/slabinfo.plugin/metrics.csv create mode 100644 collectors/tc.plugin/metrics.csv create mode 100644 collectors/timex.plugin/metrics.csv create mode 100644 collectors/xenstat.plugin/metrics.csv create mode 100644 contrib/debian/conffiles delete mode 100644 contribution-guidelines.md create mode 100644 database/contexts/Makefile.am create mode 100644 database/contexts/README.md create mode 100644 database/contexts/api_v1.c create mode 100644 database/contexts/api_v2.c create mode 100644 database/contexts/context.c create mode 100644 database/contexts/instance.c create mode 100644 database/contexts/internal.h create mode 100644 database/contexts/metric.c create mode 100644 database/contexts/query_scope.c create mode 100644 database/contexts/query_target.c create mode 100644 database/contexts/rrdcontext.c create mode 100644 database/contexts/rrdcontext.h create mode 100644 database/contexts/worker.c delete mode 100644 database/engine/journalfile.ksy create mode 100644 database/engine/journalfile_v2.ksy.in delete mode 100644 database/rrdcontext.c delete mode 100644 database/rrdcontext.h create mode 100644 diagrams/ephemeral-nodes-two-parents.xml create mode 100644 diagrams/simple-parent-child-no-cloud.xml create mode 100644 diagrams/simple-parent-child.xml create mode 100644 diagrams/windows.xml delete mode 100644 docs/Add-more-charts-to-netdata.md delete mode 100644 docs/Donations-netdata-has-received.md delete mode 100644 docs/README.md delete mode 100644 docs/a-github-star-is-important.md delete mode 100644 docs/agent-cloud.md create mode 100644 docs/category-overview-pages/deployment-strategies.md create mode 100644 docs/category-overview-pages/installation-overview.md create mode 100644 docs/category-overview-pages/integrations-overview.md create mode 100644 docs/category-overview-pages/misc-overview.md create mode 100644 docs/category-overview-pages/reverse-proxies.md create mode 100644 docs/category-overview-pages/secure-nodes.md create mode 100644 docs/category-overview-pages/troubleshooting-overview.md create mode 100644 docs/category-overview-pages/visualizations-overview.md create mode 100644 docs/cloud/alerts-notifications/add-opsgenie-notification-configuration.md create mode 100644 docs/cloud/alerts-notifications/notifications.md delete mode 100644 docs/cloud/alerts-notifications/notifications.mdx delete mode 100644 docs/cloud/alerts-notifications/smartboard.mdx delete mode 100644 docs/cloud/alerts-notifications/view-active-alerts.mdx delete mode 100644 docs/cloud/beta-architecture/new-architecture.md create mode 100644 docs/cloud/cheatsheet.md delete mode 100644 docs/cloud/cheatsheet.mdx delete mode 100644 docs/cloud/cloud.mdx delete mode 100644 docs/cloud/data-privacy.mdx delete mode 100644 docs/cloud/get-started.mdx create mode 100644 docs/cloud/insights/anomaly-advisor.md delete mode 100644 docs/cloud/insights/anomaly-advisor.mdx create mode 100644 docs/cloud/insights/events-feed.md create mode 100644 docs/cloud/manage/plans.md create mode 100644 docs/cloud/manage/role-based-access.md create mode 100644 docs/cloud/manage/sign-in.md delete mode 100644 docs/cloud/manage/sign-in.mdx create mode 100644 docs/cloud/manage/view-plan-billing.md create mode 100644 docs/cloud/visualize/node-filter.md delete mode 100644 docs/collect/enable-configure.md delete mode 100644 docs/collect/how-collectors-work.md delete mode 100644 docs/configure/secure-nodes.md delete mode 100644 docs/contributing/contributing-documentation.md create mode 100644 docs/dashboard/customize.md delete mode 100644 docs/dashboard/customize.mdx create mode 100644 docs/dashboard/dimensions-contexts-families.md delete mode 100644 docs/dashboard/dimensions-contexts-families.mdx delete mode 100644 docs/dashboard/how-dashboard-works.mdx create mode 100644 docs/dashboard/import-export-print-snapshot.md delete mode 100644 docs/dashboard/import-export-print-snapshot.mdx delete mode 100644 docs/dashboard/interact-charts.mdx delete mode 100644 docs/dashboard/reference-web-server.mdx create mode 100644 docs/dashboard/visualization-date-and-time-controls.md delete mode 100644 docs/dashboard/visualization-date-and-time-controls.mdx delete mode 100644 docs/get-started.mdx delete mode 100644 docs/getting-started/integrations.md create mode 100644 docs/glossary.md delete mode 100644 docs/guides/deploy/ansible.md delete mode 100644 docs/guides/export/export-netdata-metrics-graphite.md delete mode 100644 docs/guides/longer-metrics-storage.md delete mode 100644 docs/guides/monitor/anomaly-detection-python.md delete mode 100644 docs/guides/monitor/dimension-templates.md delete mode 100644 docs/guides/monitor/statsd.md delete mode 100644 docs/guides/monitor/stop-notifications-alarms.md delete mode 100644 docs/guides/monitor/visualize-monitor-anomalies.md delete mode 100644 docs/guides/step-by-step/step-00.md delete mode 100644 docs/guides/step-by-step/step-01.md delete mode 100644 docs/guides/step-by-step/step-02.md delete mode 100644 docs/guides/step-by-step/step-03.md delete mode 100644 docs/guides/step-by-step/step-04.md delete mode 100644 docs/guides/step-by-step/step-05.md delete mode 100644 docs/guides/step-by-step/step-06.md delete mode 100644 docs/guides/step-by-step/step-07.md delete mode 100644 docs/guides/step-by-step/step-08.md delete mode 100644 docs/guides/step-by-step/step-09.md delete mode 100644 docs/guides/step-by-step/step-10.md delete mode 100644 docs/guides/step-by-step/step-99.md create mode 100644 docs/metrics-storage-management/enable-streaming.md delete mode 100644 docs/metrics-storage-management/enable-streaming.mdx delete mode 100644 docs/metrics-storage-management/how-streaming-works.mdx delete mode 100644 docs/metrics-storage-management/reference-streaming.mdx delete mode 100644 docs/monitor/configure-alarms.md delete mode 100644 docs/overview/netdata-monitoring-stack.md delete mode 100644 docs/overview/what-is-netdata.md delete mode 100644 docs/overview/why-netdata.md delete mode 100644 docs/quickstart/single-node.md delete mode 100644 docs/visualize/create-dashboards.md delete mode 100644 docs/visualize/interact-dashboards-charts.md delete mode 100644 docs/why-netdata/1s-granularity.md delete mode 100644 docs/why-netdata/README.md delete mode 100644 docs/why-netdata/immediate-results.md delete mode 100644 docs/why-netdata/meaningful-presentation.md delete mode 100644 docs/why-netdata/unlimited-metrics.md create mode 100644 health/health.d/docker.conf delete mode 100644 health/health.d/dockerd.conf create mode 100644 health/health.d/windows.conf delete mode 100644 health/health.d/wmi.conf create mode 100644 health/notifications/ntfy/Makefile.inc create mode 100644 health/notifications/ntfy/README.md create mode 100644 libnetdata/parser/Makefile.am create mode 100644 libnetdata/parser/README.md create mode 100644 libnetdata/parser/parser.c create mode 100644 libnetdata/parser/parser.h delete mode 100644 ml/ADCharts.cc delete mode 100644 ml/ADCharts.h delete mode 100644 ml/Chart.cc delete mode 100644 ml/Chart.h delete mode 100644 ml/Config.h delete mode 100644 ml/Dimension.cc delete mode 100644 ml/Dimension.h delete mode 100644 ml/Host.cc delete mode 100644 ml/Host.h delete mode 100644 ml/KMeans.cc delete mode 100644 ml/KMeans.h delete mode 100644 ml/Mutex.h delete mode 100644 ml/Query.h delete mode 100644 ml/Queue.h delete mode 100644 ml/SamplesBuffer.cc delete mode 100644 ml/SamplesBuffer.h delete mode 100644 ml/Stats.h create mode 100644 ml/ad_charts.cc create mode 100644 ml/ad_charts.h create mode 100644 packaging/installer/methods/ansible.md create mode 100644 packaging/installer/methods/aws.md create mode 100644 packaging/installer/methods/azure.md delete mode 100644 packaging/installer/methods/cloud-providers.md delete mode 100644 packaging/installer/methods/freenas.md create mode 100644 packaging/installer/methods/gcp.md create mode 100644 packaging/installer/methods/methods.md create mode 100644 packaging/installer/methods/systems.md create mode 100755 packaging/makeself/jobs/50-libnetfilter_acct-1.0.3.install.sh create mode 100644 packaging/repoconfig/netdata-edge.repo.al create mode 100644 packaging/repoconfig/netdata.repo.al create mode 100644 packaging/yaml.checksums create mode 100644 packaging/yaml.version delete mode 100644 parser/Makefile.am delete mode 100644 parser/README.md delete mode 100644 parser/parser.c delete mode 100644 parser/parser.h create mode 100644 system/cron/netdata-updater-daily.in create mode 100644 system/freebsd/rc.d/netdata.in create mode 100644 system/initd/init.d/netdata.in create mode 100644 system/launchd/netdata.plist.in create mode 100644 system/logrotate/netdata.in create mode 100644 system/lsb/init.d/netdata.in delete mode 100644 system/netdata-freebsd.in delete mode 100644 system/netdata-init-d.in delete mode 100644 system/netdata-lsb.in delete mode 100644 system/netdata-openrc.in delete mode 100644 system/netdata-updater.service.in delete mode 100644 system/netdata-updater.timer delete mode 100644 system/netdata.crontab.in delete mode 100644 system/netdata.logrotate.in delete mode 100644 system/netdata.plist.in delete mode 100644 system/netdata.service.in delete mode 100644 system/netdata.service.v235.in create mode 100644 system/openrc/conf.d/netdata.in create mode 100644 system/openrc/init.d/netdata.in create mode 100644 system/runit/run.in create mode 100644 system/systemd/50-netdata.preset create mode 100644 system/systemd/netdata-updater.service.in create mode 100644 system/systemd/netdata-updater.timer create mode 100644 system/systemd/netdata.service.in create mode 100644 system/systemd/netdata.service.v235.in create mode 100644 system/vnodes/vnodes.conf create mode 100644 web/api/web_api.c create mode 100644 web/api/web_api.h create mode 100644 web/api/web_api_v2.c create mode 100644 web/api/web_api_v2.h create mode 100644 web/gui/dashboard/precache-manifest.21dcd7c609bff6504512face054c360f.js delete mode 100644 web/gui/dashboard/precache-manifest.5fec6109084644adf7bf854243e1a044.js delete mode 100644 web/gui/dashboard/static/js/2.92ca8446.chunk.js delete mode 100644 web/gui/dashboard/static/js/2.92ca8446.chunk.js.LICENSE delete mode 100644 web/gui/dashboard/static/js/2.92ca8446.chunk.js.map create mode 100644 web/gui/dashboard/static/js/2.ae48988e.chunk.js create mode 100644 web/gui/dashboard/static/js/2.ae48988e.chunk.js.LICENSE create mode 100644 web/gui/dashboard/static/js/2.ae48988e.chunk.js.map create mode 100644 web/gui/dashboard/static/js/main.76dfe4de.chunk.js create mode 100644 web/gui/dashboard/static/js/main.76dfe4de.chunk.js.LICENSE create mode 100644 web/gui/dashboard/static/js/main.76dfe4de.chunk.js.map delete mode 100644 web/gui/dashboard/static/js/main.7d1bdca1.chunk.js delete mode 100644 web/gui/dashboard/static/js/main.7d1bdca1.chunk.js.LICENSE delete mode 100644 web/gui/dashboard/static/js/main.7d1bdca1.chunk.js.map create mode 100644 web/rtc/Makefile.am create mode 100644 web/rtc/README.md create mode 100644 web/rtc/webrtc.c create mode 100644 web/rtc/webrtc.h diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..aa079ec5 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length=120 diff --git a/.gitattributes b/.gitattributes index 45ec5156..e594b59f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ *.c diff=cpp *.h diff=cpp +*.ksy.in linguist-language=ksy diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 34c93455..7857b9a7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -19,7 +19,7 @@ collectors/cups.plugin/ @thiagoftsm exporting/ @thiagoftsm daemon/ @thiagoftsm @vkalintiris database/ @thiagoftsm @vkalintiris -docs/ @tkatsoulas +docs/ @tkatsoulas @andrewm4894 @cakrit health/ @thiagoftsm @vkalintiris @MrZammler health/health.d/ @thiagoftsm @MrZammler health/notifications/ @Ferroin @thiagoftsm @MrZammler @@ -35,7 +35,8 @@ web/gui/ @jacekkolasa # Ownership by filetype (overwrites ownership by directory) *.am @Ferroin @tkatsoulas -*.md @tkatsoulas +*.md @tkatsoulas @andrewm4894 @cakrit +*.mdx @tkatsoulas @andrewm4894 @cakrit Dockerfile* @Ferroin @tkatsoulas # Ownership of specific files diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml index d82d4633..b63daba8 100644 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml @@ -51,12 +51,11 @@ body: Describe the method in the "Additional info" section if you chose "other". options: - "kickstart.sh" - - "kickstart-static64.sh" - - "native binary packages (.deb/.rpm)" - - "from git" - - "from source" - "docker" - "helmchart (kubernetes)" + - "manual setup of official DEB/RPM packages" + - "from git" + - "from source" - "other" validations: required: true diff --git a/.github/data/distros.yml b/.github/data/distros.yml index 452170c0..bfe8b761 100644 --- a/.github/data/distros.yml +++ b/.github/data/distros.yml @@ -1,6 +1,6 @@ # This defines the full set of distros we run CI on. --- -platform_map: # map packaging architectures to docker platforms +platform_map: # map packaging architectures to docker platforms aarch64: linux/arm64/v8 amd64: linux/amd64 arm64: linux/arm64/v8 @@ -8,7 +8,7 @@ platform_map: # map packaging architectures to docker platforms armhfp: linux/arm/v7 i386: linux/i386 x86_64: linux/amd64 -arch_order: # sort order for per-architecture jobs in CI +arch_order: # sort order for per-architecture jobs in CI - amd64 - x86_64 - i386 @@ -20,6 +20,7 @@ include: - &alpine distro: alpine version: edge + eol_check: false env_prep: | apk add -U bash jsonc_removal: | @@ -28,15 +29,17 @@ include: ebpf-core: true - <<: *alpine version: "3.17" + eol_check: true - <<: *alpine version: "3.16" + eol_check: true - <<: *alpine version: "3.15" - - <<: *alpine - version: "3.14" + eol_check: true - distro: archlinux version: latest + eol_check: false env_prep: | pacman --noconfirm -Syu && pacman --noconfirm -Sy grep libffi test: @@ -45,9 +48,9 @@ include: - &alma distro: almalinux version: "9" - base_image: almalinux jsonc_removal: | dnf remove -y json-c-devel + eol_check: true packages: &alma_packages type: rpm repo_distro: el/9 @@ -68,8 +71,28 @@ include: - el/8Server - el/8Client + - &amzn + distro: amazonlinux + version: "2" + eol_check: 'amazon-linux' + packages: &amzn_packages + type: rpm + repo_distro: amazonlinux/2 + arches: + - x86_64 + - aarch64 + test: + ebpf-core: false + - <<: *amzn + version: "2023" + packages: + <<: *amzn_packages + repo_distro: amazonlinux/2023 + + - distro: centos version: "7" + eol_check: false packages: type: rpm repo_distro: el/7 @@ -83,14 +106,16 @@ include: - &debian distro: debian - version: "11" + version: "12" + base_image: debian:bookworm + eol_check: true env_prep: | apt-get update jsonc_removal: | apt-get purge -y libjson-c-dev packages: &debian_packages type: deb - repo_distro: debian/bullseye + repo_distro: debian/bookworm arches: - i386 - amd64 @@ -98,8 +123,17 @@ include: - arm64 test: ebpf-core: true + - <<: *debian + version: "11" + base_image: debian:bullseye + packages: + <<: *debian_packages + repo_distro: debian/bullseye + test: + ebpf-core: false - <<: *debian version: "10" + base_image: debian:buster packages: <<: *debian_packages repo_distro: debian/buster @@ -108,17 +142,25 @@ include: - &fedora distro: fedora - version: "37" + version: "38" + eol_check: true jsonc_removal: | dnf remove -y json-c-devel packages: &fedora_packages type: rpm - repo_distro: fedora/37 + repo_distro: fedora/38 arches: - x86_64 - aarch64 test: ebpf-core: true + - <<: *fedora + version: "37" + packages: + <<: *fedora_packages + repo_distro: fedora/37 + test: + ebpf-core: true - <<: *fedora version: "36" packages: @@ -130,7 +172,8 @@ include: - &opensuse distro: opensuse version: "15.4" - base_image: opensuse/leap + eol_check: true + base_image: opensuse/leap:15.4 jsonc_removal: | zypper rm -y libjson-c-devel packages: &opensuse_packages @@ -145,6 +188,7 @@ include: - &oracle distro: oraclelinux version: "8" + eol_check: true jsonc_removal: | dnf remove -y json-c-devel packages: &oracle_packages @@ -164,6 +208,7 @@ include: - &ubuntu distro: ubuntu version: "22.10" + eol_check: true env_prep: | rm -f /etc/apt/apt.conf.d/docker && apt-get update jsonc_removal: | @@ -177,6 +222,11 @@ include: - arm64 test: ebpf-core: true + - <<: *ubuntu + version: "23.04" + packages: + <<: *ubuntu_packages + repo_distro: ubuntu/lunar - <<: *ubuntu version: "22.04" packages: @@ -187,15 +237,3 @@ include: packages: <<: *ubuntu_packages repo_distro: ubuntu/focal - - <<: *ubuntu - version: "18.04" - packages: - <<: *ubuntu_packages - repo_distro: ubuntu/bionic - arches: - - i386 - - amd64 - - armhf - - arm64 - test: - ebpf-core: false diff --git a/.github/scripts/ci-support-pkgs.sh b/.github/scripts/ci-support-pkgs.sh index bfa9c83a..9ba11b68 100755 --- a/.github/scripts/ci-support-pkgs.sh +++ b/.github/scripts/ci-support-pkgs.sh @@ -5,10 +5,13 @@ set -e -if [ -f /etc/centos-release ] || [ -f /etc/redhat-release ] || [ -f /etc/fedora-release ] || [ -f /etc/almalinux-release ]; then - # Alma, Fedora, CentOS, Redhat - dnf install -y procps-ng cronie cronie-anacron || yum install -y procps-ng cronie cronie-anacron -elif [ -f /etc/arch-release ]; then - # Arch - pacman -S --noconfirm cronie -fi +. /etc/os-release + +case "${ID}" in + amzn|almalinux|centos|fedora) + dnf install -y procps-ng cronie cronie-anacron || yum install -y procps-ng cronie cronie-anacron + ;; + arch) + pacman -S --noconfirm cronie + ;; +esac diff --git a/.github/scripts/gen-matrix-build.py b/.github/scripts/gen-matrix-build.py index 28406470..3185e883 100755 --- a/.github/scripts/gen-matrix-build.py +++ b/.github/scripts/gen-matrix-build.py @@ -17,7 +17,7 @@ for i, v in enumerate(data['include']): } if 'base_image' in v: - e['distro'] = ':'.join([v['base_image'], str(v['version'])]) + e['distro'] = v['base_image'] else: e['distro'] = ':'.join([v['distro'], str(v['version'])]) diff --git a/.github/scripts/gen-matrix-eol-check.py b/.github/scripts/gen-matrix-eol-check.py new file mode 100755 index 00000000..63852728 --- /dev/null +++ b/.github/scripts/gen-matrix-eol-check.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +'''Generate the build matrix for the EOL check jobs.''' + +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 item in data['include']: + if 'eol_check' in item and item['eol_check']: + if isinstance(item['eol_check'], str): + distro = item['eol_check'] + else: + distro = item['distro'] + + entries.append({ + 'distro': distro, + 'release': item['version'], + 'full_name': f'{ item["distro"] } { item["version"] }' + }) + +entries.sort(key=lambda k: (k['distro'], k['release'])) +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 index 01e9ec79..9347cd76 100755 --- a/.github/scripts/gen-matrix-packaging.py +++ b/.github/scripts/gen-matrix-packaging.py @@ -26,7 +26,7 @@ for i, v in enumerate(data['include']): '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'], + 'base_image': data['include'][i]['base_image'] if 'base_image' in data['include'][i] else ':'.join([data['include'][i]['distro'], data['include'][i]['version']]), 'platform': data['platform_map'][arch], 'arch': arch }) diff --git a/.github/scripts/gen-matrix-repoconfig.py b/.github/scripts/gen-matrix-repoconfig.py index 46f67169..264cd53e 100755 --- a/.github/scripts/gen-matrix-repoconfig.py +++ b/.github/scripts/gen-matrix-repoconfig.py @@ -17,7 +17,7 @@ for i, v in enumerate(data['include']): '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'], + 'base_image': data['include'][i]['base_image'] if 'base_image' in data['include'][i] else ':'.join([data['include'][i]['distro'], data['include'][i]['version']]), 'platform': data['platform_map']['amd64'], 'arches': ' '.join(['"' + x + '"' for x in data['include'][i]['packages']['arches']]) }) diff --git a/.github/scripts/pkg-test.sh b/.github/scripts/pkg-test.sh index e3bc3e7d..45b8c320 100755 --- a/.github/scripts/pkg-test.sh +++ b/.github/scripts/pkg-test.sh @@ -13,6 +13,7 @@ install_debian_like() { apt-get update # Install Netdata + # Strange quoting is required here so that glob matching works. apt-get install -y /netdata/artifacts/netdata_"${VERSION}"*_*.deb || exit 1 # Install testing tools @@ -27,8 +28,13 @@ install_fedora_like() { pkg_version="$(echo "${VERSION}" | tr - .)" + if [ "${PKGMGR}" = "dnf" ]; then + opts="--allowerasing" + fi + # Install Netdata - "$PKGMGR" install -y /netdata/artifacts/netdata-"${pkg_version}"-*.rpm + # Strange quoting is required here so that glob matching works. + "$PKGMGR" install -y /netdata/artifacts/netdata-"${pkg_version}"-*.rpm || exit 1 # Install testing tools "$PKGMGR" install -y curl nc jq || exit 1 @@ -50,9 +56,29 @@ install_centos() { "$PKGMGR" install -y epel-release || exit 1 # Install Netdata - "$PKGMGR" install -y /netdata/artifacts/netdata-"${pkg_version}"-*.rpm + # Strange quoting is required here so that glob matching works. + "$PKGMGR" install -y /netdata/artifacts/netdata-"${pkg_version}"-*.rpm || exit 1 # Install testing tools + # shellcheck disable=SC2086 + "$PKGMGR" install -y ${opts} curl nc jq || exit 1 +} + +install_amazon_linux() { + PKGMGR="$( (command -v dnf > /dev/null && echo "dnf") || echo "yum")" + + pkg_version="$(echo "${VERSION}" | tr - .)" + + if [ "${PKGMGR}" = "dnf" ]; then + opts="--allowerasing" + fi + + # Install Netdata + # Strange quoting is required here so that glob matching works. + "$PKGMGR" install -y /netdata/artifacts/netdata-"${pkg_version}"-*.rpm || exit 1 + + # Install testing tools + # shellcheck disable=SC2086 "$PKGMGR" install -y ${opts} curl nc jq || exit 1 } @@ -63,7 +89,8 @@ install_suse_like() { pkg_version="$(echo "${VERSION}" | tr - .)" # Install Netdata - zypper install -y --allow-unsigned-rpm /netdata/artifacts/netdata-"${pkg_version}"-*.rpm + # Strange quoting is required here so that glob matching works. + zypper install -y --allow-unsigned-rpm /netdata/artifacts/netdata-"${pkg_version}"-*.rpm || exit 1 # Install testing tools zypper install -y --no-recommends curl netcat-openbsd jq || exit 1 @@ -114,6 +141,9 @@ case "${DISTRO}" in centos | rockylinux | almalinux) install_centos ;; + amazonlinux) + install_amazon_linux + ;; opensuse) install_suse_like ;; diff --git a/.github/scripts/platform-impending-eol.py b/.github/scripts/platform-impending-eol.py new file mode 100755 index 00000000..c57e5edd --- /dev/null +++ b/.github/scripts/platform-impending-eol.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +'''Check if a given distro is going to be EOL soon. + + This queries the public API of https://endoflife.date to fetch EOL dates. + + ‘soon’ is defined by LEAD_DAYS, currently 30 days.''' + +import datetime +import json +import sys +import urllib.request + +URL_BASE = 'https://endoflife.date/api' +NOW = datetime.date.today() +LEAD_DAYS = datetime.timedelta(days=30) + +DISTRO = sys.argv[1] +RELEASE = sys.argv[2] + +EXIT_NOT_IMPENDING = 0 +EXIT_IMPENDING = 1 +EXIT_NO_DATA = 2 +EXIT_FAILURE = 3 + +try: + with urllib.request.urlopen(f'{ URL_BASE }/{ DISTRO }/{ RELEASE }.json') as response: + match response.status: + case 200: + data = json.load(response) + case _: + print( + f'Failed to retrieve data for { DISTRO } { RELEASE } ' + + f'(status: { response.status }).', + file=sys.stderr + ) + sys.exit(EXIT_FAILURE) +except urllib.error.HTTPError as e: + match e.code: + case 404: + print(f'No data available for { DISTRO } { RELEASE }.', file=sys.stderr) + sys.exit(EXIT_NO_DATA) + case _: + print( + f'Failed to retrieve data for { DISTRO } { RELEASE } ' + + f'(status: { e.code }).', + file=sys.stderr + ) + sys.exit(EXIT_FAILURE) + +eol = datetime.date.fromisoformat(data['eol']) + +offset = abs(eol - NOW) + +if offset <= LEAD_DAYS: + print(data['eol']) + sys.exit(EXIT_IMPENDING) +else: + sys.exit(EXIT_NOT_IMPENDING) diff --git a/.github/scripts/run-updater-check.sh b/.github/scripts/run-updater-check.sh index a96a1d6e..1224d8f6 100755 --- a/.github/scripts/run-updater-check.sh +++ b/.github/scripts/run-updater-check.sh @@ -2,6 +2,7 @@ echo ">>> Installing CI support packages..." /netdata/.github/scripts/ci-support-pkgs.sh +mkdir -p /etc/cron.daily # Needed to make auto-update checking work correctly on some platforms. echo ">>> Installing Netdata..." /netdata/packaging/installer/kickstart.sh --dont-wait --build-only --disable-telemetry || exit 1 echo "::group::>>> Pre-Update Environment File Contents" diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml index a80d8b41..986d836a 100644 --- a/.github/workflows/add-to-project.yml +++ b/.github/workflows/add-to-project.yml @@ -13,13 +13,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Add issues to Agent project board - uses: actions/add-to-project@v0.4.0 + uses: actions/add-to-project@v0.5.0 with: project-url: https://github.com/orgs/netdata/projects/32 github-token: ${{ secrets.NETDATABOT_ORG_GITHUB_TOKEN }} - name: Add issues to Product Bug project board - uses: actions/add-to-project@v0.4.0 + uses: actions/add-to-project@v0.5.0 with: project-url: https://github.com/orgs/netdata/projects/45 github-token: ${{ secrets.NETDATABOT_ORG_GITHUB_TOKEN }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c3924fb0..c349e4fd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -118,9 +118,11 @@ jobs: run: | sed -i 's/^RELEASE_CHANNEL="nightly" *#/RELEASE_CHANNEL="stable" #/' netdata-installer.sh packaging/makeself/install-or-update.sh - name: Get Cache Key + if: github.event_name != 'pull_request' || ! contains(github.event.pull_request.labels.*.name, 'run-ci/no-cache') id: cache-key - run: .github/scripts/get-static-cache-key.sh ${{ matrix.arch }} + run: .github/scripts/get-static-cache-key.sh ${{ matrix.arch }} "${{ contains(github.event.pull_request.labels.*.name, 'run-ci/no-cache') }}" - name: Cache + if: github.event_name != 'pull_request' || ! contains(github.event.pull_request.labels.*.name, 'run-ci/no-cache') id: cache uses: actions/cache@v3 with: @@ -135,7 +137,7 @@ jobs: uses: nick-fields/retry@v2 with: timeout_minutes: 180 - retries: 3 + max_attempts: 3 command: .github/scripts/build-static.sh ${{ matrix.arch }} - name: Store id: store @@ -232,7 +234,7 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Build test environment id: build1 - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 continue-on-error: true # We retry 3 times at 5 minute intervals if there is a failure here. with: push: false @@ -250,7 +252,7 @@ jobs: - name: Build test environment (attempt 2) if: ${{ steps.build1.outcome == 'failure' }} id: build2 - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 continue-on-error: true # We retry 3 times at 5 minute intervals if there is a failure here. with: push: false @@ -268,7 +270,7 @@ jobs: - name: Build test environment (attempt 3) if: ${{ steps.build1.outcome == 'failure' && steps.build2.outcome == 'failure' }} id: build3 - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: push: false load: false @@ -657,10 +659,10 @@ jobs: credentials_json: ${{ secrets.GCS_STORAGE_SERVICE_KEY_JSON }} - name: Setup GCS id: gcs-setup - uses: google-github-actions/setup-gcloud@v1.0.1 + uses: google-github-actions/setup-gcloud@v1.1.0 - name: Upload Artifacts id: upload - uses: google-github-actions/upload-cloud-storage@v1.0.0 + uses: google-github-actions/upload-cloud-storage@v1.0.1 with: destination: ${{ secrets.GCP_NIGHTLY_STORAGE_BUCKET }} gzip: false diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index 9d1119a8..8a1ee248 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -30,7 +30,7 @@ jobs: run: | ./packaging/installer/install-required-packages.sh \ --dont-wait --non-interactive netdata - sudo apt-get install -y libjson-c-dev libipmimonitoring-dev \ + sudo apt-get install -y libjson-c-dev libyaml-dev libipmimonitoring-dev \ libcups2-dev libsnappy-dev libprotobuf-dev \ libprotoc-dev libssl-dev protobuf-compiler \ libnetfilter-acct-dev diff --git a/.github/workflows/dashboard-pr.yml b/.github/workflows/dashboard-pr.yml index c99f9891..c7d14c48 100644 --- a/.github/workflows/dashboard-pr.yml +++ b/.github/workflows/dashboard-pr.yml @@ -28,7 +28,7 @@ jobs: web/gui/bundle_dashboard.py ${{ github.event.inputs.dashboard_version }} - name: Create Pull Request id: pr - uses: peter-evans/create-pull-request@v4 + uses: peter-evans/create-pull-request@v5 with: title: 'Update dashboard to version ${{ github.event.inputs.dashboard_version }}.' body: 'See https://github.com/netdata/dashboard/releases/tag/${{ github.event.inputs.dashboard_version }} for changes.' diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 78a39d5a..5eec3df7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -31,7 +31,7 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Test Build id: build - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: load: true push: false @@ -89,7 +89,7 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Build id: build - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: platforms: ${{ matrix.platforms }} load: false @@ -193,7 +193,7 @@ jobs: password: ${{ secrets.NETDATABOT_QUAY_TOKEN }} - name: Docker Build id: build - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: platforms: linux/amd64,linux/i386,linux/arm/v7,linux/arm64,linux/ppc64le push: ${{ github.repository == 'netdata/netdata' }} @@ -236,6 +236,15 @@ jobs: workflow: Agent Version PR ref: refs/heads/master inputs: '{"agent_version": "${{ needs.normalize-tag.outputs.tag }}"}' + - name: Trigger MSI build + if: github.event_name == 'workflow_dispatch' && github.event.inputs.version != 'nightly' && github.repository == 'netdata/netdata' + uses: benc-uk/workflow-dispatch@v1 + with: + token: ${{ secrets.NETDATABOT_GITHUB_TOKEN }} + repo: netdata/msi-installer + workflow: Build + ref: refs/heads/master + inputs: '{"tag": "${{ needs.normalize-tag.outputs.tag }}", "pwd": "${{ secrets.MSI_CODE_SIGNING_PASSWORD }}"}' docker-dbg-publish: if: github.event_name == 'workflow_dispatch' @@ -296,7 +305,7 @@ jobs: password: ${{ secrets.NETDATABOT_QUAY_TOKEN }} - name: Docker Build id: build - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: platforms: linux/amd64,linux/i386,linux/arm/v7,linux/arm64,linux/ppc64le push: ${{ github.repository == 'netdata/netdata' }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 2b8b41fc..a1e3b52f 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -4,7 +4,7 @@ name: Pull Request Labeler on: pull_request_target: null concurrency: - group: pr-label-${{ github.ref }} + group: pr-label-${{ github.repository_id }}-${{ github.event.pull_request.number }} cancel-in-progress: true jobs: labeler: diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml index c99f535a..a8d50284 100644 --- a/.github/workflows/packaging.yml +++ b/.github/workflows/packaging.yml @@ -170,7 +170,7 @@ jobs: retry_wait_seconds: 30 timeout_seconds: 900 command: | - docker pull --platform ${{ matrix.platform }} ${{ matrix.base_image }}:${{ matrix.version }} + docker pull --platform ${{ matrix.platform }} ${{ matrix.base_image }} docker pull --platform ${{ matrix.platform }} netdata/package-builders:${{ matrix.distro }}${{ matrix.version }} - name: Build Packages id: build @@ -191,12 +191,25 @@ jobs: run: | docker run --security-opt seccomp=unconfined -e DISABLE_TELEMETRY=1 -e DISTRO=${{ matrix.distro }} \ -e VERSION=${{ needs.version-check.outputs.version }} -e DISTRO_VERSION=${{ matrix.version }} \ - --platform=${{ matrix.platform }} -v "$PWD":/netdata ${{ matrix.base_image }}:${{ matrix.version }} \ + --platform=${{ matrix.platform }} -v "$PWD":/netdata ${{ matrix.base_image }} \ /netdata/.github/scripts/pkg-test.sh + - name: Upload to PackageCloud + id: upload + if: github.event_name == 'workflow_dispatch' && github.repository == 'netdata/netdata' + continue-on-error: true + shell: bash + env: + PKG_CLOUD_TOKEN: ${{ secrets.PACKAGE_CLOUD_API_KEY }} + run: | + printf "Packages to upload:\n%s" "$(ls artifacts/*.${{ matrix.format }})" + for pkgfile in artifacts/*.${{ matrix.format }} ; do + .github/scripts/package_cloud_wrapper.sh yank ${{ needs.version-check.outputs.repo }}/${{ matrix.repo_distro }} \ + "$(basename "${pkgfile}")" || true + .github/scripts/package_cloud_wrapper.sh push ${{ needs.version-check.outputs.repo }}/${{ matrix.repo_distro }} "${pkgfile}" + done - name: SSH setup id: ssh-setup if: github.event_name == 'workflow_dispatch' && github.repository == 'netdata/netdata' - continue-on-error: true uses: shimataro/ssh-key-action@v2 with: key: ${{ secrets.NETDATABOT_PACKAGES_SSH_KEY }} @@ -204,7 +217,6 @@ jobs: known_hosts: ${{ secrets.PACKAGES_KNOWN_HOSTS }} - name: Upload to packages.netdata.cloud id: package-upload - continue-on-error: true if: github.event_name == 'workflow_dispatch' && github.repository == 'netdata/netdata' run: | .github/scripts/package-upload.sh \ @@ -212,19 +224,6 @@ jobs: ${{ matrix.arch }} \ ${{ matrix.format }} \ ${{ needs.version-check.outputs.repo }} - - name: Upload to PackageCloud - id: upload - if: github.event_name == 'workflow_dispatch' && github.repository == 'netdata/netdata' - shell: bash - env: - PKG_CLOUD_TOKEN: ${{ secrets.PACKAGE_CLOUD_API_KEY }} - run: | - printf "Packages to upload:\n%s" "$(ls artifacts/*.${{ matrix.format }})" - for pkgfile in artifacts/*.${{ matrix.format }} ; do - .github/scripts/package_cloud_wrapper.sh yank ${{ needs.version-check.outputs.repo }}/${{ matrix.repo_distro }} \ - "$(basename "${pkgfile}")" || true - .github/scripts/package_cloud_wrapper.sh push ${{ needs.version-check.outputs.repo }}/${{ matrix.repo_distro }} "${pkgfile}" - done - name: Failure Notification uses: rtCamp/action-slack-notify@v2 env: @@ -240,9 +239,9 @@ jobs: Fetch images: ${{ steps.fetch-images.outcome }} Build: ${{ steps.build.outcome }} Test: ${{ steps.test.outcome }} + Publish to PackageCloud: ${{ steps.upload.outcome }} Import SSH Key: ${{ steps.ssh-setup.outcome }} Publish to packages.netdata.cloud: ${{ steps.package-upload.outcome }} - Publish to PackageCloud: ${{ steps.upload.outcome }} SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} if: >- ${{ diff --git a/.github/workflows/platform-eol-check.yml b/.github/workflows/platform-eol-check.yml new file mode 100644 index 00000000..d1f4416c --- /dev/null +++ b/.github/workflows/platform-eol-check.yml @@ -0,0 +1,153 @@ +--- +# Auto-generate issues for EOL of platforms that are approaching their EOL date. +# Uses https://endoflife.date and their new API to check for EOL dates. +# +# Issues are created when the EOL date is within the next 30 days. +name: Check Platform EOL +on: # Run weekly and whenever manually triggered + schedule: + - cron: '0 3 * * 1' + workflow_dispatch: null +concurrency: # Simple single-instance concurrency. + group: eol-check-${{ github.repository }} + cancel-in-progress: true +jobs: + # Prepare the build matrix. + # This uses output from .github/scripts/gen-matrix-eol-check.py + matrix: + name: Prepare Build Matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Checkout + id: checkout + uses: actions/checkout@v3 + - name: Prepare tools + id: prepare + run: | + sudo apt-get update && sudo apt-get install -y python3-ruamel.yaml + - name: Read build matrix + id: set-matrix + run: | + matrix="$(.github/scripts/gen-matrix-eol-check.py)" + echo "Generated matrix: ${matrix}" + echo "matrix=${matrix}" >> "${GITHUB_OUTPUT}" + - name: Failure Notification + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_COLOR: 'danger' + SLACK_FOOTER: '' + SLACK_ICON_EMOJI: ':github-actions:' + SLACK_TITLE: 'Failed to generate build matrix for platform EOL checks:' + SLACK_USERNAME: 'GitHub Actions' + SLACK_MESSAGE: |- + ${{ github.repository }}: Build matrix generation for scheduled platform EOL check has failed: + Checkout: ${{ steps.checkout.outcome }} + Prepare Tools: ${{ steps.prepare.outcome }} + Read Build Matrix: ${{ steps.set-matrix.outcome }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} + if: >- + ${{ + failure() + && github.event_name == 'schedule' + && github.repository == 'netdata/netdata' + }} + + eol-check: + name: EOL Check + runs-on: ubuntu-latest + needs: + - matrix + strategy: + matrix: ${{ fromJson(needs.matrix.outputs.matrix) }} + fail-fast: false # We want to check everything, so don’t bail on the first failure. + max-parallel: 2 # Cap of two jobs at a time to limit impact on other CI. + steps: + - name: Checkout + id: checkout + uses: actions/checkout@v3 + # Actually check the EOL date for the platform. + - name: Check EOL Date + id: check + shell: sh {0} + run: | + d="$(.github/scripts/platform-impending-eol.py ${{ matrix.distro }} ${{ matrix.release }})" + case $? in + 0) echo "pending=false" >> "${GITHUB_OUTPUT}" ;; + 1) + echo "pending=true" >> "${GITHUB_OUTPUT}" + echo "date=${d}" >> "${GITHUB_OUTPUT}" + ;; + 2) + echo "pending=false" >> "${GITHUB_OUTPUT}" + echo "::info::No EOL information found for ${{ matrix.full_name }}" + ;; + *) + echo "::error::Failed to check EOL date for ${{ matrix.full_name }}" + exit 1 + ;; + esac + # Figure out the issue title. + # This is it’s own step so we only have to set it in one place. + - name: Determine Issue Title + id: title + if: steps.check.outputs.pending == 'true' + run: | + echo "title=[Platform EOL]: ${{ matrix.full_name }} will be EOL soon." >> "${GITHUB_OUTPUT}" + # Check if there is an existing issue in the repo for the platform EOL. + # The actual command line to make the check is unfortunately + # complicated because GitHub thinks that it’s sensible to exit + # with a status of 0 if there are no results for a search. + - name: Check for Existing Issue + id: existing + if: steps.check.outputs.pending == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + count=$(gh issue list -R netdata/netdata -s all -S '${{ steps.title.outputs.title }} in:title' --json 'id' -q '. | length') + if [ "${count}" -ge 1 ]; then + echo 'exists=true' >> "${GITHUB_OUTPUT}" + else + echo 'exists=false' >> "${GITHUB_OUTPUT}" + fi + # If the platform is near EOL and there is no existing issue, create one. + - name: Create EOL Issue + id: create-issue + if: steps.check.outputs.pending == 'true' && steps.existing.outputs.exists == 'false' + uses: imjohnbo/issue-bot@v3 + with: + assignees: Ferroin, tkatsoulas + labels: area/packaging, needs triage + title: ${{ steps.title.outputs.title }} + body: | + Based on information from https://endoflife.date/${{ matrix.distro }}, upstream support for ${{ matrix.full_name }} will be ending on ${{ steps.check.outputs.date }}. A PR should be created to remove this platform from our platform support document, CI, and packaging code. + + - [ ] Remove platform from `packaging/PLATFORM_SUPPORT.md` + - [ ] Remove platform from `.github/data/distros.yml` + - [ ] Remove platform package builder from helper-images repo (if applicable). + - [ ] Verify any other platform support code that needs to be cleaned up. + # Send a notification to Slack if a job failed. + - name: Failure Notification + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_COLOR: 'danger' + SLACK_FOOTER: '' + SLACK_ICON_EMOJI: ':github-actions:' + SLACK_TITLE: 'Platform EOL check failed:' + SLACK_USERNAME: 'GitHub Actions' + SLACK_MESSAGE: |- + ${{ github.repository }}: A scheduled check for the EOL status of ${{ matrix.full_name }} has failed. + Checkout: ${{ steps.checkout.outcome }} + Check EOL Status: ${{ steps.check.outcome }} + Generate Issue Title: ${{ steps.title.outcome }} + Check for Existing Issue: ${{ steps.existing.outcome }} + Create Issue: ${{ steps.create-issue.outcome }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} + if: >- + ${{ + failure() + && github.event_name == 'schedule' + && github.repository == 'netdata/netdata' + }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e16ecaba..ef9bf94b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout id: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 submodules: recursive @@ -116,7 +116,7 @@ jobs: steps: - name: Checkout id: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: ${{ needs.update-changelogs.outputs.ref }} - name: Trigger build @@ -151,7 +151,7 @@ jobs: steps: - name: Checkout id: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: ${{ needs.update-changelogs.outputs.ref }} - name: Trigger build @@ -186,7 +186,7 @@ jobs: steps: - name: Checkout id: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: ${{ needs.update-changelogs.outputs.ref }} - name: Trigger build diff --git a/.github/workflows/repoconfig-packages.yml b/.github/workflows/repoconfig-packages.yml index f8a3dc40..e2b41570 100644 --- a/.github/workflows/repoconfig-packages.yml +++ b/.github/workflows/repoconfig-packages.yml @@ -86,18 +86,37 @@ jobs: max_attempts: 3 retry_wait_seconds: 30 timeout_seconds: 900 - command: docker pull --platform ${{ matrix.platform }} ${{ matrix.base_image }}:${{ matrix.version }} + command: docker pull --platform ${{ matrix.platform }} ${{ matrix.base_image }} - name: Build Packages id: build shell: bash run: | docker run --security-opt seccomp=unconfined -e DISABLE_TELEMETRY=1 --platform ${{ matrix.platform }} \ - -v "$PWD":/netdata ${{ matrix.base_image }}:${{ matrix.version }} \ + -v "$PWD":/netdata ${{ matrix.base_image }} \ /netdata/packaging/repoconfig/build-${{ matrix.format }}.sh + - name: Upload Packages + id: publish + if: github.event_name != 'pull_request' && github.repository == 'netdata/netdata' + continue-on-error: true + shell: bash + env: + PKG_CLOUD_TOKEN: ${{ secrets.PACKAGE_CLOUD_API_KEY }} + run: | + printf "Packages to upload:\n%s" "$(ls artifacts/*.${{ matrix.format }})" + for pkgfile in artifacts/*.${{ matrix.format }} ; do + .github/scripts/package_cloud_wrapper.sh yank "${REPO_PREFIX}/${{ matrix.pkgclouddistro }}" \ + "$(basename "${pkgfile}")" || true + .github/scripts/package_cloud_wrapper.sh push "${REPO_PREFIX}/${{ matrix.pkgclouddistro }}" "${pkgfile}" + .github/scripts/package_cloud_wrapper.sh yank "${REPO_PREFIX}-edge/${{ matrix.pkgclouddistro }}" \ + "$(basename "${pkgfile}")" || true + .github/scripts/package_cloud_wrapper.sh push "${REPO_PREFIX}-edge/${{ matrix.pkgclouddistro }}" "${pkgfile}" + .github/scripts/package_cloud_wrapper.sh yank "${REPO_PREFIX}-repoconfig/${{ matrix.pkgclouddistro }}" \ + "$(basename "${pkgfile}")" || true + .github/scripts/package_cloud_wrapper.sh push "${REPO_PREFIX}-repoconfig/${{ matrix.pkgclouddistro }}" "${pkgfile}" + done - name: SSH setup id: ssh-setup if: github.event_name != 'pull_request' && github.repository == 'netdata/netdata' - continue-on-error: true uses: shimataro/ssh-key-action@v2 with: key: ${{ secrets.NETDATABOT_PACKAGES_SSH_KEY }} @@ -105,7 +124,6 @@ jobs: known_hosts: ${{ secrets.PACKAGES_KNOWN_HOSTS }} - name: Upload to packages.netdata.cloud id: package-upload - continue-on-error: true if: github.event_name != 'pull_request' && github.repository == 'netdata/netdata' run: | for arch in ${{ matrix.arches }}; do @@ -117,25 +135,6 @@ jobs: netdata/netdata${suffix} done done - - name: Upload Packages - id: publish - if: github.event_name != 'pull_request' && github.repository == 'netdata/netdata' - shell: bash - env: - PKG_CLOUD_TOKEN: ${{ secrets.PACKAGE_CLOUD_API_KEY }} - run: | - printf "Packages to upload:\n%s" "$(ls artifacts/*.${{ matrix.format }})" - for pkgfile in artifacts/*.${{ matrix.format }} ; do - .github/scripts/package_cloud_wrapper.sh yank "${REPO_PREFIX}/${{ matrix.pkgclouddistro }}" \ - "$(basename "${pkgfile}")" || true - .github/scripts/package_cloud_wrapper.sh push "${REPO_PREFIX}/${{ matrix.pkgclouddistro }}" "${pkgfile}" - .github/scripts/package_cloud_wrapper.sh yank "${REPO_PREFIX}-edge/${{ matrix.pkgclouddistro }}" \ - "$(basename "${pkgfile}")" || true - .github/scripts/package_cloud_wrapper.sh push "${REPO_PREFIX}-edge/${{ matrix.pkgclouddistro }}" "${pkgfile}" - .github/scripts/package_cloud_wrapper.sh yank "${REPO_PREFIX}-repoconfig/${{ matrix.pkgclouddistro }}" \ - "$(basename "${pkgfile}")" || true - .github/scripts/package_cloud_wrapper.sh push "${REPO_PREFIX}-repoconfig/${{ matrix.pkgclouddistro }}" "${pkgfile}" - done - name: Failure Notification if: ${{ failure() && github.repository == 'netdata/netdata' }} uses: rtCamp/action-slack-notify@v2 @@ -150,7 +149,7 @@ jobs: Checkout: ${{ steps.checkout.outcome }} Fetch images: ${{ steps.fetch-images.outcome }} Build: ${{ steps.build.outcome }} + Publish to PackageCloud: ${{ steps.publish.outcome }} Import SSH Key: ${{ steps.ssh-setup.outcome }} Publish to packages.netdata.cloud: ${{ steps.package-upload.outcome }} - Publish to PackageCloud: ${{ steps.publish.outcome }} SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index 7f12aeec..5756e4b2 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -1,5 +1,5 @@ --- -# Runs various ReviewDog based checks against PR with suggested changes to improve quality +# Runs various linter checks against PR with suggested changes to improve quality name: Review on: pull_request: @@ -15,7 +15,9 @@ jobs: runs-on: ubuntu-latest outputs: actionlint: ${{ steps.actionlint.outputs.run }} + clangformat: ${{ steps.clangformat.outputs.run }} eslint: ${{ steps.eslint.outputs.run }} + flake8: ${{ steps.flake8.outputs.run }} hadolint: ${{ steps.hadolint.outputs.run }} shellcheck: ${{ steps.shellcheck.outputs.run }} yamllint: ${{ steps.yamllint.outputs.run }} @@ -36,6 +38,17 @@ jobs: else echo "run=false" >> "${GITHUB_OUTPUT}" fi + - name: Check files for clang-format + id: clangformat + run: | + if [ "${{ contains(github.event.pull_request.labels.*.name, 'run-ci/clang-format') }}" = "true" ]; then + echo "run=true" >> "${GITHUB_OUTPUT}" + elif git diff --name-only origin/${{ github.base_ref }} HEAD | grep -Eq '\.cpp$|\.cxx$|\.c$|\.hpp$|\.hxx$|\.h$' ; then + echo "run=true" >> "${GITHUB_OUTPUT}" + echo 'C/C++ code has changed, need to run clang-format.' + else + echo "run=false" >> "${GITHUB_OUTPUT}" + fi - name: Check files for eslint id: eslint run: | @@ -47,6 +60,17 @@ jobs: else echo "run=false" >> "${GITHUB_OUTPUT}" fi + - name: Check files for flake8 + id: flake8 + run: | + if [ "${{ contains(github.event.pull_request.labels.*.name, 'run-ci/flake8') }}" = "true" ]; then + echo "run=true" >> "${GITHUB_OUTPUT}" + elif git diff --name-only origin/${{ github.base_ref }} HEAD | grep -Eq '.*\.py' ; then + echo "run=true" >> "${GITHUB_OUTPUT}" + echo 'Python files have changed, need to run flake8.' + else + echo "run=false" >> "${GITHUB_OUTPUT}" + fi - name: Check files for hadolint id: hadolint run: | @@ -98,6 +122,39 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} reporter: github-pr-check + clang-format: + name: clang-format + needs: prep-review + if: needs.prep-review.outputs.clangformat == 'true' + runs-on: ubuntu-latest + steps: + - name: Git clone repository + uses: actions/checkout@v3 + with: + submodules: false + fetch-depth: 0 + - name: Check for label + id: label + run: | + if [ "${{ contains(github.event.pull_request.labels.*.name, 'run-ci/clang-format') }}" = "true" ]; then + echo 'check-all=true' >> "${GITHUB_OUTPUT}" + else + echo 'check-all=false' >> "${GITHUB_OUTPUT}" + fi + - name: Run clang-format + run: | + if [ "${{ steps.label.outputs.check-all }}" == 'true' ]; then + find . -regex '.*\.\(c\|cpp\|cxx\|h\|hpp\|hxx\)$' -exec clang-format -i --style=file '{}' \; + else + git diff --name-only origin/${{ github.base_ref }} HEAD | grep -E '\.cpp$|\.cxx$|\.c$|\.hpp$|\.hxx$|\.h$' | \ + xargs -n 1 -r clang-format -i --style=file + fi + git status --porcelain=v1 > /tmp/porcelain + if [ -s /tmp/porcelain ]; then + cat /tmp/porcelain + exit 1 + fi + eslint: name: eslint needs: prep-review @@ -118,6 +175,27 @@ jobs: reporter: github-pr-check eslint_flags: '.' + flake8: + name: flake8 + needs: prep-review + if: needs.prep-review.outputs.flake8 == 'true' + runs-on: ubuntu-latest + steps: + - name: Git clone repository + uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Run flake8 + uses: reviewdog/action-flake8@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + reporter: github-pr-check + hadolint: name: hadolint needs: prep-review @@ -152,7 +230,10 @@ jobs: reporter: github-pr-check path: "." pattern: "*.sh*" - exclude: "./.git/*" + exclude: | + ./.git/* + packaging/makeself/makeself.sh + packaging/makeself/makeself-header.sh yamllint: name: yamllint diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d4838685..5f83a440 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,7 +31,7 @@ jobs: - name: Prepare environment run: | ./packaging/installer/install-required-packages.sh --dont-wait --non-interactive netdata-all - sudo apt-get install -y libjson-c-dev libipmimonitoring-dev libcups2-dev libsnappy-dev \ + sudo apt-get install -y libjson-c-dev libyaml-dev libipmimonitoring-dev libcups2-dev libsnappy-dev \ libprotobuf-dev libprotoc-dev libssl-dev protobuf-compiler \ libnetfilter-acct-dev - name: Run ./tests/run-unit-tests.sh diff --git a/.gitignore b/.gitignore index 821d72d9..14ba1c61 100644 --- a/.gitignore +++ b/.gitignore @@ -115,19 +115,20 @@ web/gui/version.txt /node_modules/ /coverage/ -system/netdata-lsb -system/netdata-openrc -system/netdata-init-d -system/netdata.logrotate -system/netdata.service -system/netdata.service.* -system/netdata-updater.service -!system/netdata.service.in -!system/netdata.service.*.in -system/netdata.plist -system/netdata-freebsd -system/netdata.crontab system/install-service.sh +system/netdata.logrotate +system/cron/netdata-updater-daily +system/freebsd/rc.d/netdata +system/initd/init.d/netdata +system/launchd/netdata.plist +system/lsb/init.d/netdata +system/openrc/init.d/netdata +system/runit/run +system/systemd/netdata.service +system/systemd/netdata.service.* +system/systemd/netdata-updater.service +!system/systemd/netdata.service.in +!system/systemd/netdata.service.*.in daemon/anonymous-statistics.sh daemon/get-kubernetes-labels.sh @@ -231,3 +232,7 @@ Session.*.vim # Judy stuff JudyLTables.c judyltablesgen + +# m4 generated ksys +database/engine/journalfile_v2.ksy +database/engine/journalfile_v2_virtmemb.ksy diff --git a/.gitmodules b/.gitmodules index d3c7ace4..dd687fee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,7 +9,3 @@ url = https://github.com/davisking/dlib.git shallow = true ignore = dirty -[submodule "ml/json"] - path = ml/json - url = https://github.com/nlohmann/json.git - shallow = true diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 00000000..c017258e --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1,3 @@ +#!/bin/sh +# shellcheck disable=SC2034 +disable=SC2317 diff --git a/CHANGELOG.md b/CHANGELOG.md index cc875810..3e5349fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,323 @@ # Changelog -## [v1.38.1](https://github.com/netdata/netdata/tree/v1.38.1) (2023-02-13) +## [v1.39.0](https://github.com/netdata/netdata/tree/v1.39.0) (2023-05-08) -[Full Changelog](https://github.com/netdata/netdata/compare/v1.38.0...v1.38.1) +[Full Changelog](https://github.com/netdata/netdata/compare/v1.38.1...v1.39.0) **Merged pull requests:** +- Fix typo in file capabilities settings in static installer. [\#15023](https://github.com/netdata/netdata/pull/15023) ([Ferroin](https://github.com/Ferroin)) +- On data and weight queries now instances filter matches also instance\_id@node\_id [\#15021](https://github.com/netdata/netdata/pull/15021) ([ktsaou](https://github.com/ktsaou)) +- add `grafana` to `apps_groups.conf` [\#15020](https://github.com/netdata/netdata/pull/15020) ([andrewm4894](https://github.com/andrewm4894)) +- Set file capabilities correctly on static installs. [\#15018](https://github.com/netdata/netdata/pull/15018) ([Ferroin](https://github.com/Ferroin)) +- Differentiate error codes better when claiming from kickstart script. [\#15015](https://github.com/netdata/netdata/pull/15015) ([Ferroin](https://github.com/Ferroin)) +- Fix coverity issues [\#15005](https://github.com/netdata/netdata/pull/15005) ([stelfrag](https://github.com/stelfrag)) +- weights endpoint: volume diff of anomaly rates [\#15004](https://github.com/netdata/netdata/pull/15004) ([ktsaou](https://github.com/ktsaou)) +- Bump Coverity scan tool version. [\#15003](https://github.com/netdata/netdata/pull/15003) ([Ferroin](https://github.com/Ferroin)) +- feat\(apps.plugin\): collect context switches [\#15002](https://github.com/netdata/netdata/pull/15002) ([ilyam8](https://github.com/ilyam8)) +- add support for monitoring thp, ballooning, zswap, ksm cow [\#15000](https://github.com/netdata/netdata/pull/15000) ([ktsaou](https://github.com/ktsaou)) +- Fix cmake errors [\#14998](https://github.com/netdata/netdata/pull/14998) ([stelfrag](https://github.com/stelfrag)) +- Remove lighttpd2 from docs [\#14997](https://github.com/netdata/netdata/pull/14997) ([Ancairon](https://github.com/Ancairon)) +- Update netdata-security.md [\#14995](https://github.com/netdata/netdata/pull/14995) ([cakrit](https://github.com/cakrit)) +- feat: add OpsGenie alert levels to payload [\#14992](https://github.com/netdata/netdata/pull/14992) ([OliverNChalk](https://github.com/OliverNChalk)) +- disable CPU full pressure at the system level [\#14991](https://github.com/netdata/netdata/pull/14991) ([ilyam8](https://github.com/ilyam8)) +- fix config generation for plugins [\#14990](https://github.com/netdata/netdata/pull/14990) ([ilyam8](https://github.com/ilyam8)) +- Load/Store ML models [\#14981](https://github.com/netdata/netdata/pull/14981) ([vkalintiris](https://github.com/vkalintiris)) +- Fix TYPO in README.md [\#14980](https://github.com/netdata/netdata/pull/14980) ([DevinNorgarb](https://github.com/DevinNorgarb)) +- add metrics.csv to proc.plugin [\#14979](https://github.com/netdata/netdata/pull/14979) ([thiagoftsm](https://github.com/thiagoftsm)) +- interrupt callback on api/v1/data [\#14978](https://github.com/netdata/netdata/pull/14978) ([ktsaou](https://github.com/ktsaou)) +- add metrics.csv to macos, freebsd and cgroups plugins [\#14977](https://github.com/netdata/netdata/pull/14977) ([ilyam8](https://github.com/ilyam8)) +- fix adding chart labels in tc.plugin [\#14976](https://github.com/netdata/netdata/pull/14976) ([ilyam8](https://github.com/ilyam8)) +- add metrics.csv to some c collectors [\#14974](https://github.com/netdata/netdata/pull/14974) ([ilyam8](https://github.com/ilyam8)) +- add metrics.csv to perf.plugin [\#14973](https://github.com/netdata/netdata/pull/14973) ([ilyam8](https://github.com/ilyam8)) +- add metrics.csv to xenstat.plugin [\#14972](https://github.com/netdata/netdata/pull/14972) ([ilyam8](https://github.com/ilyam8)) +- add metrics.csv to slabinfo.plugin [\#14971](https://github.com/netdata/netdata/pull/14971) ([ilyam8](https://github.com/ilyam8)) +- add metrics.csv to timex.plugin [\#14970](https://github.com/netdata/netdata/pull/14970) ([ilyam8](https://github.com/ilyam8)) +- do not convert to percentage, when the raw option is given [\#14969](https://github.com/netdata/netdata/pull/14969) ([ktsaou](https://github.com/ktsaou)) +- add metrics.csv to apps.plugin [\#14968](https://github.com/netdata/netdata/pull/14968) ([ilyam8](https://github.com/ilyam8)) +- add metrics.csv to charts.d [\#14966](https://github.com/netdata/netdata/pull/14966) ([ilyam8](https://github.com/ilyam8)) +- Add metrics.csv for ebpf [\#14965](https://github.com/netdata/netdata/pull/14965) ([thiagoftsm](https://github.com/thiagoftsm)) +- Update ML README.md [\#14964](https://github.com/netdata/netdata/pull/14964) ([ktsaou](https://github.com/ktsaou)) +- Document netdatacli dumpconfig option [\#14963](https://github.com/netdata/netdata/pull/14963) ([cakrit](https://github.com/cakrit)) +- Update README.md [\#14962](https://github.com/netdata/netdata/pull/14962) ([cakrit](https://github.com/cakrit)) +- Fix handling of users and groups on install. [\#14961](https://github.com/netdata/netdata/pull/14961) ([Ferroin](https://github.com/Ferroin)) +- Reject child when context is loading [\#14960](https://github.com/netdata/netdata/pull/14960) ([stelfrag](https://github.com/stelfrag)) +- Add metadata.csv to python.d.plugin [\#14959](https://github.com/netdata/netdata/pull/14959) ([Ancairon](https://github.com/Ancairon)) +- Address log issue [\#14958](https://github.com/netdata/netdata/pull/14958) ([thiagoftsm](https://github.com/thiagoftsm)) +- Add adaptec\_raid metrics.csv [\#14955](https://github.com/netdata/netdata/pull/14955) ([Ancairon](https://github.com/Ancairon)) +- Set api v2 version 2 [\#14954](https://github.com/netdata/netdata/pull/14954) ([stelfrag](https://github.com/stelfrag)) +- Cancel Pending Request [\#14953](https://github.com/netdata/netdata/pull/14953) ([underhood](https://github.com/underhood)) +- Terminate JSX element in doc file [\#14952](https://github.com/netdata/netdata/pull/14952) ([Ancairon](https://github.com/Ancairon)) +- Update dbengine README.md [\#14951](https://github.com/netdata/netdata/pull/14951) ([ktsaou](https://github.com/ktsaou)) +- Prevent pager from preventing non-interactive install [\#14950](https://github.com/netdata/netdata/pull/14950) ([bompus](https://github.com/bompus)) +- Update README.md [\#14948](https://github.com/netdata/netdata/pull/14948) ([cakrit](https://github.com/cakrit)) +- Disable SQL operations in training thread [\#14947](https://github.com/netdata/netdata/pull/14947) ([vkalintiris](https://github.com/vkalintiris)) +- Add support for acquire/release operations on RRDSETs [\#14945](https://github.com/netdata/netdata/pull/14945) ([vkalintiris](https://github.com/vkalintiris)) +- fix 32bit segv [\#14940](https://github.com/netdata/netdata/pull/14940) ([ktsaou](https://github.com/ktsaou)) +- Update using-host-labels.md [\#14939](https://github.com/netdata/netdata/pull/14939) ([cakrit](https://github.com/cakrit)) +- Fix broken image, in database/README.md [\#14936](https://github.com/netdata/netdata/pull/14936) ([Ancairon](https://github.com/Ancairon)) +- Add a description to proc.plugin/README.md [\#14935](https://github.com/netdata/netdata/pull/14935) ([Ancairon](https://github.com/Ancairon)) +- zfspool: add suspended state [\#14934](https://github.com/netdata/netdata/pull/14934) ([ilyam8](https://github.com/ilyam8)) +- bump go.d.plugin v0.52.2 [\#14933](https://github.com/netdata/netdata/pull/14933) ([ilyam8](https://github.com/ilyam8)) +- Make the document more generic [\#14932](https://github.com/netdata/netdata/pull/14932) ([cakrit](https://github.com/cakrit)) +- Replace "XYZ view" with "XYZ tab" in documentation files [\#14930](https://github.com/netdata/netdata/pull/14930) ([Ancairon](https://github.com/Ancairon)) +- Add windows MSI installer start stop restart instructions to docs [\#14929](https://github.com/netdata/netdata/pull/14929) ([Ancairon](https://github.com/Ancairon)) +- Add Docker instructions to enable Nvidia GPUs [\#14924](https://github.com/netdata/netdata/pull/14924) ([D34DC3N73R](https://github.com/D34DC3N73R)) +- Initialize machine GUID earlier in the agent startup sequence [\#14922](https://github.com/netdata/netdata/pull/14922) ([stelfrag](https://github.com/stelfrag)) +- bump go.d.plugin to v0.52.1 [\#14921](https://github.com/netdata/netdata/pull/14921) ([ilyam8](https://github.com/ilyam8)) +- Skip ML initialization when it's been disabled in netdata.conf [\#14920](https://github.com/netdata/netdata/pull/14920) ([vkalintiris](https://github.com/vkalintiris)) +- Fix warnings and error when compiling with --disable-dbengine [\#14919](https://github.com/netdata/netdata/pull/14919) ([stelfrag](https://github.com/stelfrag)) +- minor - remove RX\_MSGLEN\_MAX [\#14918](https://github.com/netdata/netdata/pull/14918) ([underhood](https://github.com/underhood)) +- change docusaurus admonitions to our style of admonitions [\#14917](https://github.com/netdata/netdata/pull/14917) ([Ancairon](https://github.com/Ancairon)) +- Add windows diagram [\#14916](https://github.com/netdata/netdata/pull/14916) ([cakrit](https://github.com/cakrit)) +- Add section for scaling parent nodes [\#14915](https://github.com/netdata/netdata/pull/14915) ([cakrit](https://github.com/cakrit)) +- Update suggested replication setups [\#14914](https://github.com/netdata/netdata/pull/14914) ([cakrit](https://github.com/cakrit)) +- Revert ML changes. [\#14908](https://github.com/netdata/netdata/pull/14908) ([vkalintiris](https://github.com/vkalintiris)) +- Remove netdatacli response size limitation [\#14906](https://github.com/netdata/netdata/pull/14906) ([stelfrag](https://github.com/stelfrag)) +- Update change-metrics-storage.md [\#14905](https://github.com/netdata/netdata/pull/14905) ([cakrit](https://github.com/cakrit)) +- /api/v2 part 10 [\#14904](https://github.com/netdata/netdata/pull/14904) ([ktsaou](https://github.com/ktsaou)) +- Optimize the cheat sheet to be in a printable form factor [\#14903](https://github.com/netdata/netdata/pull/14903) ([Ancairon](https://github.com/Ancairon)) +- Address issues on `EC2` \(eBPF\). [\#14902](https://github.com/netdata/netdata/pull/14902) ([thiagoftsm](https://github.com/thiagoftsm)) +- Update REFERENCE.md [\#14900](https://github.com/netdata/netdata/pull/14900) ([cakrit](https://github.com/cakrit)) +- Update README.md [\#14899](https://github.com/netdata/netdata/pull/14899) ([cakrit](https://github.com/cakrit)) +- Update README.md [\#14898](https://github.com/netdata/netdata/pull/14898) ([cakrit](https://github.com/cakrit)) +- Disable threads while we are investigating [\#14897](https://github.com/netdata/netdata/pull/14897) ([thiagoftsm](https://github.com/thiagoftsm)) +- add opsgenie as a business level notificaiton method [\#14895](https://github.com/netdata/netdata/pull/14895) ([hugovalente-pm](https://github.com/hugovalente-pm)) +- Remove dry run option from uninstall documentation [\#14894](https://github.com/netdata/netdata/pull/14894) ([Ancairon](https://github.com/Ancairon)) +- cgroups: add option to use Kubelet for pods metadata [\#14891](https://github.com/netdata/netdata/pull/14891) ([ilyam8](https://github.com/ilyam8)) +- /api/v2 part 9 [\#14888](https://github.com/netdata/netdata/pull/14888) ([ktsaou](https://github.com/ktsaou)) +- Add example configuration to w1sensor collector [\#14886](https://github.com/netdata/netdata/pull/14886) ([Ancairon](https://github.com/Ancairon)) +- /api/v2 part 8 [\#14885](https://github.com/netdata/netdata/pull/14885) ([ktsaou](https://github.com/ktsaou)) +- Fix/introduce links inside charts.d.plugin documentation [\#14884](https://github.com/netdata/netdata/pull/14884) ([Ancairon](https://github.com/Ancairon)) +- Add support for alert notifications to ntfy.sh [\#14875](https://github.com/netdata/netdata/pull/14875) ([Dim-P](https://github.com/Dim-P)) +- WEBRTC for communication between agents and browsers [\#14874](https://github.com/netdata/netdata/pull/14874) ([ktsaou](https://github.com/ktsaou)) +- Remove alpine 3.14 from the ci [\#14873](https://github.com/netdata/netdata/pull/14873) ([tkatsoulas](https://github.com/tkatsoulas)) +- cgroups.plugin: add image label [\#14872](https://github.com/netdata/netdata/pull/14872) ([ilyam8](https://github.com/ilyam8)) +- Fix regex syntax for clang-format checks. [\#14871](https://github.com/netdata/netdata/pull/14871) ([Ferroin](https://github.com/Ferroin)) +- bump go.d.plugin v0.52.0 [\#14870](https://github.com/netdata/netdata/pull/14870) ([ilyam8](https://github.com/ilyam8)) +- eBPF bug fixes [\#14869](https://github.com/netdata/netdata/pull/14869) ([thiagoftsm](https://github.com/thiagoftsm)) +- Update link from http to https [\#14864](https://github.com/netdata/netdata/pull/14864) ([Ancairon](https://github.com/Ancairon)) +- Fix js tag in documentation [\#14862](https://github.com/netdata/netdata/pull/14862) ([Ancairon](https://github.com/Ancairon)) +- Set a default registry unique id when there is none for statistics script [\#14861](https://github.com/netdata/netdata/pull/14861) ([MrZammler](https://github.com/MrZammler)) +- review usage of you to say user instead [\#14858](https://github.com/netdata/netdata/pull/14858) ([hugovalente-pm](https://github.com/hugovalente-pm)) +- Add labels for cgroup name [\#14856](https://github.com/netdata/netdata/pull/14856) ([thiagoftsm](https://github.com/thiagoftsm)) +- fix typo alerms -\> alarms [\#14854](https://github.com/netdata/netdata/pull/14854) ([slavox](https://github.com/slavox)) +- Add a checkpoint message to alerts stream [\#14847](https://github.com/netdata/netdata/pull/14847) ([MrZammler](https://github.com/MrZammler)) +- fix \#14841 Exception funktion call Rados.mon\_command\(\) [\#14844](https://github.com/netdata/netdata/pull/14844) ([farax4de](https://github.com/farax4de)) +- Update parent child examples [\#14842](https://github.com/netdata/netdata/pull/14842) ([cakrit](https://github.com/cakrit)) +- fix double host prefix when reading ZFS pools state [\#14840](https://github.com/netdata/netdata/pull/14840) ([ilyam8](https://github.com/ilyam8)) +- Update enable-notifications.md [\#14838](https://github.com/netdata/netdata/pull/14838) ([cakrit](https://github.com/cakrit)) +- Update deployment-strategies.md [\#14837](https://github.com/netdata/netdata/pull/14837) ([cakrit](https://github.com/cakrit)) +- Update database engine readme [\#14836](https://github.com/netdata/netdata/pull/14836) ([cakrit](https://github.com/cakrit)) +- Update change-metrics-storage.md [\#14835](https://github.com/netdata/netdata/pull/14835) ([cakrit](https://github.com/cakrit)) +- Update change-metrics-storage.md [\#14834](https://github.com/netdata/netdata/pull/14834) ([cakrit](https://github.com/cakrit)) +- Update netdata-security.md [\#14833](https://github.com/netdata/netdata/pull/14833) ([cakrit](https://github.com/cakrit)) +- Boost dbengine [\#14832](https://github.com/netdata/netdata/pull/14832) ([ktsaou](https://github.com/ktsaou)) +- add some third party collectors [\#14830](https://github.com/netdata/netdata/pull/14830) ([andrewm4894](https://github.com/andrewm4894)) +- Add opsgenie integration docs [\#14828](https://github.com/netdata/netdata/pull/14828) ([iorvd](https://github.com/iorvd)) +- Update Agent notification methods documentation [\#14827](https://github.com/netdata/netdata/pull/14827) ([Ancairon](https://github.com/Ancairon)) +- Delete installation instructions specific to FreeNAS [\#14826](https://github.com/netdata/netdata/pull/14826) ([Ancairon](https://github.com/Ancairon)) +- First batch of adding descriptions to the documentation [\#14825](https://github.com/netdata/netdata/pull/14825) ([Ancairon](https://github.com/Ancairon)) +- Fix Btrfs unallocated space accounting [\#14824](https://github.com/netdata/netdata/pull/14824) ([intelfx](https://github.com/intelfx)) +- Update the bundled version of makeself used to create static builds. [\#14822](https://github.com/netdata/netdata/pull/14822) ([Ferroin](https://github.com/Ferroin)) +- configure extent cache size [\#14821](https://github.com/netdata/netdata/pull/14821) ([ktsaou](https://github.com/ktsaou)) +- Docs, shorten too long titles, and add a description below [\#14820](https://github.com/netdata/netdata/pull/14820) ([Ancairon](https://github.com/Ancairon)) +- update posthog domain [\#14818](https://github.com/netdata/netdata/pull/14818) ([andrewm4894](https://github.com/andrewm4894)) +- minor - add capability signifying this agent can speak apiv2 [\#14817](https://github.com/netdata/netdata/pull/14817) ([underhood](https://github.com/underhood)) +- Minor improvements to netdata-security.md [\#14815](https://github.com/netdata/netdata/pull/14815) ([cakrit](https://github.com/cakrit)) +- Update privacy link in aclk doc [\#14813](https://github.com/netdata/netdata/pull/14813) ([cakrit](https://github.com/cakrit)) +- Consolidate security and privacy documents [\#14812](https://github.com/netdata/netdata/pull/14812) ([cakrit](https://github.com/cakrit)) +- Update role-based-access.md [\#14811](https://github.com/netdata/netdata/pull/14811) ([cakrit](https://github.com/cakrit)) +- Save and load ML models [\#14810](https://github.com/netdata/netdata/pull/14810) ([vkalintiris](https://github.com/vkalintiris)) +- diskspace: don't collect inodes on msdosfs [\#14809](https://github.com/netdata/netdata/pull/14809) ([ilyam8](https://github.com/ilyam8)) +- Document CetusGuard as a Docker socket proxy solution [\#14806](https://github.com/netdata/netdata/pull/14806) ([hectorm](https://github.com/hectorm)) +- Address Learn feedback from users [\#14802](https://github.com/netdata/netdata/pull/14802) ([Ancairon](https://github.com/Ancairon)) +- add validation step before using GCP metadata [\#14801](https://github.com/netdata/netdata/pull/14801) ([ilyam8](https://github.com/ilyam8)) +- /api/v2/X part 7 [\#14797](https://github.com/netdata/netdata/pull/14797) ([ktsaou](https://github.com/ktsaou)) +- Replace `/docs` links with GitHub links [\#14796](https://github.com/netdata/netdata/pull/14796) ([Ancairon](https://github.com/Ancairon)) +- Fix links in README.md [\#14794](https://github.com/netdata/netdata/pull/14794) ([cakrit](https://github.com/cakrit)) +- Fix capitalization on readme [\#14793](https://github.com/netdata/netdata/pull/14793) ([cakrit](https://github.com/cakrit)) +- Fix handling of logrotate on static installs. [\#14792](https://github.com/netdata/netdata/pull/14792) ([Ferroin](https://github.com/Ferroin)) +- use mCPU in k8s cgroup cpu charts title [\#14791](https://github.com/netdata/netdata/pull/14791) ([ilyam8](https://github.com/ilyam8)) +- Schedule node info to the cloud after child connection [\#14790](https://github.com/netdata/netdata/pull/14790) ([stelfrag](https://github.com/stelfrag)) +- uuid\_compare\(\) replaced with uuid\_memcmp\(\) [\#14787](https://github.com/netdata/netdata/pull/14787) ([ktsaou](https://github.com/ktsaou)) +- /api/v2/X part 6 [\#14785](https://github.com/netdata/netdata/pull/14785) ([ktsaou](https://github.com/ktsaou)) +- Update dashboard to version v2.30.1. [\#14784](https://github.com/netdata/netdata/pull/14784) ([netdatabot](https://github.com/netdatabot)) +- Revert "Use static thread-pool for training. \(\#14702\)" [\#14782](https://github.com/netdata/netdata/pull/14782) ([vkalintiris](https://github.com/vkalintiris)) +- Fix how we are handling system services in RPM packages. [\#14781](https://github.com/netdata/netdata/pull/14781) ([Ferroin](https://github.com/Ferroin)) +- Replace hardcoded links pointing to "learn.netdata.cloud" with github absolute links [\#14779](https://github.com/netdata/netdata/pull/14779) ([Ancairon](https://github.com/Ancairon)) +- Improve performance.md [\#14778](https://github.com/netdata/netdata/pull/14778) ([cakrit](https://github.com/cakrit)) +- Update reverse-proxies.md [\#14777](https://github.com/netdata/netdata/pull/14777) ([cakrit](https://github.com/cakrit)) +- Update performance.md [\#14776](https://github.com/netdata/netdata/pull/14776) ([cakrit](https://github.com/cakrit)) +- add validation step before using Azure metadata \(AZURE\_IMDS\_DATA\) [\#14775](https://github.com/netdata/netdata/pull/14775) ([ilyam8](https://github.com/ilyam8)) +- Create reverse-proxies.md [\#14774](https://github.com/netdata/netdata/pull/14774) ([cakrit](https://github.com/cakrit)) +- Add gzip compression info to nginx proxy readme [\#14773](https://github.com/netdata/netdata/pull/14773) ([cakrit](https://github.com/cakrit)) +- Update API [\#14772](https://github.com/netdata/netdata/pull/14772) ([cakrit](https://github.com/cakrit)) +- Add Amazon Linux 2023 to CI, packaging, and platform support. [\#14771](https://github.com/netdata/netdata/pull/14771) ([Ferroin](https://github.com/Ferroin)) +- Pass node\_id and config\_hash vars when queueing alert configurations [\#14769](https://github.com/netdata/netdata/pull/14769) ([MrZammler](https://github.com/MrZammler)) +- Assorted improvements for our platform EOL check code. [\#14768](https://github.com/netdata/netdata/pull/14768) ([Ferroin](https://github.com/Ferroin)) +- minor addition to distros matrix [\#14767](https://github.com/netdata/netdata/pull/14767) ([tkatsoulas](https://github.com/tkatsoulas)) +- Update "View active alerts" documentation [\#14766](https://github.com/netdata/netdata/pull/14766) ([Ancairon](https://github.com/Ancairon)) +- Skip alert template variables from alert snapshots [\#14763](https://github.com/netdata/netdata/pull/14763) ([MrZammler](https://github.com/MrZammler)) +- Accept all=true for alarms api v1 call [\#14762](https://github.com/netdata/netdata/pull/14762) ([MrZammler](https://github.com/MrZammler)) +- fix /sys/block/zram in docker [\#14759](https://github.com/netdata/netdata/pull/14759) ([ilyam8](https://github.com/ilyam8)) +- bump go.d.plugin to v0.51.4 [\#14756](https://github.com/netdata/netdata/pull/14756) ([ilyam8](https://github.com/ilyam8)) +- Add ethtool in third party collectors [\#14753](https://github.com/netdata/netdata/pull/14753) ([ghanapunq](https://github.com/ghanapunq)) +- Update sign-in.md [\#14751](https://github.com/netdata/netdata/pull/14751) ([cakrit](https://github.com/cakrit)) +- Update journal v2 [\#14750](https://github.com/netdata/netdata/pull/14750) ([stelfrag](https://github.com/stelfrag)) +- Update edit-config documentation [\#14749](https://github.com/netdata/netdata/pull/14749) ([cakrit](https://github.com/cakrit)) +- bump go.d.plugin version to v0.51.3 [\#14745](https://github.com/netdata/netdata/pull/14745) ([ilyam8](https://github.com/ilyam8)) +- increase RRD\_ID\_LENGTH\_MAX to 1000 [\#14744](https://github.com/netdata/netdata/pull/14744) ([ilyam8](https://github.com/ilyam8)) +- Update Performance Optimization Options [\#14743](https://github.com/netdata/netdata/pull/14743) ([cakrit](https://github.com/cakrit)) +- Update change-metrics-storage.md [\#14742](https://github.com/netdata/netdata/pull/14742) ([cakrit](https://github.com/cakrit)) +- Add contexts to privacy doc. [\#14741](https://github.com/netdata/netdata/pull/14741) ([cakrit](https://github.com/cakrit)) +- Create pci-soc-hipaa.md [\#14740](https://github.com/netdata/netdata/pull/14740) ([cakrit](https://github.com/cakrit)) +- Update data-privacy.md [\#14739](https://github.com/netdata/netdata/pull/14739) ([cakrit](https://github.com/cakrit)) +- Update data-privacy.md [\#14738](https://github.com/netdata/netdata/pull/14738) ([cakrit](https://github.com/cakrit)) +- pandas collector replace `self.warn()` with `self.warning()` [\#14736](https://github.com/netdata/netdata/pull/14736) ([andrewm4894](https://github.com/andrewm4894)) +- Add CI support for Fedora 38 & Ubuntu 23.04 native packages [\#14735](https://github.com/netdata/netdata/pull/14735) ([tkatsoulas](https://github.com/tkatsoulas)) +- Update change-metrics-storage.md [\#14734](https://github.com/netdata/netdata/pull/14734) ([cakrit](https://github.com/cakrit)) +- Correct calc and explain how to get METRICS in RAM usage [\#14733](https://github.com/netdata/netdata/pull/14733) ([cakrit](https://github.com/cakrit)) +- Update dashboard to version v2.30.0. [\#14732](https://github.com/netdata/netdata/pull/14732) ([netdatabot](https://github.com/netdatabot)) +- remove ubuntu 18.04 from our CI [\#14731](https://github.com/netdata/netdata/pull/14731) ([tkatsoulas](https://github.com/tkatsoulas)) +- Organize information from war-rooms.md to its correct location [\#14729](https://github.com/netdata/netdata/pull/14729) ([Ancairon](https://github.com/Ancairon)) +- Update change-metrics-storage.md [\#14726](https://github.com/netdata/netdata/pull/14726) ([cakrit](https://github.com/cakrit)) +- New build\_external scenario. [\#14725](https://github.com/netdata/netdata/pull/14725) ([vkalintiris](https://github.com/vkalintiris)) +- Suggest PRs to go to the community project [\#14724](https://github.com/netdata/netdata/pull/14724) ([cakrit](https://github.com/cakrit)) +- add go.d example collector to \#etc section [\#14722](https://github.com/netdata/netdata/pull/14722) ([andrewm4894](https://github.com/andrewm4894)) +- /api/v2/X part 5 [\#14718](https://github.com/netdata/netdata/pull/14718) ([ktsaou](https://github.com/ktsaou)) +- Update deployment-strategies.md [\#14716](https://github.com/netdata/netdata/pull/14716) ([cakrit](https://github.com/cakrit)) +- Change H1 of collector docs to separate from the website [\#14715](https://github.com/netdata/netdata/pull/14715) ([cakrit](https://github.com/cakrit)) +- Add instructions for reconnecting a Docker node to another Space [\#14714](https://github.com/netdata/netdata/pull/14714) ([Ancairon](https://github.com/Ancairon)) +- fix system info disk size detection on raspberry pi [\#14711](https://github.com/netdata/netdata/pull/14711) ([ilyam8](https://github.com/ilyam8)) +- /api/v2 part 4 [\#14706](https://github.com/netdata/netdata/pull/14706) ([ktsaou](https://github.com/ktsaou)) +- Don’t try to use tput in edit-config unless it’s installed. [\#14705](https://github.com/netdata/netdata/pull/14705) ([Ferroin](https://github.com/Ferroin)) +- Bundle libyaml [\#14704](https://github.com/netdata/netdata/pull/14704) ([MrZammler](https://github.com/MrZammler)) +- Handle conffiles for DEB packages explicitly instead of automatically. [\#14703](https://github.com/netdata/netdata/pull/14703) ([Ferroin](https://github.com/Ferroin)) +- Use static thread-pool for training. [\#14702](https://github.com/netdata/netdata/pull/14702) ([vkalintiris](https://github.com/vkalintiris)) +- Revert "Handle conffiles for DEB packages explicitly instead of automatically." [\#14700](https://github.com/netdata/netdata/pull/14700) ([tkatsoulas](https://github.com/tkatsoulas)) +- Fix compilation error when --disable-cloud is specified [\#14695](https://github.com/netdata/netdata/pull/14695) ([stelfrag](https://github.com/stelfrag)) +- fix: detect the host os in k8s on non-docker cri [\#14694](https://github.com/netdata/netdata/pull/14694) ([witalisoft](https://github.com/witalisoft)) +- Remove google hangouts from list of integrations [\#14689](https://github.com/netdata/netdata/pull/14689) ([cakrit](https://github.com/cakrit)) +- Fix Azure IMDS [\#14686](https://github.com/netdata/netdata/pull/14686) ([shyamvalsan](https://github.com/shyamvalsan)) +- Send an EOF from charts.d.plugin before exit [\#14680](https://github.com/netdata/netdata/pull/14680) ([MrZammler](https://github.com/MrZammler)) +- Fix conditionals for claim-only case in kickstart.sh. [\#14679](https://github.com/netdata/netdata/pull/14679) ([Ferroin](https://github.com/Ferroin)) +- Improve guideline docs [\#14678](https://github.com/netdata/netdata/pull/14678) ([Ancairon](https://github.com/Ancairon)) +- Fix kernel test script [\#14676](https://github.com/netdata/netdata/pull/14676) ([thiagoftsm](https://github.com/thiagoftsm)) +- add note on readme on how to easily see all ml related blog posts [\#14675](https://github.com/netdata/netdata/pull/14675) ([andrewm4894](https://github.com/andrewm4894)) +- Guard for null host when sending node instances [\#14673](https://github.com/netdata/netdata/pull/14673) ([MrZammler](https://github.com/MrZammler)) +- reviewed role description to be according to app [\#14672](https://github.com/netdata/netdata/pull/14672) ([hugovalente-pm](https://github.com/hugovalente-pm)) +- If a child is not streaming, send to the cloud last known version instead of unknown [\#14671](https://github.com/netdata/netdata/pull/14671) ([MrZammler](https://github.com/MrZammler)) +- /api/v2/X improvements part 3 [\#14665](https://github.com/netdata/netdata/pull/14665) ([ktsaou](https://github.com/ktsaou)) +- add FAQ information provided by Finance [\#14664](https://github.com/netdata/netdata/pull/14664) ([hugovalente-pm](https://github.com/hugovalente-pm)) +- Handle conffiles for DEB packages explicitly instead of automatically. [\#14662](https://github.com/netdata/netdata/pull/14662) ([Ferroin](https://github.com/Ferroin)) +- Fix cloud node stale status when a virtual host is created [\#14660](https://github.com/netdata/netdata/pull/14660) ([stelfrag](https://github.com/stelfrag)) +- Refactor ML code. [\#14659](https://github.com/netdata/netdata/pull/14659) ([vkalintiris](https://github.com/vkalintiris)) +- Properly handle service type detection failures when installing as a system service. [\#14658](https://github.com/netdata/netdata/pull/14658) ([Ferroin](https://github.com/Ferroin)) +- fix simple\_pattern\_create on freebsd [\#14656](https://github.com/netdata/netdata/pull/14656) ([ilyam8](https://github.com/ilyam8)) +- Move images in "interact-new-charts" from zenhub to github [\#14654](https://github.com/netdata/netdata/pull/14654) ([Ancairon](https://github.com/Ancairon)) +- Fix broken links in glossary.md [\#14653](https://github.com/netdata/netdata/pull/14653) ([Ancairon](https://github.com/Ancairon)) +- Fix links [\#14651](https://github.com/netdata/netdata/pull/14651) ([cakrit](https://github.com/cakrit)) +- Fix doc links [\#14650](https://github.com/netdata/netdata/pull/14650) ([cakrit](https://github.com/cakrit)) +- Update guidelines.md [\#14649](https://github.com/netdata/netdata/pull/14649) ([cakrit](https://github.com/cakrit)) +- Update README.md [\#14647](https://github.com/netdata/netdata/pull/14647) ([cakrit](https://github.com/cakrit)) +- Update pi-hole-raspberry-pi.md [\#14644](https://github.com/netdata/netdata/pull/14644) ([tkatsoulas](https://github.com/tkatsoulas)) +- Fix handling of missing release codename on DEB systems. [\#14642](https://github.com/netdata/netdata/pull/14642) ([Ferroin](https://github.com/Ferroin)) +- Update change-metrics-storage.md [\#14641](https://github.com/netdata/netdata/pull/14641) ([cakrit](https://github.com/cakrit)) +- Update change-metrics-storage.md [\#14640](https://github.com/netdata/netdata/pull/14640) ([cakrit](https://github.com/cakrit)) +- Collect additional BTRFS metrics [\#14636](https://github.com/netdata/netdata/pull/14636) ([Dim-P](https://github.com/Dim-P)) +- Fix broken links [\#14634](https://github.com/netdata/netdata/pull/14634) ([Ancairon](https://github.com/Ancairon)) +- Add link to native packages also on the list [\#14633](https://github.com/netdata/netdata/pull/14633) ([cakrit](https://github.com/cakrit)) +- Assorted installer code cleanup. [\#14632](https://github.com/netdata/netdata/pull/14632) ([Ferroin](https://github.com/Ferroin)) +- Re-add link from install page to DEB/RPM package documentation. [\#14631](https://github.com/netdata/netdata/pull/14631) ([Ferroin](https://github.com/Ferroin)) +- Fix broken link [\#14630](https://github.com/netdata/netdata/pull/14630) ([cakrit](https://github.com/cakrit)) +- Fix intermittent permissions issues in some Docker builds. [\#14629](https://github.com/netdata/netdata/pull/14629) ([Ferroin](https://github.com/Ferroin)) +- Update REFERENCE.md [\#14627](https://github.com/netdata/netdata/pull/14627) ([cakrit](https://github.com/cakrit)) +- Make the title metadata H1 in all markdown files [\#14625](https://github.com/netdata/netdata/pull/14625) ([Ancairon](https://github.com/Ancairon)) +- eBPF new charts \(user ring\) [\#14623](https://github.com/netdata/netdata/pull/14623) ([thiagoftsm](https://github.com/thiagoftsm)) +- rename glossary [\#14622](https://github.com/netdata/netdata/pull/14622) ([cakrit](https://github.com/cakrit)) +- Reorg learn 0227 [\#14621](https://github.com/netdata/netdata/pull/14621) ([cakrit](https://github.com/cakrit)) +- Assorted improvements to OpenRC support. [\#14620](https://github.com/netdata/netdata/pull/14620) ([Ferroin](https://github.com/Ferroin)) +- bump go.d.plugin v0.51.2 [\#14618](https://github.com/netdata/netdata/pull/14618) ([ilyam8](https://github.com/ilyam8)) +- fix python version check to work for 3.10 and above [\#14616](https://github.com/netdata/netdata/pull/14616) ([andrewm4894](https://github.com/andrewm4894)) +- fix relative link to anonymous statistics [\#14614](https://github.com/netdata/netdata/pull/14614) ([cakrit](https://github.com/cakrit)) +- fix proxy links in netdata security [\#14613](https://github.com/netdata/netdata/pull/14613) ([cakrit](https://github.com/cakrit)) +- fix links from removed docs [\#14612](https://github.com/netdata/netdata/pull/14612) ([cakrit](https://github.com/cakrit)) +- update go.d.plugin v0.51.1 [\#14611](https://github.com/netdata/netdata/pull/14611) ([ilyam8](https://github.com/ilyam8)) +- Reorg learn 0226 [\#14610](https://github.com/netdata/netdata/pull/14610) ([cakrit](https://github.com/cakrit)) +- Fix links to chart interactions [\#14609](https://github.com/netdata/netdata/pull/14609) ([cakrit](https://github.com/cakrit)) +- Reorg information and add titles [\#14608](https://github.com/netdata/netdata/pull/14608) ([cakrit](https://github.com/cakrit)) +- Update overview.md [\#14607](https://github.com/netdata/netdata/pull/14607) ([cakrit](https://github.com/cakrit)) +- Fix broken links [\#14605](https://github.com/netdata/netdata/pull/14605) ([Ancairon](https://github.com/Ancairon)) +- Misc SSL improvements 3 [\#14602](https://github.com/netdata/netdata/pull/14602) ([MrZammler](https://github.com/MrZammler)) +- Update deployment-strategies.md [\#14601](https://github.com/netdata/netdata/pull/14601) ([cakrit](https://github.com/cakrit)) +- Add deployment strategies [\#14600](https://github.com/netdata/netdata/pull/14600) ([cakrit](https://github.com/cakrit)) +- Add Amazon Linux 2 to CI and platform support. [\#14599](https://github.com/netdata/netdata/pull/14599) ([Ferroin](https://github.com/Ferroin)) +- Replace web server readme with its improved replica [\#14598](https://github.com/netdata/netdata/pull/14598) ([cakrit](https://github.com/cakrit)) +- Update interact-new-charts.md [\#14596](https://github.com/netdata/netdata/pull/14596) ([cakrit](https://github.com/cakrit)) +- Fix context unittest coredump [\#14595](https://github.com/netdata/netdata/pull/14595) ([stelfrag](https://github.com/stelfrag)) +- Delete interact-dashboard-charts [\#14594](https://github.com/netdata/netdata/pull/14594) ([cakrit](https://github.com/cakrit)) +- /api/v2/contexts [\#14592](https://github.com/netdata/netdata/pull/14592) ([ktsaou](https://github.com/ktsaou)) +- Use vector allocation whenever is possible \(eBPF\) [\#14591](https://github.com/netdata/netdata/pull/14591) ([thiagoftsm](https://github.com/thiagoftsm)) +- Change link text to collectors.md [\#14590](https://github.com/netdata/netdata/pull/14590) ([cakrit](https://github.com/cakrit)) +- Add an option to the kickstart script to override distro detection. [\#14589](https://github.com/netdata/netdata/pull/14589) ([Ferroin](https://github.com/Ferroin)) +- Merge security documents [\#14588](https://github.com/netdata/netdata/pull/14588) ([cakrit](https://github.com/cakrit)) +- Prevent core dump when the agent is performing a quick shutdown [\#14587](https://github.com/netdata/netdata/pull/14587) ([stelfrag](https://github.com/stelfrag)) +- Clean host structure [\#14584](https://github.com/netdata/netdata/pull/14584) ([stelfrag](https://github.com/stelfrag)) +- Correct the sidebar position label metdata for learn [\#14583](https://github.com/netdata/netdata/pull/14583) ([cakrit](https://github.com/cakrit)) +- final install reorg for learn [\#14580](https://github.com/netdata/netdata/pull/14580) ([cakrit](https://github.com/cakrit)) +- Learn installation reorg part 2 [\#14579](https://github.com/netdata/netdata/pull/14579) ([cakrit](https://github.com/cakrit)) +- Add link to all installation options [\#14578](https://github.com/netdata/netdata/pull/14578) ([cakrit](https://github.com/cakrit)) +- Reorg learn 2102 1 [\#14577](https://github.com/netdata/netdata/pull/14577) ([cakrit](https://github.com/cakrit)) +- Update README.md [\#14576](https://github.com/netdata/netdata/pull/14576) ([cakrit](https://github.com/cakrit)) +- Reorg learn [\#14575](https://github.com/netdata/netdata/pull/14575) ([cakrit](https://github.com/cakrit)) +- Update static binary readme [\#14574](https://github.com/netdata/netdata/pull/14574) ([cakrit](https://github.com/cakrit)) +- Get update every from page [\#14573](https://github.com/netdata/netdata/pull/14573) ([stelfrag](https://github.com/stelfrag)) +- bump go.d to v0.51.0 [\#14572](https://github.com/netdata/netdata/pull/14572) ([ilyam8](https://github.com/ilyam8)) +- Remove References category from learn [\#14571](https://github.com/netdata/netdata/pull/14571) ([cakrit](https://github.com/cakrit)) +- Fix doc capitalization and remove obsolete section [\#14569](https://github.com/netdata/netdata/pull/14569) ([cakrit](https://github.com/cakrit)) +- Remove obsolete instruction to lower memory usage [\#14568](https://github.com/netdata/netdata/pull/14568) ([cakrit](https://github.com/cakrit)) +- Port ML from C++ to C. [\#14567](https://github.com/netdata/netdata/pull/14567) ([vkalintiris](https://github.com/vkalintiris)) +- Fix broken links in our documentation [\#14565](https://github.com/netdata/netdata/pull/14565) ([Ancairon](https://github.com/Ancairon)) +- /api/v2/data - multi-host/context/instance/dimension/label queries [\#14564](https://github.com/netdata/netdata/pull/14564) ([ktsaou](https://github.com/ktsaou)) +- pandas collector add `read_sql()` support [\#14563](https://github.com/netdata/netdata/pull/14563) ([andrewm4894](https://github.com/andrewm4894)) +- reviewed plans page to be according to latest updates [\#14560](https://github.com/netdata/netdata/pull/14560) ([hugovalente-pm](https://github.com/hugovalente-pm)) +- Fix kickstart link [\#14559](https://github.com/netdata/netdata/pull/14559) ([cakrit](https://github.com/cakrit)) +- Make secure nodes a category landing page [\#14558](https://github.com/netdata/netdata/pull/14558) ([cakrit](https://github.com/cakrit)) +- Add misc landing page and move proxy guides [\#14557](https://github.com/netdata/netdata/pull/14557) ([cakrit](https://github.com/cakrit)) +- Reorg learn 021723 [\#14556](https://github.com/netdata/netdata/pull/14556) ([cakrit](https://github.com/cakrit)) +- Update email notification docs with info about setup in Docker. [\#14555](https://github.com/netdata/netdata/pull/14555) ([Ferroin](https://github.com/Ferroin)) +- Fix broken links in integrations-overview [\#14554](https://github.com/netdata/netdata/pull/14554) ([Ancairon](https://github.com/Ancairon)) +- Add vnodes default configuration file. [\#14553](https://github.com/netdata/netdata/pull/14553) ([Ferroin](https://github.com/Ferroin)) +- RPM: Added elfutils-libelf-devel for build with eBPF [\#14552](https://github.com/netdata/netdata/pull/14552) ([k0ste](https://github.com/k0ste)) +- More reorg learn 021623 [\#14550](https://github.com/netdata/netdata/pull/14550) ([cakrit](https://github.com/cakrit)) +- Update learn path of python plugin readme [\#14549](https://github.com/netdata/netdata/pull/14549) ([cakrit](https://github.com/cakrit)) +- Hide netdata for IoT from learn. [\#14548](https://github.com/netdata/netdata/pull/14548) ([cakrit](https://github.com/cakrit)) +- Reorg markdown files for learn [\#14547](https://github.com/netdata/netdata/pull/14547) ([cakrit](https://github.com/cakrit)) +- Fix two issues with the edit-config script. [\#14545](https://github.com/netdata/netdata/pull/14545) ([Ferroin](https://github.com/Ferroin)) +- Reorganize system directory to better reflect what files are actually used for. [\#14544](https://github.com/netdata/netdata/pull/14544) ([Ferroin](https://github.com/Ferroin)) +- Fix coverity issues [\#14543](https://github.com/netdata/netdata/pull/14543) ([stelfrag](https://github.com/stelfrag)) +- Remove unused config options and functions [\#14542](https://github.com/netdata/netdata/pull/14542) ([stelfrag](https://github.com/stelfrag)) +- Add renamed markdown files [\#14540](https://github.com/netdata/netdata/pull/14540) ([cakrit](https://github.com/cakrit)) +- Fix broken svgs and improve database queries API doc [\#14539](https://github.com/netdata/netdata/pull/14539) ([cakrit](https://github.com/cakrit)) +- Reorganize learn documents under Integrations part 2 [\#14538](https://github.com/netdata/netdata/pull/14538) ([cakrit](https://github.com/cakrit)) +- Roles docs: Add Early Bird and Member role [\#14537](https://github.com/netdata/netdata/pull/14537) ([hugovalente-pm](https://github.com/hugovalente-pm)) +- Fix broken Alma Linux entries in build matrix generation. [\#14536](https://github.com/netdata/netdata/pull/14536) ([Ferroin](https://github.com/Ferroin)) +- Re-index when machine guid changes [\#14535](https://github.com/netdata/netdata/pull/14535) ([MrZammler](https://github.com/MrZammler)) +- Use BoxListItemRegexLink component in docs/quickstart/insfrastructure.md [\#14533](https://github.com/netdata/netdata/pull/14533) ([Ancairon](https://github.com/Ancairon)) +- Update main metric retention docs [\#14530](https://github.com/netdata/netdata/pull/14530) ([cakrit](https://github.com/cakrit)) +- Add Debian 12 to our CI and platform support document. [\#14529](https://github.com/netdata/netdata/pull/14529) ([Ferroin](https://github.com/Ferroin)) +- Update role-based-access.md [\#14528](https://github.com/netdata/netdata/pull/14528) ([cakrit](https://github.com/cakrit)) +- added section to explain impacts on member role [\#14527](https://github.com/netdata/netdata/pull/14527) ([hugovalente-pm](https://github.com/hugovalente-pm)) +- fix setting go.d.plugin capabilities [\#14525](https://github.com/netdata/netdata/pull/14525) ([ilyam8](https://github.com/ilyam8)) +- Simplify parser README.md and add parser files to CMakeLists.txt [\#14523](https://github.com/netdata/netdata/pull/14523) ([stelfrag](https://github.com/stelfrag)) +- Link statically libnetfilter\_acct into our static builds [\#14516](https://github.com/netdata/netdata/pull/14516) ([tkatsoulas](https://github.com/tkatsoulas)) +- Fix broken links in markdown files [\#14513](https://github.com/netdata/netdata/pull/14513) ([Ancairon](https://github.com/Ancairon)) - Make external plugins a category page in learn [\#14511](https://github.com/netdata/netdata/pull/14511) ([cakrit](https://github.com/cakrit)) - Learn integrations category changes [\#14510](https://github.com/netdata/netdata/pull/14510) ([cakrit](https://github.com/cakrit)) - Move collectors under Integrations/Monitoring [\#14509](https://github.com/netdata/netdata/pull/14509) ([cakrit](https://github.com/cakrit)) - Guides and collectors reorg and cleanup part 1 [\#14507](https://github.com/netdata/netdata/pull/14507) ([cakrit](https://github.com/cakrit)) +- replicating gaps [\#14506](https://github.com/netdata/netdata/pull/14506) ([ktsaou](https://github.com/ktsaou)) - More learn reorg/reordering [\#14505](https://github.com/netdata/netdata/pull/14505) ([cakrit](https://github.com/cakrit)) - Revert changes to platform support policy [\#14504](https://github.com/netdata/netdata/pull/14504) ([cakrit](https://github.com/cakrit)) - Top level learn changes [\#14503](https://github.com/netdata/netdata/pull/14503) ([cakrit](https://github.com/cakrit)) @@ -19,7 +327,9 @@ - Reorganize contents of Getting Started [\#14499](https://github.com/netdata/netdata/pull/14499) ([cakrit](https://github.com/cakrit)) - Correct title of contribute to doccumentation [\#14498](https://github.com/netdata/netdata/pull/14498) ([cakrit](https://github.com/cakrit)) - Delete getting-started-overview.md [\#14497](https://github.com/netdata/netdata/pull/14497) ([Ancairon](https://github.com/Ancairon)) +- added Challenge secret and rooms object on the payload [\#14496](https://github.com/netdata/netdata/pull/14496) ([hugovalente-pm](https://github.com/hugovalente-pm)) - Category overview pages [\#14495](https://github.com/netdata/netdata/pull/14495) ([Ancairon](https://github.com/Ancairon)) +- JSON internal API, IEEE754 base64/hex streaming, weights endpoint optimization [\#14493](https://github.com/netdata/netdata/pull/14493) ([ktsaou](https://github.com/ktsaou)) - Fix crash when child connects [\#14492](https://github.com/netdata/netdata/pull/14492) ([stelfrag](https://github.com/stelfrag)) - Plans docs [\#14491](https://github.com/netdata/netdata/pull/14491) ([hugovalente-pm](https://github.com/hugovalente-pm)) - Try making it landing page of getting started directly [\#14489](https://github.com/netdata/netdata/pull/14489) ([cakrit](https://github.com/cakrit)) @@ -39,10 +349,13 @@ - Moved contents of get started to installer readme [\#14467](https://github.com/netdata/netdata/pull/14467) ([cakrit](https://github.com/cakrit)) - Add markdown files in Learn [\#14466](https://github.com/netdata/netdata/pull/14466) ([Ancairon](https://github.com/Ancairon)) - Virtual hosts for data collection [\#14464](https://github.com/netdata/netdata/pull/14464) ([ktsaou](https://github.com/ktsaou)) +- Memory management eBPF [\#14462](https://github.com/netdata/netdata/pull/14462) ([thiagoftsm](https://github.com/thiagoftsm)) - Add contents of packaging/installer/readme.md [\#14461](https://github.com/netdata/netdata/pull/14461) ([cakrit](https://github.com/cakrit)) - Add mention of cloud in next steps UI etc [\#14459](https://github.com/netdata/netdata/pull/14459) ([cakrit](https://github.com/cakrit)) - Fix links and add to learn [\#14458](https://github.com/netdata/netdata/pull/14458) ([cakrit](https://github.com/cakrit)) - Add export for people running their own registry [\#14457](https://github.com/netdata/netdata/pull/14457) ([cakrit](https://github.com/cakrit)) +- Support installing extra packages in Docker images at runtime. [\#14456](https://github.com/netdata/netdata/pull/14456) ([Ferroin](https://github.com/Ferroin)) +- Prevent crash when running '-W createdataset' [\#14455](https://github.com/netdata/netdata/pull/14455) ([MrZammler](https://github.com/MrZammler)) - remove deprecated python.d collectors announced in v1.38.0 [\#14454](https://github.com/netdata/netdata/pull/14454) ([ilyam8](https://github.com/ilyam8)) - Update static build dependencies [\#14450](https://github.com/netdata/netdata/pull/14450) ([tkatsoulas](https://github.com/tkatsoulas)) - do not report dimensions that failed to be queried [\#14447](https://github.com/netdata/netdata/pull/14447) ([ktsaou](https://github.com/ktsaou)) @@ -56,17 +369,15 @@ - Add .NET info [\#14429](https://github.com/netdata/netdata/pull/14429) ([thiagoftsm](https://github.com/thiagoftsm)) - Minor fix, convert metadata of the learn to hidden sections [\#14427](https://github.com/netdata/netdata/pull/14427) ([tkatsoulas](https://github.com/tkatsoulas)) - kickstart.sh: Fix `--release-channel` as `--nightly-channel` options [\#14424](https://github.com/netdata/netdata/pull/14424) ([vobruba-martin](https://github.com/vobruba-martin)) +- Use curl from static builds if no system-wide copy exists. [\#14403](https://github.com/netdata/netdata/pull/14403) ([Ferroin](https://github.com/Ferroin)) - add @andrewm4894 as docs/ codeowner [\#14398](https://github.com/netdata/netdata/pull/14398) ([andrewm4894](https://github.com/andrewm4894)) - Roles permissions docs [\#14391](https://github.com/netdata/netdata/pull/14391) ([hugovalente-pm](https://github.com/hugovalente-pm)) - add note about not needing to have room id [\#14390](https://github.com/netdata/netdata/pull/14390) ([andrewm4894](https://github.com/andrewm4894)) - update the "Install Netdata with Docker" doc [\#14385](https://github.com/netdata/netdata/pull/14385) ([Ancairon](https://github.com/Ancairon)) -- generates dual ksy for njfv2 + fix for padding after page blocks [\#14383](https://github.com/netdata/netdata/pull/14383) ([underhood](https://github.com/underhood)) -- Delete docs/cloud/get-started.mdx [\#14382](https://github.com/netdata/netdata/pull/14382) ([Ancairon](https://github.com/Ancairon)) -- Update the "Kubernetes visualizations" doc [\#14347](https://github.com/netdata/netdata/pull/14347) ([Ancairon](https://github.com/Ancairon)) -- Update the "Deploy Kubernetes monitoring with Netdata" doc [\#14345](https://github.com/netdata/netdata/pull/14345) ([Ancairon](https://github.com/Ancairon)) -- Events docs [\#14341](https://github.com/netdata/netdata/pull/14341) ([hugovalente-pm](https://github.com/hugovalente-pm)) -- Update the "Install Netdata with kickstart.sh" doc [\#14338](https://github.com/netdata/netdata/pull/14338) ([Ancairon](https://github.com/Ancairon)) -- Indicate what root privileges are needed for in kickstart.sh. [\#14314](https://github.com/netdata/netdata/pull/14314) ([Ferroin](https://github.com/Ferroin)) + +## [v1.38.1](https://github.com/netdata/netdata/tree/v1.38.1) (2023-02-13) + +[Full Changelog](https://github.com/netdata/netdata/compare/v1.38.0...v1.38.1) ## [v1.38.0](https://github.com/netdata/netdata/tree/v1.38.0) (2023-02-06) @@ -96,177 +407,6 @@ - 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)) -- add basic dashboard info for NGINX Plus [\#14080](https://github.com/netdata/netdata/pull/14080) ([ilyam8](https://github.com/ilyam8)) -- replication fixes 9 [\#14079](https://github.com/netdata/netdata/pull/14079) ([ktsaou](https://github.com/ktsaou)) -- optimize workers statistics performance [\#14077](https://github.com/netdata/netdata/pull/14077) ([ktsaou](https://github.com/ktsaou)) -- 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) @@ -276,110 +416,6 @@ [Full Changelog](https://github.com/netdata/netdata/compare/v1.36.1...v1.37.0) -**Merged pull requests:** - -- fix spinlock [\#14068](https://github.com/netdata/netdata/pull/14068) ([ktsaou](https://github.com/ktsaou)) -- Sanitize command arguments. [\#14064](https://github.com/netdata/netdata/pull/14064) ([vkalintiris](https://github.com/vkalintiris)) -- Strict control of streaming API keys and MACHINE GUIDs in stream.conf [\#14063](https://github.com/netdata/netdata/pull/14063) ([ktsaou](https://github.com/ktsaou)) -- replication fixes No 8 [\#14061](https://github.com/netdata/netdata/pull/14061) ([ktsaou](https://github.com/ktsaou)) -- fix\(updater\): don't produce any output if static update completed successfully [\#14058](https://github.com/netdata/netdata/pull/14058) ([ilyam8](https://github.com/ilyam8)) -- Add Alpine 3.17 to supported distros. [\#14056](https://github.com/netdata/netdata/pull/14056) ([Ferroin](https://github.com/Ferroin)) -- replication fixes No 7 [\#14053](https://github.com/netdata/netdata/pull/14053) ([ktsaou](https://github.com/ktsaou)) -- bump go.d.plugin to v0.45.0 [\#14052](https://github.com/netdata/netdata/pull/14052) ([ilyam8](https://github.com/ilyam8)) -- Remove eBPF plugin warning [\#14047](https://github.com/netdata/netdata/pull/14047) ([thiagoftsm](https://github.com/thiagoftsm)) -- replication fixes \#6 [\#14046](https://github.com/netdata/netdata/pull/14046) ([ktsaou](https://github.com/ktsaou)) -- fix build on old openssl versions on centos [\#14045](https://github.com/netdata/netdata/pull/14045) ([underhood](https://github.com/underhood)) -- Don't let slow disk plugin thread delay shutdown [\#14044](https://github.com/netdata/netdata/pull/14044) ([MrZammler](https://github.com/MrZammler)) -- minor - wss to point to master instead of branch [\#14043](https://github.com/netdata/netdata/pull/14043) ([underhood](https://github.com/underhood)) -- fix dictionaries unittest [\#14042](https://github.com/netdata/netdata/pull/14042) ([ktsaou](https://github.com/ktsaou)) -- minor - Adds better information in case of SSL error [\#14041](https://github.com/netdata/netdata/pull/14041) ([underhood](https://github.com/underhood)) -- replication fixes \#5 [\#14038](https://github.com/netdata/netdata/pull/14038) ([ktsaou](https://github.com/ktsaou)) -- do not merge duplicate replication requests [\#14037](https://github.com/netdata/netdata/pull/14037) ([ktsaou](https://github.com/ktsaou)) -- Replication fixes \#3 [\#14035](https://github.com/netdata/netdata/pull/14035) ([ktsaou](https://github.com/ktsaou)) -- improve performance of worker utilization statistics [\#14034](https://github.com/netdata/netdata/pull/14034) ([ktsaou](https://github.com/ktsaou)) -- use 2 levels of judy arrays to speed up replication on very busy parents [\#14031](https://github.com/netdata/netdata/pull/14031) ([ktsaou](https://github.com/ktsaou)) -- bump go.d.plugin v0.44.0 [\#14030](https://github.com/netdata/netdata/pull/14030) ([ilyam8](https://github.com/ilyam8)) -- remove retries from SSL [\#14026](https://github.com/netdata/netdata/pull/14026) ([ktsaou](https://github.com/ktsaou)) -- Fix documentation TLS streaming [\#14024](https://github.com/netdata/netdata/pull/14024) ([thiagoftsm](https://github.com/thiagoftsm)) -- streaming compression, query planner and replication fixes [\#14023](https://github.com/netdata/netdata/pull/14023) ([ktsaou](https://github.com/ktsaou)) -- Change relative links to absolute for learn components [\#14015](https://github.com/netdata/netdata/pull/14015) ([tkatsoulas](https://github.com/tkatsoulas)) -- allow statsd tags to modify chart metadata on the fly [\#14014](https://github.com/netdata/netdata/pull/14014) ([ktsaou](https://github.com/ktsaou)) -- minor - silence misleading error [\#14013](https://github.com/netdata/netdata/pull/14013) ([underhood](https://github.com/underhood)) -- Improve eBPF exit [\#14012](https://github.com/netdata/netdata/pull/14012) ([thiagoftsm](https://github.com/thiagoftsm)) -- Change static image urls to app.netdata.cloud in alarm-notify.sh [\#14007](https://github.com/netdata/netdata/pull/14007) ([MrZammler](https://github.com/MrZammler)) -- Fix connection resets on big parents [\#14004](https://github.com/netdata/netdata/pull/14004) ([underhood](https://github.com/underhood)) -- minor typo in the uninstall command [\#14002](https://github.com/netdata/netdata/pull/14002) ([tkatsoulas](https://github.com/tkatsoulas)) -- Revert "New journal disk based indexing for agent memory reduction" [\#14000](https://github.com/netdata/netdata/pull/14000) ([ktsaou](https://github.com/ktsaou)) -- Revert "enable dbengine tiering by default" [\#13999](https://github.com/netdata/netdata/pull/13999) ([ktsaou](https://github.com/ktsaou)) -- fixes MQTT-NG QoS0 [\#13997](https://github.com/netdata/netdata/pull/13997) ([underhood](https://github.com/underhood)) -- remove python.d/nginx\_plus [\#13995](https://github.com/netdata/netdata/pull/13995) ([ilyam8](https://github.com/ilyam8)) -- bump go.d.plugin to v0.43.1 [\#13993](https://github.com/netdata/netdata/pull/13993) ([ilyam8](https://github.com/ilyam8)) -- Add 'funcs' capability [\#13992](https://github.com/netdata/netdata/pull/13992) ([underhood](https://github.com/underhood)) -- added debug info on left-over query targets [\#13990](https://github.com/netdata/netdata/pull/13990) ([ktsaou](https://github.com/ktsaou)) -- replication improvements [\#13989](https://github.com/netdata/netdata/pull/13989) ([ktsaou](https://github.com/ktsaou)) -- use calculator app instead of spreadsheet [\#13981](https://github.com/netdata/netdata/pull/13981) ([andrewm4894](https://github.com/andrewm4894)) -- apps.plugin function processes: keys should not have spaces [\#13980](https://github.com/netdata/netdata/pull/13980) ([ktsaou](https://github.com/ktsaou)) -- dont crash when netdata cannot execute its external plugins [\#13978](https://github.com/netdata/netdata/pull/13978) ([ktsaou](https://github.com/ktsaou)) -- Add \_total suffix to raw increment metrics for remote write [\#13977](https://github.com/netdata/netdata/pull/13977) ([vlvkobal](https://github.com/vlvkobal)) -- add Cassandra icon to dashboard info [\#13975](https://github.com/netdata/netdata/pull/13975) ([ilyam8](https://github.com/ilyam8)) -- Change the db-engine calculator to a read only gsheet [\#13974](https://github.com/netdata/netdata/pull/13974) ([tkatsoulas](https://github.com/tkatsoulas)) -- enable collecting ECC memory errors by default [\#13970](https://github.com/netdata/netdata/pull/13970) ([ilyam8](https://github.com/ilyam8)) -- break active-active loop from replicating non-existing child to each other [\#13968](https://github.com/netdata/netdata/pull/13968) ([ktsaou](https://github.com/ktsaou)) -- document password param for tor collector [\#13966](https://github.com/netdata/netdata/pull/13966) ([andrewm4894](https://github.com/andrewm4894)) -- Fallback to ar and ranlib if llvm-ar and llvm-ranlib are not there [\#13959](https://github.com/netdata/netdata/pull/13959) ([MrZammler](https://github.com/MrZammler)) -- require -DENABLE\_DLSYM=1 to use dlsym\(\) [\#13958](https://github.com/netdata/netdata/pull/13958) ([ktsaou](https://github.com/ktsaou)) -- health/ping: use 'host' label in alerts info [\#13955](https://github.com/netdata/netdata/pull/13955) ([ilyam8](https://github.com/ilyam8)) -- bump go.d.plugin to v0.43.0 [\#13954](https://github.com/netdata/netdata/pull/13954) ([ilyam8](https://github.com/ilyam8)) -- Fix local dashboard cloud links [\#13953](https://github.com/netdata/netdata/pull/13953) ([underhood](https://github.com/underhood)) -- Remove health\_thread\_stop [\#13948](https://github.com/netdata/netdata/pull/13948) ([MrZammler](https://github.com/MrZammler)) -- Provide improved messaging in the kickstart script for existing installs managed by the system package manager. [\#13947](https://github.com/netdata/netdata/pull/13947) ([Ferroin](https://github.com/Ferroin)) -- do not resend charts upstream when chart variables are being updated [\#13946](https://github.com/netdata/netdata/pull/13946) ([ktsaou](https://github.com/ktsaou)) -- recalculate last\_collected\_total [\#13945](https://github.com/netdata/netdata/pull/13945) ([ktsaou](https://github.com/ktsaou)) -- fix chart definition end time\_t printing and parsing [\#13942](https://github.com/netdata/netdata/pull/13942) ([ktsaou](https://github.com/ktsaou)) -- Setup default certificates path [\#13941](https://github.com/netdata/netdata/pull/13941) ([MrZammler](https://github.com/MrZammler)) -- update link to point at demo spaces [\#13939](https://github.com/netdata/netdata/pull/13939) ([andrewm4894](https://github.com/andrewm4894)) -- Statsd dictionaries should be multi-threaded [\#13938](https://github.com/netdata/netdata/pull/13938) ([ktsaou](https://github.com/ktsaou)) -- Fix oracle linux \(eBPF plugin\) [\#13935](https://github.com/netdata/netdata/pull/13935) ([thiagoftsm](https://github.com/thiagoftsm)) -- Update print message on startup [\#13934](https://github.com/netdata/netdata/pull/13934) ([andrewm4894](https://github.com/andrewm4894)) -- Rrddim acquire on replay set [\#13932](https://github.com/netdata/netdata/pull/13932) ([ktsaou](https://github.com/ktsaou)) -- fix compiling without dbengine [\#13931](https://github.com/netdata/netdata/pull/13931) ([ilyam8](https://github.com/ilyam8)) -- bump go.d.plugin to v0.42.1 [\#13930](https://github.com/netdata/netdata/pull/13930) ([ilyam8](https://github.com/ilyam8)) -- Remove pluginsd action param & dead code. [\#13928](https://github.com/netdata/netdata/pull/13928) ([vkalintiris](https://github.com/vkalintiris)) -- Do not force internal collectors to call rrdset\_next. [\#13926](https://github.com/netdata/netdata/pull/13926) ([vkalintiris](https://github.com/vkalintiris)) -- Return accidentaly removed 32bit RPi keep alive fix [\#13925](https://github.com/netdata/netdata/pull/13925) ([underhood](https://github.com/underhood)) -- error\_limit\(\) function to limit number of error lines per instance [\#13924](https://github.com/netdata/netdata/pull/13924) ([ktsaou](https://github.com/ktsaou)) -- fix crash on query plan switch [\#13920](https://github.com/netdata/netdata/pull/13920) ([ktsaou](https://github.com/ktsaou)) -- Enable aclk conversation log even without NETDATA\_INTERNAL CHECKS [\#13917](https://github.com/netdata/netdata/pull/13917) ([MrZammler](https://github.com/MrZammler)) -- add ping dashboard info and alarms [\#13916](https://github.com/netdata/netdata/pull/13916) ([ilyam8](https://github.com/ilyam8)) -- enable dbengine tiering by default [\#13914](https://github.com/netdata/netdata/pull/13914) ([ktsaou](https://github.com/ktsaou)) -- bump go.d.plugin to v0.42.0 [\#13913](https://github.com/netdata/netdata/pull/13913) ([ilyam8](https://github.com/ilyam8)) -- do not free hosts if a change on db mode is not needed [\#13912](https://github.com/netdata/netdata/pull/13912) ([ktsaou](https://github.com/ktsaou)) -- timeframe matching should take into account the update frequency of the chart [\#13911](https://github.com/netdata/netdata/pull/13911) ([ktsaou](https://github.com/ktsaou)) -- WMI Process \(Dashboard, Documentation\) [\#13910](https://github.com/netdata/netdata/pull/13910) ([thiagoftsm](https://github.com/thiagoftsm)) -- feat\(packaging\): add CAP\_NET\_RAW to go.d.plugin [\#13909](https://github.com/netdata/netdata/pull/13909) ([ilyam8](https://github.com/ilyam8)) -- Reference the bash collector for RPi [\#13907](https://github.com/netdata/netdata/pull/13907) ([cakrit](https://github.com/cakrit)) -- Improve intro paragraph [\#13906](https://github.com/netdata/netdata/pull/13906) ([cakrit](https://github.com/cakrit)) -- bump go.d.plugin v0.41.2 [\#13903](https://github.com/netdata/netdata/pull/13903) ([ilyam8](https://github.com/ilyam8)) -- apps.plugin function add max value on all value columns [\#13899](https://github.com/netdata/netdata/pull/13899) ([ktsaou](https://github.com/ktsaou)) -- Reduce unnecessary alert events to the cloud [\#13897](https://github.com/netdata/netdata/pull/13897) ([MrZammler](https://github.com/MrZammler)) -- add pandas collector to collectors.md [\#13895](https://github.com/netdata/netdata/pull/13895) ([andrewm4894](https://github.com/andrewm4894)) -- Fix reading health "enable" from the configuration [\#13894](https://github.com/netdata/netdata/pull/13894) ([stelfrag](https://github.com/stelfrag)) -- fix\(proc.plugin\): fix read retry logic when reading interface speed [\#13893](https://github.com/netdata/netdata/pull/13893) ([ilyam8](https://github.com/ilyam8)) -- Record installation command in telemetry events. [\#13892](https://github.com/netdata/netdata/pull/13892) ([Ferroin](https://github.com/Ferroin)) -- Prompt users about updates/claiming on unknown install types. [\#13890](https://github.com/netdata/netdata/pull/13890) ([Ferroin](https://github.com/Ferroin)) -- tune rrdcontext timings [\#13889](https://github.com/netdata/netdata/pull/13889) ([ktsaou](https://github.com/ktsaou)) -- filtering out charts in context queries, includes them in full\_xxx variables [\#13886](https://github.com/netdata/netdata/pull/13886) ([ktsaou](https://github.com/ktsaou)) -- New journal disk based indexing for agent memory reduction [\#13885](https://github.com/netdata/netdata/pull/13885) ([stelfrag](https://github.com/stelfrag)) -- Fix systemd chart update \(eBPF\) [\#13884](https://github.com/netdata/netdata/pull/13884) ([thiagoftsm](https://github.com/thiagoftsm)) -- apps.plugin function processes cosmetic changes [\#13880](https://github.com/netdata/netdata/pull/13880) ([ktsaou](https://github.com/ktsaou)) -- allow single chart to be filtered in context queries [\#13879](https://github.com/netdata/netdata/pull/13879) ([ktsaou](https://github.com/ktsaou)) -- Add description for TCP WMI [\#13878](https://github.com/netdata/netdata/pull/13878) ([thiagoftsm](https://github.com/thiagoftsm)) -- Use print macros [\#13876](https://github.com/netdata/netdata/pull/13876) ([MrZammler](https://github.com/MrZammler)) -- Suppress ML and dlib ABI warnings [\#13875](https://github.com/netdata/netdata/pull/13875) ([Dim-P](https://github.com/Dim-P)) -- bump go.d.plugin v0.41.1 [\#13874](https://github.com/netdata/netdata/pull/13874) ([ilyam8](https://github.com/ilyam8)) -- Replication of metrics \(gaps filling\) during streaming [\#13873](https://github.com/netdata/netdata/pull/13873) ([vkalintiris](https://github.com/vkalintiris)) -- Don't create a REMOVED alert event after a REMOVED. [\#13871](https://github.com/netdata/netdata/pull/13871) ([MrZammler](https://github.com/MrZammler)) - ## [v1.36.1](https://github.com/netdata/netdata/tree/v1.36.1) (2022-08-15) [Full Changelog](https://github.com/netdata/netdata/compare/v1.36.0...v1.36.1) diff --git a/CMakeLists.txt b/CMakeLists.txt index 263fb812..2447abb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -352,8 +352,7 @@ ENDIF() # Detect ml dependencies file(STRINGS "${CMAKE_SOURCE_DIR}/config.h" DEFINE_ENABLE_ML REGEX "^#define ENABLE_ML 1$") IF(DEFINE_ENABLE_ML MATCHES ".+" AND - EXISTS "${CMAKE_SOURCE_DIR}/ml/dlib/dlib/all/source.cpp" AND - EXISTS "${CMAKE_SOURCE_DIR}/ml/json/single_include/nlohmann/json.hpp") + EXISTS "${CMAKE_SOURCE_DIR}/ml/dlib/dlib/all/source.cpp") set(ENABLE_ML True) list(APPEND NETDATA_COMMON_CFLAGS "-DDLIB_NO_GUI_SUPPORT") list(APPEND NETDATA_COMMON_INCLUDE_DIRS "ml/dlib") @@ -512,6 +511,8 @@ set(LIBNETDATA_FILES libnetdata/string/utf8.h libnetdata/worker_utilization/worker_utilization.c libnetdata/worker_utilization/worker_utilization.h + libnetdata/parser/parser.h + libnetdata/parser/parser.c ) IF(ENABLE_PLUGIN_EBPF) @@ -695,11 +696,6 @@ set(PLUGINSD_PLUGIN_FILES collectors/plugins.d/pluginsd_parser.h ) -set(PARSER_PLUGIN_FILES - parser/parser.c - parser/parser.h - ) - set(REGISTRY_PLUGIN_FILES registry/registry.c registry/registry.h @@ -721,12 +717,21 @@ set(STATSD_PLUGIN_FILES ) set(RRD_PLUGIN_FILES + database/contexts/api_v1.c + database/contexts/api_v2.c + database/contexts/context.c + database/contexts/instance.c + database/contexts/internal.h + database/contexts/metric.c + database/contexts/query_scope.c + database/contexts/query_target.c + database/contexts/rrdcontext.c + database/contexts/rrdcontext.h + database/contexts/worker.c database/rrdcalc.c database/rrdcalc.h database/rrdcalctemplate.c database/rrdcalctemplate.h - database/rrdcontext.c - database/rrdcontext.h database/rrddim.c database/rrddimvar.c database/rrddimvar.h @@ -750,7 +755,7 @@ set(RRD_PLUGIN_FILES database/sqlite/sqlite_metadata.h database/sqlite/sqlite_functions.c database/sqlite/sqlite_functions.h - database/sqlite/sqlite_context.c + database/sqlite/sqlite_context.c database/sqlite/sqlite_context.h database/sqlite/sqlite_db_migration.c database/sqlite/sqlite_db_migration.h @@ -799,8 +804,12 @@ set(WEB_PLUGIN_FILES ) set(API_PLUGIN_FILES + web/api/web_api.c + web/api/web_api.h web/api/web_api_v1.c web/api/web_api_v1.h + web/api/web_api_v2.c + web/api/web_api_v2.h web/api/badges/web_buffer_svg.c web/api/badges/web_buffer_svg.h web/api/exporters/allmetrics.c @@ -854,6 +863,8 @@ set(API_PLUGIN_FILES web/api/formatters/rrdset2json.c web/api/formatters/rrdset2json.h web/api/health/health_cmdapi.c + web/rtc/webrtc.c + web/rtc/webrtc.h ) set(STREAMING_PLUGIN_FILES @@ -877,6 +888,8 @@ set(ACLK_ALWAYS_BUILD aclk/aclk_proxy.h aclk/aclk.c aclk/aclk.h + aclk/aclk_capas.c + aclk/aclk_capas.h ) set(ACLK_FILES @@ -900,8 +913,6 @@ set(ACLK_FILES aclk/aclk_alarm_api.h aclk/aclk_contexts_api.c aclk/aclk_contexts_api.h - aclk/aclk_capas.c - aclk/aclk_capas.h aclk/schema-wrappers/connection.cc aclk/schema-wrappers/connection.h aclk/schema-wrappers/node_connection.cc @@ -925,6 +936,8 @@ set(ACLK_FILES aclk/schema-wrappers/schema_wrappers.h aclk/schema-wrappers/schema_wrapper_utils.cc aclk/schema-wrappers/schema_wrapper_utils.h + aclk/schema-wrappers/agent_cmds.cc + aclk/schema-wrappers/agent_cmds.h aclk/helpers/mqtt_wss_pal.h aclk/helpers/ringbuffer_pal.h ) @@ -1036,24 +1049,10 @@ 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/ad_charts.h + ml/ad_charts.cc ml/Config.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 - ml/SamplesBuffer.h - ml/SamplesBuffer.cc ml/dlib/dlib/all/source.cpp - ml/json/single_include/nlohmann/json.hpp ml/ml.cc ml/ml-private.h ) @@ -1077,7 +1076,6 @@ set(NETDATA_FILES ${WEB_PLUGIN_FILES} ${CLAIM_PLUGIN_FILES} ${SPAWN_PLUGIN_FILES} - ${PARSER_PLUGIN_FILES} ) set(NETDATACLI_FILES @@ -1263,6 +1261,7 @@ set(ACLK_PROTO_DEFS aclk/aclk-schemas/proto/nodeinstance/info/v1/info.proto aclk/aclk-schemas/proto/context/v1/context.proto aclk/aclk-schemas/proto/context/v1/stream.proto + aclk/aclk-schemas/proto/agent/v1/cmds.proto ) PROTOBUF_ACLK_GENERATE_CPP(ACLK_PROTO_BUILT_SRCS ACLK_PROTO_BUILT_HDRS ${ACLK_PROTO_DEFS}) diff --git a/Makefile.am b/Makefile.am index 3eaf1ee8..7d9abd54 100644 --- a/Makefile.am +++ b/Makefile.am @@ -37,6 +37,8 @@ EXTRA_DIST = \ build/m4/ax_c_mallopt.m4 \ build/m4/tcmalloc.m4 \ build/m4/ax_c__generic.m4 \ + build/m4/ax_compiler_vendor.m4 \ + build/m4/ax_cxx_compile_stdcxx.m4 \ ml/dlib \ README.md \ LICENSE \ @@ -73,11 +75,14 @@ dist_noinst_DATA = \ packaging/installer/UPDATE.md \ packaging/jsonc.checksums \ packaging/jsonc.version \ + packaging/yaml.checksums \ + packaging/yaml.version \ packaging/libbpf.checksums \ packaging/libbpf.version \ packaging/protobuf.checksums \ packaging/protobuf.version \ packaging/version \ + database/engine/journalfile_v2.ksy.in \ $(NULL) # until integrated within build @@ -106,7 +111,6 @@ SUBDIRS += \ streaming \ web \ claim \ - parser \ spawn \ $(NULL) @@ -117,6 +121,7 @@ AM_CFLAGS = \ $(OPTIONAL_UUID_CFLAGS) \ $(OPTIONAL_MQTT_CFLAGS) \ $(OPTIONAL_LIBCAP_LIBS) \ + $(OPTIONAL_DATACHANNEL_CFLAGS) \ $(OPTIONAL_IPMIMONITORING_CFLAGS) \ $(OPTIONAL_CUPS_CFLAGS) \ $(OPTIONAL_XENSTAT_CFLAGS) \ @@ -159,6 +164,8 @@ LIBNETDATA_FILES = \ libnetdata/log/log.h \ libnetdata/onewayalloc/onewayalloc.c \ libnetdata/onewayalloc/onewayalloc.h \ + libnetdata/parser/parser.c \ + libnetdata/parser/parser.h \ libnetdata/popen/popen.c \ libnetdata/popen/popen.h \ libnetdata/procfile/procfile.c \ @@ -233,37 +240,18 @@ ML_FILES = \ if ENABLE_ML ML_FILES += \ - ml/ADCharts.h \ - ml/ADCharts.cc \ - ml/Config.h \ + ml/ad_charts.h \ + ml/ad_charts.cc \ 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 \ - ml/SamplesBuffer.h \ - ml/SamplesBuffer.cc \ ml/dlib/dlib/all/source.cpp \ - ml/json/single_include/nlohmann/json.hpp \ - ml/ml.cc \ ml/ml-private.h \ + ml/ml.cc \ $(NULL) # Disable warnings from dlib library ml/dlib/dlib/all/source.$(OBJEXT) : CXXFLAGS += -Wno-sign-compare -Wno-type-limits -Wno-aggressive-loop-optimizations -Wno-stringop-overflow -Wno-psabi # Disable ml warnings -ml/Dimension.$(OBJEXT) : CXXFLAGS += -Wno-psabi -ml/Host.$(OBJEXT) : CXXFLAGS += -Wno-psabi -ml/KMeans.$(OBJEXT) : CXXFLAGS += -Wno-psabi ml/ml.$(OBJEXT) : CXXFLAGS += -Wno-psabi endif @@ -425,12 +413,21 @@ PLUGINSD_PLUGIN_FILES = \ $(NULL) RRD_PLUGIN_FILES = \ + database/contexts/rrdcontext.c \ + database/contexts/rrdcontext.h \ + database/contexts/metric.c \ + database/contexts/instance.c \ + database/contexts/context.c \ + database/contexts/worker.c \ + database/contexts/query_target.c \ + database/contexts/query_scope.c \ + database/contexts/api_v1.c \ + database/contexts/api_v2.c \ + database/contexts/internal.h \ database/rrdcalc.c \ database/rrdcalc.h \ database/rrdcalctemplate.c \ database/rrdcalctemplate.h \ - database/rrdcontext.c \ - database/rrdcontext.h \ database/rrddim.c \ database/rrddimvar.c \ database/rrddimvar.h \ @@ -555,6 +552,20 @@ if ENABLE_DBENGINE database/engine/pdc.c \ database/engine/pdc.h \ $(NULL) + + RRD_PLUGIN_KSY_BUILTFILES = \ + database/engine/journalfile_v2.ksy \ + database/engine/journalfile_v2_virtmemb.ksy \ + $(NULL) + + BUILT_SOURCES += $(RRD_PLUGIN_KSY_BUILTFILES) + CLEANFILES += $(RRD_PLUGIN_KSY_BUILTFILES) + +database/engine/journalfile_v2.ksy: $(abs_top_srcdir)/database/engine/journalfile_v2.ksy.in + m4 $(abs_top_srcdir)/database/engine/journalfile_v2.ksy.in > $@ + +database/engine/journalfile_v2_virtmemb.ksy: $(abs_top_srcdir)/database/engine/journalfile_v2.ksy.in + m4 -DVIRT_MEMBERS $(abs_top_srcdir)/database/engine/journalfile_v2.ksy.in > $@ endif API_PLUGIN_FILES = \ @@ -612,8 +623,12 @@ API_PLUGIN_FILES = \ web/api/formatters/rrdset2json.h \ web/api/health/health_cmdapi.c \ web/api/health/health_cmdapi.h \ + web/api/web_api.c \ + web/api/web_api.h \ web/api/web_api_v1.c \ web/api/web_api_v1.h \ + web/api/web_api_v2.c \ + web/api/web_api_v2.h \ $(NULL) STREAMING_PLUGIN_FILES = \ @@ -647,6 +662,8 @@ STATSD_PLUGIN_FILES = \ $(NULL) WEB_PLUGIN_FILES = \ + web/rtc/webrtc.c \ + web/rtc/webrtc.h \ web/server/web_client.c \ web/server/web_client.h \ web/server/web_server.c \ @@ -662,11 +679,6 @@ CLAIM_FILES = \ claim/claim.h \ $(NULL) -PARSER_FILES = \ - parser/parser.c \ - parser/parser.h \ - $(NULL) - if ENABLE_ACLK ACLK_FILES = \ aclk/aclk_util.c \ @@ -689,8 +701,6 @@ ACLK_FILES = \ aclk/aclk_alarm_api.h \ aclk/aclk_contexts_api.c \ aclk/aclk_contexts_api.h \ - aclk/aclk_capas.c \ - aclk/aclk_capas.h \ aclk/helpers/mqtt_wss_pal.h \ aclk/helpers/ringbuffer_pal.h \ aclk/schema-wrappers/connection.cc \ @@ -716,6 +726,8 @@ ACLK_FILES = \ aclk/schema-wrappers/context_stream.h \ aclk/schema-wrappers/context.cc \ aclk/schema-wrappers/context.h \ + aclk/schema-wrappers/agent_cmds.cc \ + aclk/schema-wrappers/agent_cmds.h \ $(NULL) noinst_LIBRARIES += libmqttwebsockets.a @@ -758,6 +770,7 @@ ACLK_PROTO_DEFINITIONS = \ aclk/aclk-schemas/proto/nodeinstance/info/v1/info.proto \ aclk/aclk-schemas/proto/context/v1/context.proto \ aclk/aclk-schemas/proto/context/v1/stream.proto \ + aclk/aclk-schemas/proto/agent/v1/cmds.proto \ $(NULL) dist_noinst_DATA += $(ACLK_PROTO_DEFINITIONS) @@ -782,6 +795,8 @@ ACLK_PROTO_BUILT_FILES = aclk/aclk-schemas/proto/agent/v1/connection.pb.cc \ aclk/aclk-schemas/proto/context/v1/context.pb.h \ aclk/aclk-schemas/proto/context/v1/stream.pb.cc \ aclk/aclk-schemas/proto/context/v1/stream.pb.h \ + aclk/aclk-schemas/proto/agent/v1/cmds.pb.cc \ + aclk/aclk-schemas/proto/agent/v1/cmds.pb.h \ $(NULL) BUILT_SOURCES += $(ACLK_PROTO_BUILT_FILES) @@ -828,6 +843,10 @@ aclk/aclk-schemas/proto/context/v1/stream.pb.cc \ aclk/aclk-schemas/proto/context/v1/stream.pb.h: aclk/aclk-schemas/proto/context/v1/stream.proto $(PROTOC) -I=aclk/aclk-schemas --cpp_out=$(builddir)/aclk/aclk-schemas $^ +aclk/aclk-schemas/proto/agent/v1/cmds.pb.cc \ +aclk/aclk-schemas/proto/agent/v1/cmds.pb.h: aclk/aclk-schemas/proto/agent/v1/cmds.proto + $(PROTOC) -I=aclk/aclk-schemas --cpp_out=$(builddir)/aclk/aclk-schemas $^ + endif #ENABLE_ACLK ACLK_ALWAYS_BUILD_FILES = \ @@ -836,6 +855,8 @@ ACLK_ALWAYS_BUILD_FILES = \ aclk/aclk_proxy.h \ aclk/aclk.c \ aclk/aclk.h \ + aclk/aclk_capas.c \ + aclk/aclk_capas.h \ $(NULL) SPAWN_PLUGIN_FILES = \ @@ -934,7 +955,6 @@ NETDATA_FILES = \ $(STATSD_PLUGIN_FILES) \ $(WEB_PLUGIN_FILES) \ $(CLAIM_FILES) \ - $(PARSER_FILES) \ $(ACLK_ALWAYS_BUILD_FILES) \ $(ACLK_FILES) \ $(SPAWN_PLUGIN_FILES) \ @@ -974,9 +994,11 @@ NETDATA_COMMON_LIBS = \ $(OPTIONAL_MQTT_LIBS) \ $(OPTIONAL_UV_LIBS) \ $(OPTIONAL_LZ4_LIBS) \ + $(OPTIONAL_DATACHANNEL_LIBS) \ libjudy.a \ $(OPTIONAL_SSL_LIBS) \ $(OPTIONAL_JSONC_LIBS) \ + $(OPTIONAL_YAML_LIBS) \ $(OPTIONAL_ATOMIC_LIBS) \ $(OPTIONAL_DL_LIBS) \ $(NULL) @@ -989,6 +1011,10 @@ if LINK_STATIC_JSONC NETDATA_COMMON_LIBS += $(abs_top_srcdir)/externaldeps/jsonc/libjson-c.a endif +if LINK_STATIC_YAML + NETDATA_COMMON_LIBS += $(abs_top_srcdir)/externaldeps/libyaml/libyaml.a +endif + NETDATACLI_FILES = \ daemon/commands.h \ $(LIBNETDATA_FILES) \ diff --git a/README.md b/README.md index a25438db..c44d4c0a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ It gives you the ability to automatically identify processes, collect and store [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. +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.md), [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). @@ -77,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://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. +- **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.md), 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, diff --git a/aclk/README.md b/aclk/README.md index 5b338dc2..4f469302 100644 --- a/aclk/README.md +++ b/aclk/README.md @@ -1,15 +1,4 @@ - - -# Agent-cloud link (ACLK) +# Agent-Cloud link (ACLK) The Agent-Cloud link (ACLK) is the mechanism responsible for securely connecting a Netdata Agent to your web browser through Netdata Cloud. The ACLK establishes an outgoing secure WebSocket (WSS) connection to Netdata Cloud on port @@ -20,29 +9,22 @@ The Cloud App lives at app.netdata.cloud which currently resolves to the followi - 54.198.178.11 - 44.207.131.212 - 44.196.50.41 - -:::caution -This list of IPs can change without notice, we strongly advise you to whitelist following domains `api.netdata.cloud`, `mqtt.netdata.cloud`, if -this is not an option in your case always verify the current domain resolution (e.g via the `host` command). +> ### Caution +> +>This list of IPs can change without notice, we strongly advise you to whitelist following domains `api.netdata.cloud`, `mqtt.netdata.cloud`, if this is not an option in your case always verify the current domain resolution (e.g via the `host` command). -::: - -For a guide to connecting a node using the ACLK, plus additional troubleshooting and reference information, read our [get -started with Cloud](https://github.com/netdata/netdata/blob/master/docs/cloud/get-started.mdx) guide or the full [connect to Cloud +For a guide to connecting a node using the ACLK, plus additional troubleshooting and reference information, read our [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 you. This is why **we don't store any metric data in 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. - -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://github.com/netdata/netdata/blob/master/docs/cloud/data-privacy.mdx) in the documentation. +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. +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 our [security and privacy design](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md). ## Enable and configure the ACLK diff --git a/aclk/aclk.c b/aclk/aclk.c index e8089722..399bc987 100644 --- a/aclk/aclk.c +++ b/aclk/aclk.c @@ -49,8 +49,6 @@ 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; @@ -249,14 +247,10 @@ void aclk_mqtt_wss_log_cb(mqtt_wss_log_type_t log_type, const char* str) } } -//TODO prevent big buffer on stack -#define RX_MSGLEN_MAX 4096 static void msg_callback(const char *topic, const void *msg, size_t msglen, int qos) { UNUSED(qos); aclk_rcvd_cloud_msgs++; - if (msglen > RX_MSGLEN_MAX) - error("Incoming ACLK message was bigger than MAX of %d and got truncated.", RX_MSGLEN_MAX); debug(D_ACLK, "Got Message From Broker Topic \"%s\" QOS %d", topic, qos); @@ -870,6 +864,11 @@ void aclk_send_node_instances() uuid_unparse_lower(list->host_id, host_id); RRDHOST *host = rrdhost_find_by_guid(host_id); + if (unlikely(!host)) { + freez((void*)node_state_update.node_id); + freez(query); + continue; + } node_state_update.capabilities = aclk_get_node_instance_capas(host); rrdhost_aclk_state_lock(localhost); @@ -927,14 +926,10 @@ static void fill_alert_status_for_host(BUFFER *wb, RRDHOST *host) } buffer_sprintf(wb, "\n\t\tUpdates: %d" - "\n\t\tBatch ID: %"PRIu64 - "\n\t\tLast Acked Seq ID: %"PRIu64 "\n\t\tPending Min Seq ID: %"PRIu64 "\n\t\tPending Max Seq ID: %"PRIu64 "\n\t\tLast Submitted Seq ID: %"PRIu64, status.alert_updates, - status.alerts_batch_id, - status.last_acked_sequence_id, status.pending_min_sequence_id, status.pending_max_sequence_id, status.last_submitted_sequence_id @@ -1042,12 +1037,6 @@ static void fill_alert_status_for_host_json(json_object *obj, RRDHOST *host) json_object *tmp = json_object_new_int(status.alert_updates); json_object_object_add(obj, "updates", tmp); - tmp = json_object_new_int(status.alerts_batch_id); - json_object_object_add(obj, "batch-id", tmp); - - tmp = json_object_new_int(status.last_acked_sequence_id); - json_object_object_add(obj, "last-acked-seq-id", tmp); - tmp = json_object_new_int(status.pending_min_sequence_id); json_object_object_add(obj, "pending-min-seq-id", tmp); @@ -1216,9 +1205,9 @@ void add_aclk_host_labels(void) { #endif } -void aclk_queue_node_info(RRDHOST *host) { - struct aclk_database_worker_config *wc = (struct aclk_database_worker_config *) host->dbsync_worker; - if (likely(wc)) { - wc->node_info_send = 1; - } +void aclk_queue_node_info(RRDHOST *host, bool immediate) +{ + struct aclk_sync_host_config *wc = (struct aclk_sync_host_config *) host->aclk_sync_host_config; + if (likely(wc)) + wc->node_info_send_time = (host == localhost || immediate) ? 1 : now_realtime_sec(); } diff --git a/aclk/aclk.h b/aclk/aclk.h index 56b24add..bd8375fb 100644 --- a/aclk/aclk.h +++ b/aclk/aclk.h @@ -26,8 +26,6 @@ extern time_t aclk_block_until; extern int disconnect_req; -extern int aclk_alert_reloaded; - #ifdef ENABLE_ACLK void *aclk_main(void *ptr); @@ -54,6 +52,6 @@ void aclk_send_bin_msg(char *msg, size_t msg_len, enum aclk_topics subtopic, con char *aclk_state(void); char *aclk_state_json(void); void add_aclk_host_labels(void); -void aclk_queue_node_info(RRDHOST *host); +void aclk_queue_node_info(RRDHOST *host, bool immediate); #endif /* ACLK_H */ diff --git a/aclk/aclk_alarm_api.c b/aclk/aclk_alarm_api.c index 7df51a7b..664671f7 100644 --- a/aclk/aclk_alarm_api.c +++ b/aclk/aclk_alarm_api.c @@ -8,12 +8,12 @@ #include "aclk.h" -void aclk_send_alarm_log_health(struct alarm_log_health *log_health) +void aclk_send_provide_alarm_checkpoint(struct alarm_checkpoint *checkpoint) { - aclk_query_t query = aclk_query_new(ALARM_LOG_HEALTH); - query->data.bin_payload.payload = generate_alarm_log_health(&query->data.bin_payload.size, log_health); - query->data.bin_payload.topic = ACLK_TOPICID_ALARM_HEALTH; - query->data.bin_payload.msg_name = "AlarmLogHealth"; + aclk_query_t query = aclk_query_new(ALARM_PROVIDE_CHECKPOINT); + query->data.bin_payload.payload = generate_alarm_checkpoint(&query->data.bin_payload.size, checkpoint); + query->data.bin_payload.topic = ACLK_TOPICID_ALARM_CHECKPOINT; + query->data.bin_payload.msg_name = "AlarmCheckpoint"; QUEUE_IF_PAYLOAD_PRESENT(query); } diff --git a/aclk/aclk_alarm_api.h b/aclk/aclk_alarm_api.h index e3fa92b5..4d9d9447 100644 --- a/aclk/aclk_alarm_api.h +++ b/aclk/aclk_alarm_api.h @@ -6,7 +6,7 @@ #include "../daemon/common.h" #include "schema-wrappers/schema_wrappers.h" -void aclk_send_alarm_log_health(struct alarm_log_health *log_health); +void aclk_send_provide_alarm_checkpoint(struct alarm_checkpoint *checkpoint); void aclk_send_alarm_log_entry(struct alarm_log_entry *log_entry); void aclk_send_provide_alarm_cfg(struct provide_alarm_configuration *cfg); void aclk_send_alarm_snapshot(alarm_snapshot_proto_ptr_t snapshot); diff --git a/aclk/aclk_capas.c b/aclk/aclk_capas.c index 290e7d8f..55f6fd3b 100644 --- a/aclk/aclk_capas.c +++ b/aclk/aclk_capas.c @@ -7,13 +7,16 @@ const struct capability *aclk_get_agent_capas() { static struct capability agent_capabilities[] = { - { .name = "json", .version = 2, .enabled = 0 }, - { .name = "proto", .version = 1, .enabled = 1 }, - { .name = "ml", .version = 0, .enabled = 0 }, - { .name = "mc", .version = 0, .enabled = 0 }, - { .name = "ctx", .version = 1, .enabled = 1 }, - { .name = "funcs", .version = 1, .enabled = 1 }, - { .name = NULL, .version = 0, .enabled = 0 } + { .name = "json", .version = 2, .enabled = 0 }, + { .name = "proto", .version = 1, .enabled = 1 }, + { .name = "ml", .version = 0, .enabled = 0 }, + { .name = "mc", .version = 0, .enabled = 0 }, + { .name = "ctx", .version = 1, .enabled = 1 }, + { .name = "funcs", .version = 1, .enabled = 1 }, + { .name = "http_api_v2", .version = 1, .enabled = 1 }, + { .name = "health", .version = 1, .enabled = 0 }, + { .name = "req_cancel", .version = 1, .enabled = 1 }, + { .name = NULL, .version = 0, .enabled = 0 } }; agent_capabilities[2].version = ml_capable() ? 1 : 0; agent_capabilities[2].enabled = ml_enabled(localhost); @@ -21,27 +24,34 @@ const struct capability *aclk_get_agent_capas() agent_capabilities[3].version = enable_metric_correlations ? metric_correlations_version : 0; agent_capabilities[3].enabled = enable_metric_correlations; + agent_capabilities[7].enabled = localhost->health.health_enabled; + return agent_capabilities; } struct capability *aclk_get_node_instance_capas(RRDHOST *host) { struct capability ni_caps[] = { - { .name = "proto", .version = 1, .enabled = 1 }, - { .name = "ml", .version = ml_capable(), .enabled = ml_enabled(host) }, + { .name = "proto", .version = 1, .enabled = 1 }, + { .name = "ml", .version = ml_capable(), .enabled = ml_enabled(host) }, { .name = "mc", .version = enable_metric_correlations ? metric_correlations_version : 0, .enabled = enable_metric_correlations }, - { .name = "ctx", .version = 1, .enabled = 1 }, - { .name = "funcs", .version = 0, .enabled = 0 }, - { .name = NULL, .version = 0, .enabled = 0 } + { .name = "ctx", .version = 1, .enabled = 1 }, + { .name = "funcs", .version = 0, .enabled = 0 }, + { .name = "http_api_v2", .version = 2, .enabled = 1 }, + { .name = "health", .version = 1, .enabled = host->health.health_enabled }, + { .name = "req_cancel", .version = 1, .enabled = 1 }, + { .name = NULL, .version = 0, .enabled = 0 } }; - if (host == localhost || (host->receiver && stream_has_capability(host->receiver, STREAM_CAP_FUNCTIONS))) { - ni_caps[4].version = 1; - ni_caps[4].enabled = 1; - } struct capability *ret = mallocz(sizeof(ni_caps)); memcpy(ret, ni_caps, sizeof(ni_caps)); + + if (host == localhost || (host->receiver && stream_has_capability(host->receiver, STREAM_CAP_FUNCTIONS))) { + ret[4].version = 1; + ret[4].enabled = 1; + } + return ret; } diff --git a/aclk/aclk_query.c b/aclk/aclk_query.c index 9eced081..46d1e1e5 100644 --- a/aclk/aclk_query.c +++ b/aclk/aclk_query.c @@ -3,67 +3,97 @@ #include "aclk_query.h" #include "aclk_stats.h" #include "aclk_tx_msgs.h" +#include "../../web/server/web_client_cache.h" #define WEB_HDR_ACCEPT_ENC "Accept-Encoding:" +#define ACLK_MAX_WEB_RESPONSE_SIZE (30 * 1024 * 1024) pthread_cond_t query_cond_wait = PTHREAD_COND_INITIALIZER; pthread_mutex_t query_lock_wait = PTHREAD_MUTEX_INITIALIZER; #define QUERY_THREAD_LOCK pthread_mutex_lock(&query_lock_wait) #define QUERY_THREAD_UNLOCK pthread_mutex_unlock(&query_lock_wait) -static usec_t aclk_web_api_v1_request(RRDHOST *host, struct web_client *w, char *url) -{ - usec_t t; +struct pending_req_list { + const char *msg_id; + uint32_t hash; - t = now_monotonic_high_precision_usec(); - w->response.code = web_client_api_request_v1(host, w, url); - t = now_monotonic_high_precision_usec() - t; + int canceled; - if (aclk_stats_enabled) { - ACLK_STATS_LOCK; - aclk_metrics_per_sample.cloud_q_process_total += t; - aclk_metrics_per_sample.cloud_q_process_count++; - if (aclk_metrics_per_sample.cloud_q_process_max < t) - aclk_metrics_per_sample.cloud_q_process_max = t; - ACLK_STATS_UNLOCK; - } + struct pending_req_list *next; +}; + +static struct pending_req_list *pending_req_list_head = NULL; +static pthread_mutex_t pending_req_list_lock = PTHREAD_MUTEX_INITIALIZER; - return t; +static struct pending_req_list *pending_req_list_add(const char *msg_id) +{ + struct pending_req_list *new = callocz(1, sizeof(struct pending_req_list)); + new->msg_id = msg_id; + new->hash = simple_hash(msg_id); + + pthread_mutex_lock(&pending_req_list_lock); + new->next = pending_req_list_head; + pending_req_list_head = new; + pthread_mutex_unlock(&pending_req_list_lock); + return new; } -static RRDHOST *node_id_2_rrdhost(const char *node_id) +void pending_req_list_rm(const char *msg_id) { - int res; - uuid_t node_id_bin, host_id_bin; + uint32_t hash = simple_hash(msg_id); + struct pending_req_list *prev = NULL; - RRDHOST *host = find_host_by_node_id((char *)node_id); - if (host) - return host; + pthread_mutex_lock(&pending_req_list_lock); + struct pending_req_list *curr = pending_req_list_head; - char host_id[UUID_STR_LEN]; - if (uuid_parse(node_id, node_id_bin)) { - error("Couldn't parse UUID %s", node_id); - return NULL; + while (curr) { + if (curr->hash == hash && strcmp(curr->msg_id, msg_id) == 0) { + if (prev) + prev->next = curr->next; + else + pending_req_list_head = curr->next; + + freez(curr); + break; + } + + prev = curr; + curr = curr->next; } - if ((res = get_host_id(&node_id_bin, &host_id_bin))) { - error("node not found rc=%d", res); - return NULL; + pthread_mutex_unlock(&pending_req_list_lock); +} + +int mark_pending_req_cancelled(const char *msg_id) +{ + uint32_t hash = simple_hash(msg_id); + + pthread_mutex_lock(&pending_req_list_lock); + struct pending_req_list *curr = pending_req_list_head; + + while (curr) { + if (curr->hash == hash && strcmp(curr->msg_id, msg_id) == 0) { + curr->canceled = 1; + pthread_mutex_unlock(&pending_req_list_lock); + return 0; + } + + curr = curr->next; } - uuid_unparse_lower(host_id_bin, host_id); - return rrdhost_find_by_guid(host_id); + pthread_mutex_unlock(&pending_req_list_lock); + return 1; } -#define NODE_ID_QUERY "/node/" -// TODO this function should be quarantied and written nicely -// lots of skeletons from initial ACLK Legacy impl. -// quick and dirty from the start -static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query) +static bool aclk_web_client_interrupt_cb(struct web_client *w __maybe_unused, void *data) { + struct pending_req_list *req = (struct pending_req_list *)data; + return req->canceled; +} + +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, &netdata_buffers_statistics.buffers_aclk); - RRDHOST *query_host = localhost; + size_t size = 0; + size_t sent = 0; #ifdef NETDATA_WITH_ZLIB int z_ret; @@ -71,73 +101,55 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query) 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, &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() + struct web_client *w = web_client_get_from_cache(); w->acl = WEB_CLIENT_ACL_ACLK; + w->mode = WEB_CLIENT_MODE_GET; + w->timings.tv_in = query->created_tv; - buffer_strcat(log_buffer, query->data.http_api_v2.query); - size_t size = 0; - size_t sent = 0; - w->tv_in = query->created_tv; - now_realtime_timeval(&w->tv_ready); - - if (query->timeout) { - int in_queue = (int) (dt_usec(&w->tv_in, &w->tv_ready) / 1000); - if (in_queue > query->timeout) { - log_access("QUERY CANCELED: QUEUE TIME EXCEEDED %d ms (LIMIT %d ms)", in_queue, query->timeout); - retval = 1; - w->response.code = HTTP_RESP_BACKEND_FETCH_FAILED; - aclk_http_msg_v2_err(query_thr->client, query->callback_topic, query->msg_id, w->response.code, CLOUD_EC_SND_TIMEOUT, CLOUD_EMSG_SND_TIMEOUT, NULL, 0); - goto cleanup; - } + w->interrupt.callback = aclk_web_client_interrupt_cb; + w->interrupt.callback_data = pending_req_list_add(query->msg_id); + + usec_t t; + web_client_timeout_checkpoint_set(w, query->timeout); + if(web_client_timeout_checkpoint_and_check(w, &t)) { + log_access("QUERY CANCELED: QUEUE TIME EXCEEDED %llu ms (LIMIT %d ms)", t / USEC_PER_MS, query->timeout); + retval = 1; + w->response.code = HTTP_RESP_BACKEND_FETCH_FAILED; + aclk_http_msg_v2_err(query_thr->client, query->callback_topic, query->msg_id, w->response.code, CLOUD_EC_SND_TIMEOUT, CLOUD_EMSG_SND_TIMEOUT, NULL, 0); + goto cleanup; } - if (!strncmp(query->data.http_api_v2.query, NODE_ID_QUERY, strlen(NODE_ID_QUERY))) { - char *node_uuid = query->data.http_api_v2.query + strlen(NODE_ID_QUERY); - char nodeid[UUID_STR_LEN]; - if (strlen(node_uuid) < (UUID_STR_LEN - 1)) { - error_report(CLOUD_EMSG_MALFORMED_NODE_ID); - retval = 1; - w->response.code = 404; - aclk_http_msg_v2_err(query_thr->client, query->callback_topic, query->msg_id, w->response.code, CLOUD_EC_MALFORMED_NODE_ID, CLOUD_EMSG_MALFORMED_NODE_ID, NULL, 0); - goto cleanup; - } - strncpyz(nodeid, node_uuid, UUID_STR_LEN - 1); - - query_host = node_id_2_rrdhost(nodeid); - if (!query_host) { - error_report("Host with node_id \"%s\" not found! Returning 404 to Cloud!", nodeid); - retval = 1; - w->response.code = 404; - aclk_http_msg_v2_err(query_thr->client, query->callback_topic, query->msg_id, w->response.code, CLOUD_EC_NODE_NOT_FOUND, CLOUD_EMSG_NODE_NOT_FOUND, NULL, 0); - goto cleanup; - } + web_client_decode_path_and_query_string(w, query->data.http_api_v2.query); + char *path = (char *)buffer_tostring(w->url_path_decoded); + + if (aclk_stats_enabled) { + char *url_path_endpoint = strrchr(path, '/'); + ACLK_STATS_LOCK; + int stat_idx = aclk_cloud_req_http_type_to_idx(url_path_endpoint ? url_path_endpoint + 1 : "other"); + aclk_metrics_per_sample.cloud_req_http_by_type[stat_idx]++; + ACLK_STATS_UNLOCK; } - char *mysep = strchr(query->data.http_api_v2.query, '?'); - if (mysep) { - url_decode_r(w->decoded_query_string, mysep, NETDATA_WEB_REQUEST_URL_SIZE + 1); - *mysep = '\0'; - } else - url_decode_r(w->decoded_query_string, query->data.http_api_v2.query, NETDATA_WEB_REQUEST_URL_SIZE + 1); + w->response.code = web_client_api_request_with_node_selection(localhost, w, path); + web_client_timeout_checkpoint_response_ready(w, &t); - mysep = strrchr(query->data.http_api_v2.query, '/'); + if(buffer_strlen(w->response.data) > ACLK_MAX_WEB_RESPONSE_SIZE) { + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "response is too big"); + w->response.data->content_type = CT_TEXT_PLAIN; + w->response.code = HTTP_RESP_CONTENT_TOO_LONG; + } if (aclk_stats_enabled) { ACLK_STATS_LOCK; - int stat_idx = aclk_cloud_req_http_type_to_idx(mysep ? mysep + 1 : "other"); - aclk_metrics_per_sample.cloud_req_http_by_type[stat_idx]++; + aclk_metrics_per_sample.cloud_q_process_total += t; + aclk_metrics_per_sample.cloud_q_process_count++; + if (aclk_metrics_per_sample.cloud_q_process_max < t) + aclk_metrics_per_sample.cloud_q_process_max = t; ACLK_STATS_UNLOCK; } - // execute the query - t = aclk_web_api_v1_request(query_host, w, mysep ? mysep + 1 : "noop"); - size = (w->mode == WEB_CLIENT_MODE_FILECOPY) ? w->response.rlen : w->response.data->len; + size = w->response.data->len; sent = size; #ifdef NETDATA_WITH_ZLIB @@ -152,8 +164,8 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query) w->response.zstream.zfree = Z_NULL; w->response.zstream.opaque = Z_NULL; if(deflateInit2(&w->response.zstream, web_gzip_level, Z_DEFLATED, 15 + 16, 8, web_gzip_strategy) == Z_OK) { - w->response.zinitialized = 1; - w->response.zoutput = 1; + w->response.zinitialized = true; + w->response.zoutput = true; } else error("Failed to initialize zlib. Proceeding without compression."); } @@ -189,10 +201,10 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query) } #endif - w->response.data->date = w->tv_ready.tv_sec; + w->response.data->date = w->timings.tv_ready.tv_sec; web_client_build_http_header(w); local_buffer = buffer_create(NETDATA_WEB_RESPONSE_INITIAL_SIZE, &netdata_buffers_statistics.buffers_aclk); - local_buffer->contenttype = CT_APPLICATION_JSON; + local_buffer->content_type = CT_APPLICATION_JSON; buffer_strcat(local_buffer, w->response.header_output->buffer); @@ -217,7 +229,7 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query) struct timeval tv; cleanup: - now_realtime_timeval(&tv); + now_monotonic_high_precision_timeval(&tv); log_access("%llu: %d '[ACLK]:%d' '%s' (sent/all = %zu/%zu bytes %0.0f%%, prep/sent/total = %0.2f/%0.2f/%0.2f ms) %d '%s'", w->id , gettid() @@ -226,24 +238,21 @@ cleanup: , sent , size , size > sent ? -(((size - sent) / (double)size) * 100.0) : ((size > 0) ? (((sent - size ) / (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 + , dt_usec(&w->timings.tv_ready, &w->timings.tv_in) / 1000.0 + , dt_usec(&tv, &w->timings.tv_ready) / 1000.0 + , dt_usec(&tv, &w->timings.tv_in) / 1000.0 , w->response.code - , strip_control_characters((char *)buffer_tostring(log_buffer)) + , strip_control_characters((char *)buffer_tostring(w->url_as_received)) ); + web_client_release_to_cache(w); + + pending_req_list_rm(query->msg_id); + #ifdef NETDATA_WITH_ZLIB - if(w->response.zinitialized) - deflateEnd(&w->response.zstream); buffer_free(z_buffer); #endif - buffer_free(w->response.data); - buffer_free(w->response.header); - buffer_free(w->response.header_output); - freez(w); buffer_free(local_buffer); - buffer_free(log_buffer); return retval; } @@ -257,19 +266,19 @@ static int send_bin_msg(struct aclk_query_thread *query_thr, aclk_query_t query) const char *aclk_query_get_name(aclk_query_type_t qt, int unknown_ok) { switch (qt) { - case HTTP_API_V2: return "http_api_request_v2"; - case REGISTER_NODE: return "register_node"; - case NODE_STATE_UPDATE: return "node_state_update"; - case CHART_DIMS_UPDATE: return "chart_and_dim_update"; - case CHART_CONFIG_UPDATED: return "chart_config_updated"; - case CHART_RESET: return "reset_chart_messages"; - case RETENTION_UPDATED: return "update_retention_info"; - case UPDATE_NODE_INFO: return "update_node_info"; - case ALARM_LOG_HEALTH: return "alarm_log_health"; - case ALARM_PROVIDE_CFG: return "provide_alarm_config"; - case ALARM_SNAPSHOT: return "alarm_snapshot"; - case UPDATE_NODE_COLLECTORS: return "update_node_collectors"; - case PROTO_BIN_MESSAGE: return "generic_binary_proto_message"; + case HTTP_API_V2: return "http_api_request_v2"; + case REGISTER_NODE: return "register_node"; + case NODE_STATE_UPDATE: return "node_state_update"; + case CHART_DIMS_UPDATE: return "chart_and_dim_update"; + case CHART_CONFIG_UPDATED: return "chart_config_updated"; + case CHART_RESET: return "reset_chart_messages"; + case RETENTION_UPDATED: return "update_retention_info"; + case UPDATE_NODE_INFO: return "update_node_info"; + case ALARM_PROVIDE_CHECKPOINT: return "alarm_checkpoint"; + case ALARM_PROVIDE_CFG: return "provide_alarm_config"; + case ALARM_SNAPSHOT: return "alarm_snapshot"; + case UPDATE_NODE_COLLECTORS: return "update_node_collectors"; + case PROTO_BIN_MESSAGE: return "generic_binary_proto_message"; default: if (!unknown_ok) error_report("Unknown query type used %d", (int) qt); diff --git a/aclk/aclk_query.h b/aclk/aclk_query.h index c006b013..dbe6f9e5 100644 --- a/aclk/aclk_query.h +++ b/aclk/aclk_query.h @@ -33,4 +33,6 @@ void aclk_query_threads_cleanup(struct aclk_query_threads *query_threads); const char *aclk_query_get_name(aclk_query_type_t qt, int unknown_ok); +int mark_pending_req_cancelled(const char *msg_id); + #endif //NETDATA_AGENT_CLOUD_LINK_H diff --git a/aclk/aclk_query_queue.c b/aclk/aclk_query_queue.c index e7cad5de..78a906d9 100644 --- a/aclk/aclk_query_queue.c +++ b/aclk/aclk_query_queue.c @@ -20,7 +20,7 @@ static struct aclk_query_queue { static inline int _aclk_queue_query(aclk_query_t query) { - now_realtime_timeval(&query->created_tv); + now_monotonic_high_precision_timeval(&query->created_tv); query->created = now_realtime_usec(); ACLK_QUEUE_LOCK; diff --git a/aclk/aclk_query_queue.h b/aclk/aclk_query_queue.h index ab94b638..944fc079 100644 --- a/aclk/aclk_query_queue.h +++ b/aclk/aclk_query_queue.h @@ -19,7 +19,7 @@ typedef enum { CHART_RESET, RETENTION_UPDATED, UPDATE_NODE_INFO, - ALARM_LOG_HEALTH, + ALARM_PROVIDE_CHECKPOINT, ALARM_PROVIDE_CFG, ALARM_SNAPSHOT, UPDATE_NODE_COLLECTORS, diff --git a/aclk/aclk_rx_msgs.c b/aclk/aclk_rx_msgs.c index 104fbcb3..60bff9ba 100644 --- a/aclk/aclk_rx_msgs.c +++ b/aclk/aclk_rx_msgs.c @@ -6,6 +6,7 @@ #include "aclk_query_queue.h" #include "aclk.h" #include "aclk_capas.h" +#include "aclk_query.h" #include "schema-wrappers/proto_2_json.h" @@ -272,13 +273,12 @@ int create_node_instance_result(const char *msg, size_t msg_len) .live = 0, .queryable = 1, .session_id = aclk_session_newarch, - .node_id = res.node_id + .node_id = res.node_id, + .capabilities = NULL }; RRDHOST *host = rrdhost_find_by_guid(res.machine_guid); - if (host) { - // not all host must have RRDHOST struct created for them - // if they never connected during runtime of agent + if (likely(host)) { if (host == localhost) { node_state_update.live = 1; node_state_update.hops = 0; @@ -286,10 +286,9 @@ int create_node_instance_result(const char *msg, size_t msg_len) node_state_update.live = (!rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN)); node_state_update.hops = host->system_info->hops; } + node_state_update.capabilities = aclk_get_node_instance_capas(host); } - node_state_update.capabilities = aclk_get_node_instance_capas(host); - rrdhost_aclk_state_lock(localhost); node_state_update.claim_id = localhost->aclk_state.claimed_id; query->data.bin_payload.payload = generate_node_instance_connection(&query->data.bin_payload.size, &node_state_update); @@ -341,25 +340,27 @@ int update_chart_configs(const char *msg, size_t msg_len) int start_alarm_streaming(const char *msg, size_t msg_len) { struct start_alarm_streaming res = parse_start_alarm_streaming(msg, msg_len); - if (!res.node_id || !res.batch_id) { + if (!res.node_id) { error("Error parsing StartAlarmStreaming"); - freez(res.node_id); return 1; } - aclk_start_alert_streaming(res.node_id, res.batch_id, res.start_seq_id); + aclk_start_alert_streaming(res.node_id, res.resets); freez(res.node_id); return 0; } -int send_alarm_log_health(const char *msg, size_t msg_len) +int send_alarm_checkpoint(const char *msg, size_t msg_len) { - char *node_id = parse_send_alarm_log_health(msg, msg_len); - if (!node_id) { - error("Error parsing SendAlarmLogHealth"); + struct send_alarm_checkpoint sac = parse_send_alarm_checkpoint(msg, msg_len); + if (!sac.node_id || !sac.claim_id) { + error("Error parsing SendAlarmCheckpoint"); + freez(sac.node_id); + freez(sac.claim_id); return 1; } - aclk_send_alarm_health_log(node_id); - freez(node_id); + aclk_send_alarm_checkpoint(sac.node_id, sac.claim_id); + freez(sac.node_id); + freez(sac.claim_id); return 0; } @@ -379,12 +380,12 @@ int send_alarm_configuration(const char *msg, size_t msg_len) int send_alarm_snapshot(const char *msg, size_t msg_len) { struct send_alarm_snapshot *sas = parse_send_alarm_snapshot(msg, msg_len); - if (!sas->node_id || !sas->claim_id) { + if (!sas->node_id || !sas->claim_id || !sas->snapshot_uuid) { error("Error parsing SendAlarmSnapshot"); destroy_send_alarm_snapshot(sas); return 1; } - aclk_process_send_alarm_snapshot(sas->node_id, sas->claim_id, sas->snapshot_id, sas->sequence_id); + aclk_process_send_alarm_snapshot(sas->node_id, sas->claim_id, sas->snapshot_uuid); destroy_send_alarm_snapshot(sas); return 0; } @@ -446,6 +447,23 @@ int stop_streaming_contexts(const char *msg, size_t msg_len) return 0; } +int cancel_pending_req(const char *msg, size_t msg_len) +{ + struct aclk_cancel_pending_req cmd; + if(parse_cancel_pending_req(msg, msg_len, &cmd)) { + error_report("Error parsing CancelPendingReq"); + return 1; + } + + log_access("ACLK CancelPendingRequest REQ: %s, cloud trace-id: %s", cmd.request_id, cmd.trace_id); + + if (mark_pending_req_cancelled(cmd.request_id)) + error_report("CancelPending Request for %s failed. No such pending request.", cmd.request_id); + + free_cancel_pending_req(&cmd); + return 0; +} + typedef struct { const char *name; simple_hash_t name_hash; @@ -460,12 +478,13 @@ new_cloud_rx_msg_t rx_msgs[] = { { .name = "ChartsAndDimensionsAck", .name_hash = 0, .fnc = charts_and_dimensions_ack }, { .name = "UpdateChartConfigs", .name_hash = 0, .fnc = update_chart_configs }, { .name = "StartAlarmStreaming", .name_hash = 0, .fnc = start_alarm_streaming }, - { .name = "SendAlarmLogHealth", .name_hash = 0, .fnc = send_alarm_log_health }, + { .name = "SendAlarmCheckpoint", .name_hash = 0, .fnc = send_alarm_checkpoint }, { .name = "SendAlarmConfiguration", .name_hash = 0, .fnc = send_alarm_configuration }, { .name = "SendAlarmSnapshot", .name_hash = 0, .fnc = send_alarm_snapshot }, { .name = "DisconnectReq", .name_hash = 0, .fnc = handle_disconnect_req }, { .name = "ContextsCheckpoint", .name_hash = 0, .fnc = contexts_checkpoint }, { .name = "StopStreamingContexts", .name_hash = 0, .fnc = stop_streaming_contexts }, + { .name = "CancelPendingRequest", .name_hash = 0, .fnc = cancel_pending_req }, { .name = NULL, .name_hash = 0, .fnc = NULL }, }; diff --git a/aclk/aclk_util.c b/aclk/aclk_util.c index ebf428ff..7d03f97f 100644 --- a/aclk/aclk_util.c +++ b/aclk/aclk_util.c @@ -120,10 +120,10 @@ struct topic_name { { .id = ACLK_TOPICID_CHART_RESET, .name = "reset-charts" }, { .id = ACLK_TOPICID_RETENTION_UPDATED, .name = "chart-retention-updated" }, { .id = ACLK_TOPICID_NODE_INFO, .name = "node-instance-info" }, - { .id = ACLK_TOPICID_ALARM_LOG, .name = "alarm-log" }, - { .id = ACLK_TOPICID_ALARM_HEALTH, .name = "alarm-health" }, + { .id = ACLK_TOPICID_ALARM_LOG, .name = "alarm-log-v2" }, + { .id = ACLK_TOPICID_ALARM_CHECKPOINT, .name = "alarm-checkpoint" }, { .id = ACLK_TOPICID_ALARM_CONFIG, .name = "alarm-config" }, - { .id = ACLK_TOPICID_ALARM_SNAPSHOT, .name = "alarm-snapshot" }, + { .id = ACLK_TOPICID_ALARM_SNAPSHOT, .name = "alarm-snapshot-v2" }, { .id = ACLK_TOPICID_NODE_COLLECTORS, .name = "node-instance-collectors" }, { .id = ACLK_TOPICID_CTXS_SNAPSHOT, .name = "contexts-snapshot" }, { .id = ACLK_TOPICID_CTXS_UPDATED, .name = "contexts-updated" }, @@ -146,7 +146,7 @@ enum aclk_topics compulsory_topics[] = { ACLK_TOPICID_RETENTION_UPDATED, ACLK_TOPICID_NODE_INFO, ACLK_TOPICID_ALARM_LOG, - ACLK_TOPICID_ALARM_HEALTH, + ACLK_TOPICID_ALARM_CHECKPOINT, ACLK_TOPICID_ALARM_CONFIG, ACLK_TOPICID_ALARM_SNAPSHOT, ACLK_TOPICID_NODE_COLLECTORS, diff --git a/aclk/aclk_util.h b/aclk/aclk_util.h index 76dc8cad..6b7e4e9c 100644 --- a/aclk/aclk_util.h +++ b/aclk/aclk_util.h @@ -85,7 +85,7 @@ enum aclk_topics { ACLK_TOPICID_RETENTION_UPDATED = 12, ACLK_TOPICID_NODE_INFO = 13, ACLK_TOPICID_ALARM_LOG = 14, - ACLK_TOPICID_ALARM_HEALTH = 15, + ACLK_TOPICID_ALARM_CHECKPOINT = 15, ACLK_TOPICID_ALARM_CONFIG = 16, ACLK_TOPICID_ALARM_SNAPSHOT = 17, ACLK_TOPICID_NODE_COLLECTORS = 18, diff --git a/aclk/schema-wrappers/agent_cmds.cc b/aclk/schema-wrappers/agent_cmds.cc new file mode 100644 index 00000000..6950f402 --- /dev/null +++ b/aclk/schema-wrappers/agent_cmds.cc @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "proto/agent/v1/cmds.pb.h" + +#include "agent_cmds.h" + +#include "schema_wrapper_utils.h" + +using namespace agent::v1; + +int parse_cancel_pending_req(const char *msg, size_t msg_len, struct aclk_cancel_pending_req *req) +{ + CancelPendingRequest msg_parsed; + + if (!msg_parsed.ParseFromArray(msg, msg_len)) { + error_report("Failed to parse CancelPendingRequest message"); + return 1; + } + + if (msg_parsed.request_id().c_str() == NULL) { + error_report("CancelPendingRequest message missing request_id"); + return 1; + } + req->request_id = strdupz(msg_parsed.request_id().c_str()); + + if (msg_parsed.trace_id().c_str()) + req->trace_id = strdupz(msg_parsed.trace_id().c_str()); + + set_timeval_from_google_timestamp(msg_parsed.timestamp(), &req->timestamp); + + return 0; +} + +void free_cancel_pending_req(struct aclk_cancel_pending_req *req) +{ + freez(req->request_id); + freez(req->trace_id); +} diff --git a/aclk/schema-wrappers/agent_cmds.h b/aclk/schema-wrappers/agent_cmds.h new file mode 100644 index 00000000..7e01f86c --- /dev/null +++ b/aclk/schema-wrappers/agent_cmds.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef ACLK_SCHEMA_WRAPPERS_AGENT_CMDS_H +#define ACLK_SCHEMA_WRAPPERS_AGENT_CMDS_H + +#include "libnetdata/libnetdata.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct aclk_cancel_pending_req { + char *request_id; + + struct timeval timestamp; + + char *trace_id; +}; + +int parse_cancel_pending_req(const char *msg, size_t msg_len, struct aclk_cancel_pending_req *req); +void free_cancel_pending_req(struct aclk_cancel_pending_req *req); + +#ifdef __cplusplus +} +#endif + +#endif /* ACLK_SCHEMA_WRAPPERS_AGENT_CMDS_H */ diff --git a/aclk/schema-wrappers/alarm_stream.cc b/aclk/schema-wrappers/alarm_stream.cc index f6439330..af0b891c 100644 --- a/aclk/schema-wrappers/alarm_stream.cc +++ b/aclk/schema-wrappers/alarm_stream.cc @@ -21,57 +21,24 @@ struct start_alarm_streaming parse_start_alarm_streaming(const char *data, size_ return ret; ret.node_id = strdupz(msg.node_id().c_str()); - ret.batch_id = msg.batch_id(); - ret.start_seq_id = msg.start_sequnce_id(); + ret.resets = msg.resets(); return ret; } -char *parse_send_alarm_log_health(const char *data, size_t len) +struct send_alarm_checkpoint parse_send_alarm_checkpoint(const char *data, size_t len) { - SendAlarmLogHealth msg; - if (!msg.ParseFromArray(data, len)) - return NULL; - return strdupz(msg.node_id().c_str()); -} - -char *generate_alarm_log_health(size_t *len, struct alarm_log_health *data) -{ - AlarmLogHealth msg; - LogEntries *entries; - - msg.set_claim_id(data->claim_id); - msg.set_node_id(data->node_id); - msg.set_enabled(data->enabled); - - switch (data->status) { - case alarm_log_status_aclk::ALARM_LOG_STATUS_IDLE: - msg.set_status(alarms::v1::ALARM_LOG_STATUS_IDLE); - break; - case alarm_log_status_aclk::ALARM_LOG_STATUS_RUNNING: - msg.set_status(alarms::v1::ALARM_LOG_STATUS_RUNNING); - break; - case alarm_log_status_aclk::ALARM_LOG_STATUS_UNSPECIFIED: - msg.set_status(alarms::v1::ALARM_LOG_STATUS_UNSPECIFIED); - break; - default: - error("Unknown status of AlarmLogHealth LogEntry"); - return NULL; - } - - entries = msg.mutable_log_entries(); - entries->set_first_sequence_id(data->log_entries.first_seq_id); - entries->set_last_sequence_id(data->log_entries.last_seq_id); + struct send_alarm_checkpoint ret; + memset(&ret, 0, sizeof(ret)); - set_google_timestamp_from_timeval(data->log_entries.first_when, entries->mutable_first_when()); - set_google_timestamp_from_timeval(data->log_entries.last_when, entries->mutable_last_when()); + SendAlarmCheckpoint msg; + if (!msg.ParseFromArray(data, len)) + return ret; - *len = PROTO_COMPAT_MSG_SIZE(msg); - char *bin = (char*)mallocz(*len); - if (!msg.SerializeToArray(bin, *len)) - return NULL; + ret.node_id = strdupz(msg.node_id().c_str()); + ret.claim_id = strdupz(msg.claim_id().c_str()); - return bin; + return ret; } static alarms::v1::AlarmStatus aclk_alarm_status_to_proto(enum aclk_alarm_status status) @@ -131,8 +98,6 @@ static void fill_alarm_log_entry(struct alarm_log_entry *data, AlarmLogEntry *pr if (data->family) proto->set_family(data->family); - proto->set_batch_id(data->batch_id); - proto->set_sequence_id(data->sequence_id); proto->set_when(data->when); proto->set_config_hash(data->config_hash); @@ -187,6 +152,24 @@ char *generate_alarm_log_entry(size_t *len, struct alarm_log_entry *data) return bin; } +char *generate_alarm_checkpoint(size_t *len, struct alarm_checkpoint *data) +{ + AlarmCheckpoint msg; + + msg.set_claim_id(data->claim_id); + msg.set_node_id(data->node_id); + msg.set_checksum(data->checksum); + + *len = PROTO_COMPAT_MSG_SIZE(msg); + char *bin = (char*)mallocz(*len); + if (!msg.SerializeToArray(bin, *len)) { + freez(bin); + return NULL; + } + + return bin; +} + struct send_alarm_snapshot *parse_send_alarm_snapshot(const char *data, size_t len) { SendAlarmSnapshot msg; @@ -198,8 +181,8 @@ struct send_alarm_snapshot *parse_send_alarm_snapshot(const char *data, size_t l ret->claim_id = strdupz(msg.claim_id().c_str()); if (msg.node_id().c_str()) ret->node_id = strdupz(msg.node_id().c_str()); - ret->snapshot_id = msg.snapshot_id(); - ret->sequence_id = msg.sequence_id(); + if (msg.snapshot_uuid().c_str()) + ret->snapshot_uuid = strdupz(msg.snapshot_uuid().c_str()); return ret; } @@ -208,6 +191,7 @@ void destroy_send_alarm_snapshot(struct send_alarm_snapshot *ptr) { freez(ptr->claim_id); freez(ptr->node_id); + freez(ptr->snapshot_uuid); freez(ptr); } @@ -218,7 +202,7 @@ alarm_snapshot_proto_ptr_t generate_alarm_snapshot_proto(struct alarm_snapshot * msg->set_node_id(data->node_id); msg->set_claim_id(data->claim_id); - msg->set_snapshot_id(data->snapshot_id); + msg->set_snapshot_uuid(data->snapshot_uuid); msg->set_chunks(data->chunks); msg->set_chunk(data->chunk); diff --git a/aclk/schema-wrappers/alarm_stream.h b/aclk/schema-wrappers/alarm_stream.h index 63911da3..83e7c1bc 100644 --- a/aclk/schema-wrappers/alarm_stream.h +++ b/aclk/schema-wrappers/alarm_stream.h @@ -11,38 +11,12 @@ extern "C" { #endif -enum alarm_log_status_aclk { - ALARM_LOG_STATUS_UNSPECIFIED = 0, - ALARM_LOG_STATUS_RUNNING = 1, - ALARM_LOG_STATUS_IDLE = 2 -}; - -struct alarm_log_entries { - int64_t first_seq_id; - struct timeval first_when; - - int64_t last_seq_id; - struct timeval last_when; -}; - -struct alarm_log_health { - char *claim_id; - char *node_id; - int enabled; - enum alarm_log_status_aclk status; - struct alarm_log_entries log_entries; -}; - struct start_alarm_streaming { char *node_id; - uint64_t batch_id; - uint64_t start_seq_id; + bool resets; }; struct start_alarm_streaming parse_start_alarm_streaming(const char *data, size_t len); -char *parse_send_alarm_log_health(const char *data, size_t len); - -char *generate_alarm_log_health(size_t *len, struct alarm_log_health *data); enum aclk_alarm_status { ALARM_STATUS_NULL = 0, @@ -101,17 +75,27 @@ struct alarm_log_entry { char *chart_context; }; +struct send_alarm_checkpoint { + char *node_id; + char *claim_id; +}; + +struct alarm_checkpoint { + char *node_id; + char *claim_id; + char *checksum; +}; + struct send_alarm_snapshot { char *node_id; char *claim_id; - uint64_t snapshot_id; - uint64_t sequence_id; + char *snapshot_uuid; }; struct alarm_snapshot { char *node_id; char *claim_id; - uint64_t snapshot_id; + char *snapshot_uuid; uint32_t chunks; uint32_t chunk; }; @@ -125,6 +109,9 @@ char *generate_alarm_log_entry(size_t *len, struct alarm_log_entry *data); struct send_alarm_snapshot *parse_send_alarm_snapshot(const char *data, size_t len); void destroy_send_alarm_snapshot(struct send_alarm_snapshot *ptr); +struct send_alarm_checkpoint parse_send_alarm_checkpoint(const char *data, size_t len); +char *generate_alarm_checkpoint(size_t *len, struct alarm_checkpoint *data); + alarm_snapshot_proto_ptr_t generate_alarm_snapshot_proto(struct alarm_snapshot *data); void add_alarm_log_entry2snapshot(alarm_snapshot_proto_ptr_t snapshot, struct alarm_log_entry *data); char *generate_alarm_snapshot_bin(size_t *len, alarm_snapshot_proto_ptr_t snapshot); diff --git a/aclk/schema-wrappers/proto_2_json.cc b/aclk/schema-wrappers/proto_2_json.cc index 8853b2e0..85439651 100644 --- a/aclk/schema-wrappers/proto_2_json.cc +++ b/aclk/schema-wrappers/proto_2_json.cc @@ -11,6 +11,7 @@ #include "proto/nodeinstance/info/v1/info.pb.h" #include "proto/context/v1/stream.pb.h" #include "proto/context/v1/context.pb.h" +#include "proto/agent/v1/cmds.pb.h" #include "libnetdata/libnetdata.h" @@ -29,8 +30,8 @@ static google::protobuf::Message *msg_name_to_protomsg(const char *msgname) return new nodeinstance::create::v1::CreateNodeInstance; if (!strcmp(msgname, "UpdateNodeInfo")) return new nodeinstance::info::v1::UpdateNodeInfo; - if (!strcmp(msgname, "AlarmLogHealth")) - return new alarms::v1::AlarmLogHealth; + if (!strcmp(msgname, "AlarmCheckpoint")) + return new alarms::v1::AlarmCheckpoint; if (!strcmp(msgname, "ProvideAlarmConfiguration")) return new alarms::v1::ProvideAlarmConfiguration; if (!strcmp(msgname, "AlarmSnapshot")) @@ -51,8 +52,8 @@ static google::protobuf::Message *msg_name_to_protomsg(const char *msgname) return new agent::v1::SendNodeInstances; if (!strcmp(msgname, "StartAlarmStreaming")) return new alarms::v1::StartAlarmStreaming; - if (!strcmp(msgname, "SendAlarmLogHealth")) - return new alarms::v1::SendAlarmLogHealth; + if (!strcmp(msgname, "SendAlarmCheckpoint")) + return new alarms::v1::SendAlarmCheckpoint; if (!strcmp(msgname, "SendAlarmConfiguration")) return new alarms::v1::SendAlarmConfiguration; if (!strcmp(msgname, "SendAlarmSnapshot")) @@ -63,6 +64,8 @@ static google::protobuf::Message *msg_name_to_protomsg(const char *msgname) return new context::v1::ContextsCheckpoint; if (!strcmp(msgname, "StopStreamingContexts")) return new context::v1::StopStreamingContexts; + if (!strcmp(msgname, "CancelPendingRequest")) + return new agent::v1::CancelPendingRequest; return NULL; } diff --git a/aclk/schema-wrappers/schema_wrappers.h b/aclk/schema-wrappers/schema_wrappers.h index a96f7ea7..b651b884 100644 --- a/aclk/schema-wrappers/schema_wrappers.h +++ b/aclk/schema-wrappers/schema_wrappers.h @@ -14,5 +14,6 @@ #include "capability.h" #include "context_stream.h" #include "context.h" +#include "agent_cmds.h" #endif /* SCHEMA_WRAPPERS_H */ diff --git a/build/m4/ax_compiler_vendor.m4 b/build/m4/ax_compiler_vendor.m4 new file mode 100644 index 00000000..039f99d2 --- /dev/null +++ b/build/m4/ax_compiler_vendor.m4 @@ -0,0 +1,119 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_compiler_vendor.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_COMPILER_VENDOR +# +# DESCRIPTION +# +# Determine the vendor of the C, C++ or Fortran compiler. The vendor is +# returned in the cache variable $ax_cv_c_compiler_vendor for C, +# $ax_cv_cxx_compiler_vendor for C++ or $ax_cv_fc_compiler_vendor for +# (modern) Fortran. The value is one of "intel", "ibm", "pathscale", +# "clang" (LLVM), "cray", "fujitsu", "sdcc", "sx", "nvhpc" (NVIDIA HPC +# Compiler), "portland" (PGI), "gnu" (GCC), "sun" (Oracle Developer +# Studio), "hp", "dec", "borland", "comeau", "kai", "lcc", "sgi", +# "microsoft", "metrowerks", "watcom", "tcc" (Tiny CC) or "unknown" (if +# the compiler cannot be determined). +# +# To check for a Fortran compiler, you must first call AC_FC_PP_SRCEXT +# with an appropriate preprocessor-enabled extension. For example: +# +# AC_LANG_PUSH([Fortran]) +# AC_PROG_FC +# AC_FC_PP_SRCEXT([F]) +# AX_COMPILER_VENDOR +# AC_LANG_POP([Fortran]) +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson +# Copyright (c) 2008 Matteo Frigo +# Copyright (c) 2018-19 John Zaitseff +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 32 + +AC_DEFUN([AX_COMPILER_VENDOR], [dnl + AC_CACHE_CHECK([for _AC_LANG compiler vendor], ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor, [dnl + dnl If you modify this list of vendors, please add similar support + dnl to ax_compiler_version.m4 if at all possible. + dnl + dnl Note: Do NOT check for GCC first since some other compilers + dnl define __GNUC__ to remain compatible with it. Compilers that + dnl are very slow to start (such as Intel) are listed first. + + vendors=" + intel: __ICC,__ECC,__INTEL_COMPILER + ibm: __xlc__,__xlC__,__IBMC__,__IBMCPP__,__ibmxl__ + pathscale: __PATHCC__,__PATHSCALE__ + clang: __clang__ + cray: _CRAYC + fujitsu: __FUJITSU + sdcc: SDCC,__SDCC + sx: _SX + nvhpc: __NVCOMPILER + portland: __PGI + gnu: __GNUC__ + sun: __SUNPRO_C,__SUNPRO_CC,__SUNPRO_F90,__SUNPRO_F95 + hp: __HP_cc,__HP_aCC + dec: __DECC,__DECCXX,__DECC_VER,__DECCXX_VER + borland: __BORLANDC__,__CODEGEARC__,__TURBOC__ + comeau: __COMO__ + kai: __KCC + lcc: __LCC__ + sgi: __sgi,sgi + microsoft: _MSC_VER + metrowerks: __MWERKS__ + watcom: __WATCOMC__ + tcc: __TINYC__ + unknown: UNKNOWN + " + for ventest in $vendors; do + case $ventest in + *:) + vendor=$ventest + continue + ;; + *) + vencpp="defined("`echo $ventest | sed 's/,/) || defined(/g'`")" + ;; + esac + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [[ +#if !($vencpp) + thisisanerror; +#endif + ]])], [break]) + done + + ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor=`echo $vendor | cut -d: -f1` + ]) +])dnl diff --git a/build/m4/ax_cxx_compile_stdcxx.m4 b/build/m4/ax_cxx_compile_stdcxx.m4 new file mode 100644 index 00000000..8edf5152 --- /dev/null +++ b/build/m4/ax_cxx_compile_stdcxx.m4 @@ -0,0 +1,1018 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the specified +# version of the C++ standard. If necessary, add switches to CXX and +# CXXCPP to enable support. VERSION may be '11', '14', '17', or '20' for +# the respective C++ standard version. +# +# The second argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for no added switch, and then for an extended mode. +# +# The third argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline support for the specified C++ standard is +# required and that the macro should error out if no mode with that +# support is found. If specified 'optional', then configuration proceeds +# regardless, after defining HAVE_CXX${VERSION} if and only if a +# supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# Copyright (c) 2015 Moritz Klammler +# Copyright (c) 2016, 2018 Krzesimir Nowak +# Copyright (c) 2019 Enji Cooper +# Copyright (c) 2020 Jason Merrill +# Copyright (c) 2021 Jörn Heusipp +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 18 + +dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro +dnl (serial version number 13). + +AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl + m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], + [$1], [14], [ax_cxx_compile_alternatives="14 1y"], + [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [$1], [20], [ax_cxx_compile_alternatives="20"], + [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$2], [], [], + [$2], [ext], [], + [$2], [noext], [], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], + [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], + [$3], [optional], [ax_cxx_compile_cxx$1_required=false], + [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + + m4_if([$2], [], [dnl + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [ax_cv_cxx_compile_cxx$1=yes], + [ax_cv_cxx_compile_cxx$1=no])]) + if test x$ax_cv_cxx_compile_cxx$1 = xyes; then + ac_success=yes + fi]) + + m4_if([$2], [noext], [], [dnl + if test x$ac_success = xno; then + for alternative in ${ax_cxx_compile_alternatives}; do + switch="-std=gnu++${alternative}" + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + fi]) + + m4_if([$2], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + dnl Cray's crayCC needs "-h std=c++11" + dnl MSVC needs -std:c++NN for C++17 and later (default is C++14) + for alternative in ${ax_cxx_compile_alternatives}; do + for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}" MSVC; do + if test x"$switch" = xMSVC; then + dnl AS_TR_SH maps both `:` and `=` to `_` so -std:c++17 would collide + dnl with -std=c++17. We suffix the cache variable name with _MSVC to + dnl avoid this. + switch=-std:c++${alternative} + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_${switch}_MSVC]) + else + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + fi + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + if test x$ac_success = xyes; then + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx$1_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) + fi + fi + if test x$ac_success = xno; then + HAVE_CXX$1=0 + AC_MSG_NOTICE([No compiler with C++$1 support was found]) + else + HAVE_CXX$1=1 + AC_DEFINE(HAVE_CXX$1,1, + [define if the compiler supports basic C++$1 syntax]) + fi + AC_SUBST(HAVE_CXX$1) +]) + + +dnl Test body for checking C++11 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 +) + +dnl Test body for checking C++14 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 +) + +dnl Test body for checking C++17 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 +) + +dnl Test body for checking C++20 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_20 +) + + +dnl Tests for new features in C++11 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ + +// If the compiler admits that it is not ready for C++11, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +// MSVC always sets __cplusplus to 199711L in older versions; newer versions +// only set it correctly if /Zc:__cplusplus is specified as well as a +// /std:c++NN switch: +// https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ +#elif __cplusplus < 201103L && !defined _MSC_VER + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual ~Base() {} + virtual void f() {} + }; + + struct Derived : public Base + { + virtual ~Derived() override {} + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check single_type; + typedef check> double_type; + typedef check>> triple_type; + typedef check>>> quadruple_type; + + } + + namespace test_decltype + { + + int + f() + { + int a = 1; + decltype(a) b = 2; + return a + b; + } + + } + + namespace test_type_deduction + { + + template < typename T1, typename T2 > + struct is_same + { + static const bool value = false; + }; + + template < typename T > + struct is_same + { + static const bool value = true; + }; + + template < typename T1, typename T2 > + auto + add(T1 a1, T2 a2) -> decltype(a1 + a2) + { + return a1 + a2; + } + + int + test(const int c, volatile int v) + { + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == false, ""); + auto ac = c; + auto av = v; + auto sumi = ac + av + 'x'; + auto sumf = ac + av + 1.0; + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == true, ""); + static_assert(is_same::value == false, ""); + static_assert(is_same::value == true, ""); + return (sumf > 0.0) ? sumi : add(c, v); + } + + } + + namespace test_noexcept + { + + int f() { return 0; } + int g() noexcept { return 0; } + + static_assert(noexcept(f()) == false, ""); + static_assert(noexcept(g()) == true, ""); + + } + + namespace test_constexpr + { + + template < typename CharT > + unsigned long constexpr + strlen_c_r(const CharT *const s, const unsigned long acc) noexcept + { + return *s ? strlen_c_r(s + 1, acc + 1) : acc; + } + + template < typename CharT > + unsigned long constexpr + strlen_c(const CharT *const s) noexcept + { + return strlen_c_r(s, 0UL); + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("1") == 1UL, ""); + static_assert(strlen_c("example") == 7UL, ""); + static_assert(strlen_c("another\0example") == 7UL, ""); + + } + + namespace test_rvalue_references + { + + template < int N > + struct answer + { + static constexpr int value = N; + }; + + answer<1> f(int&) { return answer<1>(); } + answer<2> f(const int&) { return answer<2>(); } + answer<3> f(int&&) { return answer<3>(); } + + void + test() + { + int i = 0; + const int c = 0; + static_assert(decltype(f(i))::value == 1, ""); + static_assert(decltype(f(c))::value == 2, ""); + static_assert(decltype(f(0))::value == 3, ""); + } + + } + + namespace test_uniform_initialization + { + + struct test + { + static const int zero {}; + static const int one {1}; + }; + + static_assert(test::zero == 0, ""); + static_assert(test::one == 1, ""); + + } + + namespace test_lambdas + { + + void + test1() + { + auto lambda1 = [](){}; + auto lambda2 = lambda1; + lambda1(); + lambda2(); + } + + int + test2() + { + auto a = [](int i, int j){ return i + j; }(1, 2); + auto b = []() -> int { return '0'; }(); + auto c = [=](){ return a + b; }(); + auto d = [&](){ return c; }(); + auto e = [a, &b](int x) mutable { + const auto identity = [](int y){ return y; }; + for (auto i = 0; i < a; ++i) + a += b--; + return x + identity(a + b); + }(0); + return a + b + c + d + e; + } + + int + test3() + { + const auto nullary = [](){ return 0; }; + const auto unary = [](int x){ return x; }; + using nullary_t = decltype(nullary); + using unary_t = decltype(unary); + const auto higher1st = [](nullary_t f){ return f(); }; + const auto higher2nd = [unary](nullary_t f1){ + return [unary, f1](unary_t f2){ return f2(unary(f1())); }; + }; + return higher1st(nullary) + higher2nd(nullary)(unary); + } + + } + + namespace test_variadic_templates + { + + template + struct sum; + + template + struct sum + { + static constexpr auto value = N0 + sum::value; + }; + + template <> + struct sum<> + { + static constexpr auto value = 0; + }; + + static_assert(sum<>::value == 0, ""); + static_assert(sum<1>::value == 1, ""); + static_assert(sum<23>::value == 23, ""); + static_assert(sum<1, 2>::value == 3, ""); + static_assert(sum<5, 5, 11>::value == 21, ""); + static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); + + } + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function + // because of this. + namespace test_template_alias_sfinae + { + + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { func(0); } + + } + +} // namespace cxx11 + +#endif // __cplusplus >= 201103L + +]]) + + +dnl Tests for new features in C++14 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ + +// If the compiler admits that it is not ready for C++14, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201402L && !defined _MSC_VER + +#error "This is not a C++14 compiler" + +#else + +namespace cxx14 +{ + + namespace test_polymorphic_lambdas + { + + int + test() + { + const auto lambda = [](auto&&... args){ + const auto istiny = [](auto x){ + return (sizeof(x) == 1UL) ? 1 : 0; + }; + const int aretiny[] = { istiny(args)... }; + return aretiny[0]; + }; + return lambda(1, 1L, 1.0f, '1'); + } + + } + + namespace test_binary_literals + { + + constexpr auto ivii = 0b0000000000101010; + static_assert(ivii == 42, "wrong value"); + + } + + namespace test_generalized_constexpr + { + + template < typename CharT > + constexpr unsigned long + strlen_c(const CharT *const s) noexcept + { + auto length = 0UL; + for (auto p = s; *p; ++p) + ++length; + return length; + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("x") == 1UL, ""); + static_assert(strlen_c("test") == 4UL, ""); + static_assert(strlen_c("another\0test") == 7UL, ""); + + } + + namespace test_lambda_init_capture + { + + int + test() + { + auto x = 0; + const auto lambda1 = [a = x](int b){ return a + b; }; + const auto lambda2 = [a = lambda1(x)](){ return a; }; + return lambda2(); + } + + } + + namespace test_digit_separators + { + + constexpr auto ten_million = 100'000'000; + static_assert(ten_million == 100000000, ""); + + } + + namespace test_return_type_deduction + { + + auto f(int& x) { return x; } + decltype(auto) g(int& x) { return x; } + + template < typename T1, typename T2 > + struct is_same + { + static constexpr auto value = false; + }; + + template < typename T > + struct is_same + { + static constexpr auto value = true; + }; + + int + test() + { + auto x = 0; + static_assert(is_same::value, ""); + static_assert(is_same::value, ""); + return x; + } + + } + +} // namespace cxx14 + +#endif // __cplusplus >= 201402L + +]]) + + +dnl Tests for new features in C++17 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ + +// If the compiler admits that it is not ready for C++17, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 201703L && !defined _MSC_VER + +#error "This is not a C++17 compiler" + +#else + +#include +#include +#include + +namespace cxx17 +{ + + namespace test_constexpr_lambdas + { + + constexpr int foo = [](){return 42;}(); + + } + + namespace test::nested_namespace::definitions + { + + } + + namespace test_fold_expression + { + + template + int multiply(Args... args) + { + return (args * ... * 1); + } + + template + bool all(Args... args) + { + return (args && ...); + } + + } + + namespace test_extended_static_assert + { + + static_assert (true); + + } + + namespace test_auto_brace_init_list + { + + auto foo = {5}; + auto bar {5}; + + static_assert(std::is_same, decltype(foo)>::value); + static_assert(std::is_same::value); + } + + namespace test_typename_in_template_template_parameter + { + + template typename X> struct D; + + } + + namespace test_fallthrough_nodiscard_maybe_unused_attributes + { + + int f1() + { + return 42; + } + + [[nodiscard]] int f2() + { + [[maybe_unused]] auto unused = f1(); + + switch (f1()) + { + case 17: + f1(); + [[fallthrough]]; + case 42: + f1(); + } + return f1(); + } + + } + + namespace test_extended_aggregate_initialization + { + + struct base1 + { + int b1, b2 = 42; + }; + + struct base2 + { + base2() { + b3 = 42; + } + int b3; + }; + + struct derived : base1, base2 + { + int d; + }; + + derived d1 {{1, 2}, {}, 4}; // full initialization + derived d2 {{}, {}, 4}; // value-initialized bases + + } + + namespace test_general_range_based_for_loop + { + + struct iter + { + int i; + + int& operator* () + { + return i; + } + + const int& operator* () const + { + return i; + } + + iter& operator++() + { + ++i; + return *this; + } + }; + + struct sentinel + { + int i; + }; + + bool operator== (const iter& i, const sentinel& s) + { + return i.i == s.i; + } + + bool operator!= (const iter& i, const sentinel& s) + { + return !(i == s); + } + + struct range + { + iter begin() const + { + return {0}; + } + + sentinel end() const + { + return {5}; + } + }; + + void f() + { + range r {}; + + for (auto i : r) + { + [[maybe_unused]] auto v = i; + } + } + + } + + namespace test_lambda_capture_asterisk_this_by_value + { + + struct t + { + int i; + int foo() + { + return [*this]() + { + return i; + }(); + } + }; + + } + + namespace test_enum_class_construction + { + + enum class byte : unsigned char + {}; + + byte foo {42}; + + } + + namespace test_constexpr_if + { + + template + int f () + { + if constexpr(cond) + { + return 13; + } + else + { + return 42; + } + } + + } + + namespace test_selection_statement_with_initializer + { + + int f() + { + return 13; + } + + int f2() + { + if (auto i = f(); i > 0) + { + return 3; + } + + switch (auto i = f(); i + 4) + { + case 17: + return 2; + + default: + return 1; + } + } + + } + + namespace test_template_argument_deduction_for_class_templates + { + + template + struct pair + { + pair (T1 p1, T2 p2) + : m1 {p1}, + m2 {p2} + {} + + T1 m1; + T2 m2; + }; + + void f() + { + [[maybe_unused]] auto p = pair{13, 42u}; + } + + } + + namespace test_non_type_auto_template_parameters + { + + template + struct B + {}; + + B<5> b1; + B<'a'> b2; + + } + + namespace test_structured_bindings + { + + int arr[2] = { 1, 2 }; + std::pair pr = { 1, 2 }; + + auto f1() -> int(&)[2] + { + return arr; + } + + auto f2() -> std::pair& + { + return pr; + } + + struct S + { + int x1 : 2; + volatile double y1; + }; + + S f3() + { + return {}; + } + + auto [ x1, y1 ] = f1(); + auto& [ xr1, yr1 ] = f1(); + auto [ x2, y2 ] = f2(); + auto& [ xr2, yr2 ] = f2(); + const auto [ x3, y3 ] = f3(); + + } + + namespace test_exception_spec_type_system + { + + struct Good {}; + struct Bad {}; + + void g1() noexcept; + void g2(); + + template + Bad + f(T*, T*); + + template + Good + f(T1*, T2*); + + static_assert (std::is_same_v); + + } + + namespace test_inline_variables + { + + template void f(T) + {} + + template inline T g(T) + { + return T{}; + } + + template<> inline void f<>(int) + {} + + template<> int g<>(int) + { + return 5; + } + + } + +} // namespace cxx17 + +#endif // __cplusplus < 201703L && !defined _MSC_VER + +]]) + + +dnl Tests for new features in C++20 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_20], [[ + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 202002L && !defined _MSC_VER + +#error "This is not a C++20 compiler" + +#else + +#include + +namespace cxx20 +{ + +// As C++20 supports feature test macros in the standard, there is no +// immediate need to actually test for feature availability on the +// Autoconf side. + +} // namespace cxx20 + +#endif // __cplusplus < 202002L && !defined _MSC_VER + +]]) diff --git a/build_external/README.md b/build_external/README.md index d68e4d0f..9905bddf 100644 --- a/build_external/README.md +++ b/build_external/README.md @@ -1,7 +1,10 @@ # External build-system diff --git a/build_external/scenarios/children-to-localhost/README.md b/build_external/scenarios/children-to-localhost/README.md new file mode 100644 index 00000000..7b7be665 --- /dev/null +++ b/build_external/scenarios/children-to-localhost/README.md @@ -0,0 +1,10 @@ +# Stream children to localhost + +1. Run `docker-compose up --scale=50` +2. Copy `parent_stream.conf` to the `stream.conf` of a local agent +3. Restart the local agent + +You'll have 50 child agents streaming to the parent agent that runs locally. + +Useful for easily stress testing, restarting, profiling, debugging, etc, a +locally-built agent during development. diff --git a/build_external/scenarios/children-to-localhost/child_stream.conf b/build_external/scenarios/children-to-localhost/child_stream.conf new file mode 100644 index 00000000..72a353fe --- /dev/null +++ b/build_external/scenarios/children-to-localhost/child_stream.conf @@ -0,0 +1,10 @@ +[stream] + enabled = yes + destination = tcp:host.docker.internal + api key = 00000000-0000-0000-0000-000000000000 + timeout seconds = 60 + default port = 19999 + send charts matching = * + buffer size bytes = 1048576 + reconnect delay seconds = 5 + initial clock resync iterations = 60 diff --git a/build_external/scenarios/children-to-localhost/docker-compose.yml b/build_external/scenarios/children-to-localhost/docker-compose.yml new file mode 100644 index 00000000..59739f9e --- /dev/null +++ b/build_external/scenarios/children-to-localhost/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3.3' +services: + child: + image: netdata/netdata + command: /usr/sbin/netdata -D + volumes: + - ./child_stream.conf:/etc/netdata/stream.conf:ro + extra_hosts: + - "host.docker.internal:host-gateway" diff --git a/build_external/scenarios/children-to-localhost/parent_stream.conf b/build_external/scenarios/children-to-localhost/parent_stream.conf new file mode 100644 index 00000000..bf85ae25 --- /dev/null +++ b/build_external/scenarios/children-to-localhost/parent_stream.conf @@ -0,0 +1,7 @@ +[00000000-0000-0000-0000-000000000000] + enabled = yes + allow from = * + default history = 3600 + health enabled by default = auto + default postpone alarms on connect seconds = 60 + multiple connections = allow diff --git a/claim/README.md b/claim/README.md index f1d893eb..d88167ad 100644 --- a/claim/README.md +++ b/claim/README.md @@ -1,22 +1,12 @@ - - # Connect Agent to Cloud +This page will guide you through connecting a Netdata Agent to Netdata Cloud securely, via the encrypted Agent-Cloud link (ACLK). + 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)](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://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx) guide for a walkthrough of the process and simplified -instructions. +Are you just getting started with Netdata Cloud? You can find simplified instructions in the [Install Netdata documentation](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#get-started) 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. @@ -32,35 +22,37 @@ identity of the Netdata Agent when it connects to the Cloud. While the data does 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://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx#manage-spaces). +Nodes** in the [Spaces management area](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md#manage-spaces). There are two important notes regarding connecting nodes: -- _You can only connect any given node in a single Space_. You can, however, add that connected node to multiple War Rooms +- _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. +- You must repeat the connection process on every node you want to add to Netdata Cloud. ## How to connect a node There will be three main flows from where you might want to connect a node to Netdata Cloud. -* 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://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 + +- when you are on a [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 tab](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. ### Empty War Room Either at your first sign in or following ones, when you enter Netdata Cloud and are at a War Room that doesn’t have any node added to it, you will be able to: -* connect a new node to Netdata Cloud and add it to the War Room -* add a previously connected node to the War Room + +- connect a new node to Netdata Cloud and add it to the War Room +- add a previously connected node to the War Room If your case is to connect a new node and add it to the War Room, you will need to tell us what environment the node is running on (Linux, Docker, macOS, Kubernetes) and then we will provide you with a script to initiate the connection process. You just will need to copy and paste it into your node's terminal. See one of the following sections depending on your case: -* [Linux](#connect-an-agent-running-in-linux) -* [Docker](#connect-an-agent-running-in-docker) -* [macOS](#connect-an-agent-running-in-macos) -* [Kubernetes](#connect-a-kubernetes-clusters-parent-netdata-pod) + +- [Linux](#connect-an-agent-running-in-linux) +- [Docker](#connect-an-agent-running-in-docker) +- [macOS](#connect-an-agent-running-in-macos) +- [Kubernetes](#connect-a-kubernetes-clusters-parent-netdata-pod) 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. @@ -70,15 +62,16 @@ 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://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md) the room parameter is already defined to current War Room. +When coming from the [Nodes tab](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](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: -``` +```bash 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 ``` + 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). @@ -107,18 +100,18 @@ and run the script. ```bash 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 ``` + ### Connect an agent running in Docker To connect an instance of the Netdata Agent running inside of a Docker container, it is recommended that you follow -the instructions and use the commands provided either in the `Nodes` tab of an [empty War Room](#empty-war-room) on Netdata Cloud or -in the shelf that appears when you click **Connect Nodes** and select **Docker**. +the instructions and use the commands provided either in the `Nodes` tab of an [empty War Room](#empty-war-room) on Netdata Cloud or +in the shelf that appears when you click **Connect Nodes** and select **Docker**. -However, users can also claim a new node by claiming environment variables in the container to have it automatically +However, users can also claim a new node by claiming environment variables in the container to have it automatically 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](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md#create-a-new-netdata-agent-container) for details. +restarts using a persistent volume. See our [recommended `docker run` and Docker Compose 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 @@ -137,9 +130,9 @@ CONFIG_SECCOMP=y To resolve the issue, do one of the following actions: -- Update to a newer version of Docker and `libseccomp` (recommended). -- Create a custom profile and pass it for the container. -- Run [without the default seccomp profile](https://docs.docker.com/engine/security/seccomp/#run-without-the-default-seccomp-profile) (unsafe, not recommended). +- Update to a newer version of Docker and `libseccomp` (recommended). +- Create a custom profile and pass it for the container. +- Run [without the default seccomp profile](https://docs.docker.com/engine/security/seccomp/#run-without-the-default-seccomp-profile) (unsafe, not recommended).
See how to create a custom profile @@ -195,13 +188,13 @@ it will use these values to attempt to connect the container, automatically addi Rooms. If a proxy is specified, it will be used for the connection process and for connecting to Netdata Cloud. These variables can be specified using any mechanism supported by your container tooling for setting environment -variables inside containers. +variables inside containers. When using the `docker run` command, if you have an agent container already running, it is important to know that there will be a short period of downtime. This is due to the process of recreating the new agent container. The command to connect a new node to Netdata Cloud is: -```bash +```bash docker run -d --name=netdata \ -p 19999:19999 \ -v netdataconfig:/etc/netdata \ @@ -221,10 +214,11 @@ docker run -d --name=netdata \ -e NETDATA_CLAIM_PROXY=PROXY \ netdata/netdata ``` ->Note: This command is suggested for connecting a new container. Using this command for an existing container recreates the container, though data -and configuration of the old container may be preserved. If you are claiming an existing container that can not be recreated, + +>Note: This command is suggested for connecting a new container. Using this command for an existing container recreates the container, though data +and configuration of the old container may be preserved. If you are claiming an existing container that can not be recreated, you can add the container by going to Netdata Cloud, clicking the **Nodes** tab, clicking **Connect Nodes**, selecting **Docker**, and following -the instructions and commands provided or by following the instructions in an [empty War Room](#empty-war-room). +the instructions and commands provided or by following the instructions in an [empty War Room](#empty-war-room). The output that would be seen from the connection process when using other methods will be present in the container logs. @@ -274,6 +268,7 @@ Then run the following command in the same directory as the `docker-compose.yml` ```bash docker-compose up -d ``` + #### Using docker exec Connect a _running Netdata Agent container_, where you don't want to recreate the existing container, append the script offered by Netdata Cloud to a `docker exec ...` command, replacing @@ -282,7 +277,8 @@ Connect a _running Netdata Agent container_, where you don't want to recreate th ```bash docker exec -it netdata netdata-claim.sh -token=TOKEN -rooms=ROOM1,ROOM2 -url=https://api.netdata.cloud ``` -The values for `ROOM1,ROOM2` can be found by by going to Netdata Cloud, clicking the **Nodes** tab, clicking **Connect Nodes**, selecting **Docker**, and copying the `rooms=` value in the command provided. + +The values for `ROOM1,ROOM2` can be found by by going to Netdata Cloud, clicking the **Nodes** tab, clicking **Connect Nodes**, selecting **Docker**, and copying the `rooms=` value in the command provided. The script should return `Agent was successfully claimed.`. If the connection process returns errors, or if you don't see the node in your Space after 60 seconds, see the [troubleshooting information](#troubleshooting). @@ -294,6 +290,7 @@ To connect a node that is running on a macOS environment the script that will be ```bash 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). @@ -316,9 +313,9 @@ will also be used to tunnel the ACLK. The default `proxy` setting is `none`. The `proxy` setting can take one of the following values: -- `none`: Do not use a proxy, even if the system configured otherwise. -- `env`: Try to read proxy settings from set environment variables `http_proxy`. -- `http://[user:pass@]host:ip`: The ACLK and connection process will use the specified HTTP(S) proxy. +- `none`: Do not use a proxy, even if the system configured otherwise. +- `env`: Try to read proxy settings from set environment variables `http_proxy`. +- `http://[user:pass@]host:ip`: The ACLK and connection process will use the specified HTTP(S) proxy. For example, a HTTP proxy setting may look like the following: @@ -347,15 +344,15 @@ address or hostname of your Agent. The returned JSON contains four keys that wil might be having with the ACLK or connection process. ```json - "cloud-enabled" - "cloud-available" - "agent-claimed" - "aclk-available" + "cloud-enabled" + "cloud-available" + "agent-claimed" + "aclk-available" ``` -On Netdata agent version `1.32` (`netdata -v` to find your version) and newer, the `netdata -W aclk-state` command can be used to get some diagnostic information about ACLK. Sample output: +On Netdata agent version `1.32` (`netdata -v` to find your version) and newer, `sudo netdatacli aclk-state` can be used to get some diagnostic information about ACLK. Sample output: -``` +```bash ACLK Available: Yes ACLK Implementation: Next Generation New Cloud Protocol Support: Yes @@ -369,11 +366,11 @@ Use these keys and the information below to troubleshoot the ACLK. #### kickstart: unsupported Netdata installation -If you run the kickstart script and get the following error `Existing install appears to be handled manually or through the system package manager.` you most probably installed Netdata using an unsupported package. +If you run the kickstart script and get the following error `Existing install appears to be handled manually or through the system package manager.` you most probably installed Netdata using an unsupported package. 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](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx#install-on-linux-with-one-line-installer). +script](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#install-on-linux-with-one-line-installer). #### kickstart: Failed to write new machine GUID @@ -382,6 +379,7 @@ If you run the kickstart script but don't have privileges required for the actio ```bash Failed to write new machine GUID. Please make sure you have rights to write to /var/lib/netdata/registry/netdata.public.unique.id. ``` + For a successful execution you will need to run the script with 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. #### bash: netdata-claim.sh: command not found @@ -393,7 +391,7 @@ if you installed Netdata to `/opt/netdata`, use `/opt/netdata/bin/netdata-claim. 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](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx#install-on-linux-with-one-line-installer). +script](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#install-on-linux-with-one-line-installer). #### Connecting on older distributions (Ubuntu 14.04, Debian 8, CentOS 6) @@ -437,20 +435,20 @@ installer's output should give you more error details. You may see one of the following error messages during installation: -- Failed to build libmosquitto. The install process will continue, but you will not be able to connect this node to +- Failed to build libmosquitto. The install process will continue, but you will not be able to connect this node to Netdata Cloud. -- Unable to fetch sources for libmosquitto. The install process will continue, but you will not be able to connect +- Unable to fetch sources for libmosquitto. The install process will continue, but you will not be able to connect this node to Netdata Cloud. -- Failed to build libwebsockets. The install process will continue, but you may not be able to connect this node to +- Failed to build libwebsockets. The install process will continue, but you may not be able to connect this node to Netdata Cloud. -- Unable to fetch sources for libwebsockets. The install process will continue, but you may not be able to connect +- Unable to fetch sources for libwebsockets. The install process will continue, but you may not be able to connect this node to Netdata Cloud. -- Could not find cmake, which is required to build libwebsockets. The install process will continue, but you may not +- Could not find cmake, which is required to build libwebsockets. The install process will continue, but you may not be able to connect this node to Netdata Cloud. -- Could not find cmake, which is required to build JSON-C. The install process will continue, but Netdata Cloud +- Could not find cmake, which is required to build JSON-C. The install process will continue, but Netdata Cloud support will be disabled. -- Failed to build JSON-C. Netdata Cloud support will be disabled. -- Unable to fetch sources for JSON-C. Netdata Cloud support will be disabled. +- Failed to build JSON-C. Netdata Cloud support will be disabled. +- Unable to fetch sources for JSON-C. Netdata Cloud support will be disabled. One common cause of the installer failing to build Cloud features is not having one of the following dependencies on your system: `cmake`, `json-c` and `OpenSSL`, including corresponding `devel` packages. @@ -486,6 +484,8 @@ with details about your system and relevant output from `error.log`. ### Remove and reconnect a node +#### Linux based installations + To remove a node from your Space in Netdata Cloud, delete the `cloud.d/` directory in your Netdata library directory. ```bash @@ -497,53 +497,78 @@ This node no longer has access to the credentials it was used when connecting to You will still be able to see this node in your War Rooms in an **unreachable** state. If you want to reconnect this node, you need to: + 1. Ensure that the `/var/lib/netdata/cloud.d` directory doesn't exist. In some installations, the path is `/opt/netdata/var/lib/netdata/cloud.d`. 2. Stop the agent. 3. Ensure that the `uuidgen-runtime` package is installed. Run ```echo "$(uuidgen)"``` and validate you get back a UUID. 4. Copy the kickstart.sh command to add a node from your space and add to the end of it `--claim-id "$(uuidgen)"`. Run the command and look for the message `Node was successfully claimed.` 5. Start the agent +#### Docker based installations -## Connecting reference -In the sections below, you can find reference material for the kickstart script, claiming script, connecting via the Agent's command line -tool, and details about the files found in `cloud.d`. +To remove a node from you Space in Netdata Cloud, and connect it to another Space, follow these steps: -### The `cloud.conf` file +1. Enter the running container you wish to remove from your Space -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). + ```bash + docker exec -it CONTAINER_NAME sh + ``` -| 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](https://github.com/netdata/netdata/blob/master/aclk/README.md) and prevent your Agent from connecting to Netdata Cloud. | + Replacing `CONTAINER_NAME` with either the container's name or ID. -### kickstart script +2. Delete `/var/lib/netdata/cloud.d` and `/var/lib/netdata/registry/netdata.public.unique.id` -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. + ```bash + rm -rf /var/lib/netdata/cloud.d/ + + rm /var/lib/netdata/registry/netdata.public.unique.id + ``` -This works with: -* most Linux distributions, see [Netdata's platform support policy](https://github.com/netdata/netdata/blob/master/packaging/PLATFORM_SUPPORT.md) -* macOS +3. Stop and remove the container -For details on how to run this script please check [How to connect a node](#how-to-connect-a-node) and choose your environment. + **Docker CLI:** -In case Netdata Agent is already installed and you run this script to connect a node to Netdata Cloud it will not upgrade your agent automatically. If you also want to upgrade the Agent installation you'll need to run the script again without the connection options. + ```bash + docker stop CONTAINER_NAME + + docker rm CONTAINER_NAME + ``` -Our suggestion is to first run kickstart to upgrade your agent by running the command below and the run the [How to connect a node] -(#how-to-connect-a-node). + Replacing `CONTAINER_NAME` with either the container's name or ID. -**Linux** + **Docker Compose:** + Inside the directory that has the `docker-compose.yml` file, run: -```bash -wget -O /tmp/netdata-kickstart.sh https://my-netdata.io/kickstart.sh && sh /tmp/netdata-kickstart.sh -``` + ```bash + docker compose down + ``` -**macOS** + **Docker Swarm:** + Run the following, and replace `STACK` with your Stack's name: + + ```bash + docker stack rm STACK + ``` + +4. Finally, go to your new Space, copy the install command with the new claim token and run it. + If you are using a `docker-compose.yml` file, you will have to overwrite it with the new claiming token. + The node should now appear online in that Space. + +## Connecting reference + +In the sections below, you can find reference material for the kickstart script, claiming script, connecting via the Agent's command line +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 +using the [ACLK](https://github.com/netdata/netdata/blob/master/aclk/README.md). + +| setting | default | info | +|:-------------- |:------------------------- |:-------------------------------------------------------------------------------------------------------------------------------------- | +| cloud base url | | 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](https://github.com/netdata/netdata/blob/master/aclk/README.md) and prevent your Agent from connecting to Netdata Cloud. | -```bash -curl https://my-netdata.io/kickstart.sh > /tmp/netdata-kickstart.sh && sh /tmp/netdata-kickstart.sh --install-prefix /usr/local/ -``` ### Claiming script A Space's administrator can also connect an Agent by directly calling the `netdata-claim.sh` script either with root privileges @@ -609,5 +634,3 @@ Rooms you added that node to. The user can also put the Cloud endpoint's full certificate chain in `cloud.d/cloud_fullchain.pem` so that the Agent can trust the endpoint if necessary. - - diff --git a/cli/README.md b/cli/README.md index 09f20174..8dd9cce8 100644 --- a/cli/README.md +++ b/cli/README.md @@ -1,14 +1,6 @@ - +# Netdata Agent CLI -# Netdata CLI +The `netdatacli` executable provides a simple way to control the Netdata agent's operation. You can see the commands `netdatacli` supports by executing it with `netdatacli` and entering `help` in standard input. All commands are given as standard input to `netdatacli`. @@ -37,8 +29,10 @@ ping Return with 'pong' if agent is alive. aclk-state [json] Returns current state of ACLK and Cloud connection. (optionally in json) +dumpconfig + Returns the current netdata.conf on stdout. ``` -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). +See also the Netdata daemon [command line options](https://github.com/netdata/netdata/blob/master/daemon/README.md#command-line-options). diff --git a/cli/cli.c b/cli/cli.c index c14653f3..108c7762 100644 --- a/cli/cli.c +++ b/cli/cli.c @@ -10,9 +10,6 @@ static uv_shutdown_t shutdown_req; static char command_string[MAX_COMMAND_LENGTH]; static unsigned command_string_size; -static char response_string[MAX_COMMAND_LENGTH]; -static unsigned response_string_size; - static int exit_status; struct command_context { @@ -24,8 +21,10 @@ struct command_context { cmd_status_t status; }; -static void parse_command_reply(void) +static void parse_command_reply(BUFFER *buf) { + char *response_string = (char *) buffer_tostring(buf); + unsigned response_string_size = buffer_strlen(buf); FILE *stream = NULL; char *pos; int syntax_error = 0; @@ -64,28 +63,21 @@ static void parse_command_reply(void) static void pipe_read_cb(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) { - if (0 == nread) { + BUFFER *response = client->data; + + if (0 == nread) fprintf(stderr, "%s: Zero bytes read by command pipe.\n", __func__); - } else if (UV_EOF == nread) { -// fprintf(stderr, "EOF found in command pipe.\n"); - parse_command_reply(); - } else if (nread < 0) { - fprintf(stderr, "%s: %s\n", __func__, uv_strerror(nread)); + else if (UV_EOF == nread) + parse_command_reply(response); + else if (nread < 0) { + fprintf(stderr, "%s: %s\n", __func__, uv_strerror(nread)); + (void)uv_read_stop((uv_stream_t *)client); } + else + buffer_fast_rawcat(response, buf->base, nread); - if (nread < 0) { /* stop stream due to EOF or error */ - (void)uv_read_stop((uv_stream_t *)client); - } else if (nread) { - size_t to_copy; - - to_copy = MIN((unsigned int) nread, MAX_COMMAND_LENGTH - 1 - response_string_size); - memcpy(response_string + response_string_size, buf->base, to_copy); - response_string_size += to_copy; - response_string[response_string_size] = '\0'; - } - if (buf && buf->len) { + if (buf && buf->len) free(buf->base); - } } static void alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) @@ -104,8 +96,7 @@ static void shutdown_cb(uv_shutdown_t* req, int status) (void)status; /* receive reply */ - response_string_size = 0; - response_string[0] = '\0'; + client_pipe.data = req->data; ret = uv_read_start((uv_stream_t *)&client_pipe, alloc_cb, pipe_read_cb); if (ret) { @@ -113,16 +104,17 @@ static void shutdown_cb(uv_shutdown_t* req, int status) uv_close((uv_handle_t *)&client_pipe, NULL); return; } - } static void pipe_write_cb(uv_write_t* req, int status) { int ret; - (void)req; (void)status; + uv_pipe_t *clientp = req->data; + shutdown_req.data = clientp->data; + ret = uv_shutdown(&shutdown_req, (uv_stream_t *)&client_pipe, shutdown_cb); if (ret) { fprintf(stderr, "uv_shutdown(): %s\n", uv_strerror(ret)); @@ -144,10 +136,11 @@ static void connect_cb(uv_connect_t* req, int status) exit(-1); } if (0 == command_string_size) { - s = fgets(command_string, MAX_COMMAND_LENGTH, stdin); + s = fgets(command_string, MAX_COMMAND_LENGTH - 1, stdin); } (void)s; /* We don't need input to communicate with the server */ command_string_size = strlen(command_string); + client_pipe.data = req->data; write_req.data = &client_pipe; write_buf.base = command_string; @@ -191,11 +184,13 @@ int main(int argc, char **argv) } } + req.data = buffer_create(128, NULL); uv_pipe_connect(&req, &client_pipe, PIPENAME, connect_cb); uv_run(loop, UV_RUN_DEFAULT); uv_close((uv_handle_t *)&client_pipe, NULL); + buffer_free(client_pipe.data); return exit_status; } diff --git a/collectors/COLLECTORS.md b/collectors/COLLECTORS.md index a61a32dd..dbf2a9a1 100644 --- a/collectors/COLLECTORS.md +++ b/collectors/COLLECTORS.md @@ -1,46 +1,52 @@ - - -# Supported collectors list +# Monitor anything with Netdata Netdata uses collectors to help you gather metrics from your favorite applications and services and view them in real-time, interactive charts. The following list includes collectors for both external services/applications and internal system metrics. Learn more -about [how collectors work](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md), and +about [how collectors work](https://github.com/netdata/netdata/blob/master/collectors/README.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. +configure](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md#enable-and-disable-a-specific-collection-module) 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](https://github.com/netdata/netdata/blob/masterhttps://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md), +explicitly [disable the Go version](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md#enable-and-disable-a-specific-collection-module), and enable the Python version. Netdata then skips the Go version and attempts to load the Python version and its accompanying configuration file. +## Add your application to Netdata + If you don't see the app/service you'd like to monitor in this list: +- If your application has a Prometheus endpoint, Netdata can monitor it! Look at our + [generic Prometheus collector](https://github.com/netdata/go.d.plugin/blob/master/modules/prometheus/README.md). + +- If your application is instrumented to expose [StatsD](https://blog.netdata.cloud/introduction-to-statsd/) metrics, + see our [generic StatsD collector](https://github.com/netdata/netdata/blob/master/collectors/statsd.plugin/README.md). + +- If you have data in CSV, JSON, XML or other popular formats, you may be able to use our + [generic structured data (Pandas) collector](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/pandas/README.md), + - Check out our [GitHub issues](https://github.com/netdata/netdata/issues). Use the search bar to look for previous discussions about that collector—we may be looking for assistance from users such as yourself! + - If you don't see the collector there, you can make 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) + in [Go](https://github.com/netdata/go.d.plugin/blob/master/README.md#how-to-develop-a-collector) or [Python](https://github.com/netdata/netdata/blob/master/docs/guides/python-collector.md) -Supported Collectors List: +## Available Collectors -- [Service and application collectors](#service-and-application-collectors) +- [Monitor anything with Netdata](#monitor-anything-with-netdata) + - [Add your application to Netdata](#add-your-application-to-netdata) + - [Available Collectors](#available-collectors) + - [Service and application collectors](#service-and-application-collectors) - [Generic](#generic) - [APM (application performance monitoring)](#apm-application-performance-monitoring) - [Containers and VMs](#containers-and-vms) @@ -56,7 +62,7 @@ Supported Collectors List: - [Search](#search) - [Storage](#storage) - [Web](#web) -- [System collectors](#system-collectors) + - [System collectors](#system-collectors) - [Applications](#applications) - [Disks and filesystems](#disks-and-filesystems) - [eBPF](#ebpf) @@ -67,10 +73,10 @@ Supported Collectors List: - [Processes](#processes) - [Resources](#resources) - [Users](#users) -- [Netdata collectors](#netdata-collectors) -- [Orchestrators](#orchestrators) -- [Third-party collectors](#third-party-collectors) -- [Etc](#etc) + - [Netdata collectors](#netdata-collectors) + - [Orchestrators](#orchestrators) + - [Third-party collectors](#third-party-collectors) + - [Etc](#etc) ## Service and application collectors @@ -193,7 +199,7 @@ configure any of these collectors according to your setup and infrastructure. operations, and more. - [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/README.md): Find what services are running on a +- [Service discovery](https://github.com/netdata/agent-service-discovery/blob/master/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 @@ -206,8 +212,7 @@ configure any of these collectors according to your setup and infrastructure. server log files and provide summary (client, traffic) metrics. - [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://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md/): Tail access logs and provide +- [Web server logs (Go version for 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. - [Web server logs (Apache, NGINX)](https://github.com/netdata/go.d.plugin/blob/master/modules/weblog/README.md): Tail access log @@ -222,11 +227,8 @@ configure any of these collectors according to your setup and infrastructure. usage, jobs rates, commands, and more. - [Pulsar](https://github.com/netdata/go.d.plugin/blob/master/modules/pulsar/README.md): Collect summary, namespaces, and topics performance statistics. -- [RabbitMQ (Go)](https://github.com/netdata/go.d.plugin/blob/master/modules/rabbitmq/README.md): Collect message +- [RabbitMQ](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)](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://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 @@ -263,7 +265,7 @@ configure any of these collectors according to your setup and infrastructure. - [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](https://learn.netdata.cloud/docs/agent/collectors/go.d.plugin/modules/ntpd): Monitor the system variables +- [NTP daemon](https://github.com/netdata/go.d.plugin/blob/master/modules/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](https://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/opensips/README.md): Collect @@ -276,7 +278,7 @@ configure any of these collectors according to your setup and infrastructure. API. - [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://github.com/netdata/go.d.plugin/blob/master/modules/powerdns/README.md_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](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/retroshare/README.md): Monitor application bandwidth, peers, and DHT @@ -340,8 +342,6 @@ configure any of these collectors according to your setup and infrastructure. any HTTP endpoint's availability and response time. - [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://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](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. @@ -388,19 +388,18 @@ The Netdata Agent can collect these system- and hardware-level metrics using a v - [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://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`. +- [Windows](https://github.com/netdata/go.d.plugin/blob/master/modules/windows/README.md): Collect CPU, memory, + network, disk, OS, system, and log-in metrics scraping [windows_exporter](https://github.com/prometheus-community/windows_exporter). ### Disks and filesystems - [BCACHE](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitor BCACHE statistics - with the the `proc.plugin` collector. + with 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. + devices using the `proc.plugin` collector. - [Btrfs](https://github.com/netdata/netdata/blob/master/collectors/proc.plugin/README.md): Monitors Btrfs filesystems - with the the `proc.plugin` collector. + with 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. @@ -414,10 +413,9 @@ The Netdata Agent can collect these system- and hardware-level metrics using a v 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. + using the `proc.plugin` collector. - [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. + status, operation status, and more with the `proc.plugin` collector. - [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 @@ -465,8 +463,7 @@ The Netdata Agent can collect these system- and hardware-level metrics using a v ### Memory - [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. + available RAM using the `proc.plugin` collector. - [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 @@ -649,7 +646,7 @@ $ sudo echo "clickhouse: yes" >> /etc/netdata/python.d.conf $ sudo vi /etc/netdata/python.d/clickhouse.conf # restart netdata -# see docs for more information: https://learn.netdata.cloud/docs/configure/start-stop-restart +# see docs for more information: https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md $ sudo systemctl restart netdata ``` @@ -667,6 +664,11 @@ $ sudo systemctl restart netdata - [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. +- [Ethtool](https://github.com/ghanapunq/netdata_ethtool_plugin): Monitor network interfaces with ethtool. +- [netdata-needrestart](https://github.com/nodiscc/netdata-needrestart) - Check/graph the number of processes/services/kernels that should be restarted after upgrading packages. +- [netdata-debsecan](https://github.com/nodiscc/netdata-debsecan) - Check/graph the number of CVEs in currently installed packages. +- [netdata-logcount](https://github.com/nodiscc/netdata-logcount) - Check/graph the number of syslog messages, by level over time. +- [netdata-apt](https://github.com/nodiscc/netdata-apt) - Check/graph and alert on the number of upgradeable packages, and available distribution upgrades. ## Etc @@ -674,3 +676,5 @@ $ sudo systemctl restart netdata 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. +- [go.d example](https://github.com/netdata/go.d.plugin/blob/master/modules/example/README.md): An + example `go.d` collector. diff --git a/collectors/README.md b/collectors/README.md index 91a4eeb4..7676ff86 100644 --- a/collectors/README.md +++ b/collectors/README.md @@ -1,54 +1,62 @@ - - -# 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](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](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 +# Collectors + +When Netdata starts, and with zero configuration, it auto-detects thousands of data sources and immediately collects +per-second metrics. + +Netdata can immediately collect metrics from these endpoints thanks to 300+ **collectors**, which all come pre-installed +when you [install Netdata](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md). + +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. +See how you can [monitor anything with Netdata](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md). + +Upon startup, Netdata will **auto-detect** any application or service that has a collector, as long as both the collector +and the app/service are configured correctly. If you don't see charts for your application, see our [collectors' configuration reference](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md). -## Take your next steps with collectors +## How Netdata's metrics collectors work + +Every collector has two primary jobs: + +- Look for exposed metrics at a pre- or user-defined endpoint. +- Gather exposed metrics and use additional logic to build meaningful, interactive visualizations. -[Supported collectors list](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) +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](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/cloud/visualize/interact-new-charts.md) +on dashboards. -[Collectors configuration reference](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md) +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. The endpoint is user-configurable, as are many other +specifics of what a given collector does. -## Guides +## Collector architecture and terminology -[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) +- **Collectors** are the processes/programs that actually gather metrics from various sources. -[Monitor CockroachDB metrics with Netdata](https://github.com/netdata/netdata/blob/master/docs/guides/monitor-cockroachdb.md) +- **Plugins** help manage all the independent data collection processes in a variety of programming languages, based on + their purpose and performance requirements. There are three types of plugins: -[Monitor Unbound DNS servers with Netdata](https://github.com/netdata/netdata/blob/master/docs/guides/collect-unbound-metrics.md) + - **Internal** plugins organize collectors that gather metrics from `/proc`, `/sys` and other Linux kernel sources. + They are written in `C`, and run as threads within the Netdata daemon. -[Monitor a Hadoop cluster with Netdata](https://github.com/netdata/netdata/blob/master/docs/guides/monitor-hadoop-cluster.md) + - **External** plugins organize collectors that gather metrics from external processes, such as a MySQL database or + Nginx web server. They can be written in any language, and the `netdata` daemon spawns them as long-running + independent processes. They communicate with the daemon via pipes. 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. -## Related features +- **Orchestrators** are external plugins that run and manage one or more modules. They run as independent processes. + The Go orchestrator is in active development. -**[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). + - [go.d.plugin](https://github.com/netdata/go.d.plugin/blob/master/README.md): An orchestrator for data + collection modules written in `go`. -**[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. + - [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+. +- **Modules** are the individual programs controlled by an orchestrator to collect data from a specific application, or type of endpoint. diff --git a/collectors/REFERENCE.md b/collectors/REFERENCE.md index 270dded2..f19533f2 100644 --- a/collectors/REFERENCE.md +++ b/collectors/REFERENCE.md @@ -4,81 +4,29 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/REFE sidebar_label: "Collectors configuration" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Setup" +learn_rel_path: "Configuration" --> # Collectors configuration reference -Welcome to the collector configuration reference guide. +The list of supported collectors can be found in [the documentation](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md), +and on [our website](https://www.netdata.cloud/integrations). The documentation of each collector provides all the +necessary configuration options and prerequisites for that collector. In most cases, either the charts are automatically generated +without any configuration, or you just fulfil those prerequisites and [configure the collector](#configure-a-collector). -This guide contains detailed information about enabling/disabling plugins or modules, in addition a quick reference to -the internal plugins API. +If the application you are interested in monitoring is not listed in our integrations, the collectors list includes +the available options to +[add your application to Netdata](https://github.com/netdata/netdata/edit/master/collectors/COLLECTORS.md#add-your-application-to-netdata). -## Netdata's collector architecture +If we do support your collector but the charts described in the documentation don't appear on your dashboard, the reason will +be one of the following: -Netdata has an intricate system for organizing and managing its collectors. **Collectors** are the processes/programs -that actually gather metrics from various sources. Collectors are organized by **plugins**, which help manage all the -independent processes in a variety of programming languages based on their purpose and performance requirements. -**Modules** are a type of collector, used primarily to connect to external applications, such as an Nginx web server or -MySQL database, among many others. +- The entire data collection plugin is disabled by default. Read how to [enable and disable plugins](#enable-and-disable-plugins) -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](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. +- The data collection plugin is enabled, but a specific data collection module is disabled. Read how to + [enable and disable a specific collection module](#enable-and-disable-a-specific-collection-module). -There are three types of plugins: - -- **Internal** plugins organize collectors that gather metrics from `/proc`, `/sys` and other Linux kernel sources. - They are written in `C`, and run as threads within the Netdata daemon. -- **External** plugins organize collectors that gather metrics from external processes, such as a MySQL database or - Nginx web server. They can be written in any language, and the `netdata` daemon spawns them as long-running - 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://github.com/netdata/go.d.plugin/blob/master/README.md). - -## Enable, configure, and disable modules - -Most collector modules come with **auto-detection**, configured to work out-of-the-box on popular operating systems with -the default settings. - -However, there are cases that auto-detection fails. Usually, the reason is that the applications to be monitored do not -allow Netdata to connect. In most of the cases, allowing the user `netdata` from `localhost` to connect and collect -metrics, will automatically enable data collection for the application in question (it will require a Netdata restart). - - -## Troubleshoot a collector - -First, navigate to your plugins directory, which is usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case -on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the plugins directory, -switch to the `netdata` user. - -```bash -cd /usr/libexec/netdata/plugins.d/ -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](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 -# Go orchestrator (go.d.plugin) -./go.d.plugin -d -m - -# Python orchestrator (python.d.plugin) -./python.d.plugin debug trace - -# Bash orchestrator (bash.d.plugin) -./charts.d.plugin debug 1 -``` - -The output from the relevant command will provide valuable troubleshooting information. If you can't figure out how to -enable the collector using the details from this output, feel free to [create an issue on our -GitHub](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml) to get some -help from our collectors experts. +- Autodetection failed. Read how to [configure](#configure-a-collector) and [troubleshoot](#troubleshoot-a-collector) a collector. ## Enable and disable plugins @@ -88,87 +36,114 @@ This section features a list of Netdata's plugins, with a boolean setting to ena ```conf [plugins] - # proc = yes - # diskspace = yes # timex = yes - # cgroups = yes - # tc = yes # idlejitter = yes + # netdata monitoring = yes + # tc = yes + # diskspace = yes + # proc = yes + # cgroups = yes # enable running new plugins = yes # check for new plugins every = 60 # slabinfo = no - # ioping = yes # python.d = yes + # perf = yes + # ioping = yes + # fping = yes + # nfacct = yes # go.d = yes # apps = yes - # perf = yes + # ebpf = yes # charts.d = yes + # statsd = yes ``` 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](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md), which provides additional management options. +## Enable and disable a specific collection module -## Internal plugins +You can enable/disable of the collection modules supported by `go.d`, `python.d` or `charts.d` individually, using the +configuration file of that orchestrator. For example, you can change the behavior of the Go orchestrator, or any of its +collectors, by editing `go.d.conf`. -Each of the internal plugins runs as a thread inside the `netdata` daemon. Once this thread has started, the plugin may -spawn additional threads according to its design. +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: -### Internal plugins API +```bash +cd /etc/netdata +sudo ./edit-config go.d.conf +``` -The internal data collection API consists of the following calls: +Within this file, you can either disable the orchestrator entirely (`enabled: yes`), or find a specific collector and +enable/disable it with `yes` and `no` settings. Uncomment any line you change to ensure the Netdata daemon reads it on +start. -```c -collect_data() { - // collect data here (one iteration) +After you make your changes, restart the 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. - collected_number collected_value = collect_a_value(); +## Configure a collector - // give the metrics to Netdata +Most collector modules come with **auto-detection**, configured to work out-of-the-box on popular operating systems with +the default settings. - static RRDSET *st = NULL; // the chart - static RRDDIM *rd = NULL; // a dimension attached to this chart +However, there are cases that auto-detection fails. Usually, the reason is that the applications to be monitored do not +allow Netdata to connect. In most of the cases, allowing the user `netdata` from `localhost` to connect and collect +metrics, will automatically enable data collection for the application in question (it will require a Netdata restart). - if(unlikely(!st)) { - // we haven't created this chart before - // create it now - st = rrdset_create_localhost( - "type" - , "id" - , "name" - , "family" - , "context" - , "Chart Title" - , "units" - , "plugin-name" - , "module-name" - , priority - , update_every - , chart_type - ); +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://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. - // attach a metric to it - rd = rrddim_add(st, "id", "name", multiplier, divider, algorithm); - } +However, not every node or infrastructure uses standard ports, paths, files, or naming conventions. You may need to +enable or configure a collector to gather all available metrics from your systems, containers, or applications. - // give the collected value(s) to the chart - rrddim_set_by_pointer(st, rd, collected_value); +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. - // signal Netdata we are done with this iteration - rrdset_done(st); -} +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 +./edit-config go.d/nginx.conf ``` -Of course, Netdata has a lot of libraries to help you also in collecting the metrics. The best way to find your way -through this, is to examine what other similar plugins do. +Each configuration file describes every available option and offers examples to help you tweak Netdata's settings +according to your needs. In addition, every collector's documentation shows the exact command you need to run to +configure that collector. Uncomment any line you change to ensure the collector's orchestrator or the Netdata daemon +read it on start. + +After you make your changes, restart the 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. + +## Troubleshoot a collector -## External Plugins +First, navigate to your plugins directory, which is usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case +on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the plugins directory, +switch to the `netdata` user. -**External plugins** use the API and are managed -by [plugins.d](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md). +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` -## Write a custom collector +The next step is based on the collector's orchestrator. -You can add custom collectors by following the [external plugins documentation](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md). +```bash +# Go orchestrator (go.d.plugin) +./go.d.plugin -d -m +# Python orchestrator (python.d.plugin) +./python.d.plugin debug trace + +# Bash orchestrator (bash.d.plugin) +./charts.d.plugin debug 1 +``` + +The output from the relevant command will provide valuable troubleshooting information. If you can't figure out how to +enable the collector using the details from this output, feel free to [join our Discord server](https://discord.com/invite/mPZ6WZKKG2), +to get help from our experts. diff --git a/collectors/all.h b/collectors/all.h index 74fdde3f..a0ce5d7f 100644 --- a/collectors/all.h +++ b/collectors/all.h @@ -25,6 +25,7 @@ #define NETDATA_CHART_PRIO_SYSTEM_RAM 200 #define NETDATA_CHART_PRIO_SYSTEM_SWAP 201 #define NETDATA_CHART_PRIO_SYSTEM_SWAPIO 250 +#define NETDATA_CHART_PRIO_SYSTEM_ZSWAPIO 300 #define NETDATA_CHART_PRIO_SYSTEM_NET 500 #define NETDATA_CHART_PRIO_SYSTEM_IPV4 500 // freebsd only #define NETDATA_CHART_PRIO_SYSTEM_IP 501 @@ -80,9 +81,18 @@ #define NETDATA_CHART_PRIO_MEM_KERNEL 1100 #define NETDATA_CHART_PRIO_MEM_SLAB 1200 #define NETDATA_CHART_PRIO_MEM_HUGEPAGES 1250 +#define NETDATA_CHART_PRIO_MEM_HUGEPAGES_FAULTS 1251 +#define NETDATA_CHART_PRIO_MEM_HUGEPAGES_FILE 1252 +#define NETDATA_CHART_PRIO_MEM_HUGEPAGES_ZERO 1253 +#define NETDATA_CHART_PRIO_MEM_HUGEPAGES_KHUGEPAGED 1254 +#define NETDATA_CHART_PRIO_MEM_HUGEPAGES_SPLITS 1255 +#define NETDATA_CHART_PRIO_MEM_HUGEPAGES_SWAPOUT 1256 +#define NETDATA_CHART_PRIO_MEM_HUGEPAGES_COMPACT 1257 #define NETDATA_CHART_PRIO_MEM_KSM 1300 #define NETDATA_CHART_PRIO_MEM_KSM_SAVINGS 1301 #define NETDATA_CHART_PRIO_MEM_KSM_RATIOS 1302 +#define NETDATA_CHART_PRIO_MEM_KSM_COW 1303 +#define NETDATA_CHART_PRIO_MEM_BALLOON 1350 #define NETDATA_CHART_PRIO_MEM_NUMA 1400 #define NETDATA_CHART_PRIO_MEM_NUMA_NODES 1410 #define NETDATA_CHART_PRIO_MEM_PAGEFRAG 1450 @@ -182,6 +192,10 @@ #define NETDATA_CHART_PRIO_BTRFS_DATA 2401 #define NETDATA_CHART_PRIO_BTRFS_METADATA 2402 #define NETDATA_CHART_PRIO_BTRFS_SYSTEM 2403 +#define NETDATA_CHART_PRIO_BTRFS_COMMITS 2404 +#define NETDATA_CHART_PRIO_BTRFS_COMMITS_PERC_TIME 2405 +#define NETDATA_CHART_PRIO_BTRFS_COMMIT_TIMINGS 2406 +#define NETDATA_CHART_PRIO_BTRFS_ERRORS 2407 // ZFS diff --git a/collectors/apps.plugin/README.md b/collectors/apps.plugin/README.md index ac0d349a..ad4e0882 100644 --- a/collectors/apps.plugin/README.md +++ b/collectors/apps.plugin/README.md @@ -4,12 +4,13 @@ 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" +learn_rel_path: "Integrations/Monitor/System metrics" --> -# apps.plugin +# Application monitoring (apps.plugin) -`apps.plugin` breaks down system resource usage to **processes**, **users** and **user groups**. +`apps.plugin` breaks down system resource usage to **processes**, **users** and **user groups**. +It is enabled by default on every Netdata installation. To achieve this task, it iterates through the whole process tree, collecting resource usage information for every process found running. diff --git a/collectors/apps.plugin/apps_groups.conf b/collectors/apps.plugin/apps_groups.conf index fdb04860..f35454fd 100644 --- a/collectors/apps.plugin/apps_groups.conf +++ b/collectors/apps.plugin/apps_groups.conf @@ -171,6 +171,7 @@ nvidia-smi: nvidia-smi htop: htop watchdog: watchdog telegraf: telegraf +grafana: grafana* # ----------------------------------------------------------------------------- # storage, file systems and file servers diff --git a/collectors/apps.plugin/apps_plugin.c b/collectors/apps.plugin/apps_plugin.c index 84506c8e..3132b224 100644 --- a/collectors/apps.plugin/apps_plugin.c +++ b/collectors/apps.plugin/apps_plugin.c @@ -251,6 +251,8 @@ struct target { kernel_uint_t status_rssfile; kernel_uint_t status_rssshmem; kernel_uint_t status_vmswap; + kernel_uint_t status_voluntary_ctxt_switches; + kernel_uint_t status_nonvoluntary_ctxt_switches; kernel_uint_t io_logical_bytes_read; kernel_uint_t io_logical_bytes_written; @@ -381,12 +383,17 @@ struct pid_stat { uid_t uid; gid_t gid; + kernel_uint_t status_voluntary_ctxt_switches_raw; + kernel_uint_t status_nonvoluntary_ctxt_switches_raw; + kernel_uint_t status_vmsize; kernel_uint_t status_vmrss; kernel_uint_t status_vmshared; kernel_uint_t status_rssfile; kernel_uint_t status_rssshmem; kernel_uint_t status_vmswap; + kernel_uint_t status_voluntary_ctxt_switches; + kernel_uint_t status_nonvoluntary_ctxt_switches; #ifndef __FreeBSD__ ARL_BASE *status_arl; #endif @@ -638,9 +645,9 @@ int read_user_or_group_ids(struct user_or_group_ids *ids, struct timespec *last_ struct user_or_group_id *user_or_group_id = callocz(1, sizeof(struct user_or_group_id)); if(ids->type == USER_ID) - user_or_group_id->id.uid = (uid_t)str2ull(id_string); + user_or_group_id->id.uid = (uid_t) str2ull(id_string, NULL); else - user_or_group_id->id.gid = (uid_t)str2ull(id_string); + user_or_group_id->id.gid = (uid_t) str2ull(id_string, NULL); user_or_group_id->name = strdupz(name); user_or_group_id->updated = 1; @@ -1263,6 +1270,26 @@ void arl_callback_status_rssshmem(const char *name, uint32_t hash, const char *v aptr->p->status_rssshmem = str2kernel_uint_t(procfile_lineword(aptr->ff, aptr->line, 1)); } +void arl_callback_status_voluntary_ctxt_switches(const char *name, uint32_t hash, const char *value, void *dst) { + (void)name; (void)hash; (void)value; + struct arl_callback_ptr *aptr = (struct arl_callback_ptr *)dst; + if(unlikely(procfile_linewords(aptr->ff, aptr->line) < 2)) return; + + struct pid_stat *p = aptr->p; + pid_incremental_rate( + stat, p->status_voluntary_ctxt_switches, str2kernel_uint_t(procfile_lineword(aptr->ff, aptr->line, 1))); +} + +void arl_callback_status_nonvoluntary_ctxt_switches(const char *name, uint32_t hash, const char *value, void *dst) { + (void)name; (void)hash; (void)value; + struct arl_callback_ptr *aptr = (struct arl_callback_ptr *)dst; + if(unlikely(procfile_linewords(aptr->ff, aptr->line) < 2)) return; + + struct pid_stat *p = aptr->p; + pid_incremental_rate( + stat, p->status_nonvoluntary_ctxt_switches, str2kernel_uint_t(procfile_lineword(aptr->ff, aptr->line, 1))); +} + static void update_proc_state_count(char proc_state) { switch (proc_state) { case 'S': @@ -1293,6 +1320,8 @@ static inline int read_proc_pid_status(struct pid_stat *p, void *ptr) { p->status_rssfile = 0; p->status_rssshmem = 0; p->status_vmswap = 0; + p->status_voluntary_ctxt_switches = 0; + p->status_nonvoluntary_ctxt_switches = 0; #ifdef __FreeBSD__ struct kinfo_proc *proc_info = (struct kinfo_proc *)ptr; @@ -1318,6 +1347,8 @@ static inline int read_proc_pid_status(struct pid_stat *p, void *ptr) { arl_expect_custom(p->status_arl, "RssFile", arl_callback_status_rssfile, &arl_ptr); arl_expect_custom(p->status_arl, "RssShmem", arl_callback_status_rssshmem, &arl_ptr); arl_expect_custom(p->status_arl, "VmSwap", arl_callback_status_vmswap, &arl_ptr); + arl_expect_custom(p->status_arl, "voluntary_ctxt_switches", arl_callback_status_voluntary_ctxt_switches, &arl_ptr); + arl_expect_custom(p->status_arl, "nonvoluntary_ctxt_switches", arl_callback_status_nonvoluntary_ctxt_switches, &arl_ptr); } @@ -1452,7 +1483,7 @@ static inline int read_proc_pid_stat(struct pid_stat *p, void *ptr) { pid_incremental_rate(stat, p->cstime, str2kernel_uint_t(procfile_lineword(ff, 0, 16))); // p->priority = str2kernel_uint_t(procfile_lineword(ff, 0, 17)); // p->nice = str2kernel_uint_t(procfile_lineword(ff, 0, 18)); - p->num_threads = (int32_t)str2uint32_t(procfile_lineword(ff, 0, 19)); + p->num_threads = (int32_t) str2uint32_t(procfile_lineword(ff, 0, 19), NULL); // p->itrealvalue = str2kernel_uint_t(procfile_lineword(ff, 0, 20)); p->collected_starttime = str2kernel_uint_t(procfile_lineword(ff, 0, 21)) / system_hz; p->uptime = (global_uptime > p->collected_starttime)?(global_uptime - p->collected_starttime):0; @@ -2905,6 +2936,8 @@ static size_t zero_all_targets(struct target *root) { w->status_rssfile = 0; w->status_rssshmem = 0; w->status_vmswap = 0; + w->status_voluntary_ctxt_switches = 0; + w->status_nonvoluntary_ctxt_switches = 0; w->io_logical_bytes_read = 0; w->io_logical_bytes_written = 0; @@ -3095,6 +3128,8 @@ static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p, w->status_rssfile += p->status_rssfile; w->status_rssshmem += p->status_rssshmem; w->status_vmswap += p->status_vmswap; + w->status_voluntary_ctxt_switches += p->status_voluntary_ctxt_switches; + w->status_nonvoluntary_ctxt_switches += p->status_nonvoluntary_ctxt_switches; w->io_logical_bytes_read += p->io_logical_bytes_read; w->io_logical_bytes_written += p->io_logical_bytes_written; @@ -3540,6 +3575,22 @@ static void send_collected_data_to_netdata(struct target *root, const char *type send_END(); } +#ifndef __FreeBSD__ + send_BEGIN(type, "voluntary_ctxt_switches", dt); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed && w->processes)) + send_SET(w->name, w->status_voluntary_ctxt_switches); + } + send_END(); + + send_BEGIN(type, "involuntary_ctxt_switches", dt); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed && w->processes)) + send_SET(w->name, w->status_nonvoluntary_ctxt_switches); + } + send_END(); +#endif + send_BEGIN(type, "threads", dt); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) @@ -3822,6 +3873,22 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type APPS_PLUGIN_FUNCTIONS(); } +#ifndef __FreeBSD__ + fprintf(stdout, "CHART %s.voluntary_ctxt_switches '' '%s Voluntary Context Switches' 'switches/s' cpu %s.voluntary_ctxt_switches stacked 20023 %d\n", type, title, type, update_every); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, RATES_DETAIL); + } + APPS_PLUGIN_FUNCTIONS(); + + fprintf(stdout, "CHART %s.involuntary_ctxt_switches '' '%s Involuntary Context Switches' 'switches/s' cpu %s.involuntary_ctxt_switches stacked 20024 %d\n", type, title, type, update_every); + for (w = root; w ; w = w->next) { + if(unlikely(w->exposed)) + fprintf(stdout, "DIMENSION %s '' absolute 1 %llu\n", w->name, RATES_DETAIL); + } + APPS_PLUGIN_FUNCTIONS(); +#endif + #ifndef __FreeBSD__ fprintf(stdout, "CHART %s.swap '' '%s Swap Memory' 'MiB' swap %s.swap stacked 20011 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { @@ -3846,6 +3913,7 @@ static void send_charts_updates_to_netdata(struct target *root, const char *type APPS_PLUGIN_FUNCTIONS(); #ifdef __FreeBSD__ + // FIXME: same metric name as in Linux but different units. fprintf(stdout, "CHART %s.preads '' '%s Disk Reads' 'blocks/s' disk %s.preads stacked 20002 %d\n", type, title, type, update_every); for (w = root; w ; w = w->next) { if(unlikely(w->exposed)) @@ -4234,7 +4302,7 @@ static void get_MemTotal(void) { for(line = 0; line < lines ;line++) { size_t words = procfile_linewords(ff, line); if(words == 3 && strcmp(procfile_lineword(ff, line, 0), "MemTotal") == 0 && strcmp(procfile_lineword(ff, line, 2), "kB") == 0) { - kernel_uint_t n = str2ull(procfile_lineword(ff, line, 1)); + kernel_uint_t n = str2ull(procfile_lineword(ff, line, 1), NULL); if(n) MemTotal = n; break; } @@ -4289,54 +4357,48 @@ static void apps_plugin_function_processes_help(const char *transaction) { } #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); \ - buffer_sprintf(wb, "\n \"unique_key\":%s,", (unique_key)?"true":"false"); \ - buffer_sprintf(wb, "\n \"name\":\"%s\",", name); \ - buffer_sprintf(wb, "\n \"visible\":%s,", (visible)?"true":"false"); \ - 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 },"); \ + buffer_json_member_add_object(wb, key); \ + buffer_json_member_add_uint64(wb, "index", fields_added); \ + buffer_json_member_add_boolean(wb, "unique_key", unique_key); \ + buffer_json_member_add_string(wb, "name", name); \ + buffer_json_member_add_boolean(wb, "visible", visible); \ + buffer_json_member_add_string(wb, "type", type); \ + buffer_json_member_add_string_or_omit(wb, "units", (char*)(units)); \ + buffer_json_member_add_string(wb, "visualization", visualization); \ + buffer_json_member_add_object(wb, "value_options"); \ + buffer_json_member_add_string_or_omit(wb, "units", (char*)(units)); \ + buffer_json_member_add_string(wb, "transform", transform); \ + buffer_json_member_add_uint64(wb, "decimal_points", decimal_points); \ + buffer_json_object_close(wb); \ if(!isnan((NETDATA_DOUBLE)(max))) \ - buffer_sprintf(wb, "\n \"max\":%f,", (NETDATA_DOUBLE)(max)); \ - if(pointer_to) \ - buffer_sprintf(wb, "\n \"pointer_to\":\"%s\",", (char *)(pointer_to)); \ - 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 \"filter\":\"%s\"", (range)?"range":"multiselect"); \ - buffer_sprintf(wb, "\n }"); \ + buffer_json_member_add_double(wb, "max", (NETDATA_DOUBLE)(max)); \ + buffer_json_member_add_string_or_omit(wb, "pointer_to", (char *)(pointer_to)); \ + buffer_json_member_add_string(wb, "sort", sort); \ + buffer_json_member_add_boolean(wb, "sortable", sortable); \ + buffer_json_member_add_boolean(wb, "sticky", sticky); \ + buffer_json_member_add_string(wb, "summary", summary); \ + buffer_json_member_add_string(wb, "filter", (range)?"range":"multiselect"); \ + buffer_json_object_close(wb); \ fields_added++; \ } while(0) #define add_value_field_llu_with_max(wb, key, value) do { \ unsigned long long _tmp = (value); \ key ## _max = (rows == 0) ? (_tmp) : MAX(key ## _max, _tmp); \ - buffer_fast_strcat(wb, ",", 1); \ - buffer_print_llu(wb, _tmp); \ + buffer_json_add_array_item_uint64(wb, _tmp); \ } while(0) #define add_value_field_ndd_with_max(wb, key, value) do { \ NETDATA_DOUBLE _tmp = (value); \ key ## _max = (rows == 0) ? (_tmp) : MAX(key ## _max, _tmp); \ - buffer_fast_strcat(wb, ",", 1); \ - buffer_rrd_value(wb, _tmp); \ + buffer_json_add_array_item_double(wb, _tmp); \ } while(0) static void apps_plugin_function_processes(const char *transaction, char *function __maybe_unused, char *line_buffer __maybe_unused, int line_max __maybe_unused, int timeout __maybe_unused) { struct pid_stat *p; char *words[PLUGINSD_MAX_WORDS] = { NULL }; - size_t num_words = pluginsd_split_words(function, words, PLUGINSD_MAX_WORDS, NULL, NULL, 0); + size_t num_words = pluginsd_split_words(function, words, PLUGINSD_MAX_WORDS); struct target *category = NULL, *user = NULL, *group = NULL; const char *process_name = NULL; @@ -4406,18 +4468,12 @@ static void apps_plugin_function_processes(const char *transaction, char *functi unsigned int io_divisor = 1024 * RATES_DETAIL; 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 - ); + buffer_json_initialize(wb, "\"", "\"", 0, true, false); + buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK); + buffer_json_member_add_string(wb, "type", "table"); + buffer_json_member_add_time_t(wb, "update_every", update_every); + buffer_json_member_add_string(wb, "help", APPS_PLUGIN_PROCESSES_FUNCTION_DESCRIPTION); + buffer_json_member_add_array(wb, "data"); NETDATA_DOUBLE UserCPU_max = 0.0 @@ -4437,6 +4493,8 @@ static void apps_plugin_function_processes(const char *transaction, char *functi unsigned long long Processes_max = 0 , Threads_max = 0 + , VoluntaryCtxtSwitches_max = 0 + , NonVoluntaryCtxtSwitches_max = 0 , Uptime_max = 0 , MinFlt_max = 0 , CMinFlt_max = 0 @@ -4493,52 +4551,41 @@ static void apps_plugin_function_processes(const char *transaction, char *functi if(filter_gid && p->gid != gid) continue; - if(rows) buffer_fast_strcat(wb, ",\n", 2); rows++; - buffer_strcat(wb, " ["); + buffer_json_add_array_item_array(wb); // IMPORTANT! // THE ORDER SHOULD BE THE SAME WITH THE FIELDS! // pid - buffer_print_llu(wb, p->pid); + buffer_json_add_array_item_uint64(wb, p->pid); // cmd - buffer_fast_strcat(wb, ",\"", 2); - buffer_strcat_jsonescape(wb, p->comm); - buffer_fast_strcat(wb, "\"", 1); + buffer_json_add_array_item_string(wb, p->comm); #ifdef NETDATA_DEV_MODE // cmdline - buffer_fast_strcat(wb, ",\"", 2); - buffer_strcat_jsonescape(wb, (p->cmdline && *p->cmdline) ? p->cmdline : p->comm); - buffer_fast_strcat(wb, "\"", 1); + buffer_json_add_array_item_string(wb, (p->cmdline && *p->cmdline) ? p->cmdline : p->comm); #endif // ppid - buffer_fast_strcat(wb, ",", 1); buffer_print_llu(wb, p->ppid); + buffer_json_add_array_item_uint64(wb, p->ppid); // category - buffer_fast_strcat(wb, ",\"", 2); - buffer_strcat_jsonescape(wb, p->target ? p->target->name : "-"); - buffer_fast_strcat(wb, "\"", 1); + buffer_json_add_array_item_string(wb, p->target ? p->target->name : "-"); // user - buffer_fast_strcat(wb, ",\"", 2); - buffer_strcat_jsonescape(wb, p->user_target ? p->user_target->name : "-"); - buffer_fast_strcat(wb, "\"", 1); + buffer_json_add_array_item_string(wb, p->user_target ? p->user_target->name : "-"); // uid - buffer_fast_strcat(wb, ",", 1); buffer_print_llu(wb, p->uid); + buffer_json_add_array_item_uint64(wb, p->uid); // group - buffer_fast_strcat(wb, ",\"", 2); - buffer_strcat_jsonescape(wb, p->group_target ? p->group_target->name : "-"); - buffer_fast_strcat(wb, "\"", 1); + buffer_json_add_array_item_string(wb, p->group_target ? p->group_target->name : "-"); // gid - buffer_fast_strcat(wb, ",", 1); buffer_print_llu(wb, p->gid); + buffer_json_add_array_item_uint64(wb, p->gid); // 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); @@ -4549,6 +4596,9 @@ static void apps_plugin_function_processes(const char *transaction, char *functi 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_llu_with_max(wb, VoluntaryCtxtSwitches, p->status_voluntary_ctxt_switches / RATES_DETAIL); + add_value_field_llu_with_max(wb, NonVoluntaryCtxtSwitches, p->status_nonvoluntary_ctxt_switches / RATES_DETAIL); + // memory MiB if(MemTotal) add_value_field_ndd_with_max(wb, Memory, (NETDATA_DOUBLE)p->status_vmrss * 100.0 / (NETDATA_DOUBLE)MemTotal); @@ -4599,18 +4649,15 @@ static void apps_plugin_function_processes(const char *transaction, char *functi 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); - - fwrite(buffer_tostring(wb), buffer_strlen(wb), 1, stdout); - buffer_flush(wb); + buffer_json_array_close(wb); } + buffer_json_array_close(wb); + buffer_json_member_add_object(wb, "columns"); + { int fields_added = 0; - buffer_flush(wb); - buffer_sprintf(wb, "\n ],\n \"columns\": {"); - // IMPORTANT! // THE ORDER SHOULD BE THE SAME WITH THE VALUES! add_table_field(wb, "PID", "Process ID", true, "integer", "value", "number", 0, NULL, NAN, "ascending", true, true, true, NULL, "count_unique", false); @@ -4635,6 +4682,10 @@ static void apps_plugin_function_processes(const char *transaction, char *functi 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); + // CPU context switches + add_table_field(wb, "vCtxSwitch", "Voluntary Context Switches", false, "bar-with-integer", "bar", "number", 2, "switches/s", VoluntaryCtxtSwitches_max, "descending", true, false, false, NULL, "sum", true); + add_table_field(wb, "iCtxSwitch", "Involuntary Context Switches", false, "bar-with-integer", "bar", "number", 2, "switches/s", NonVoluntaryCtxtSwitches_max, "descending", true, false, false, NULL, "sum", true); + // memory if(MemTotal) add_table_field(wb, "Memory", "Memory Percentage", true, "bar-with-integer", "bar", "number", 2, "%", 100.0, "descending", true, false, false, NULL, "sum", true); @@ -4684,118 +4735,284 @@ static void apps_plugin_function_processes(const char *transaction, char *functi 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_json_object_close(wb); - buffer_strcat( - wb, - "" - "\n }," - "\n \"default_sort_column\": \"CPU\"," - "\n \"charts\": {" - "\n \"CPU\": {" - "\n \"name\":\"CPU Utilization\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"UserCPU\", \"SysCPU\", \"GuestCPU\", \"CUserCPU\", \"CSysCPU\", \"CGuestCPU\" ]" - "\n }," - "\n \"Memory\": {" - "\n \"name\":\"Memory\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"Virtual\", \"Resident\", \"Shared\", \"Swap\" ]" - "\n }," - ); + buffer_json_member_add_string(wb, "default_sort_column", "CPU"); - if(MemTotal) - buffer_strcat( - wb, - "" - "\n \"MemoryPercent\": {" - "\n \"name\":\"Memory Percentage\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"Memory\" ]" - "\n }," - ); + buffer_json_member_add_object(wb, "charts"); + { + // CPU chart + buffer_json_member_add_object(wb, "CPU"); + { + buffer_json_member_add_string(wb, "name", "CPU Utilization"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "UserCPU"); + buffer_json_add_array_item_string(wb, "SysCPU"); + buffer_json_add_array_item_string(wb, "GuestCPU"); + buffer_json_add_array_item_string(wb, "CUserCPU"); + buffer_json_add_array_item_string(wb, "CSysCPU"); + buffer_json_add_array_item_string(wb, "CGuestCPU"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "CPUCtxSwitches"); + { + buffer_json_member_add_string(wb, "name", "CPU Context Switches"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "vCtxSwitch"); + buffer_json_add_array_item_string(wb, "iCtxSwitch"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // Memory chart + buffer_json_member_add_object(wb, "Memory"); + { + buffer_json_member_add_string(wb, "name", "Memory"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Virtual"); + buffer_json_add_array_item_string(wb, "Resident"); + buffer_json_add_array_item_string(wb, "Shared"); + buffer_json_add_array_item_string(wb, "Swap"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + if(MemTotal) { + // Memory chart + buffer_json_member_add_object(wb, "MemoryPercent"); + { + buffer_json_member_add_string(wb, "name", "Memory Percentage"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Memory"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + } - buffer_strcat( - wb, "" - #ifndef __FreeBSD__ - "\n \"Reads\": {" - "\n \"name\":\"I/O Reads\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"LReads\", \"PReads\" ]" - "\n }," - "\n \"Writes\": {" - "\n \"name\":\"I/O Writes\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"LWrites\", \"PWrites\" ]" - "\n }," - "\n \"LogicalIO\": {" - "\n \"name\":\"Logical I/O\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"LReads\", \"LWrites\" ]" - "\n }," - #endif - "\n \"PhysicalIO\": {" - "\n \"name\":\"Physical I/O\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"PReads\", \"PWrites\" ]" - "\n }," - "\n \"IOCalls\": {" - "\n \"name\":\"I/O Calls\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"RCalls\", \"WCalls\" ]" - "\n }," - "\n \"MinFlt\": {" - "\n \"name\":\"Minor Page Faults\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"MinFlt\", \"CMinFlt\" ]" - "\n }," - "\n \"MajFlt\": {" - "\n \"name\":\"Major Page Faults\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"MajFlt\", \"CMajFlt\" ]" - "\n }," - "\n \"Threads\": {" - "\n \"name\":\"Threads\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"Threads\" ]" - "\n }," - "\n \"Processes\": {" - "\n \"name\":\"Processes\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"Processes\" ]" - "\n }," - "\n \"FDs\": {" - "\n \"name\":\"File Descriptors\"," - "\n \"type\":\"stacked-bar\"," - "\n \"columns\": [ \"Files\", \"Pipes\", \"Sockets\", \"iNotiFDs\", \"EventFDs\", \"TimerFDs\", \"SigFDs\", \"EvPollFDs\", \"OtherFDs\" ]" - "\n }" - "\n }," - "\n \"group_by\": {" - "\n \"pid\": {" - "\n \"name\":\"Process Tree by PID\"," - "\n \"columns\":[ \"PPID\" ]" - "\n }," - "\n \"category\": {" - "\n \"name\":\"Process Tree by Category\"," - "\n \"columns\":[ \"Category\", \"PPID\" ]" - "\n }," - "\n \"user\": {" - "\n \"name\":\"Process Tree by User\"," - "\n \"columns\":[ \"User\", \"PPID\" ]" - "\n }," - "\n \"group\": {" - "\n \"name\":\"Process Tree by Group\"," - "\n \"columns\":[ \"Group\", \"PPID\" ]" - "\n }" - "\n }" - ); +#ifndef __FreeBSD__ + // I/O Reads chart + buffer_json_member_add_object(wb, "Reads"); + { + buffer_json_member_add_string(wb, "name", "I/O Reads"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "LReads"); + buffer_json_add_array_item_string(wb, "PReads"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // I/O Writes chart + buffer_json_member_add_object(wb, "Writes"); + { + buffer_json_member_add_string(wb, "name", "I/O Writes"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "LWrites"); + buffer_json_add_array_item_string(wb, "PWrites"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // Logical I/O chart + buffer_json_member_add_object(wb, "LogicalIO"); + { + buffer_json_member_add_string(wb, "name", "Logical I/O"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "LReads"); + buffer_json_add_array_item_string(wb, "LWrites"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); +#endif + + // Physical I/O chart + buffer_json_member_add_object(wb, "PhysicalIO"); + { + buffer_json_member_add_string(wb, "name", "Physical I/O"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "PReads"); + buffer_json_add_array_item_string(wb, "PWrites"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // I/O Calls chart + buffer_json_member_add_object(wb, "IOCalls"); + { + buffer_json_member_add_string(wb, "name", "I/O Calls"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "RCalls"); + buffer_json_add_array_item_string(wb, "WCalls"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // Minor Page Faults chart + buffer_json_member_add_object(wb, "MinFlt"); + { + buffer_json_member_add_string(wb, "name", "Minor Page Faults"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "MinFlt"); + buffer_json_add_array_item_string(wb, "CMinFlt"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // Major Page Faults chart + buffer_json_member_add_object(wb, "MajFlt"); + { + buffer_json_member_add_string(wb, "name", "Major Page Faults"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "MajFlt"); + buffer_json_add_array_item_string(wb, "CMajFlt"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // Threads chart + buffer_json_member_add_object(wb, "Threads"); + { + buffer_json_member_add_string(wb, "name", "Threads"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Threads"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // Processes chart + buffer_json_member_add_object(wb, "Processes"); + { + buffer_json_member_add_string(wb, "name", "Processes"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Processes"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // FDs chart + buffer_json_member_add_object(wb, "FDs"); + { + buffer_json_member_add_string(wb, "name", "File Descriptors"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Files"); + buffer_json_add_array_item_string(wb, "Pipes"); + buffer_json_add_array_item_string(wb, "Sockets"); + buffer_json_add_array_item_string(wb, "iNotiFDs"); + buffer_json_add_array_item_string(wb, "EventFDs"); + buffer_json_add_array_item_string(wb, "TimerFDs"); + buffer_json_add_array_item_string(wb, "SigFDs"); + buffer_json_add_array_item_string(wb, "EvPollFDs"); + buffer_json_add_array_item_string(wb, "OtherFDs"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + } + buffer_json_object_close(wb); // charts - fwrite(buffer_tostring(wb), buffer_strlen(wb), 1, stdout); + buffer_json_member_add_object(wb, "group_by"); + { + // group by PID + buffer_json_member_add_object(wb, "PID"); + { + buffer_json_member_add_string(wb, "name", "Process Tree by PID"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "PPID"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // group by Category + buffer_json_member_add_object(wb, "Category"); + { + buffer_json_member_add_string(wb, "name", "Process Tree by Category"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Category"); + buffer_json_add_array_item_string(wb, "PPID"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // group by User + buffer_json_member_add_object(wb, "User"); + { + buffer_json_member_add_string(wb, "name", "Process Tree by User"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "User"); + buffer_json_add_array_item_string(wb, "PPID"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + // group by Group + buffer_json_member_add_object(wb, "Group"); + { + buffer_json_member_add_string(wb, "name", "Process Tree by Group"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Group"); + buffer_json_add_array_item_string(wb, "PPID"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); } + buffer_json_object_close(wb); // group_by - buffer_free(wb); + buffer_json_member_add_time_t(wb, "expires", expires); + buffer_json_finalize(wb); - fprintf(stdout, ",\n \"expires\":%lld", (long long)expires); - fprintf(stdout, "\n}"); + fwrite(buffer_tostring(wb), buffer_strlen(wb), 1, stdout); + buffer_free(wb); pluginsd_function_result_end_to_stdout(); } @@ -4809,7 +5026,7 @@ void *reader_main(void *arg __maybe_unused) { while(!apps_plugin_exit && (s = fgets(buffer, PLUGINSD_LINE_MAX, stdin))) { char *words[PLUGINSD_MAX_WORDS] = { NULL }; - size_t num_words = pluginsd_split_words(buffer, words, PLUGINSD_MAX_WORDS, NULL, NULL, 0); + size_t num_words = pluginsd_split_words(buffer, words, PLUGINSD_MAX_WORDS); const char *keyword = get_word(words, num_words, 0); @@ -5038,4 +5255,5 @@ int main(int argc, char **argv) { debug_log("done Loop No %zu", global_iterations_counter); } + netdata_mutex_unlock(&mutex); } diff --git a/collectors/apps.plugin/metrics.csv b/collectors/apps.plugin/metrics.csv new file mode 100644 index 00000000..e1ca3434 --- /dev/null +++ b/collectors/apps.plugin/metrics.csv @@ -0,0 +1,81 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +system.processes_state,,"running, sleeping_interruptible, sleeping_uninterruptible, zombie, stopped",processes,"System Processes State",line,,apps.plugin, +apps.cpu,,a dimension per app group,percentage,"Apps CPU Time (100% = 1 core)",stacked,,apps.plugin, +apps.cpu_user,,a dimension per app group,percentage,"Apps CPU User Time (100% = 1 core)",stacked,,apps.plugin, +apps.cpu_system,,a dimension per app group,percentage,"Apps CPU System Time (100% = 1 core)",stacked,,apps.plugin, +apps.cpu_guest,,a dimension per app group,percentage,"Apps CPU Guest Time (100% = 1 core)",stacked,,apps.plugin, +apps.mem,,a dimension per app group,MiB,"Apps Real Memory (w/o shared)",stacked,,apps.plugin, +apps.rss,,a dimension per app group,MiB,"Apps Resident Set Size (w/shared)",stacked,,apps.plugin, +apps.vmem,,a dimension per app group,MiB,"Apps Virtual Memory Size",stacked,,apps.plugin, +apps.swap,,a dimension per app group,MiB,"Apps Swap Memory",stacked,,apps.plugin, +apps.major_faults,,a dimension per app group,"page faults/s","Apps Major Page Faults (swap read)",stacked,,apps.plugin, +apps.minor_faults,,a dimension per app group,"page faults/s","Apps Minor Page Faults (swap read)",stacked,,apps.plugin, +apps.preads,,a dimension per app group,"KiB/s","Apps Disk Reads",stacked,,apps.plugin, +apps.pwrites,,a dimension per app group,"KiB/s","Apps Disk Writes",stacked,,apps.plugin, +apps.lreads,,a dimension per app group,"KiB/s","Apps Disk Logical Reads",stacked,,apps.plugin, +apps.lwrites,,a dimension per app group,"KiB/s","Apps I/O Logical Writes",stacked,,apps.plugin, +apps.threads,,a dimension per app group,threads,"Apps Threads",stacked,,apps.plugin, +apps.processes,,a dimension per app group,processes,"Apps Processes",stacked,,apps.plugin, +apps.voluntary_ctxt_switches,,a dimension per app group,processes,"Apps Voluntary Context Switches",stacked,,apps.plugin, +apps.involuntary_ctxt_switches,,a dimension per app group,processes,"Apps Involuntary Context Switches",stacked,,apps.plugin, +apps.uptime,,a dimension per app group,seconds,"Apps Carried Over Uptime",line,,apps.plugin, +apps.uptime_min,,a dimension per app group,seconds,"Apps Minimum Uptime",line,,apps.plugin, +apps.uptime_avg,,a dimension per app group,seconds,"Apps Average Uptime",line,,apps.plugin, +apps.uptime_max,,a dimension per app group,seconds,"Apps Maximum Uptime",line,,apps.plugin, +apps.files,,a dimension per app group,"open files","Apps Open Files",stacked,,apps.plugin, +apps.sockets,,a dimension per app group,"open sockets","Apps Open Sockets",stacked,,apps.plugin, +apps.pipes,,a dimension per app group,"open pipes","Apps Open Pipes",stacked,,apps.plugin, +groups.cpu,,a dimension per user group,percentage,"User Groups CPU Time (100% = 1 core)",stacked,,apps.plugin, +groups.cpu_user,,a dimension per user group,percentage,"User Groups CPU User Time (100% = 1 core)",stacked,,apps.plugin, +groups.cpu_system,,a dimension per user group,percentage,"User Groups CPU System Time (100% = 1 core)",stacked,,apps.plugin, +groups.cpu_guest,,a dimension per user group,percentage,"User Groups CPU Guest Time (100% = 1 core)",stacked,,apps.plugin, +groups.mem,,a dimension per user group,MiB,"User Groups Real Memory (w/o shared)",stacked,,apps.plugin, +groups.rss,,a dimension per user group,MiB,"User Groups Resident Set Size (w/shared)",stacked,,apps.plugin, +groups.vmem,,a dimension per user group,MiB,"User Groups Virtual Memory Size",stacked,,apps.plugin, +groups.swap,,a dimension per user group,MiB,"User Groups Swap Memory",stacked,,apps.plugin, +groups.major_faults,,a dimension per user group,"page faults/s","User Groups Major Page Faults (swap read)",stacked,,apps.plugin, +groups.minor_faults,,a dimension per user group,"page faults/s","User Groups Page Faults (swap read)",stacked,,apps.plugin, +groups.preads,,a dimension per user group,"KiB/s","User Groups Disk Reads",stacked,,apps.plugin, +groups.pwrites,,a dimension per user group,"KiB/s","User Groups Disk Writes",stacked,,apps.plugin, +groups.lreads,,a dimension per user group,"KiB/s","User Groups Disk Logical Reads",stacked,,apps.plugin, +groups.lwrites,,a dimension per user group,"KiB/s","User Groups I/O Logical Writes",stacked,,apps.plugin, +groups.threads,,a dimension per user group,threads,"User Groups Threads",stacked,,apps.plugin, +groups.processes,,a dimension per user group,processes,"User Groups Processes",stacked,,apps.plugin, +groups.voluntary_ctxt_switches,,a dimension per app group,processes,"User Groups Voluntary Context Switches",stacked,,apps.plugin, +groups.involuntary_ctxt_switches,,a dimension per app group,processes,"User Groups Involuntary Context Switches",stacked,,apps.plugin, +groups.uptime,,a dimension per user group,seconds,"User Groups Carried Over Uptime",line,,apps.plugin, +groups.uptime_min,,a dimension per user group,seconds,"User Groups Minimum Uptime",line,,apps.plugin, +groups.uptime_avg,,a dimension per user group,seconds,"User Groups Average Uptime",line,,apps.plugin, +groups.uptime_max,,a dimension per user group,seconds,"User Groups Maximum Uptime",line,,apps.plugin, +groups.files,,a dimension per user group,"open files","User Groups Open Files",stacked,,apps.plugin, +groups.sockets,,a dimension per user group,"open sockets","User Groups Open Sockets",stacked,,apps.plugin, +groups.pipes,,a dimension per user group,"open pipes","User Groups Open Pipes",stacked,,apps.plugin, +users.cpu,,a dimension per user,percentage,"Users CPU Time (100% = 1 core)",stacked,,apps.plugin, +users.cpu_user,,a dimension per user,percentage,"Users CPU User Time (100% = 1 core)",stacked,,apps.plugin, +users.cpu_system,,a dimension per user,percentage,"Users CPU System Time (100% = 1 core)",stacked,,apps.plugin, +users.cpu_guest,,a dimension per user,percentage,"Users CPU Guest Time (100% = 1 core)",stacked,,apps.plugin, +users.mem,,a dimension per user,MiB,"Users Real Memory (w/o shared)",stacked,,apps.plugin, +users.rss,,a dimension per user,MiB,"Users Resident Set Size (w/shared)",stacked,,apps.plugin, +users.vmem,,a dimension per user,MiB,"Users Virtual Memory Size",stacked,,apps.plugin, +users.swap,,a dimension per user,MiB,"Users Swap Memory",stacked,,apps.plugin, +users.major_faults,,a dimension per user,"page faults/s","Users Major Page Faults (swap read)",stacked,,apps.plugin, +users.minor_faults,,a dimension per user,"page faults/s","Users Page Faults (swap read)",stacked,,apps.plugin, +users.preads,,a dimension per user,"KiB/s","Users Disk Reads",stacked,,apps.plugin, +users.pwrites,,a dimension per user,"KiB/s","Users Disk Writes",stacked,,apps.plugin, +users.lreads,,a dimension per user,"KiB/s","Users Disk Logical Reads",stacked,,apps.plugin, +users.lwrites,,a dimension per user,"KiB/s","Users I/O Logical Writes",stacked,,apps.plugin, +users.threads,,a dimension per user,threads,"Users Threads",stacked,,apps.plugin, +users.processes,,a dimension per user,processes,"Users Processes",stacked,,apps.plugin, +users.voluntary_ctxt_switches,,a dimension per app group,processes,"Users Voluntary Context Switches",stacked,,apps.plugin, +users.involuntary_ctxt_switches,,a dimension per app group,processes,"Users Involuntary Context Switches",stacked,,apps.plugin, +users.uptime,,a dimension per user,seconds,"Users Carried Over Uptime",line,,apps.plugin, +users.uptime_min,,a dimension per user,seconds,"Users Minimum Uptime",line,,apps.plugin, +users.uptime_avg,,a dimension per user,seconds,"Users Average Uptime",line,,apps.plugin, +users.uptime_max,,a dimension per user,seconds,"Users Maximum Uptime",line,,apps.plugin, +users.files,,a dimension per user,"open files","Users Open Files",stacked,,apps.plugin, +users.sockets,,a dimension per user,"open sockets","Users Open Sockets",stacked,,apps.plugin, +users.pipes,,a dimension per user,"open pipes","Users Open Pipes",stacked,,apps.plugin, +netdata.apps_cpu,,"user, system",milliseconds/s,"Apps Plugin CPU",stacked,,apps.plugin, +netdata.apps_sizes,,"calls, files, filenames, inode_changes, link_changes, pids, fds, targets, new_pids",files/s,"Apps Plugin Files",line,,apps.plugin, +netdata.apps_fix,,"utime, stime, gtime, minflt, majflt",percentage,"Apps Plugin Normalization Ratios",line,,apps.plugin, +netdata.apps_children_fix,,"utime, stime, gtime, minflt, majflt",percentage,"Apps Plugin Exited Children Normalization Ratios",line,,apps.plugin, \ No newline at end of file diff --git a/collectors/cgroups.plugin/README.md b/collectors/cgroups.plugin/README.md index e58f1ba0..2e4fff23 100644 --- a/collectors/cgroups.plugin/README.md +++ b/collectors/cgroups.plugin/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/cgro sidebar_label: "Monitor Cgroups" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Virtualized environments/Containers" +learn_rel_path: "Integrations/Monitor/Virtualized environments/Containers" --> -# cgroups.plugin +# Monitor Cgroups (cgroups.plugin) You can monitor containers and virtual machines using **cgroups**. @@ -26,7 +26,7 @@ and **virtual machines** spawn by managers that register them with cgroups (qemu In general, no additional settings are required. Netdata discovers all available cgroups on the host system and collects their metrics. -### how Netdata finds the available cgroups +### How Netdata finds the available cgroups Linux exposes resource usage reporting and provides dynamic configuration for cgroups, using virtual files (usually) under `/sys/fs/cgroup`. Netdata reads `/proc/self/mountinfo` to detect the exact mount point of cgroups. Netdata also @@ -43,7 +43,7 @@ allows manual configuration of this mount point, using these settings: Netdata rescans these directories for added or removed cgroups every `check for new cgroups every` seconds. -### hierarchical search for cgroups +### Hierarchical search for cgroups Since cgroups are hierarchical, for each of the directories shown above, Netdata walks through the subdirectories recursively searching for cgroups (each subdirectory is another cgroup). @@ -61,7 +61,7 @@ cgroups ([systemd services are monitored by Netdata](#monitoring-systemd-service desktop and remote user sessions), qemu virtual machines (child cgroups of virtual machines) and `init.scope`. All others are enabled. -### unified cgroups (cgroups v2) support +### Unified cgroups (cgroups v2) support Netdata automatically detects cgroups version. If detection fails Netdata assumes v1. To switch to v2 manually add: @@ -75,19 +75,19 @@ To switch to v2 manually add: Unified cgroups use same name pattern matching as v1 cgroups. `cgroup_enable_systemd_services_detailed_memory` is currently unsupported when using unified cgroups. -### enabled cgroups +### Enabled cgroups To provide a sane default, Netdata uses the following [pattern list](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md): -- checks the pattern against the path of the cgroup +- Checks the pattern against the path of the cgroup ```text [plugin:cgroups] enable by default cgroups matching = !*/init.scope *.scope !*/vcpu* !*/emulator !*.mount !*.partition !*.service !*.slice !*.swap !*.user !/ !/docker !/libvirt !/lxc !/lxc/*/ns !/lxc/*/ns/* !/machine !/qemu !/system !/systemd !/user * ``` -- checks the pattern against the name of the cgroup (as you see it on the dashboard) +- Checks the pattern against the name of the cgroup (as you see it on the dashboard) ```text [plugin:cgroups] @@ -120,10 +120,11 @@ container names. To do this, ensure `podman system service` is running and Netda to `/run/podman/podman.sock` (the default permissions as specified by upstream are `0600`, with owner `root`, so you will have to adjust the configuration). -[docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy) can also be used to give Netdata restricted -access to the socket. Note that `PODMAN_HOST` in Netdata's environment should be set to the proxy's URL in this case. +[Docker Socket Proxy (HAProxy)](https://github.com/Tecnativa/docker-socket-proxy) or [CetusGuard](https://github.com/hectorm/cetusguard) +can also be used to give Netdata restricted access to the socket. Note that `PODMAN_HOST` in Netdata's environment should +be set to the proxy's URL in this case. -### charts with zero metrics +### Charts with zero metrics By default, Netdata will enable monitoring metrics only when they are not zero. If they are constantly zero they are ignored. Metrics that will start having values, after Netdata is started, will be detected and charts will be @@ -138,7 +139,7 @@ chart instead of `auto` to enable it permanently. For example: You can also set the `enable zero metrics` option to `yes` in the `[global]` section which enables charts with zero metrics for all internal Netdata plugins. -### alarms +### Alarms CPU and memory limits are watched and used to rise alarms. Memory usage for every cgroup is checked against `ram` and `ram+swap` limits. CPU usage for every cgroup is checked against `cpuset.cpus` and `cpu.cfs_period_us` + `cpu.cfs_quota_us` pair assigned for the cgroup. Configuration for the alarms is available in `health.d/cgroups.conf` @@ -190,7 +191,7 @@ Support per distribution: - Merged disk read operations - Merged disk write operations -### how to enable cgroup accounting on systemd systems that is by default disabled +### How to enable cgroup accounting on systemd systems that is by default disabled You can verify there is no accounting enabled, by running `systemd-cgtop`. The program will show only resources for cgroup `/`, but all services will show nothing. @@ -259,28 +260,17 @@ Which systemd services are monitored by Netdata is determined by the following p Netdata monitors containers automatically when it is installed at the host, or when it is installed in a container that has access to the `/proc` and `/sys` filesystems of the host. -Netdata prior to v1.6 had 2 issues when such containers were monitored: +Network interfaces and cgroups (containers) are self-cleaned. When a network interface or container stops, Netdata might log +a few errors in error.log complaining about files it cannot find, but immediately: -1. network interface alarms where triggering when containers were stopped - -2. charts were never cleaned up, so after some time dozens of containers were showing up on the dashboard, and they were - occupying memory. - -### the current Netdata - -network interfaces and cgroups (containers) are now self-cleaned. - -So, when a network interface or container stops, Netdata might log a few errors in error.log complaining about files it -cannot find, but immediately: - -1. it will detect this is a removed container or network interface -2. it will freeze/pause all alarms for them -3. it will mark their charts as obsolete -4. obsolete charts are not be offered on new dashboard sessions (so hit F5 and the charts are gone) -5. existing dashboard sessions will continue to see them, but of course they will not refresh -6. obsolete charts will be removed from memory, 1 hour after the last user viewed them (configurable +1. It will detect this is a removed container or network interface +2. It will freeze/pause all alarms for them +3. It will mark their charts as obsolete +4. Obsolete charts are not be offered on new dashboard sessions (so hit F5 and the charts are gone) +5. Existing dashboard sessions will continue to see them, but of course they will not refresh +6. Obsolete charts will be removed from memory, 1 hour after the last user viewed them (configurable with `[global].cleanup obsolete charts after seconds = 3600` (at `netdata.conf`). -7. when obsolete charts are removed from memory they are also deleted from disk (configurable +7. When obsolete charts are removed from memory they are also deleted from disk (configurable with `[global].delete obsolete charts files = yes`) ### Monitored container metrics diff --git a/collectors/cgroups.plugin/cgroup-name.sh b/collectors/cgroups.plugin/cgroup-name.sh index 55b02ac7..9a5812f3 100755 --- a/collectors/cgroups.plugin/cgroup-name.sh +++ b/collectors/cgroups.plugin/cgroup-name.sh @@ -47,11 +47,14 @@ fatal() { function parse_docker_like_inspect_output() { local output="${1}" - eval "$(grep -E "^(NOMAD_NAMESPACE|NOMAD_JOB_NAME|NOMAD_TASK_NAME|NOMAD_SHORT_ALLOC_ID|CONT_NAME)=" <<<"$output")" + eval "$(grep -E "^(NOMAD_NAMESPACE|NOMAD_JOB_NAME|NOMAD_TASK_NAME|NOMAD_SHORT_ALLOC_ID|CONT_NAME|IMAGE_NAME)=" <<<"$output")" if [ -n "$NOMAD_NAMESPACE" ] && [ -n "$NOMAD_JOB_NAME" ] && [ -n "$NOMAD_TASK_NAME" ] && [ -n "$NOMAD_SHORT_ALLOC_ID" ]; then - echo "${NOMAD_NAMESPACE}-${NOMAD_JOB_NAME}-${NOMAD_TASK_NAME}-${NOMAD_SHORT_ALLOC_ID}" + NAME="${NOMAD_NAMESPACE}-${NOMAD_JOB_NAME}-${NOMAD_TASK_NAME}-${NOMAD_SHORT_ALLOC_ID}" else - echo "${CONT_NAME}" | sed 's|^/||' + NAME=$(echo "${CONT_NAME}" | sed 's|^/||') + fi + if [ -n "${IMAGE_NAME}" ]; then + LABELS="image=\"${IMAGE_NAME}\"" fi } @@ -59,9 +62,9 @@ function docker_like_get_name_command() { local command="${1}" local id="${2}" info "Running command: ${command} inspect --format='{{range .Config.Env}}{{println .}}{{end}}CONT_NAME={{ .Name}}' \"${id}\"" - if OUTPUT="$(${command} inspect --format='{{range .Config.Env}}{{println .}}{{end}}CONT_NAME={{ .Name}}' "${id}")" && + if OUTPUT="$(${command} inspect --format='{{range .Config.Env}}{{println .}}{{end}}CONT_NAME={{ .Name}}{{println}}IMAGE_NAME={{ .Config.Image}}' "${id}")" && [ -n "$OUTPUT" ]; then - NAME="$(parse_docker_like_inspect_output "$OUTPUT")" + parse_docker_like_inspect_output "$OUTPUT" fi return 0 } @@ -85,8 +88,8 @@ function docker_like_get_name_api() { info "Running API command: curl \"${host}${path}\"" JSON=$(curl -sS "${host}${path}") fi - if OUTPUT=$(echo "${JSON}" | jq -r '.Config.Env[],"CONT_NAME=\(.Name)"') && [ -n "$OUTPUT" ]; then - NAME="$(parse_docker_like_inspect_output "$OUTPUT")" + if OUTPUT=$(echo "${JSON}" | jq -r '.Config.Env[],"CONT_NAME=\(.Name)","IMAGE_NAME=\(.Config.Image)"') && [ -n "$OUTPUT" ]; then + parse_docker_like_inspect_output "$OUTPUT" fi return 0 } @@ -303,8 +306,14 @@ function k8s_get_kubepod_name() { fi fi - url="https://$host/api/v1/pods" - [ -n "$MY_NODE_NAME" ] && url+="?fieldSelector=spec.nodeName==$MY_NODE_NAME" + local url + if [ -n "${USE_KUBELET_FOR_PODS_METADATA}" ]; then + url="${KUBELET_URL:-https://localhost:10250}/pods" + else + url="https://$host/api/v1/pods" + [ -n "$MY_NODE_NAME" ] && url+="?fieldSelector=spec.nodeName==$MY_NODE_NAME" + fi + # FIX: check HTTP response code if ! pods=$(curl --fail -sSk -H "$header" "$url" 2>&1); then warning "${fn}: error on curl '${url}': ${pods}." @@ -401,6 +410,10 @@ function k8s_get_kubepod_name() { # jq filter nonexistent field and nonexistent label value is 'null' if [[ $name =~ _null(_|$) ]]; then warning "${fn}: invalid name: $name (cgroup '$id')" + if [ -n "${USE_KUBELET_FOR_PODS_METADATA}" ]; then + # local data is cached and may not contain the correct id + return 2 + fi return 1 fi @@ -413,20 +426,25 @@ function k8s_get_name() { local fn="${FUNCNAME[0]}" local cgroup_path="${1}" local id="${2}" + local kubepod_name="" - NAME=$(k8s_get_kubepod_name "$cgroup_path" "$id") + kubepod_name=$(k8s_get_kubepod_name "$cgroup_path" "$id") case "$?" in 0) - NAME="k8s_${NAME}" + kubepod_name="k8s_${kubepod_name}" local name labels - name=${NAME%% *} - labels=${NAME#* } + name=${kubepod_name%% *} + labels=${kubepod_name#* } + if [ "$name" != "$labels" ]; then info "${fn}: cgroup '${id}' has chart name '${name}', labels '${labels}" + NAME="$name" + LABELS="$labels" else info "${fn}: cgroup '${id}' has chart name '${NAME}'" + NAME="$name" fi EXIT_CODE=$EXIT_SUCCESS ;; @@ -512,6 +530,7 @@ EXIT_RETRY=2 EXIT_DISABLE=3 EXIT_CODE=$EXIT_SUCCESS NAME= +LABELS= # ----------------------------------------------------------------------------- @@ -591,7 +610,13 @@ if [ -z "${NAME}" ]; then [ ${#NAME} -gt 100 ] && NAME="${NAME:0:100}" fi -info "cgroup '${CGROUP}' is called '${NAME}'" -echo "${NAME}" +NAME="${NAME// /_}" + +info "cgroup '${CGROUP}' is called '${NAME}', labels '${LABELS}'" +if [ -n "$LABELS" ]; then + echo "${NAME} ${LABELS}" +else + echo "${NAME}" +fi exit ${EXIT_CODE} diff --git a/collectors/cgroups.plugin/metrics.csv b/collectors/cgroups.plugin/metrics.csv new file mode 100644 index 00000000..aae057ba --- /dev/null +++ b/collectors/cgroups.plugin/metrics.csv @@ -0,0 +1,109 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +cgroup.cpu_limit,cgroup,used,percentage,"CPU Usage within the limits",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.cpu,cgroup,"user, system",percentage,"CPU Usage (100% = 1 core)",stacked,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.cpu_per_core,cgroup,a dimension per core,percentage,"CPU Usage (100% = 1 core) Per Core",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.throttled,cgroup,throttled,percentage,"CPU Throttled Runnable Periods",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.throttled_duration,cgroup,duration,ms,"CPU Throttled Time Duration",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.cpu_shares,cgroup,shares,shares,"CPU Time Relative Share",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.mem,cgroup,"cache, rss, swap, rss_huge, mapped_file",MiB,"Memory Usage",stacked,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.writeback,cgroup,"dirty, writeback",MiB,"Writeback Memory",area,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.mem_activity,cgroup,"in, out",MiB/s,"Memory Activity",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.pgfaults,cgroup,"pgfault, swap",MiB/s,"Memory Page Faults",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.mem_usage,cgroup,"ram, swap",MiB,"Used Memory",stacked,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.mem_usage_limit,cgroup,"available, used",MiB,"Used RAM within the limits",stacked,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.mem_utilization,cgroup,utilization,percentage,"Memory Utilization",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.mem_failcnt,cgroup,failures,count,"Memory Limit Failures",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.io,cgroup,"read, write",KiB/s,"I/O Bandwidth (all disks)",area,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.serviced_ops,cgroup,"read, write",operations/s,"Serviced I/O Operations (all disks)",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.throttle_io,cgroup,"read, write",KiB/s,"Throttle I/O Bandwidth (all disks)",area,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.throttle_serviced_ops,cgroup,"read, write",operations/s,"Throttle Serviced I/O Operations (all disks)",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.queued_ops,cgroup,"read, write",operations,"Queued I/O Operations (all disks)",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.merged_ops,cgroup,"read, write",operations/s,"Merged I/O Operations (all disks)",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.cpu_some_pressure,cgroup,"some10, some60, some300",percentage,"CPU some pressure",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.cpu_some_pressure_stall_time,cgroup,time,ms,"CPU some pressure stall time",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.cpu_full_pressure,cgroup,"some10, some60, some300",percentage,"CPU full pressure",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.cpu_full_pressure_stall_time,cgroup,time,ms,"CPU full pressure stall time",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.memory_some_pressure,cgroup,"some10, some60, some300",percentage,"Memory some pressure",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.memory_some_pressure_stall_time,cgroup,time,ms,"Memory some pressure stall time",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.memory_full_pressure,cgroup,"some10, some60, some300",percentage,"Memory full pressure",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.memory_full_pressure_stall_time,cgroup,time,ms,"Memory full pressure stall time",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.io_some_pressure,cgroup,"some10, some60, some300",percentage,"I/O some pressure",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.io_some_pressure_stall_time,cgroup,time,ms,"I/O some pressure stall time",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.io_full_pressure,cgroup,"some10, some60, some300",percentage,"I/O some pressure",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.io_full_pressure_stall_time,cgroup,time,ms,"I/O some pressure stall time",line,"container_name, image",cgroups.plugin,/sys/fs/cgroup +cgroup.net_net,"cgroup, network device","received, sent",kilobits/s,"Bandwidth",area,"container_name, image, device, interface_type",cgroups.plugin,/proc/net/dev +cgroup.net_packets,"cgroup, network device","received, sent, multicast",pps,"Packets",line,"container_name, image, device, interface_type",cgroups.plugin,/proc/net/dev +cgroup.net_errors,"cgroup, network device","inbound, outbound",errors/s,"Interface Errors",line,"container_name, image, device, interface_type",cgroups.plugin,/proc/net/dev +cgroup.net_drops,"cgroup, network device","inbound, outbound",errors/s,"Interface Drops",line,"container_name, image, device, interface_type",cgroups.plugin,/proc/net/dev +cgroup.net_fifo,"cgroup, network device","receive, transmit",errors/s,"Interface FIFO Buffer Errors",line,"container_name, image, device, interface_type",cgroups.plugin,/proc/net/dev +cgroup.net_compressed,"cgroup, network device","receive, sent",pps,"Interface FIFO Buffer Errors",line,"container_name, image, device, interface_type",cgroups.plugin,/proc/net/dev +cgroup.net_events,"cgroup, network device","frames, collisions, carrier",events/s,"Network Interface Events",line,"container_name, image, device, interface_type",cgroups.plugin,/proc/net/dev +cgroup.net_operstate,"cgroup, network device","up, down, notpresent, lowerlayerdown, testing, dormant, unknown",state,"Interface Operational State",line,"container_name, image, device, interface_type",cgroups.plugin,/proc/net/dev +cgroup.net_carrier,"cgroup, network device","up, down",state,"Interface Physical Link State",line,"container_name, image, device, interface_type",cgroups.plugin,/proc/net/dev +cgroup.net_mtu,"cgroup, network device",mtu,octets,"Interface MTU",line,"container_name, image, device, interface_type",cgroups.plugin,/proc/net/dev +k8s.cgroup.cpu_limit,k8s cgroup,used,percentage,"CPU Usage within the limits",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.cpu,k8s cgroup,"user, system",percentage,"CPU Usage (100% = 1000 mCPU)",stacked,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.cpu_per_core,k8s cgroup,a dimension per core,percentage,"CPU Usage (100% = 1000 mCPU) Per Core",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.throttled,k8s cgroup,throttled,percentage,"CPU Throttled Runnable Periods",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.throttled_duration,k8s cgroup,duration,ms,"CPU Throttled Time Duration",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.cpu_shares,k8s cgroup,shares,shares,"CPU Time Relative Share",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.mem,k8s cgroup,"cache, rss, swap, rss_huge, mapped_file",MiB,"Memory Usage",stacked,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.writeback,k8s cgroup,"dirty, writeback",MiB,"Writeback Memory",area,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.mem_activity,k8s cgroup,"in, out",MiB/s,"Memory Activity",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.pgfaults,k8s cgroup,"pgfault, swap",MiB/s,"Memory Page Faults",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.mem_usage,k8s cgroup,"ram, swap",MiB,"Used Memory",stacked,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.mem_usage_limit,k8s cgroup,"available, used",MiB,"Used RAM within the limits",stacked,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.mem_utilization,k8s cgroup,utilization,percentage,"Memory Utilization",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.mem_failcnt,k8s cgroup,failures,count,"Memory Limit Failures",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.io,k8s cgroup,"read, write",KiB/s,"I/O Bandwidth (all disks)",area,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.serviced_ops,k8s cgroup,"read, write",operations/s,"Serviced I/O Operations (all disks)",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.throttle_io,k8s cgroup,"read, write",KiB/s,"Throttle I/O Bandwidth (all disks)",area,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.throttle_serviced_ops,k8s cgroup,"read, write",operations/s,"Throttle Serviced I/O Operations (all disks)",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.queued_ops,k8s cgroup,"read, write",operations,"Queued I/O Operations (all disks)",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.merged_ops,k8s cgroup,"read, write",operations/s,"Merged I/O Operations (all disks)",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.cpu_some_pressure,k8s cgroup,"some10, some60, some300",percentage,"CPU some pressure",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.cpu_some_pressure_stall_time,k8s cgroup,time,ms,"CPU some pressure stall time",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.cpu_full_pressure,k8s cgroup,"some10, some60, some300",percentage,"CPU full pressure",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.cpu_full_pressure_stall_time,k8s cgroup,time,ms,"CPU full pressure stall time",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.memory_some_pressure,k8s cgroup,"some10, some60, some300",percentage,"Memory some pressure",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.memory_some_pressure_stall_time,k8s cgroup,time,ms,"Memory some pressure stall time",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.memory_full_pressure,k8s cgroup,"some10, some60, some300",percentage,"Memory full pressure",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.memory_full_pressure_stall_time,k8s cgroup,time,ms,"Memory full pressure stall time",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.io_some_pressure,k8s cgroup,"some10, some60, some300",percentage,"I/O some pressure",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.io_some_pressure_stall_time,k8s cgroup,time,ms,"I/O some pressure stall time",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.io_full_pressure,k8s cgroup,"some10, some60, some300",percentage,"I/O some pressure",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.io_full_pressure_stall_time,k8s cgroup,time,ms,"I/O some pressure stall time",line,"k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/sys/fs/cgroup +k8s.cgroup.net_net,"k8s cgroup, network device","received, sent",kilobits/s,"Bandwidth",area,"device, interface_type, k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/proc/net/dev +k8s.cgroup.net_packets,"k8s cgroup, network device","received, sent, multicast",pps,"Packets",line,"device, interface_type, k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/proc/net/dev +k8s.cgroup.net_errors,"k8s cgroup, network device","inbound, outbound",errors/s,"Interface Errors",line,"device, interface_type, k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/proc/net/dev +k8s.cgroup.net_drops,"k8s cgroup, network device","inbound, outbound",errors/s,"Interface Drops",line,"device, interface_type, k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/proc/net/dev +k8s.cgroup.net_fifo,"k8s cgroup, network device","receive, transmit",errors/s,"Interface FIFO Buffer Errors",line,"device, interface_type, k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/proc/net/dev +k8s.cgroup.net_compressed,"k8s cgroup, network device","receive, sent",pps,"Interface FIFO Buffer Errors",line,"device, interface_type, k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/proc/net/dev +k8s.cgroup.net_events,"k8s cgroup, network device","frames, collisions, carrier",events/s,"Network Interface Events",line,"device, interface_type, k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/proc/net/dev +k8s.cgroup.net_operstate,"k8s cgroup, network device","up, down, notpresent, lowerlayerdown, testing, dormant, unknown",state,"Interface Operational State",line,"device, interface_type, k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/proc/net/dev +k8s.cgroup.net_carrier,"k8s cgroup, network device","up, down",state,"Interface Physical Link State",line,"device, interface_type, k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/proc/net/dev +k8s.cgroup.net_mtu,"k8s cgroup, network device",mtu,octets,"Interface MTU",line,"device, interface_type, k8s_namespace, k8s_pod_name, k8s_pod_uid, k8s_controller_kind, k8s_controller_name, k8s_node_name, k8s_container_name, k8s_container_id, k8s_kind, k8s_qos_class, k8s_cluster_id",cgroups.plugin,/proc/net/dev +services.cpu,,a dimension per systemd service,percentage,"Systemd Services CPU utilization (100% = 1 core)",stacked,,cgroups.plugin,systemd +services.mem_usage,,a dimension per systemd service,MiB,"Systemd Services Used Memory",stacked,,cgroups.plugin,systemd +services.mem_rss,,a dimension per systemd service,MiB,"Systemd Services RSS Memory",stacked,,cgroups.plugin,systemd +services.mem_mapped,,a dimension per systemd service,MiB,"Systemd Services Mapped Memory",stacked,,cgroups.plugin,systemd +services.mem_cache,,a dimension per systemd service,MiB,"Systemd Services Cache Memory",stacked,,cgroups.plugin,systemd +services.mem_writeback,,a dimension per systemd service,MiB,"Systemd Services Writeback Memory",stacked,,cgroups.plugin,systemd +services.mem_pgfault,,a dimension per systemd service,MiB/s,"Systemd Services Memory Minor Page Faults",stacked,,cgroups.plugin,systemd +services.mem_pgmajfault,,a dimension per systemd service,MiB/s,"Systemd Services Memory Major Page Faults",stacked,,cgroups.plugin,systemd +services.mem_pgpgin,,a dimension per systemd service,MiB/s,"Systemd Services Memory Charging Activity",stacked,,cgroups.plugin,systemd +services.mem_pgpgout,,a dimension per systemd service,MiB/s,"Systemd Services Memory Uncharging Activity",stacked,,cgroups.plugin,systemd +services.mem_failcnt,,a dimension per systemd service,failures,"Systemd Services Memory Limit Failures",stacked,,cgroups.plugin,systemd +services.swap_usage,,a dimension per systemd service,MiB,"Systemd Services Swap Memory Used",stacked,,cgroups.plugin,systemd +services.io_read,,a dimension per systemd service,KiB/s,"Systemd Services Disk Read Bandwidth",stacked,,cgroups.plugin,systemd +services.io_write,,a dimension per systemd service,KiB/s,"Systemd Services Disk Write Bandwidth",stacked,,cgroups.plugin,systemd +services.io_ops_read,,a dimension per systemd service,operations/s,"Systemd Services Disk Read Operations",stacked,,cgroups.plugin,systemd +services.io_ops_write,,a dimension per systemd service,operations/s,"Systemd Services Disk Write Operations",stacked,,cgroups.plugin,systemd +services.throttle_io_read,,a dimension per systemd service,KiB/s,"Systemd Services Throttle Disk Read Bandwidth",stacked,,cgroups.plugin,systemd +services.services.throttle_io_write,,a dimension per systemd service,KiB/s,"Systemd Services Throttle Disk Write Bandwidth",stacked,,cgroups.plugin,systemd +services.throttle_io_ops_read,,a dimension per systemd service,operations/s,"Systemd Services Throttle Disk Read Operations",stacked,,cgroups.plugin,systemd +throttle_io_ops_write,,a dimension per systemd service,operations/s,"Systemd Services Throttle Disk Write Operations",stacked,,cgroups.plugin,systemd +services.queued_io_ops_read,,a dimension per systemd service,operations/s,"Systemd Services Queued Disk Read Operations",stacked,,cgroups.plugin,systemd +services.queued_io_ops_write,,a dimension per systemd service,operations/s,"Systemd Services Queued Disk Write Operations",stacked,,cgroups.plugin,systemd +services.merged_io_ops_read,,a dimension per systemd service,operations/s,"Systemd Services Merged Disk Read Operations",stacked,,cgroups.plugin,systemd +services.merged_io_ops_write,,a dimension per systemd service,operations/s,"Systemd Services Merged Disk Write Operations",stacked,,cgroups.plugin,systemd \ No newline at end of file diff --git a/collectors/cgroups.plugin/sys_fs_cgroup.c b/collectors/cgroups.plugin/sys_fs_cgroup.c index e63e042d..007d4245 100644 --- a/collectors/cgroups.plugin/sys_fs_cgroup.c +++ b/collectors/cgroups.plugin/sys_fs_cgroup.c @@ -449,70 +449,70 @@ void read_cgroup_plugin_configuration() { config_get("plugin:cgroups", "enable by default cgroups matching", // ---------------------------------------------------------------- - " !*/init.scope " // ignore init.scope - " !/system.slice/run-*.scope " // ignore system.slice/run-XXXX.scope - " *.scope " // we need all other *.scope for sure - - // ---------------------------------------------------------------- - - " /machine.slice/*.service " // #3367 systemd-nspawn - - // ---------------------------------------------------------------- - - " */kubepods/pod*/* " // k8s containers - " */kubepods/*/pod*/* " // k8s containers - " */*-kubepods-pod*/* " // k8s containers - " */*-kubepods-*-pod*/* " // k8s containers - " !*kubepods* !*kubelet* " // all other k8s cgroups - - // ---------------------------------------------------------------- - - " !*/vcpu* " // libvirtd adds these sub-cgroups - " !*/emulator " // libvirtd adds these sub-cgroups - " !*.mount " - " !*.partition " - " !*.service " - " !*.socket " - " !*.slice " - " !*.swap " - " !*.user " - " !/ " - " !/docker " - " !*/libvirt " - " !/lxc " - " !/lxc/*/* " // #1397 #2649 - " !/lxc.monitor* " - " !/lxc.pivot " - " !/lxc.payload " - " !/machine " - " !/qemu " - " !/system " - " !/systemd " - " !/user " - " * " // enable anything else - ), NULL, SIMPLE_PATTERN_EXACT); + " !*/init.scope " // ignore init.scope + " !/system.slice/run-*.scope " // ignore system.slice/run-XXXX.scope + " *.scope " // we need all other *.scope for sure + + // ---------------------------------------------------------------- + + " /machine.slice/*.service " // #3367 systemd-nspawn + + // ---------------------------------------------------------------- + + " */kubepods/pod*/* " // k8s containers + " */kubepods/*/pod*/* " // k8s containers + " */*-kubepods-pod*/* " // k8s containers + " */*-kubepods-*-pod*/* " // k8s containers + " !*kubepods* !*kubelet* " // all other k8s cgroups + + // ---------------------------------------------------------------- + + " !*/vcpu* " // libvirtd adds these sub-cgroups + " !*/emulator " // libvirtd adds these sub-cgroups + " !*.mount " + " !*.partition " + " !*.service " + " !*.socket " + " !*.slice " + " !*.swap " + " !*.user " + " !/ " + " !/docker " + " !*/libvirt " + " !/lxc " + " !/lxc/*/* " // #1397 #2649 + " !/lxc.monitor* " + " !/lxc.pivot " + " !/lxc.payload " + " !/machine " + " !/qemu " + " !/system " + " !/systemd " + " !/user " + " * " // enable anything else + ), NULL, SIMPLE_PATTERN_EXACT, true); enabled_cgroup_names = simple_pattern_create( config_get("plugin:cgroups", "enable by default cgroups names matching", - " * " - ), NULL, SIMPLE_PATTERN_EXACT); + " * " + ), NULL, SIMPLE_PATTERN_EXACT, true); search_cgroup_paths = simple_pattern_create( config_get("plugin:cgroups", "search for cgroups in subpaths matching", - " !*/init.scope " // ignore init.scope - " !*-qemu " // #345 - " !*.libvirt-qemu " // #3010 - " !/init.scope " - " !/system " - " !/systemd " - " !/user " - " !/user.slice " - " !/lxc/*/* " // #2161 #2649 - " !/lxc.monitor " - " !/lxc.payload/*/* " - " !/lxc.payload.* " - " * " - ), NULL, SIMPLE_PATTERN_EXACT); + " !*/init.scope " // ignore init.scope + " !*-qemu " // #345 + " !*.libvirt-qemu " // #3010 + " !/init.scope " + " !/system " + " !/systemd " + " !/user " + " !/user.slice " + " !/lxc/*/* " // #2161 #2649 + " !/lxc.monitor " + " !/lxc.payload/*/* " + " !/lxc.payload.* " + " * " + ), NULL, SIMPLE_PATTERN_EXACT, true); snprintfz(filename, FILENAME_MAX, "%s/cgroup-name.sh", netdata_configured_primary_plugins_dir); cgroups_rename_script = config_get("plugin:cgroups", "script to get cgroup names", filename); @@ -522,37 +522,37 @@ void read_cgroup_plugin_configuration() { enabled_cgroup_renames = simple_pattern_create( config_get("plugin:cgroups", "run script to rename cgroups matching", - " !/ " - " !*.mount " - " !*.socket " - " !*.partition " - " /machine.slice/*.service " // #3367 systemd-nspawn - " !*.service " - " !*.slice " - " !*.swap " - " !*.user " - " !init.scope " - " !*.scope/vcpu* " // libvirtd adds these sub-cgroups - " !*.scope/emulator " // libvirtd adds these sub-cgroups - " *.scope " - " *docker* " - " *lxc* " - " *qemu* " - " */kubepods/pod*/* " // k8s containers - " */kubepods/*/pod*/* " // k8s containers - " */*-kubepods-pod*/* " // k8s containers - " */*-kubepods-*-pod*/* " // k8s containers - " !*kubepods* !*kubelet* " // all other k8s cgroups - " *.libvirt-qemu " // #3010 - " * " - ), NULL, SIMPLE_PATTERN_EXACT); + " !/ " + " !*.mount " + " !*.socket " + " !*.partition " + " /machine.slice/*.service " // #3367 systemd-nspawn + " !*.service " + " !*.slice " + " !*.swap " + " !*.user " + " !init.scope " + " !*.scope/vcpu* " // libvirtd adds these sub-cgroups + " !*.scope/emulator " // libvirtd adds these sub-cgroups + " *.scope " + " *docker* " + " *lxc* " + " *qemu* " + " */kubepods/pod*/* " // k8s containers + " */kubepods/*/pod*/* " // k8s containers + " */*-kubepods-pod*/* " // k8s containers + " */*-kubepods-*-pod*/* " // k8s containers + " !*kubepods* !*kubelet* " // all other k8s cgroups + " *.libvirt-qemu " // #3010 + " * " + ), NULL, SIMPLE_PATTERN_EXACT, true); if(cgroup_enable_systemd_services) { systemd_services_cgroups = simple_pattern_create( config_get("plugin:cgroups", "cgroups to match as systemd services", - " !/system.slice/*/*.service " - " /system.slice/*.service " - ), NULL, SIMPLE_PATTERN_EXACT); + " !/system.slice/*/*.service " + " /system.slice/*.service " + ), NULL, SIMPLE_PATTERN_EXACT, true); } mountinfo_free_all(root); @@ -1089,10 +1089,10 @@ static inline void cgroup_read_cpuacct_stat(struct cpuacct_stat *cp) { uint32_t hash = simple_hash(s); if(unlikely(hash == user_hash && !strcmp(s, "user"))) - cp->user = str2ull(procfile_lineword(ff, i, 1)); + cp->user = str2ull(procfile_lineword(ff, i, 1), NULL); else if(unlikely(hash == system_hash && !strcmp(s, "system"))) - cp->system = str2ull(procfile_lineword(ff, i, 1)); + cp->system = str2ull(procfile_lineword(ff, i, 1), NULL); } cp->updated = 1; @@ -1138,11 +1138,11 @@ static inline void cgroup_read_cpuacct_cpu_stat(struct cpuacct_cpu_throttling *c uint32_t hash = simple_hash(s); if (unlikely(hash == nr_periods_hash && !strcmp(s, "nr_periods"))) { - cp->nr_periods = str2ull(procfile_lineword(ff, i, 1)); + cp->nr_periods = str2ull(procfile_lineword(ff, i, 1), NULL); } else if (unlikely(hash == nr_throttled_hash && !strcmp(s, "nr_throttled"))) { - cp->nr_throttled = str2ull(procfile_lineword(ff, i, 1)); + cp->nr_throttled = str2ull(procfile_lineword(ff, i, 1), NULL); } else if (unlikely(hash == throttled_time_hash && !strcmp(s, "throttled_time"))) { - cp->throttled_time = str2ull(procfile_lineword(ff, i, 1)); + cp->throttled_time = str2ull(procfile_lineword(ff, i, 1), NULL); } } cp->nr_throttled_perc = @@ -1195,15 +1195,15 @@ static inline void cgroup2_read_cpuacct_cpu_stat(struct cpuacct_stat *cp, struct uint32_t hash = simple_hash(s); if (unlikely(hash == user_usec_hash && !strcmp(s, "user_usec"))) { - cp->user = str2ull(procfile_lineword(ff, i, 1)); + cp->user = str2ull(procfile_lineword(ff, i, 1), NULL); } else if (unlikely(hash == system_usec_hash && !strcmp(s, "system_usec"))) { - cp->system = str2ull(procfile_lineword(ff, i, 1)); + cp->system = str2ull(procfile_lineword(ff, i, 1), NULL); } else if (unlikely(hash == nr_periods_hash && !strcmp(s, "nr_periods"))) { - cpt->nr_periods = str2ull(procfile_lineword(ff, i, 1)); + cpt->nr_periods = str2ull(procfile_lineword(ff, i, 1), NULL); } else if (unlikely(hash == nr_throttled_hash && !strcmp(s, "nr_throttled"))) { - cpt->nr_throttled = str2ull(procfile_lineword(ff, i, 1)); + cpt->nr_throttled = str2ull(procfile_lineword(ff, i, 1), NULL); } else if (unlikely(hash == throttled_usec_hash && !strcmp(s, "throttled_usec"))) { - cpt->throttled_time = str2ull(procfile_lineword(ff, i, 1)) * 1000; // usec -> ns + cpt->throttled_time = str2ull(procfile_lineword(ff, i, 1), NULL) * 1000; // usec -> ns } } cpt->nr_throttled_perc = @@ -1289,7 +1289,7 @@ static inline void cgroup_read_cpuacct_usage(struct cpuacct_usage *ca) { unsigned long long total = 0; for(i = 0; i < ca->cpus ;i++) { - unsigned long long n = str2ull(procfile_lineword(ff, 0, i)); + unsigned long long n = str2ull(procfile_lineword(ff, 0, i), NULL); ca->cpu_percpu[i] = n; total += n; } @@ -1346,10 +1346,10 @@ static inline void cgroup_read_blkio(struct blkio *io) { uint32_t hash = simple_hash(s); if(unlikely(hash == Read_hash && !strcmp(s, "Read"))) - io->Read += str2ull(procfile_lineword(ff, i, 2)); + io->Read += str2ull(procfile_lineword(ff, i, 2), NULL); else if(unlikely(hash == Write_hash && !strcmp(s, "Write"))) - io->Write += str2ull(procfile_lineword(ff, i, 2)); + io->Write += str2ull(procfile_lineword(ff, i, 2), NULL); /* else if(unlikely(hash == Sync_hash && !strcmp(s, "Sync"))) @@ -1409,8 +1409,8 @@ static inline void cgroup2_read_blkio(struct blkio *io, unsigned int word_offset io->Write = 0; for (i = 0; i < lines; i++) { - io->Read += str2ull(procfile_lineword(ff, i, 2 + word_offset)); - io->Write += str2ull(procfile_lineword(ff, i, 4 + word_offset)); + io->Read += str2ull(procfile_lineword(ff, i, 2 + word_offset), NULL); + io->Write += str2ull(procfile_lineword(ff, i, 4 + word_offset), NULL); } io->updated = 1; @@ -1452,13 +1452,13 @@ static inline void cgroup2_read_pressure(struct pressure *res) { res->some.share_time.value10 = strtod(procfile_lineword(ff, 0, 2), NULL); res->some.share_time.value60 = strtod(procfile_lineword(ff, 0, 4), NULL); res->some.share_time.value300 = strtod(procfile_lineword(ff, 0, 6), NULL); - res->some.total_time.value_total = str2ull(procfile_lineword(ff, 0, 8)) / 1000; // us->ms + res->some.total_time.value_total = str2ull(procfile_lineword(ff, 0, 8), NULL) / 1000; // us->ms if (lines > 2) { 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, 1, 8)) / 1000; // us->ms + res->full.total_time.value_total = str2ull(procfile_lineword(ff, 1, 8), NULL) / 1000; // us->ms } res->updated = 1; @@ -1769,13 +1769,13 @@ static inline void substitute_dots_in_id(char *s) { // ---------------------------------------------------------------------------- // parse k8s labels -char *k8s_parse_resolved_name_and_labels(DICTIONARY *labels, char *data) { +char *cgroup_parse_resolved_name_and_labels(DICTIONARY *labels, char *data) { // the first word, up to the first space is the name - char *name = mystrsep(&data, " "); + char *name = strsep_skip_consecutive_separators(&data, " "); // the rest are key=value pairs separated by comma while(data) { - char *pair = mystrsep(&data, ","); + char *pair = strsep_skip_consecutive_separators(&data, ","); rrdlabels_add_pair(labels, pair, RRDLABEL_SRC_AUTO| RRDLABEL_SRC_K8S); } @@ -1898,19 +1898,21 @@ static inline void discovery_rename_cgroup(struct cgroup *cg) { break; } - if(cg->pending_renames || cg->processed) return; - if(!new_name || !*new_name || *new_name == '\n') return; - if(!(new_name = trim(new_name))) return; + if (cg->pending_renames || cg->processed) + return; + if (!new_name || !*new_name || *new_name == '\n') + return; + if (!(new_name = trim(new_name))) + return; char *name = new_name; - if (!strncmp(new_name, "k8s_", 4)) { - if(!cg->chart_labels) cg->chart_labels = rrdlabels_create(); - // read the new labels and remove the obsolete ones - rrdlabels_unmark_all(cg->chart_labels); - name = k8s_parse_resolved_name_and_labels(cg->chart_labels, new_name); - rrdlabels_remove_all_unmarked(cg->chart_labels); - } + if (!cg->chart_labels) + cg->chart_labels = rrdlabels_create(); + // read the new labels and remove the obsolete ones + rrdlabels_unmark_all(cg->chart_labels); + name = cgroup_parse_resolved_name_and_labels(cg->chart_labels, new_name); + rrdlabels_remove_all_unmarked(cg->chart_labels); freez(cg->chart_title); cg->chart_title = cgroup_title_strdupz(name); @@ -2713,6 +2715,16 @@ static inline void discovery_process_cgroup(struct cgroup *cg) { return; } + if (!cg->chart_labels) + cg->chart_labels = rrdlabels_create(); + + if (!k8s_is_kubepod(cg)) { + rrdlabels_add(cg->chart_labels, "cgroup_name", cg->chart_id, RRDLABEL_SRC_AUTO); + if (!dictionary_get(cg->chart_labels, "image")) { + rrdlabels_add(cg->chart_labels, "image", "", RRDLABEL_SRC_AUTO); + } + } + worker_is_busy(WORKER_DISCOVERY_PROCESS_NETWORK); read_cgroup_network_interfaces(cg); } @@ -2784,10 +2796,10 @@ void cgroup_discovery_worker(void *ptr) worker_register_job_name(WORKER_DISCOVERY_LOCK, "lock"); entrypoint_parent_process_comm = simple_pattern_create( - " runc:[* " // http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2021/12/28/runc-internals-3) - " exe ", // https://github.com/falcosecurity/falco/blob/9d41b0a151b83693929d3a9c84f7c5c85d070d3a/rules/falco_rules.yaml#L1961 - NULL, - SIMPLE_PATTERN_EXACT); + " runc:[* " // http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2021/12/28/runc-internals-3) + " exe ", // https://github.com/falcosecurity/falco/blob/9d41b0a151b83693929d3a9c84f7c5c85d070d3a/rules/falco_rules.yaml#L1961 + NULL, + SIMPLE_PATTERN_EXACT, true); while (service_running(SERVICE_COLLECTORS)) { worker_is_idle(); @@ -3566,14 +3578,14 @@ static inline void update_cpu_limits2(struct cgroup *cg) { return; } - cg->cpu_cfs_period = str2ull(procfile_lineword(ff, 0, 1)); + cg->cpu_cfs_period = str2ull(procfile_lineword(ff, 0, 1), NULL); cg->cpuset_cpus = get_system_cpus(); char *s = "max\n\0"; if(strcmp(s, procfile_lineword(ff, 0, 0)) == 0){ cg->cpu_cfs_quota = cg->cpu_cfs_period * cg->cpuset_cpus; } else { - cg->cpu_cfs_quota = str2ull(procfile_lineword(ff, 0, 0)); + cg->cpu_cfs_quota = str2ull(procfile_lineword(ff, 0, 0), NULL); } debug(D_CGROUP, "CPU limits values: %llu %llu %llu", cg->cpu_cfs_period, cg->cpuset_cpus, cg->cpu_cfs_quota); return; @@ -3623,7 +3635,7 @@ static inline int update_memory_limits(char **filename, const RRDSETVAR_ACQUIRED rrdsetvar_custom_chart_variable_set(cg->st_mem_usage, *chart_var, (NETDATA_DOUBLE)(*value / (1024 * 1024))); return 1; } - *value = str2ull(buffer); + *value = str2ull(buffer, NULL); rrdsetvar_custom_chart_variable_set(cg->st_mem_usage, *chart_var, (NETDATA_DOUBLE)(*value / (1024 * 1024))); return 1; } @@ -3676,7 +3688,10 @@ void update_cgroup_charts(int update_every) { if(likely(cg->cpuacct_stat.updated && cg->cpuacct_stat.enabled == CONFIG_BOOLEAN_YES)) { if(unlikely(!cg->st_cpu)) { - snprintfz(title, CHART_TITLE_MAX, "CPU Usage (100%% = 1 core)"); + snprintfz( + title, + CHART_TITLE_MAX, + k8s_is_kubepod(cg) ? "CPU Usage (100%% = 1000 mCPU)" : "CPU Usage (100%% = 1 core)"); cg->st_cpu = rrdset_create_localhost( cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX) @@ -3879,7 +3894,11 @@ void update_cgroup_charts(int update_every) { unsigned int i; if(unlikely(!cg->st_cpu_per_core)) { - snprintfz(title, CHART_TITLE_MAX, "CPU Usage (100%% = 1 core) Per Core"); + snprintfz( + title, + CHART_TITLE_MAX, + k8s_is_kubepod(cg) ? "CPU Usage (100%% = 1000 mCPU) Per Core" : + "CPU Usage (100%% = 1 core) Per Core"); cg->st_cpu_per_core = rrdset_create_localhost( cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX) @@ -4111,7 +4130,7 @@ void update_cgroup_charts(int update_every) { if(likely(ff)) ff = procfile_readall(ff); if(likely(ff && procfile_lines(ff) && !strncmp(procfile_word(ff, 0), "MemTotal", 8))) - ram_total = str2ull(procfile_word(ff, 1)) * 1024; + ram_total = str2ull(procfile_word(ff, 1), NULL) * 1024; else { collector_error("Cannot read file %s. Will not update cgroup %s RAM limit anymore.", filename, cg->id); freez(cg->filename_memory_limit); @@ -4771,6 +4790,7 @@ static void cgroup_main_cleanup(void *ptr) { } if (shm_cgroup_ebpf.header) { + shm_cgroup_ebpf.header->cgroup_root_count = 0; munmap(shm_cgroup_ebpf.header, shm_cgroup_ebpf.header->body_length); } diff --git a/collectors/cgroups.plugin/sys_fs_cgroup.h b/collectors/cgroups.plugin/sys_fs_cgroup.h index d1adf8a9..dc800ba9 100644 --- a/collectors/cgroups.plugin/sys_fs_cgroup.h +++ b/collectors/cgroups.plugin/sys_fs_cgroup.h @@ -39,6 +39,6 @@ typedef struct netdata_ebpf_cgroup_shm { #include "../proc.plugin/plugin_proc.h" -char *k8s_parse_resolved_name_and_labels(DICTIONARY *labels, char *data); +char *cgroup_parse_resolved_name_and_labels(DICTIONARY *labels, char *data); #endif //NETDATA_SYS_FS_CGROUP_H diff --git a/collectors/cgroups.plugin/tests/test_cgroups_plugin.c b/collectors/cgroups.plugin/tests/test_cgroups_plugin.c index 25939a9c..a0f91530 100644 --- a/collectors/cgroups.plugin/tests/test_cgroups_plugin.c +++ b/collectors/cgroups.plugin/tests/test_cgroups_plugin.c @@ -33,7 +33,7 @@ static int read_label_callback(const char *name, const char *value, RRDLABEL_SRC return 1; } -static void test_k8s_parse_resolved_name(void **state) +static void test_cgroup_parse_resolved_name(void **state) { UNUSED(state); @@ -96,7 +96,7 @@ static void test_k8s_parse_resolved_name(void **state) for (int i = 0; test_data[i].data != NULL; i++) { char *data = strdup(test_data[i].data); - char *name = k8s_parse_resolved_name_and_labels(labels, data); + char *name = cgroup_parse_resolved_name_and_labels(labels, data); assert_string_equal(name, test_data[i].name); @@ -122,10 +122,10 @@ static void test_k8s_parse_resolved_name(void **state) int main(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test(test_k8s_parse_resolved_name), + cmocka_unit_test(test_cgroup_parse_resolved_name), }; - int test_res = cmocka_run_group_tests_name("test_k8s_parse_resolved_name", tests, NULL, NULL); + int test_res = cmocka_run_group_tests_name("test_cgroup_parse_resolved_name", tests, NULL, NULL); return test_res; } diff --git a/collectors/charts.d.plugin/README.md b/collectors/charts.d.plugin/README.md index 092a3f02..3e4edf56 100644 --- a/collectors/charts.d.plugin/README.md +++ b/collectors/charts.d.plugin/README.md @@ -1,20 +1,14 @@ - - # charts.d.plugin `charts.d.plugin` is a Netdata external plugin. It is an **orchestrator** for data collection modules written in `BASH` v4+. -1. It runs as an independent process `ps fax` shows it -2. It is started and stopped automatically by Netdata -3. It communicates with Netdata via a unidirectional pipe (sending data to the `netdata` daemon) -4. Supports any number of data collection **modules** +1. It runs as an independent process `ps fax` shows it +2. It is started and stopped automatically by Netdata +3. It communicates with Netdata via a unidirectional pipe (sending data to the `netdata` daemon) +4. Supports any number of data collection **modules** + +To better understand the guidelines and the API behind our External plugins, please have a look at the [Introduction to External plugins](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md) prior to reading this page. + `charts.d.plugin` has been designed so that the actual script that will do data collection will be permanently in memory, collecting data with as little overheads as possible @@ -25,12 +19,11 @@ The scripts should have the filename suffix: `.chart.sh`. ## Configuration -`charts.d.plugin` itself can be configured using the configuration file `/etc/netdata/charts.d.conf` -(to edit it on your system run `/etc/netdata/edit-config charts.d.conf`). This file is also a BASH script. +`charts.d.plugin` itself can be [configured](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) using the configuration file `/etc/netdata/charts.d.conf`. This file is also a BASH script. In this file, you can place statements like this: -``` +```conf enable_all_charts="yes" X="yes" Y="no" @@ -48,36 +41,31 @@ A `charts.d.plugin` module is a BASH script defining a few functions. For a module called `X`, the following criteria must be met: -1. The module script must be called `X.chart.sh` and placed in `/usr/libexec/netdata/charts.d`. +1. The module script must be called `X.chart.sh` and placed in `/usr/libexec/netdata/charts.d`. -2. If the module needs a configuration, it should be called `X.conf` and placed in `/etc/netdata/charts.d`. - The configuration file `X.conf` is also a BASH script itself. - To edit the default files supplied by Netdata, run `/etc/netdata/edit-config charts.d/X.conf`, - where `X` is the name of the module. +2. If the module needs a configuration, it should be called `X.conf` and placed in `/etc/netdata/charts.d`. + The configuration file `X.conf` is also a BASH script itself. + You can edit the default files supplied by Netdata, by editing `/etc/netdata/edit-config charts.d/X.conf`, where `X` is the name of the module. -3. All functions and global variables defined in the script and its configuration, must begin with `X_`. +3. All functions and global variables defined in the script and its configuration, must begin with `X_`. -4. The following functions must be defined: +4. The following functions must be defined: - - `X_check()` - returns 0 or 1 depending on whether the module is able to run or not + - `X_check()` - returns 0 or 1 depending on whether the module is able to run or not (following the standard Linux command line return codes: 0 = OK, the collector can operate and 1 = FAILED, the collector cannot be used). - - `X_create()` - creates the Netdata charts, following the standard Netdata plugin guides as described in - **[External Plugins](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md)** (commands `CHART` and `DIMENSION`). + - `X_create()` - creates the Netdata charts (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](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md)** (commands `BEGIN`, `SET`, `END`). + - `X_update()` - collects the values for the defined charts (commands `BEGIN`, `SET`, `END`). The return value also matters: 0 = OK, 1 = FAILED. -5. The following global variables are available to be set: - - `X_update_every` - is the data collection frequency for the module script, in seconds. +5. The following global variables are available to be set: + - `X_update_every` - is the data collection frequency for the module script, in seconds. 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](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md)**). - ### X_check() The purpose of the BASH function `X_check()` is to check if the module can collect data (or check its config). @@ -90,7 +78,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](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md)**). +plugin guidelines. `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). @@ -100,7 +88,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](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md)**). +following the Netdata plugin guidelines. 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. @@ -187,16 +175,14 @@ You can have multiple `charts.d.plugin` running to overcome this problem. This is what you need to do: -1. Decide a new name for the new charts.d instance: example `charts2.d`. +1. Decide a new name for the new charts.d instance: example `charts2.d`. -2. Create/edit the files `/etc/netdata/charts.d.conf` and `/etc/netdata/charts2.d.conf` and enable / disable the +2. Create/edit the files `/etc/netdata/charts.d.conf` and `/etc/netdata/charts2.d.conf` and enable / disable the module you want each to run. Remember to set `enable_all_charts="no"` to both of them, and enable the individual modules for each. -3. link `/usr/libexec/netdata/plugins.d/charts.d.plugin` to `/usr/libexec/netdata/plugins.d/charts2.d.plugin`. +3. link `/usr/libexec/netdata/plugins.d/charts.d.plugin` to `/usr/libexec/netdata/plugins.d/charts2.d.plugin`. Netdata will spawn a new charts.d process. Execute the above in this order, since Netdata will (by default) attempt to start new plugins soon after they are created in `/usr/libexec/netdata/plugins.d/`. - - diff --git a/collectors/charts.d.plugin/ap/README.md b/collectors/charts.d.plugin/ap/README.md index 03ab6d13..bc7460a2 100644 --- a/collectors/charts.d.plugin/ap/README.md +++ b/collectors/charts.d.plugin/ap/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/char sidebar_label: "Access points" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Remotes/Devices" +learn_rel_path: "Integrations/Monitor/Remotes/Devices" --> -# Access point monitoring with Netdata +# Access point collector The `ap` collector visualizes data related to access points. diff --git a/collectors/charts.d.plugin/ap/metrics.csv b/collectors/charts.d.plugin/ap/metrics.csv new file mode 100644 index 00000000..8428cf6d --- /dev/null +++ b/collectors/charts.d.plugin/ap/metrics.csv @@ -0,0 +1,7 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +ap.clients,wireless device,clients,clients,"Connected clients to ${ssid} on ${dev}",line,,charts.d.plugin,ap +ap.net,wireless device,"received, sent",kilobits/s,"Bandwidth for ${ssid} on ${dev}",area,,charts.d.plugin,ap +ap.packets,wireless device,"received, sent",packets/s,"Packets for ${ssid} on ${dev}",line,,charts.d.plugin,ap +ap.issues,wireless device,"retries, failures",issues/s,"Transmit Issues for ${ssid} on ${dev}",line,,charts.d.plugin,ap +ap.signal,wireless device,"average signal",dBm,"Average Signal for ${ssid} on ${dev}",line,,charts.d.plugin,ap +ap.bitrate,wireless device,"receive, transmit, expected",Mbps,"Bitrate for ${ssid} on ${dev}",line,,charts.d.plugin,ap \ No newline at end of file diff --git a/collectors/charts.d.plugin/apcupsd/README.md b/collectors/charts.d.plugin/apcupsd/README.md index 602977be..6934d59c 100644 --- a/collectors/charts.d.plugin/apcupsd/README.md +++ b/collectors/charts.d.plugin/apcupsd/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/char sidebar_label: "APC UPS" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Remotes/Devices" +learn_rel_path: "Integrations/Monitor/Remotes/Devices" --> -# APC UPS monitoring with Netdata +# APC UPS collector Monitors different APC UPS models and retrieves status information using `apcaccess` tool. diff --git a/collectors/charts.d.plugin/apcupsd/metrics.csv b/collectors/charts.d.plugin/apcupsd/metrics.csv new file mode 100644 index 00000000..828abf1f --- /dev/null +++ b/collectors/charts.d.plugin/apcupsd/metrics.csv @@ -0,0 +1,11 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +apcupsd.charge,ups,charge,percentage,"UPS Charge",area,,charts.d.plugin,apcupsd +apcupsd.battery.voltage,ups,"voltage, nominal",Volts,"UPS Battery Voltage",line,,charts.d.plugin,apcupsd +apcupsd.input.voltage,ups,"voltage, min, max",Volts,"UPS Input Voltage",line,,charts.d.plugin,apcupsd +apcupsd.output.voltage,ups,"absolute, nominal",Volts,"UPS Output Voltage",line,,charts.d.plugin,apcupsd +apcupsd.input.frequency,ups,frequency,Hz,"UPS Input Voltage",line,,charts.d.plugin,apcupsd +apcupsd.load,ups,load,percentage,"UPS Load",area,,charts.d.plugin,apcupsd +apcupsd.load_usage,ups,load,Watts,"UPS Load Usage",area,,charts.d.plugin,apcupsd +apcupsd.temperature,ups,temp,Celsius,"UPS Temperature",line,,charts.d.plugin,apcupsd +apcupsd.time,ups,time,Minutes,"UPS Time Remaining",area,,charts.d.plugin,apcupsd +apcupsd.online,ups,online,boolean,"UPS ONLINE flag",line,,charts.d.plugin,apcupsd \ No newline at end of file diff --git a/collectors/charts.d.plugin/charts.d.plugin.in b/collectors/charts.d.plugin/charts.d.plugin.in index 9187fc25..20996eb9 100755 --- a/collectors/charts.d.plugin/charts.d.plugin.in +++ b/collectors/charts.d.plugin/charts.d.plugin.in @@ -32,6 +32,7 @@ chartsd_cleanup() { [ $debug -eq 1 ] && echo >&2 "$PROGRAM_NAME: cleaning up temporary directory $TMP_DIR ..." rm -rf "$TMP_DIR" fi + echo "EXIT" exit 0 } trap chartsd_cleanup EXIT QUIT HUP INT TERM diff --git a/collectors/charts.d.plugin/example/README.md b/collectors/charts.d.plugin/example/README.md index d5faaabf..c2860eb3 100644 --- a/collectors/charts.d.plugin/example/README.md +++ b/collectors/charts.d.plugin/example/README.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/char sidebar_label: "example-charts.d.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Mock Collectors" +learn_rel_path: "Integrations/Monitor/Mock Collectors" --> # Example diff --git a/collectors/charts.d.plugin/libreswan/README.md b/collectors/charts.d.plugin/libreswan/README.md index 7c4eabcf..a20eb86c 100644 --- a/collectors/charts.d.plugin/libreswan/README.md +++ b/collectors/charts.d.plugin/libreswan/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/char sidebar_label: "Libreswan IPSec tunnels" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Networking" +learn_rel_path: "Integrations/Monitor/Networking" --> -# Libreswan IPSec tunnel monitoring with Netdata +# Libreswan IPSec tunnel collector Collects bytes-in, bytes-out and uptime for all established libreswan IPSEC tunnels. diff --git a/collectors/charts.d.plugin/libreswan/metrics.csv b/collectors/charts.d.plugin/libreswan/metrics.csv new file mode 100644 index 00000000..e81c43b2 --- /dev/null +++ b/collectors/charts.d.plugin/libreswan/metrics.csv @@ -0,0 +1,3 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +libreswan.net,IPSEC tunnel,"in, out",kilobits/s,"LibreSWAN Tunnel ${name} Traffic",area,,charts.d.plugin,libreswan +libreswan.uptime,IPSEC tunnel,uptime,seconds,"LibreSWAN Tunnel ${name} Uptime",line,,charts.d.plugin,libreswan \ No newline at end of file diff --git a/collectors/charts.d.plugin/nut/README.md b/collectors/charts.d.plugin/nut/README.md index 7bb8a550..44882544 100644 --- a/collectors/charts.d.plugin/nut/README.md +++ b/collectors/charts.d.plugin/nut/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/char sidebar_label: "UPS/PDU" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Remotes/Devices" +learn_rel_path: "Integrations/Monitor/Remotes/Devices" --> -# UPS/PDU monitoring with Netdata +# UPS/PDU collector Collects UPS data for all power devices configured in the system. diff --git a/collectors/charts.d.plugin/nut/metrics.csv b/collectors/charts.d.plugin/nut/metrics.csv new file mode 100644 index 00000000..2abd5725 --- /dev/null +++ b/collectors/charts.d.plugin/nut/metrics.csv @@ -0,0 +1,12 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +nut.charge,ups,charge,percentage,"UPS Charge",area,,charts.d.plugin,nut +nut.runtime,ups,runtime,seconds,"UPS Runtime",line,,charts.d.plugin,nut +nut.battery.voltage,ups,"voltage, high, low, nominal",Volts,"UPS Battery Voltage",line,,charts.d.plugin,nut +nut.input.voltage,ups,"voltage, fault, nominal",Volts,"UPS Input Voltage",line,,charts.d.plugin,nut +nut.input.current,ups,nominal,Ampere,"UPS Input Current",line,,charts.d.plugin,nut +nut.input.frequency,ups,"frequency, nominal",Hz,"UPS Input Frequency",line,,charts.d.plugin,nut +nut.output.voltage,ups,voltage,Volts,"UPS Output Voltage",line,,charts.d.plugin,nut +nut.load,ups,load,percentage,"UPS Load",area,,charts.d.plugin,nut +nut.load_usage,ups,load_usage,Watts,"UPS Load Usage",area,,charts.d.plugin,nut +nut.temperature,ups,temp,temperature,"UPS Temperature",line,,charts.d.plugin,nut +nut.clients,ups,clients,clients,"UPS Connected Clients",area,,charts.d.plugin,nut \ No newline at end of file diff --git a/collectors/charts.d.plugin/opensips/README.md b/collectors/charts.d.plugin/opensips/README.md index 74624c7f..c278b53a 100644 --- a/collectors/charts.d.plugin/opensips/README.md +++ b/collectors/charts.d.plugin/opensips/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/char sidebar_label: "OpenSIPS" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Networking" +learn_rel_path: "Integrations/Monitor/Networking" --> -# OpenSIPS monitoring with Netdata +# OpenSIPS collector ## Configuration diff --git a/collectors/charts.d.plugin/opensips/metrics.csv b/collectors/charts.d.plugin/opensips/metrics.csv new file mode 100644 index 00000000..2efab370 --- /dev/null +++ b/collectors/charts.d.plugin/opensips/metrics.csv @@ -0,0 +1,20 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +opensips.dialogs_active,,"active, early",dialogs,"OpenSIPS Active Dialogs",area,,charts.d.plugin,opensips +opensips.users,,"registered, location, contacts, expires",users,"OpenSIPS Users",line,,charts.d.plugin,opensips +opensips.registrar,,"accepted, rejected",registrations/s,"OpenSIPS Registrar",line,,charts.d.plugin,opensips +opensips.transactions,,"UAS, UAC",transactions/s,"OpenSIPS Transactions",line,,charts.d.plugin,opensips +opensips.core_rcv,,"requests, replies",queries/s,"OpenSIPS Core Receives",line,,charts.d.plugin,opensips +opensips.core_fwd,,"requests, replies",queries/s,"OpenSIPS Core Forwards",line,,charts.d.plugin,opensips +opensips.core_drop,,"requests, replies",queries/s,"OpenSIPS Core Drops",line,,charts.d.plugin,opensips +opensips.core_err,,"requests, replies",queries/s,"OpenSIPS Core Errors",line,,charts.d.plugin,opensips +opensips.core_bad,,"bad_URIs_rcvd, unsupported_methods, bad_msg_hdr",queries/s,"OpenSIPS Core Bad",line,,charts.d.plugin,opensips +opensips.tm_replies,,"received, relayed, local",replies/s,"OpenSIPS TM Replies",line,,charts.d.plugin,opensips +opensips.transactions_status,,"2xx, 3xx, 4xx, 5xx, 6xx",transactions/s,"OpenSIPS Transactions Status",line,,charts.d.plugin,opensips +opensips.transactions_inuse,,inuse,transactions,"OpenSIPS InUse Transactions",line,,charts.d.plugin,opensips +opensips.sl_replies,,"1xx, 2xx, 3xx, 4xx, 5xx, 6xx, sent, error, ACKed",replies/s,OpenSIPS SL Replies,line,,charts.d.plugin,opensips +opensips.dialogs,,"processed, expire, failed",dialogs/s,"OpenSIPS Dialogs",line,,charts.d.plugin,opensips +opensips.net_waiting,,"UDP, TCP",kilobytes,"OpenSIPS Network Waiting",line,,charts.d.plugin,opensips +opensips.uri_checks,,"positive, negative","checks / sec","OpenSIPS URI Checks",line,,charts.d.plugin,opensips +opensips.traces,,"requests, replies","traces / sec","OpenSIPS Traces",line,,charts.d.plugin,opensips +opensips.shmem,,"total, used, real_used, max_used, free",kilobytes,"OpenSIPS Shared Memory",line,,charts.d.plugin,opensips +opensips.shmem_fragment,,fragments,fragments,"OpenSIPS Shared Memory Fragmentation",line,,charts.d.plugin,opensips \ No newline at end of file diff --git a/collectors/charts.d.plugin/sensors/README.md b/collectors/charts.d.plugin/sensors/README.md index 142ae14a..2601a2b6 100644 --- a/collectors/charts.d.plugin/sensors/README.md +++ b/collectors/charts.d.plugin/sensors/README.md @@ -1,16 +1,7 @@ - - -# Linux machine sensors monitoring with Netdata - -Use this collector when `lm-sensors` doesn't work on your device (e.g. for RPi temperatures). -For all other cases use the [Python collector](/collectors/python.d.plugin/sensors), which supports multiple +# Linux machine sensors collector + +Use this collector when `lm-sensors` doesn't work on your device (e.g. for RPi temperatures). +For all other cases use the [Python collector](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/sensors), which supports multiple jobs, is more efficient and performs calculations on top of the kernel provided values. This plugin will provide charts for all configured system sensors, by reading sensors directly from the kernel. @@ -30,15 +21,23 @@ 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](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md), which is typically at `/etc/netdata`. +The `sensors` collector is disabled by default. + +To enable the collector, you need to edit the configuration file of `charts.d/sensors.conf`. You can do so by using the `edit config` script. + +> ### Info +> +> To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> It is recommended to use this way for configuring Netdata. +> +> Please also note that after most configuration changes you will need to [restart the Agent](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for the changes to take effect. ```bash cd /etc/netdata # Replace this path with your Netdata config directory, if different sudo ./edit-config charts.d.conf ``` -It also needs to be set to "force" to be enabled: +You need to uncomment the regarding `sensors`, and set the value to `force`. ```shell # example=force @@ -47,8 +46,7 @@ sensors=force ## Configuration -Edit the `charts.d/sensors.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`. +Edit the `charts.d/sensors.conf` configuration file using `edit-config`: ```bash cd /etc/netdata # Replace this path with your Netdata config directory, if different @@ -79,5 +77,3 @@ sensors_excluded=() ``` --- - - diff --git a/collectors/charts.d.plugin/sensors/metrics.csv b/collectors/charts.d.plugin/sensors/metrics.csv new file mode 100644 index 00000000..5b5a4c57 --- /dev/null +++ b/collectors/charts.d.plugin/sensors/metrics.csv @@ -0,0 +1,8 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +sensors.temp,sensor chip,"{filename}",Celsius,"Temperature",line,,charts.d.plugin,sensors +sensors.volt,sensor chip,"{filename}",Volts,"Voltage",line,,charts.d.plugin,sensors +sensors.curr,sensor chip,"{filename}",Ampere,"Current",line,,charts.d.plugin,sensors +sensors.power,sensor chip,"{filename}",Watt,"Power",line,,charts.d.plugin,sensors +sensors.fans,sensor chip,"{filename}","Rotations / Minute","Fans Speed",line,,charts.d.plugin,sensors +sensors.energy,sensor chip,"{filename}",Joule,"Energy",area,,charts.d.plugin,sensors +sensors.humidity,sensor chip,"{filename}",Percent,"Humidity",line,,charts.d.plugin,sensors \ No newline at end of file diff --git a/collectors/charts.d.plugin/sensors/sensors.chart.sh b/collectors/charts.d.plugin/sensors/sensors.chart.sh index 0527e1e7..9576e2ab 100644 --- a/collectors/charts.d.plugin/sensors/sensors.chart.sh +++ b/collectors/charts.d.plugin/sensors/sensors.chart.sh @@ -187,7 +187,7 @@ sensors_create() { files="$(ls "$path"/energy*_input 2>/dev/null)" files="$(sensors_check_files "$files")" [ -z "$files" ] && continue - echo "CHART 'sensors.energy_${id}_${name}' '' 'Energy' 'Joule' 'energy' 'sensors.energy' areastack $((sensors_priority + 6)) $sensors_update_every '' '' 'sensors'" + echo "CHART 'sensors.energy_${id}_${name}' '' 'Energy' 'Joule' 'energy' 'sensors.energy' area $((sensors_priority + 6)) $sensors_update_every '' '' 'sensors'" echo >>"$TMP_DIR/sensors.sh" "echo \"BEGIN 'sensors.energy_${id}_${name}' \$1\"" algorithm="incremental" divisor=1000000 diff --git a/collectors/cups.plugin/README.md b/collectors/cups.plugin/README.md index 0658cc8b..8652ec57 100644 --- a/collectors/cups.plugin/README.md +++ b/collectors/cups.plugin/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/cups sidebar_label: "cups.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Remotes/Devices" +learn_rel_path: "Integrations/Monitor/Remotes/Devices" --> -# cups.plugin +# Printers (cups.plugin) `cups.plugin` collects Common Unix Printing System (CUPS) metrics. diff --git a/collectors/cups.plugin/cups_plugin.c b/collectors/cups.plugin/cups_plugin.c index b9d91c85..ecadc4ec 100644 --- a/collectors/cups.plugin/cups_plugin.c +++ b/collectors/cups.plugin/cups_plugin.c @@ -159,12 +159,12 @@ struct job_metrics *get_job_metrics(char *dest) { reset_job_metrics(NULL, &new_job_metrics, NULL); jm = dictionary_set(dict_dest_job_metrics, dest, &new_job_metrics, sizeof(struct job_metrics)); - printf("CHART cups.job_num_%s '' 'Active jobs of %s' jobs '%s' cups.job_num stacked %i %i\n", dest, dest, dest, netdata_priority++, netdata_update_every); + printf("CHART cups.job_num_%s '' 'Active jobs of %s' jobs '%s' cups.destination_job_num stacked %i %i\n", dest, dest, dest, netdata_priority++, netdata_update_every); printf("DIMENSION pending '' absolute 1 1\n"); printf("DIMENSION held '' absolute 1 1\n"); printf("DIMENSION processing '' absolute 1 1\n"); - printf("CHART cups.job_size_%s '' 'Active jobs size of %s' KB '%s' cups.job_size stacked %i %i\n", dest, dest, dest, netdata_priority++, netdata_update_every); + printf("CHART cups.job_size_%s '' 'Active jobs size of %s' KB '%s' cups.destination_job_size stacked %i %i\n", dest, dest, dest, netdata_priority++, netdata_update_every); printf("DIMENSION pending '' absolute 1 1\n"); printf("DIMENSION held '' absolute 1 1\n"); printf("DIMENSION processing '' absolute 1 1\n"); @@ -193,12 +193,12 @@ int collect_job_metrics(const DICTIONARY_ITEM *item, void *entry, void *data __m "END\n", name, jm->size_pending, jm->size_held, jm->size_processing); } else { - printf("CHART cups.job_num_%s '' 'Active jobs of %s' jobs '%s' cups.job_num stacked 1 %i 'obsolete'\n", name, name, name, netdata_update_every); + printf("CHART cups.job_num_%s '' 'Active jobs of %s' jobs '%s' cups.destination_job_num stacked 1 %i 'obsolete'\n", name, name, name, netdata_update_every); printf("DIMENSION pending '' absolute 1 1\n"); printf("DIMENSION held '' absolute 1 1\n"); printf("DIMENSION processing '' absolute 1 1\n"); - printf("CHART cups.job_size_%s '' 'Active jobs size of %s' KB '%s' cups.job_size stacked 1 %i 'obsolete'\n", name, name, name, netdata_update_every); + printf("CHART cups.job_size_%s '' 'Active jobs size of %s' KB '%s' cups.destination_job_size stacked 1 %i 'obsolete'\n", name, name, name, netdata_update_every); printf("DIMENSION pending '' absolute 1 1\n"); printf("DIMENSION held '' absolute 1 1\n"); printf("DIMENSION processing '' absolute 1 1\n"); diff --git a/collectors/cups.plugin/metrics.csv b/collectors/cups.plugin/metrics.csv new file mode 100644 index 00000000..0262f58a --- /dev/null +++ b/collectors/cups.plugin/metrics.csv @@ -0,0 +1,7 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +cups.dests_state,,"idle, printing, stopped",dests,"Destinations by state",stacked,,cups.plugin, +cups.dests_option,,"total, acceptingjobs, shared",dests,"Destinations by option",line,,cups.plugin, +cups.job_num,,"pending, held, processing",jobs,"Active jobs",stacked,,cups.plugin, +cups.job_size,,"pending, held, processing",KB,"Active jobs size",stacked,,cups.plugin, +cups.destination_job_num,destination,"pending, held, processing",jobs,"Active jobs of {destination}",stacked,,cups.plugin, +cups.destination_job_size,destination,"pending, held, processing",KB,"Active jobs size of {destination}",stacked,,cups.plugin, \ No newline at end of file diff --git a/collectors/diskspace.plugin/README.md b/collectors/diskspace.plugin/README.md index 6d1ec7ca..b70bbf00 100644 --- a/collectors/diskspace.plugin/README.md +++ b/collectors/diskspace.plugin/README.md @@ -1,29 +1,37 @@ - - -# diskspace.plugin +# Monitor disk (diskspace.plugin) This plugin monitors the disk space usage of mounted disks, under Linux. The plugin requires Netdata to have execute/search permissions on the mount point itself, as well as each component of the absolute path to the mount point. Two charts are available for every mount: -- Disk Space Usage -- Disk Files (inodes) Usage +- Disk Space Usage +- Disk Files (inodes) Usage ## configuration Simple patterns can be used to exclude mounts from showed statistics based on path or filesystem. By default read-only mounts are not displayed. To display them `yes` should be set for a chart instead of `auto`. -By default, Netdata will enable monitoring metrics only when they are not zero. If they are constantly zero they are ignored. Metrics that will start having values, after Netdata is started, will be detected and charts will be automatically added to the dashboard (a refresh of the dashboard is needed for them to appear though). Set `yes` for a chart instead of `auto` to enable it permanently. You can also set the `enable zero metrics` option to `yes` in the `[global]` section which enables charts with zero metrics for all internal Netdata plugins. +By default, Netdata will enable monitoring metrics only when they are not zero. If they are constantly zero they are ignored. Metrics that will start having values, after Netdata is started, will be detected and charts will be automatically added to the dashboard (a refresh of the dashboard is needed for them to appear though). +To configure this plugin, you need to edit the configuration file `netdata.conf`. You can do so by using the `edit config` script. + +> ### Info +> +> To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> It is recommended to use this way for configuring Netdata. +> +> Please also note that after most configuration changes you will need to [restart the Agent](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for the changes to take effect. + +```bash +cd /etc/netdata # Replace this path with your Netdata config directory, if different +sudo ./edit-config netdata.conf ``` + +You can enable the effect of each line by uncommenting it. + +You can set `yes` for a chart instead of `auto` to enable it permanently. You can also set the `enable zero metrics` option to `yes` in the `[global]` section which enables charts with zero metrics for all internal Netdata plugins. + +```conf [plugin:proc:diskspace] # remove charts of unmounted disks = yes # update every = 1 @@ -34,14 +42,12 @@ By default, Netdata will enable monitoring metrics only when they are not zero. # inodes usage for all disks = auto ``` -Charts can be enabled/disabled for every mount separately: +Charts can be enabled/disabled for every mount separately, just look for the name of the mount after `[plugin:proc:diskspace:`. -``` +```conf [plugin:proc:diskspace:/] # space usage = auto # inodes usage = auto ``` > 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/metrics.csv b/collectors/diskspace.plugin/metrics.csv new file mode 100644 index 00000000..2b61ee9a --- /dev/null +++ b/collectors/diskspace.plugin/metrics.csv @@ -0,0 +1,3 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +disk.space,mount point,"avail, used, reserved_for_root",GiB,"Disk Space Usage",stacked,"mount_point, filesystem, mount_root",diskspace.plugin, +disk.inodes,mount point,"avail, used, reserved_for_root",inodes,"Disk Files (inodes) Usage",stacked,"mount_point, filesystem, mount_root",diskspace.plugin, \ No newline at end of file diff --git a/collectors/diskspace.plugin/plugin_diskspace.c b/collectors/diskspace.plugin/plugin_diskspace.c index 743612ff..2153494d 100644 --- a/collectors/diskspace.plugin/plugin_diskspace.c +++ b/collectors/diskspace.plugin/plugin_diskspace.c @@ -6,6 +6,7 @@ #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" +#define DEFAULT_EXCLUDED_FILESYSTEMS_INODES "msdosfs msdos vfat overlayfs aufs* *unionfs" #define CONFIG_SECTION_DISKSPACE "plugin:proc:diskspace" #define MAX_STAT_USEC 10000LU @@ -294,6 +295,7 @@ static inline void do_disk_space_stats(struct mountinfo *mi, int update_every) { static SIMPLE_PATTERN *excluded_mountpoints = NULL; static SIMPLE_PATTERN *excluded_filesystems = NULL; + static SIMPLE_PATTERN *excluded_filesystems_inodes = NULL; usec_t slow_timeout = MAX_STAT_USEC * update_every; @@ -308,16 +310,22 @@ static inline void do_disk_space_stats(struct mountinfo *mi, int update_every) { } excluded_mountpoints = simple_pattern_create( - config_get(CONFIG_SECTION_DISKSPACE, "exclude space metrics on paths", DEFAULT_EXCLUDED_PATHS) - , NULL - , mode - ); + config_get(CONFIG_SECTION_DISKSPACE, "exclude space metrics on paths", DEFAULT_EXCLUDED_PATHS), + NULL, + mode, + true); excluded_filesystems = simple_pattern_create( - config_get(CONFIG_SECTION_DISKSPACE, "exclude space metrics on filesystems", DEFAULT_EXCLUDED_FILESYSTEMS) - , NULL - , SIMPLE_PATTERN_EXACT - ); + config_get(CONFIG_SECTION_DISKSPACE, "exclude space metrics on filesystems", DEFAULT_EXCLUDED_FILESYSTEMS), + NULL, + SIMPLE_PATTERN_EXACT, + true); + + excluded_filesystems_inodes = simple_pattern_create( + config_get(CONFIG_SECTION_DISKSPACE, "exclude inode metrics on filesystems", DEFAULT_EXCLUDED_FILESYSTEMS_INODES), + NULL, + SIMPLE_PATTERN_EXACT, + true); dict_mountpoints = dictionary_create_advanced(DICT_OPTION_NONE, &dictionary_stats_category_collectors, 0); } @@ -340,6 +348,9 @@ static inline void do_disk_space_stats(struct mountinfo *mi, int update_every) { def_space = CONFIG_BOOLEAN_NO; def_inodes = CONFIG_BOOLEAN_NO; } + if (unlikely(simple_pattern_matches(excluded_filesystems_inodes, mi->filesystem))) { + def_inodes = CONFIG_BOOLEAN_NO; + } // check if the mount point is a directory #2407 // but only when it is enabled by default #4491 diff --git a/collectors/ebpf.plugin/README.md b/collectors/ebpf.plugin/README.md index deedf4d7..75f44a6e 100644 --- a/collectors/ebpf.plugin/README.md +++ b/collectors/ebpf.plugin/README.md @@ -5,10 +5,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/ebpf sidebar_label: "Kernel traces/metrics (eBPF)" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/System metrics" +learn_rel_path: "Integrations/Monitor/System metrics" --> -# eBPF monitoring with Netdata +# Kernel traces/metrics (eBPF) collector The Netdata Agent provides many [eBPF](https://ebpf.io/what-is-ebpf/) programs to help you troubleshoot and debug how applications interact with the Linux kernel. The `ebpf.plugin` uses [tracepoints, trampoline, and2 kprobes](#how-netdata-collects-data-using-probes-and-tracepoints) to collect a wide array of high value data about the host that would otherwise be impossible to capture. diff --git a/collectors/ebpf.plugin/ebpf.c b/collectors/ebpf.plugin/ebpf.c index 67fe477c..c0764c60 100644 --- a/collectors/ebpf.plugin/ebpf.c +++ b/collectors/ebpf.plugin/ebpf.c @@ -28,11 +28,22 @@ int running_on_kernel = 0; int ebpf_nprocs; int isrh = 0; int main_thread_id = 0; +int process_pid_fd = -1; pthread_mutex_t lock; pthread_mutex_t ebpf_exit_cleanup; pthread_mutex_t collect_data_mutex; -pthread_cond_t collect_data_cond_var; + +struct netdata_static_thread cgroup_integration_thread = { + .name = "EBPF CGROUP INT", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL +}; ebpf_module_t ebpf_modules[] = { { .thread_name = "process", .config_name = "process", .enabled = 0, .start_routine = ebpf_process_thread, @@ -435,9 +446,6 @@ ebpf_sync_syscalls_t local_syscalls[] = { }; -// Link with apps.plugin -ebpf_process_stat_t *global_process_stat = NULL; - // Link with cgroup.plugin netdata_ebpf_cgroup_shm_t shm_ebpf_cgroup = {NULL, NULL}; int shm_fd_ebpf_cgroup = -1; @@ -449,15 +457,53 @@ ebpf_network_viewer_options_t network_viewer_opt; // Statistic ebpf_plugin_stats_t plugin_statistics = {.core = 0, .legacy = 0, .running = 0, .threads = 0, .tracepoints = 0, - .probes = 0, .retprobes = 0, .trampolines = 0}; + .probes = 0, .retprobes = 0, .trampolines = 0, .memlock_kern = 0, + .hash_tables = 0}; #ifdef LIBBPF_MAJOR_VERSION struct btf *default_btf = NULL; +struct cachestat_bpf *cachestat_bpf_obj = NULL; +struct dc_bpf *dc_bpf_obj = NULL; +struct fd_bpf *fd_bpf_obj = NULL; +struct mount_bpf *mount_bpf_obj = NULL; +struct shm_bpf *shm_bpf_obj = NULL; +struct socket_bpf *socket_bpf_obj = NULL; +struct swap_bpf *bpf_obj = NULL; +struct vfs_bpf *vfs_bpf_obj = NULL; #else void *default_btf = NULL; #endif char *btf_path = NULL; +/***************************************************************** + * + * FUNCTIONS USED TO ALLOCATE APPS/CGROUP MEMORIES (ARAL) + * + *****************************************************************/ + +/** + * Allocate PID ARAL + * + * Allocate memory using ARAL functions to speed up processing. + * + * @param name the internal name used for allocated region. + * @param size size of each element inside allocated space + * + * @return It returns the address on success and NULL otherwise. + */ +ARAL *ebpf_allocate_pid_aral(char *name, size_t size) +{ + static size_t max_elements = NETDATA_EBPF_ALLOC_MAX_PID; + if (max_elements < NETDATA_EBPF_ALLOC_MIN_ELEMENTS) { + error("Number of elements given is too small, adjusting it for %d", NETDATA_EBPF_ALLOC_MIN_ELEMENTS); + max_elements = NETDATA_EBPF_ALLOC_MIN_ELEMENTS; + } + + return aral_create(name, size, + 0, max_elements, + NULL, NULL, NULL, false, false); +} + /***************************************************************** * * FUNCTIONS USED TO CLEAN MEMORY AND OPERATE SYSTEM FILES @@ -488,10 +534,12 @@ static void ebpf_exit() #endif printf("DISABLE\n"); + pthread_mutex_lock(&mutex_cgroup_shm); if (shm_ebpf_cgroup.header) { - munmap(shm_ebpf_cgroup.header, shm_ebpf_cgroup.header->body_length); + ebpf_unmap_cgroup_shared_memory(); shm_unlink(NETDATA_SHARED_MEMORY_EBPF_CGROUP_NAME); } + pthread_mutex_unlock(&mutex_cgroup_shm); exit(0); } @@ -518,6 +566,126 @@ static void ebpf_unload_legacy_code(struct bpf_object *objects, struct bpf_link bpf_object__close(objects); } +/** + * Unload Unique maps + * + * This function unload all BPF maps from threads using one unique BPF object. + */ +static void ebpf_unload_unique_maps() +{ + int i; + for (i = 0; ebpf_modules[i].thread_name; i++) { + if (ebpf_modules[i].enabled != NETDATA_THREAD_EBPF_STOPPED) { + if (ebpf_modules[i].enabled != NETDATA_THREAD_EBPF_NOT_RUNNING) + error("Cannot unload maps for thread %s, because it is not stopped.", ebpf_modules[i].thread_name); + + continue; + } + + ebpf_unload_legacy_code(ebpf_modules[i].objects, ebpf_modules[i].probe_links); + switch (i) { + case EBPF_MODULE_CACHESTAT_IDX: { +#ifdef LIBBPF_MAJOR_VERSION + if (cachestat_bpf_obj) + cachestat_bpf__destroy(cachestat_bpf_obj); +#endif + break; + } + case EBPF_MODULE_DCSTAT_IDX: { +#ifdef LIBBPF_MAJOR_VERSION + if (dc_bpf_obj) + dc_bpf__destroy(dc_bpf_obj); +#endif + break; + } + case EBPF_MODULE_FD_IDX: { +#ifdef LIBBPF_MAJOR_VERSION + if (fd_bpf_obj) + fd_bpf__destroy(fd_bpf_obj); +#endif + break; + } + case EBPF_MODULE_MOUNT_IDX: { +#ifdef LIBBPF_MAJOR_VERSION + if (mount_bpf_obj) + mount_bpf__destroy(mount_bpf_obj); +#endif + break; + } + case EBPF_MODULE_SHM_IDX: { +#ifdef LIBBPF_MAJOR_VERSION + if (shm_bpf_obj) + shm_bpf__destroy(shm_bpf_obj); +#endif + break; + } + case EBPF_MODULE_SOCKET_IDX: { +#ifdef LIBBPF_MAJOR_VERSION + if (socket_bpf_obj) + socket_bpf__destroy(socket_bpf_obj); +#endif + break; + } + case EBPF_MODULE_SWAP_IDX: { +#ifdef LIBBPF_MAJOR_VERSION + if (bpf_obj) + swap_bpf__destroy(bpf_obj); +#endif + break; + } + case EBPF_MODULE_VFS_IDX: { +#ifdef LIBBPF_MAJOR_VERSION + if (vfs_bpf_obj) + vfs_bpf__destroy(vfs_bpf_obj); +#endif + break; + } + case EBPF_MODULE_PROCESS_IDX: + case EBPF_MODULE_DISK_IDX: + case EBPF_MODULE_HARDIRQ_IDX: + case EBPF_MODULE_SOFTIRQ_IDX: + case EBPF_MODULE_OOMKILL_IDX: + case EBPF_MODULE_MDFLUSH_IDX: + default: + continue; + } + } +} + +/** + * Unload filesystem maps + * + * This function unload all BPF maps from filesystem thread. + */ +static void ebpf_unload_filesystems() +{ + if (ebpf_modules[EBPF_MODULE_FILESYSTEM_IDX].enabled == NETDATA_THREAD_EBPF_NOT_RUNNING || + ebpf_modules[EBPF_MODULE_SYNC_IDX].enabled == NETDATA_THREAD_EBPF_RUNNING) + return; + + int i; + for (i = 0; localfs[i].filesystem != NULL; i++) { + ebpf_unload_legacy_code(localfs[i].objects, localfs[i].probe_links); + } +} + +/** + * Unload sync maps + * + * This function unload all BPF maps from sync thread. + */ +static void ebpf_unload_sync() +{ + if (ebpf_modules[EBPF_MODULE_SYNC_IDX].enabled == NETDATA_THREAD_EBPF_NOT_RUNNING || + ebpf_modules[EBPF_MODULE_SYNC_IDX].enabled == NETDATA_THREAD_EBPF_RUNNING) + return; + + int i; + for (i = 0; local_syscalls[i].syscall != NULL; i++) { + ebpf_unload_legacy_code(local_syscalls[i].objects, local_syscalls[i].probe_links); + } +} + int ebpf_exit_plugin = 0; /** * Close the collector gracefully @@ -529,7 +697,6 @@ static void ebpf_stop_threads(int sig) UNUSED(sig); static int only_one = 0; - int i; // Child thread should be closed by itself. pthread_mutex_lock(&ebpf_exit_cleanup); if (main_thread_id != gettid() || only_one) { @@ -537,13 +704,26 @@ static void ebpf_stop_threads(int sig) return; } only_one = 1; - for (i = 0; ebpf_threads[i].name != NULL; i++) { - if (ebpf_threads[i].enabled != NETDATA_THREAD_EBPF_STOPPED) - netdata_thread_cancel(*ebpf_threads[i].thread); + int i; + for (i = 0; ebpf_modules[i].thread_name != NULL; i++) { + if (ebpf_modules[i].enabled == NETDATA_THREAD_EBPF_RUNNING) { + netdata_thread_cancel(*ebpf_modules[i].thread->thread); +#ifdef NETDATA_DEV_MODE + info("Sending cancel for thread %s", ebpf_modules[i].thread_name); +#endif + } } pthread_mutex_unlock(&ebpf_exit_cleanup); + pthread_mutex_lock(&mutex_cgroup_shm); + netdata_thread_cancel(*cgroup_integration_thread.thread); +#ifdef NETDATA_DEV_MODE + info("Sending cancel for thread %s", cgroup_integration_thread.name); +#endif + pthread_mutex_unlock(&mutex_cgroup_shm); + ebpf_exit_plugin = 1; + usec_t max = USEC_PER_SEC, step = 100000; while (i && max) { max -= step; @@ -551,42 +731,18 @@ static void ebpf_stop_threads(int sig) i = 0; int j; pthread_mutex_lock(&ebpf_exit_cleanup); - for (j = 0; ebpf_threads[j].name != NULL; j++) { - if (ebpf_threads[j].enabled != NETDATA_THREAD_EBPF_STOPPED) + for (j = 0; ebpf_modules[j].thread_name != NULL; j++) { + if (ebpf_modules[j].enabled == NETDATA_THREAD_EBPF_RUNNING) i++; } 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); - } - } - 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); - } - } - pthread_mutex_unlock(&ebpf_exit_cleanup); - - } + pthread_mutex_lock(&ebpf_exit_cleanup); + ebpf_unload_unique_maps(); + ebpf_unload_filesystems(); + ebpf_unload_sync(); + pthread_mutex_unlock(&ebpf_exit_cleanup); ebpf_exit(); } @@ -597,6 +753,58 @@ static void ebpf_stop_threads(int sig) * *****************************************************************/ +/** + * Create apps charts + * + * Call ebpf_create_chart to create the charts on apps submenu. + * + * @param root a pointer for the targets. + */ +static void ebpf_create_apps_charts(struct ebpf_target *root) +{ + if (unlikely(!ebpf_all_pids)) + return; + + struct ebpf_target *w; + int newly_added = 0; + + for (w = root; w; w = w->next) { + if (w->target) + continue; + + if (unlikely(w->processes && (debug_enabled || w->debug_enabled))) { + struct ebpf_pid_on_target *pid_on_target; + + fprintf( + stderr, "ebpf.plugin: target '%s' has aggregated %u process%s:", w->name, w->processes, + (w->processes == 1) ? "" : "es"); + + for (pid_on_target = w->root_pid; pid_on_target; pid_on_target = pid_on_target->next) { + fprintf(stderr, " %d", pid_on_target->pid); + } + + fputc('\n', stderr); + } + + if (!w->exposed && w->processes) { + newly_added++; + w->exposed = 1; + if (debug_enabled || w->debug_enabled) + debug_log_int("%s just added - regenerating charts.", w->name); + } + } + + if (!newly_added) + return; + + int counter; + for (counter = 0; ebpf_modules[counter].thread_name; counter++) { + ebpf_module_t *current = &ebpf_modules[counter]; + if (current->enabled == NETDATA_THREAD_EBPF_RUNNING && current->apps_charts && current->apps_routine) + current->apps_routine(current, root); + } +} + /** * Get a value from a structure. * @@ -876,9 +1084,9 @@ void ebpf_create_chart(char *type, * @param module chart module name, this is the eBPF thread. */ void ebpf_create_charts_on_apps(char *id, char *title, char *units, char *family, char *charttype, int order, - char *algorithm, struct target *root, int update_every, char *module) + char *algorithm, struct ebpf_target *root, int update_every, char *module) { - struct target *w; + struct ebpf_target *w; ebpf_write_chart_cmd(NETDATA_APPS_FAMILY, id, title, units, family, charttype, NULL, order, update_every, module); @@ -913,6 +1121,79 @@ void write_histogram_chart(char *family, char *name, const netdata_idx_t *hist, fflush(stdout); } +/** + * ARAL Charts + * + * Add chart to monitor ARAL usage + * Caller must call this function with mutex locked. + * + * @param name the name used to create aral + * @param em a pointer to the structure with the default values. + */ +void ebpf_statistic_create_aral_chart(char *name, ebpf_module_t *em) +{ + static int priority = 140100; + char *mem = { NETDATA_EBPF_STAT_DIMENSION_MEMORY }; + char *aral = { NETDATA_EBPF_STAT_DIMENSION_ARAL }; + + snprintfz(em->memory_usage, NETDATA_EBPF_CHART_MEM_LENGTH -1, "aral_%s_size", name); + snprintfz(em->memory_allocations, NETDATA_EBPF_CHART_MEM_LENGTH -1, "aral_%s_alloc", name); + + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + em->memory_usage, + "Bytes allocated for ARAL.", + "bytes", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_STACKED, + "netdata.ebpf_aral_stat_size", + priority++, + em->update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + + ebpf_write_global_dimension(mem, + mem, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + em->memory_allocations, + "Calls to allocate memory.", + "calls", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_STACKED, + "netdata.ebpf_aral_stat_alloc", + priority++, + em->update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + + ebpf_write_global_dimension(aral, + aral, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); +} + +/** + * Send data from aral chart + * + * Send data for eBPF plugin + * + * @param memory a pointer to the allocated address + * @param em a pointer to the structure with the default values. + */ +void ebpf_send_data_aral_chart(ARAL *memory, ebpf_module_t *em) +{ + char *mem = { NETDATA_EBPF_STAT_DIMENSION_MEMORY }; + char *aral = { NETDATA_EBPF_STAT_DIMENSION_ARAL }; + + struct aral_statistics *stats = aral_statistics(memory); + + write_begin_chart(NETDATA_MONITORING_FAMILY, em->memory_usage); + write_chart_dimension(mem, (long long)stats->structures.allocated_bytes); + write_end_chart(); + + write_begin_chart(NETDATA_MONITORING_FAMILY, em->memory_allocations); + write_chart_dimension(aral, (long long)stats->structures.allocations); + write_end_chart(); +} + /***************************************************************** * * FUNCTIONS TO DEFINE OPTIONS @@ -944,7 +1225,7 @@ void ebpf_global_labels(netdata_syscall_stat_t *is, netdata_publish_syscall_t *p pio[i].dimension = dim[i]; pio[i].name = name[i]; - pio[i].algorithm = strdupz(ebpf_algorithms[algorithm[i]]); + pio[i].algorithm = ebpf_algorithms[algorithm[i]]; if (publish_prev) { publish_prev->next = &pio[i]; } @@ -1342,21 +1623,13 @@ static void read_local_addresses() * Start Pthread Variable * * This function starts all pthread variables. - * - * @return It returns 0 on success and -1. */ -int ebpf_start_pthread_variables() +void ebpf_start_pthread_variables() { pthread_mutex_init(&lock, NULL); pthread_mutex_init(&ebpf_exit_cleanup, NULL); pthread_mutex_init(&collect_data_mutex, NULL); - - if (pthread_cond_init(&collect_data_cond_var, NULL)) { - error("Cannot start conditional variable to control Apps charts."); - return -1; - } - - return 0; + pthread_mutex_init(&mutex_cgroup_shm, NULL); } /** @@ -1386,8 +1659,8 @@ static void ebpf_allocate_common_vectors() return; } - all_pids = callocz((size_t)pid_max, sizeof(struct pid_stat *)); - global_process_stat = callocz((size_t)ebpf_nprocs, sizeof(ebpf_process_stat_t)); + ebpf_all_pids = callocz((size_t)pid_max, sizeof(struct ebpf_pid_stat *)); + ebpf_aral_init(); } /** @@ -1720,8 +1993,9 @@ void set_global_variables() ebpf_configured_log_dir = LOG_DIR; ebpf_nprocs = (int)sysconf(_SC_NPROCESSORS_ONLN); - if (ebpf_nprocs > NETDATA_MAX_PROCESSOR) { + if (ebpf_nprocs < 0) { ebpf_nprocs = NETDATA_MAX_PROCESSOR; + error("Cannot identify number of process, using default value %d", ebpf_nprocs); } isrh = get_redhat_release(); @@ -2088,7 +2362,7 @@ static pid_t ebpf_read_previous_pid(char *filename) length = 63; buffer[length] = '\0'; - old_pid = (pid_t)str2uint32_t(buffer); + old_pid = (pid_t) str2uint32_t(buffer, NULL); } fclose(fp); @@ -2219,10 +2493,7 @@ int main(int argc, char **argv) signal(SIGTERM, ebpf_stop_threads); signal(SIGPIPE, ebpf_stop_threads); - if (ebpf_start_pthread_variables()) { - error("Cannot start mutex to control overall charts."); - ebpf_exit(); - } + ebpf_start_pthread_variables(); netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX"); if(verify_netdata_host_prefix() == -1) ebpf_exit(6); @@ -2241,6 +2512,12 @@ int main(int argc, char **argv) ebpf_set_static_routine(); + cgroup_integration_thread.thread = mallocz(sizeof(netdata_thread_t)); + cgroup_integration_thread.start_routine = ebpf_cgroup_integration; + + netdata_thread_create(cgroup_integration_thread.thread, cgroup_integration_thread.name, + NETDATA_THREAD_OPTION_DEFAULT, ebpf_cgroup_integration, NULL); + int i; for (i = 0; ebpf_threads[i].name != NULL; i++) { struct netdata_static_thread *st = &ebpf_threads[i]; @@ -2251,30 +2528,37 @@ int main(int argc, char **argv) if (em->enabled || !i) { st->thread = mallocz(sizeof(netdata_thread_t)); em->thread_id = i; - st->enabled = NETDATA_THREAD_EBPF_RUNNING; + em->enabled = NETDATA_THREAD_EBPF_RUNNING; netdata_thread_create(st->thread, st->name, NETDATA_THREAD_OPTION_DEFAULT, st->start_routine, em); } else { - st->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_NOT_RUNNING; } } usec_t step = USEC_PER_SEC; - int counter = NETDATA_EBPF_CGROUP_UPDATE - 1; heartbeat_t hb; heartbeat_init(&hb); + int update_apps_every = (int) EBPF_CFG_UPDATE_APPS_EVERY_DEFAULT; + int update_apps_list = update_apps_every - 1; //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(); + pthread_mutex_lock(&ebpf_exit_cleanup); + if (ebpf_modules[i].enabled == NETDATA_THREAD_EBPF_RUNNING && process_pid_fd != -1) { + pthread_mutex_lock(&collect_data_mutex); + if (++update_apps_list == update_apps_every) { + update_apps_list = 0; + cleanup_exited_pids(); + collect_data_for_all_processes(process_pid_fd); + + pthread_mutex_lock(&lock); + ebpf_create_apps_charts(apps_groups_root_target); + pthread_mutex_unlock(&lock); + } + pthread_mutex_unlock(&collect_data_mutex); } + pthread_mutex_unlock(&ebpf_exit_cleanup); } ebpf_stop_threads(0); diff --git a/collectors/ebpf.plugin/ebpf.d.conf b/collectors/ebpf.plugin/ebpf.d.conf index 112df275..6a5ec5c3 100644 --- a/collectors/ebpf.plugin/ebpf.d.conf +++ b/collectors/ebpf.plugin/ebpf.d.conf @@ -55,7 +55,7 @@ disk = no fd = yes filesystem = no - hardirq = yes + hardirq = no mdflush = no mount = yes oomkill = yes diff --git a/collectors/ebpf.plugin/ebpf.h b/collectors/ebpf.plugin/ebpf.h index 16e62498..5b48adc6 100644 --- a/collectors/ebpf.plugin/ebpf.h +++ b/collectors/ebpf.plugin/ebpf.h @@ -36,6 +36,26 @@ #define NETDATA_EBPF_OLD_CONFIG_FILE "ebpf.conf" #define NETDATA_EBPF_CONFIG_FILE "ebpf.d.conf" +#ifdef LIBBPF_MAJOR_VERSION // BTF code +#include "includes/cachestat.skel.h" +#include "includes/dc.skel.h" +#include "includes/fd.skel.h" +#include "includes/mount.skel.h" +#include "includes/shm.skel.h" +#include "includes/socket.skel.h" +#include "includes/swap.skel.h" +#include "includes/vfs.skel.h" + +extern struct cachestat_bpf *cachestat_bpf_obj; +extern struct dc_bpf *dc_bpf_obj; +extern struct fd_bpf *fd_bpf_obj; +extern struct mount_bpf *mount_bpf_obj; +extern struct shm_bpf *shm_bpf_obj; +extern struct socket_bpf *socket_bpf_obj; +extern struct swap_bpf *bpf_obj; +extern struct vfs_bpf *vfs_bpf_obj; +#endif + typedef struct netdata_syscall_stat { unsigned long bytes; // total number of bytes uint64_t call; // total number of calls @@ -108,12 +128,6 @@ typedef struct ebpf_tracepoint { char *event; } ebpf_tracepoint_t; -enum ebpf_threads_status { - NETDATA_THREAD_EBPF_RUNNING, - NETDATA_THREAD_EBPF_STOPPING, - NETDATA_THREAD_EBPF_STOPPED -}; - // Copied from musl header #ifndef offsetof #if __GNUC__ > 3 @@ -143,6 +157,8 @@ enum ebpf_threads_status { // Statistics charts #define NETDATA_EBPF_THREADS "ebpf_threads" #define NETDATA_EBPF_LOAD_METHOD "ebpf_load_methods" +#define NETDATA_EBPF_KERNEL_MEMORY "ebpf_kernel_memory" +#define NETDATA_EBPF_HASH_TABLES_LOADED "ebpf_hash_tables_count" // Log file #define NETDATA_DEVELOPER_LOG_FILE "developer.log" @@ -176,9 +192,9 @@ extern int ebpf_nprocs; extern int running_on_kernel; extern int isrh; extern char *ebpf_plugin_dir; +extern int process_pid_fd; extern pthread_mutex_t collect_data_mutex; -extern pthread_cond_t collect_data_cond_var; // Common functions void ebpf_global_labels(netdata_syscall_stat_t *is, @@ -235,14 +251,12 @@ void ebpf_create_charts_on_apps(char *name, char *charttype, int order, char *algorithm, - struct target *root, + struct ebpf_target *root, int update_every, char *module); void write_end_chart(); -void ebpf_cleanup_publish_syscall(netdata_publish_syscall_t *nps); - int ebpf_enable_tracepoint(ebpf_tracepoint_t *tp); int ebpf_disable_tracepoint(ebpf_tracepoint_t *tp); uint32_t ebpf_enable_tracepoints(ebpf_tracepoint_t *tps); @@ -264,16 +278,15 @@ void ebpf_pid_file(char *filename, size_t length); // Common variables extern int debug_enabled; -extern struct pid_stat *root_of_pids; +extern struct ebpf_pid_stat *ebpf_root_of_pids; extern ebpf_cgroup_target_t *ebpf_cgroup_pids; extern char *ebpf_algorithms[]; extern struct config collector_config; -extern ebpf_process_stat_t *global_process_stat; extern netdata_ebpf_cgroup_shm_t shm_ebpf_cgroup; extern int shm_fd_ebpf_cgroup; extern sem_t *shm_sem_ebpf_cgroup; extern pthread_mutex_t mutex_cgroup_shm; -extern size_t all_pids_count; +extern size_t ebpf_all_pids_count; extern ebpf_plugin_stats_t plugin_statistics; #ifdef LIBBPF_MAJOR_VERSION extern struct btf *default_btf; @@ -293,6 +306,7 @@ void ebpf_write_chart_obsolete(char *type, char *id, char *title, char *units, c char *charttype, char *context, int order, int update_every); void write_histogram_chart(char *family, char *name, const netdata_idx_t *hist, char **dimensions, uint32_t end); void ebpf_update_disabled_plugin_stats(ebpf_module_t *em); +ARAL *ebpf_allocate_pid_aral(char *name, size_t size); extern ebpf_filesystem_partitions_t localfs[]; extern ebpf_sync_syscalls_t local_syscalls[]; extern int ebpf_exit_plugin; diff --git a/collectors/ebpf.plugin/ebpf_apps.c b/collectors/ebpf.plugin/ebpf_apps.c index 7519e064..d6db4c67 100644 --- a/collectors/ebpf.plugin/ebpf_apps.c +++ b/collectors/ebpf.plugin/ebpf_apps.c @@ -4,6 +4,344 @@ #include "ebpf_socket.h" #include "ebpf_apps.h" +// ---------------------------------------------------------------------------- +// ARAL vectors used to speed up processing +ARAL *ebpf_aral_apps_pid_stat = NULL; +ARAL *ebpf_aral_process_stat = NULL; +ARAL *ebpf_aral_socket_pid = NULL; +ARAL *ebpf_aral_cachestat_pid = NULL; +ARAL *ebpf_aral_dcstat_pid = NULL; +ARAL *ebpf_aral_vfs_pid = NULL; +ARAL *ebpf_aral_fd_pid = NULL; +ARAL *ebpf_aral_shm_pid = NULL; + +// ---------------------------------------------------------------------------- +// Global vectors used with apps +ebpf_socket_publish_apps_t **socket_bandwidth_curr = NULL; +netdata_publish_cachestat_t **cachestat_pid = NULL; +netdata_publish_dcstat_t **dcstat_pid = NULL; +netdata_publish_swap_t **swap_pid = NULL; +netdata_publish_vfs_t **vfs_pid = NULL; +netdata_fd_stat_t **fd_pid = NULL; +netdata_publish_shm_t **shm_pid = NULL; +ebpf_process_stat_t **global_process_stats = NULL; + +/** + * eBPF ARAL Init + * + * Initiallize array allocator that will be used when integration with apps and ebpf is created. + */ +void ebpf_aral_init(void) +{ + size_t max_elements = NETDATA_EBPF_ALLOC_MAX_PID; + if (max_elements < NETDATA_EBPF_ALLOC_MIN_ELEMENTS) { + error("Number of elements given is too small, adjusting it for %d", NETDATA_EBPF_ALLOC_MIN_ELEMENTS); + max_elements = NETDATA_EBPF_ALLOC_MIN_ELEMENTS; + } + + ebpf_aral_apps_pid_stat = ebpf_allocate_pid_aral("ebpf_pid_stat", sizeof(struct ebpf_pid_stat)); + + ebpf_aral_process_stat = ebpf_allocate_pid_aral(NETDATA_EBPF_PROC_ARAL_NAME, sizeof(ebpf_process_stat_t)); + +#ifdef NETDATA_DEV_MODE + info("Plugin is using ARAL with values %d", NETDATA_EBPF_ALLOC_MAX_PID); +#endif +} + +/** + * eBPF pid stat get + * + * Get a ebpf_pid_stat entry to be used with a specific PID. + * + * @return it returns the address on success. + */ +struct ebpf_pid_stat *ebpf_pid_stat_get(void) +{ + struct ebpf_pid_stat *target = aral_mallocz(ebpf_aral_apps_pid_stat); + memset(target, 0, sizeof(struct ebpf_pid_stat)); + return target; +} + +/** + * eBPF target release + * + * @param stat Release a target after usage. + */ +void ebpf_pid_stat_release(struct ebpf_pid_stat *stat) +{ + aral_freez(ebpf_aral_apps_pid_stat, stat); +} + +/***************************************************************** + * + * PROCESS ARAL FUNCTIONS + * + *****************************************************************/ + +/** + * eBPF process stat get + * + * Get a ebpf_pid_stat entry to be used with a specific PID. + * + * @return it returns the address on success. + */ +ebpf_process_stat_t *ebpf_process_stat_get(void) +{ + ebpf_process_stat_t *target = aral_mallocz(ebpf_aral_process_stat); + memset(target, 0, sizeof(ebpf_process_stat_t)); + return target; +} + +/** + * eBPF process release + * + * @param stat Release a target after usage. + */ +void ebpf_process_stat_release(ebpf_process_stat_t *stat) +{ + aral_freez(ebpf_aral_process_stat, stat); +} + +/***************************************************************** + * + * SOCKET ARAL FUNCTIONS + * + *****************************************************************/ + +/** + * eBPF socket Aral init + * + * Initiallize array allocator that will be used when integration with apps is enabled. + */ +void ebpf_socket_aral_init() +{ + ebpf_aral_socket_pid = ebpf_allocate_pid_aral(NETDATA_EBPF_SOCKET_ARAL_NAME, sizeof(ebpf_socket_publish_apps_t)); +} + +/** + * eBPF socket get + * + * Get a ebpf_socket_publish_apps_t entry to be used with a specific PID. + * + * @return it returns the address on success. + */ +ebpf_socket_publish_apps_t *ebpf_socket_stat_get(void) +{ + ebpf_socket_publish_apps_t *target = aral_mallocz(ebpf_aral_socket_pid); + memset(target, 0, sizeof(ebpf_socket_publish_apps_t)); + return target; +} + +/** + * eBPF socket release + * + * @param stat Release a target after usage. + */ +void ebpf_socket_release(ebpf_socket_publish_apps_t *stat) +{ + aral_freez(ebpf_aral_socket_pid, stat); +} + +/***************************************************************** + * + * CACHESTAT ARAL FUNCTIONS + * + *****************************************************************/ + +/** + * eBPF Cachestat Aral init + * + * Initiallize array allocator that will be used when integration with apps is enabled. + */ +void ebpf_cachestat_aral_init() +{ + ebpf_aral_cachestat_pid = ebpf_allocate_pid_aral(NETDATA_EBPF_CACHESTAT_ARAL_NAME, sizeof(netdata_publish_cachestat_t)); +} + +/** + * eBPF publish cachestat get + * + * Get a netdata_publish_cachestat_t entry to be used with a specific PID. + * + * @return it returns the address on success. + */ +netdata_publish_cachestat_t *ebpf_publish_cachestat_get(void) +{ + netdata_publish_cachestat_t *target = aral_mallocz(ebpf_aral_cachestat_pid); + memset(target, 0, sizeof(netdata_publish_cachestat_t)); + return target; +} + +/** + * eBPF cachestat release + * + * @param stat Release a target after usage. + */ +void ebpf_cachestat_release(netdata_publish_cachestat_t *stat) +{ + aral_freez(ebpf_aral_cachestat_pid, stat); +} + +/***************************************************************** + * + * DCSTAT ARAL FUNCTIONS + * + *****************************************************************/ + +/** + * eBPF directory cache Aral init + * + * Initiallize array allocator that will be used when integration with apps is enabled. + */ +void ebpf_dcstat_aral_init() +{ + ebpf_aral_dcstat_pid = ebpf_allocate_pid_aral(NETDATA_EBPF_DCSTAT_ARAL_NAME, sizeof(netdata_publish_dcstat_t)); +} + +/** + * eBPF publish dcstat get + * + * Get a netdata_publish_dcstat_t entry to be used with a specific PID. + * + * @return it returns the address on success. + */ +netdata_publish_dcstat_t *ebpf_publish_dcstat_get(void) +{ + netdata_publish_dcstat_t *target = aral_mallocz(ebpf_aral_dcstat_pid); + memset(target, 0, sizeof(netdata_publish_dcstat_t)); + return target; +} + +/** + * eBPF dcstat release + * + * @param stat Release a target after usage. + */ +void ebpf_dcstat_release(netdata_publish_dcstat_t *stat) +{ + aral_freez(ebpf_aral_dcstat_pid, stat); +} + +/***************************************************************** + * + * VFS ARAL FUNCTIONS + * + *****************************************************************/ + +/** + * eBPF VFS Aral init + * + * Initiallize array allocator that will be used when integration with apps is enabled. + */ +void ebpf_vfs_aral_init() +{ + ebpf_aral_vfs_pid = ebpf_allocate_pid_aral(NETDATA_EBPF_VFS_ARAL_NAME, sizeof(netdata_publish_vfs_t)); +} + +/** + * eBPF publish VFS get + * + * Get a netdata_publish_vfs_t entry to be used with a specific PID. + * + * @return it returns the address on success. + */ +netdata_publish_vfs_t *ebpf_vfs_get(void) +{ + netdata_publish_vfs_t *target = aral_mallocz(ebpf_aral_vfs_pid); + memset(target, 0, sizeof(netdata_publish_vfs_t)); + return target; +} + +/** + * eBPF VFS release + * + * @param stat Release a target after usage. + */ +void ebpf_vfs_release(netdata_publish_vfs_t *stat) +{ + aral_freez(ebpf_aral_vfs_pid, stat); +} + +/***************************************************************** + * + * FD ARAL FUNCTIONS + * + *****************************************************************/ + +/** + * eBPF file descriptor Aral init + * + * Initiallize array allocator that will be used when integration with apps is enabled. + */ +void ebpf_fd_aral_init() +{ + ebpf_aral_fd_pid = ebpf_allocate_pid_aral(NETDATA_EBPF_FD_ARAL_NAME, sizeof(netdata_fd_stat_t)); +} + +/** + * eBPF publish file descriptor get + * + * Get a netdata_fd_stat_t entry to be used with a specific PID. + * + * @return it returns the address on success. + */ +netdata_fd_stat_t *ebpf_fd_stat_get(void) +{ + netdata_fd_stat_t *target = aral_mallocz(ebpf_aral_fd_pid); + memset(target, 0, sizeof(netdata_fd_stat_t)); + return target; +} + +/** + * eBPF file descriptor release + * + * @param stat Release a target after usage. + */ +void ebpf_fd_release(netdata_fd_stat_t *stat) +{ + aral_freez(ebpf_aral_fd_pid, stat); +} + +/***************************************************************** + * + * SHM ARAL FUNCTIONS + * + *****************************************************************/ + +/** + * eBPF shared memory Aral init + * + * Initiallize array allocator that will be used when integration with apps is enabled. + */ +void ebpf_shm_aral_init() +{ + ebpf_aral_shm_pid = ebpf_allocate_pid_aral(NETDATA_EBPF_SHM_ARAL_NAME, sizeof(netdata_publish_shm_t)); +} + +/** + * eBPF shared memory get + * + * Get a netdata_publish_shm_t entry to be used with a specific PID. + * + * @return it returns the address on success. + */ +netdata_publish_shm_t *ebpf_shm_stat_get(void) +{ + netdata_publish_shm_t *target = aral_mallocz(ebpf_aral_shm_pid); + memset(target, 0, sizeof(netdata_publish_shm_t)); + return target; +} + +/** + * eBPF shared memory release + * + * @param stat Release a target after usage. + */ +void ebpf_shm_release(netdata_publish_shm_t *stat) +{ + aral_freez(ebpf_aral_shm_pid, stat); +} + // ---------------------------------------------------------------------------- // internal flags // handled in code (automatically set) @@ -49,7 +387,7 @@ int ebpf_read_hash_table(void *ep, int fd, uint32_t pid) * * @return */ -size_t read_bandwidth_statistic_using_pid_on_target(ebpf_bandwidth_t **ep, int fd, struct pid_on_target *pids) +size_t read_bandwidth_statistic_using_pid_on_target(ebpf_bandwidth_t **ep, int fd, struct ebpf_pid_on_target *pids) { size_t count = 0; while (pids) { @@ -120,19 +458,19 @@ int am_i_running_as_root() * * @return it returns the number of structures that was reset. */ -size_t zero_all_targets(struct target *root) +size_t zero_all_targets(struct ebpf_target *root) { - struct target *w; + struct ebpf_target *w; size_t count = 0; for (w = root; w; w = w->next) { count++; if (unlikely(w->root_pid)) { - struct pid_on_target *pid_on_target = w->root_pid; + struct ebpf_pid_on_target *pid_on_target = w->root_pid; while (pid_on_target) { - struct pid_on_target *pid_on_target_to_free = pid_on_target; + struct ebpf_pid_on_target *pid_on_target_to_free = pid_on_target; pid_on_target = pid_on_target->next; freez(pid_on_target_to_free); } @@ -149,9 +487,9 @@ size_t zero_all_targets(struct target *root) * * @param agrt the pointer to be cleaned. */ -void clean_apps_groups_target(struct target *agrt) +void clean_apps_groups_target(struct ebpf_target *agrt) { - struct target *current_target; + struct ebpf_target *current_target; while (agrt) { current_target = agrt; agrt = current_target->target; @@ -170,7 +508,7 @@ void clean_apps_groups_target(struct target *agrt) * * @return It returns the target on success and NULL otherwise */ -struct target *get_apps_groups_target(struct target **agrt, const char *id, struct target *target, const char *name) +struct ebpf_target *get_apps_groups_target(struct ebpf_target **agrt, const char *id, struct ebpf_target *target, const char *name) { int tdebug = 0, thidden = target ? target->hidden : 0, ends_with = 0; const char *nid = id; @@ -188,9 +526,9 @@ struct target *get_apps_groups_target(struct target **agrt, const char *id, stru uint32_t hash = simple_hash(id); // find if it already exists - struct target *w, *last = *agrt; + struct ebpf_target *w, *last = *agrt; for (w = *agrt; w; w = w->next) { - if (w->idhash == hash && strncmp(nid, w->id, MAX_NAME) == 0) + if (w->idhash == hash && strncmp(nid, w->id, EBPF_MAX_NAME) == 0) return w; last = w; @@ -215,18 +553,18 @@ struct target *get_apps_groups_target(struct target **agrt, const char *id, stru "Internal Error: request to link process '%s' to target '%s' which is linked to target '%s'", id, target->id, target->target->id); - w = callocz(1, sizeof(struct target)); - strncpyz(w->id, nid, MAX_NAME); + w = callocz(1, sizeof(struct ebpf_target)); + strncpyz(w->id, nid, EBPF_MAX_NAME); w->idhash = simple_hash(w->id); if (unlikely(!target)) // copy the name - strncpyz(w->name, name, MAX_NAME); + strncpyz(w->name, name, EBPF_MAX_NAME); else // copy the id - strncpyz(w->name, nid, MAX_NAME); + strncpyz(w->name, nid, EBPF_MAX_NAME); - strncpyz(w->compare, nid, MAX_COMPARE_NAME); + strncpyz(w->compare, nid, EBPF_MAX_COMPARE_NAME); size_t len = strlen(w->compare); if (w->compare[len - 1] == '*') { w->compare[len - 1] = '\0'; @@ -267,7 +605,7 @@ struct target *get_apps_groups_target(struct target **agrt, const char *id, stru * * @return It returns 0 on success and -1 otherwise */ -int ebpf_read_apps_groups_conf(struct target **agdt, struct target **agrt, const char *path, const char *file) +int ebpf_read_apps_groups_conf(struct ebpf_target **agdt, struct ebpf_target **agrt, const char *path, const char *file) { char filename[FILENAME_MAX + 1]; @@ -297,7 +635,7 @@ int ebpf_read_apps_groups_conf(struct target **agdt, struct target **agrt, const continue; // find a possibly existing target - struct target *w = NULL; + struct ebpf_target *w = NULL; // loop through all words, skipping the first one (the name) for (word = 0; word < words; word++) { @@ -312,7 +650,7 @@ int ebpf_read_apps_groups_conf(struct target **agdt, struct target **agrt, const continue; // add this target - struct target *n = get_apps_groups_target(agrt, s, w, name); + struct ebpf_target *n = get_apps_groups_target(agrt, s, w, name); if (!n) { error("Cannot create target '%s' (line %zu, word %zu)", s, line, word); continue; @@ -331,7 +669,7 @@ int ebpf_read_apps_groups_conf(struct target **agdt, struct target **agrt, const if (!*agdt) fatal("Cannot create default target"); - struct target *ptr = *agdt; + struct ebpf_target *ptr = *agdt; if (ptr->target) *agdt = ptr->target; @@ -345,17 +683,15 @@ int ebpf_read_apps_groups_conf(struct target **agdt, struct target **agrt, const // ---------------------------------------------------------------------------- // string lengths -#define MAX_COMPARE_NAME 100 -#define MAX_NAME 100 #define MAX_CMDLINE 16384 -struct pid_stat **all_pids = NULL; // to avoid allocations, we pre-allocate the +struct ebpf_pid_stat **ebpf_all_pids = NULL; // to avoid allocations, we pre-allocate the // the entire pid space. -struct pid_stat *root_of_pids = NULL; // global list of all processes running +struct ebpf_pid_stat *ebpf_root_of_pids = NULL; // global list of all processes running -size_t all_pids_count = 0; // the number of processes running +size_t ebpf_all_pids_count = 0; // the number of processes running -struct target +struct ebpf_target *apps_groups_default_target = NULL, // the default target *apps_groups_root_target = NULL, // apps_groups.conf defined *users_root_target = NULL, // users @@ -416,7 +752,7 @@ static inline void debug_log_dummy(void) * * @return It returns the status value. */ -static inline int managed_log(struct pid_stat *p, uint32_t log, int status) +static inline int managed_log(struct ebpf_pid_stat *p, uint32_t log, int status) { if (unlikely(!status)) { // error("command failed log %u, errno %d", log, errno); @@ -476,23 +812,23 @@ static inline int managed_log(struct pid_stat *p, uint32_t log, int status) * * @return It returns the pid entry structure */ -static inline struct pid_stat *get_pid_entry(pid_t pid) +static inline struct ebpf_pid_stat *get_pid_entry(pid_t pid) { - if (unlikely(all_pids[pid])) - return all_pids[pid]; + if (unlikely(ebpf_all_pids[pid])) + return ebpf_all_pids[pid]; - struct pid_stat *p = callocz(1, sizeof(struct pid_stat)); + struct ebpf_pid_stat *p = ebpf_pid_stat_get(); - if (likely(root_of_pids)) - root_of_pids->prev = p; + if (likely(ebpf_root_of_pids)) + ebpf_root_of_pids->prev = p; - p->next = root_of_pids; - root_of_pids = p; + p->next = ebpf_root_of_pids; + ebpf_root_of_pids = p; p->pid = pid; - all_pids[pid] = p; - all_pids_count++; + ebpf_all_pids[pid] = p; + ebpf_all_pids_count++; return p; } @@ -502,14 +838,14 @@ static inline struct pid_stat *get_pid_entry(pid_t pid) * * @param p the pid_stat structure to assign for a target. */ -static inline void assign_target_to_pid(struct pid_stat *p) +static inline void assign_target_to_pid(struct ebpf_pid_stat *p) { targets_assignment_counter++; uint32_t hash = simple_hash(p->comm); size_t pclen = strlen(p->comm); - struct target *w; + struct ebpf_target *w; for (w = apps_groups_root_target; w; w = w->next) { // if(debug_enabled || (p->target && p->target->debug_enabled)) debug_log_int("\t\tcomparing '%s' with '%s'", w->compare, p->comm); @@ -543,11 +879,11 @@ static inline void assign_target_to_pid(struct pid_stat *p) /** * Read cmd line from /proc/PID/cmdline * - * @param p the pid_stat_structure. + * @param p the ebpf_pid_stat_structure. * * @return It returns 1 on success and 0 otherwise. */ -static inline int read_proc_pid_cmdline(struct pid_stat *p) +static inline int read_proc_pid_cmdline(struct ebpf_pid_stat *p) { static char cmdline[MAX_CMDLINE + 1]; @@ -596,7 +932,7 @@ cleanup: * @param p the pid stat structure to store the data. * @param ptr an useless argument. */ -static inline int read_proc_pid_stat(struct pid_stat *p, void *ptr) +static inline int read_proc_pid_stat(struct ebpf_pid_stat *p, void *ptr) { UNUSED(ptr); @@ -640,7 +976,7 @@ static inline int read_proc_pid_stat(struct pid_stat *p, void *ptr) debug_log("\tJust added %d (%s)", p->pid, comm); } - strncpyz(p->comm, comm, MAX_COMPARE_NAME); + strncpyz(p->comm, comm, EBPF_MAX_COMPARE_NAME); // /proc//cmdline if (likely(proc_pid_cmdline_is_needed)) @@ -673,7 +1009,7 @@ static inline int collect_data_for_pid(pid_t pid, void *ptr) return 0; } - struct pid_stat *p = get_pid_entry(pid); + struct ebpf_pid_stat *p = get_pid_entry(pid); if (unlikely(!p || p->read)) return 0; p->read = 1; @@ -701,11 +1037,11 @@ static inline int collect_data_for_pid(pid_t pid, void *ptr) */ static inline void link_all_processes_to_their_parents(void) { - struct pid_stat *p, *pp; + struct ebpf_pid_stat *p, *pp; // link all children to their parents // and update children count on parents - for (p = root_of_pids; p; p = p->next) { + for (p = ebpf_root_of_pids; p; p = p->next) { // for each process found p->sortlist = 0; @@ -716,7 +1052,7 @@ static inline void link_all_processes_to_their_parents(void) continue; } - pp = all_pids[p->ppid]; + pp = ebpf_all_pids[p->ppid]; if (likely(pp)) { p->parent = pp; pp->children_count++; @@ -738,7 +1074,7 @@ static inline void link_all_processes_to_their_parents(void) */ static void apply_apps_groups_targets_inheritance(void) { - struct pid_stat *p = NULL; + struct ebpf_pid_stat *p = NULL; // children that do not have a target // inherit their target from their parent @@ -747,7 +1083,7 @@ static void apply_apps_groups_targets_inheritance(void) if (unlikely(debug_enabled)) loops++; found = 0; - for (p = root_of_pids; p; p = p->next) { + for (p = ebpf_root_of_pids; p; p = p->next) { // if this process does not have a target // and it has a parent // and its parent has a target @@ -773,7 +1109,7 @@ static void apply_apps_groups_targets_inheritance(void) loops++; found = 0; - for (p = root_of_pids; p; p = p->next) { + for (p = ebpf_root_of_pids; p; p = p->next) { if (unlikely(!p->sortlist && !p->children_count)) p->sortlist = sortlist++; @@ -809,17 +1145,17 @@ static void apply_apps_groups_targets_inheritance(void) } // init goes always to default target - if (all_pids[INIT_PID]) - all_pids[INIT_PID]->target = apps_groups_default_target; + if (ebpf_all_pids[INIT_PID]) + ebpf_all_pids[INIT_PID]->target = apps_groups_default_target; // pid 0 goes always to default target - if (all_pids[0]) - all_pids[0]->target = apps_groups_default_target; + if (ebpf_all_pids[0]) + ebpf_all_pids[0]->target = apps_groups_default_target; // give a default target on all top level processes if (unlikely(debug_enabled)) loops++; - for (p = root_of_pids; p; p = p->next) { + for (p = ebpf_root_of_pids; p; p = p->next) { // if the process is not merged itself // then is is a top level process if (unlikely(!p->merged && !p->target)) @@ -830,8 +1166,8 @@ static void apply_apps_groups_targets_inheritance(void) p->sortlist = sortlist++; } - if (all_pids[1]) - all_pids[1]->sortlist = sortlist++; + if (ebpf_all_pids[1]) + ebpf_all_pids[1]->sortlist = sortlist++; // give a target to all merged child processes found = 1; @@ -839,7 +1175,7 @@ static void apply_apps_groups_targets_inheritance(void) if (unlikely(debug_enabled)) loops++; found = 0; - for (p = root_of_pids; p; p = p->next) { + for (p = ebpf_root_of_pids; p; p = p->next) { if (unlikely(!p->target && p->merged && p->parent && p->parent->target)) { p->target = p->parent->target; found++; @@ -860,9 +1196,9 @@ static void apply_apps_groups_targets_inheritance(void) * * @param root the targets that will be updated. */ -static inline void post_aggregate_targets(struct target *root) +static inline void post_aggregate_targets(struct ebpf_target *root) { - struct target *w; + struct ebpf_target *w; for (w = root; w; w = w->next) { if (w->collected_starttime) { if (!w->starttime || w->collected_starttime < w->starttime) { @@ -881,7 +1217,7 @@ static inline void post_aggregate_targets(struct target *root) */ static inline void del_pid_entry(pid_t pid) { - struct pid_stat *p = all_pids[pid]; + struct ebpf_pid_stat *p = ebpf_all_pids[pid]; if (unlikely(!p)) { error("attempted to free pid %d that is not allocated.", pid); @@ -890,8 +1226,8 @@ static inline void del_pid_entry(pid_t pid) debug_log("process %d %s exited, deleting it.", pid, p->comm); - if (root_of_pids == p) - root_of_pids = p->next; + if (ebpf_root_of_pids == p) + ebpf_root_of_pids = p->next; if (p->next) p->next->prev = p->prev; @@ -903,10 +1239,10 @@ static inline void del_pid_entry(pid_t pid) freez(p->io_filename); freez(p->cmdline_filename); freez(p->cmdline); - freez(p); + ebpf_pid_stat_release(p); - all_pids[pid] = NULL; - all_pids_count--; + ebpf_all_pids[pid] = NULL; + ebpf_all_pids_count--; } /** @@ -921,9 +1257,9 @@ static inline void del_pid_entry(pid_t pid) */ int get_pid_comm(pid_t pid, size_t n, char *dest) { - struct pid_stat *stat; + struct ebpf_pid_stat *stat; - stat = all_pids[pid]; + stat = ebpf_all_pids[pid]; if (unlikely(stat == NULL)) { return -1; } @@ -945,19 +1281,19 @@ void cleanup_variables_from_other_threads(uint32_t pid) { // Clean socket structures if (socket_bandwidth_curr) { - freez(socket_bandwidth_curr[pid]); + ebpf_socket_release(socket_bandwidth_curr[pid]); socket_bandwidth_curr[pid] = NULL; } // Clean cachestat structure if (cachestat_pid) { - freez(cachestat_pid[pid]); + ebpf_cachestat_release(cachestat_pid[pid]); cachestat_pid[pid] = NULL; } // Clean directory cache structure if (dcstat_pid) { - freez(dcstat_pid[pid]); + ebpf_dcstat_release(dcstat_pid[pid]); dcstat_pid[pid] = NULL; } @@ -969,19 +1305,19 @@ void cleanup_variables_from_other_threads(uint32_t pid) // Clean vfs structure if (vfs_pid) { - freez(vfs_pid[pid]); + ebpf_vfs_release(vfs_pid[pid]); vfs_pid[pid] = NULL; } // Clean fd structure if (fd_pid) { - freez(fd_pid[pid]); + ebpf_fd_release(fd_pid[pid]); fd_pid[pid] = NULL; } // Clean shm structure if (shm_pid) { - freez(shm_pid[pid]); + ebpf_shm_release(shm_pid[pid]); shm_pid[pid] = NULL; } } @@ -991,9 +1327,9 @@ void cleanup_variables_from_other_threads(uint32_t pid) */ void cleanup_exited_pids() { - struct pid_stat *p = NULL; + struct ebpf_pid_stat *p = NULL; - for (p = root_of_pids; p;) { + for (p = ebpf_root_of_pids; p;) { if (!p->updated && (!p->keep || p->keeploops > 0)) { if (unlikely(debug_enabled && (p->keep || p->keeploops))) debug_log(" > CLEANUP cannot keep exited process %d (%s) anymore - removing it.", p->pid, p->comm); @@ -1002,12 +1338,9 @@ void cleanup_exited_pids() p = p->next; // Clean process structure - freez(global_process_stats[r]); + ebpf_process_stat_release(global_process_stats[r]); global_process_stats[r] = NULL; - freez(current_apps_data[r]); - current_apps_data[r] = NULL; - cleanup_variables_from_other_threads(r); del_pid_entry(r); @@ -1060,7 +1393,7 @@ static inline void read_proc_filesystem() * @param p the pid with information to update * @param o never used */ -static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p, struct target *o) +static inline void aggregate_pid_on_target(struct ebpf_target *w, struct ebpf_pid_stat *p, struct ebpf_target *o) { UNUSED(o); @@ -1075,7 +1408,7 @@ static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p, } w->processes++; - struct pid_on_target *pid_on_target = mallocz(sizeof(struct pid_on_target)); + struct ebpf_pid_on_target *pid_on_target = mallocz(sizeof(struct ebpf_pid_on_target)); pid_on_target->pid = p->pid; pid_on_target->next = w->root_pid; w->root_pid = pid_on_target; @@ -1091,10 +1424,10 @@ static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p, */ void collect_data_for_all_processes(int tbl_pid_stats_fd) { - if (unlikely(!all_pids)) + if (unlikely(!ebpf_all_pids)) return; - struct pid_stat *pids = root_of_pids; // global list of all processes running + struct ebpf_pid_stat *pids = ebpf_root_of_pids; // global list of all processes running while (pids) { if (pids->updated_twice) { pids->read = 0; // mark it as not read, so that collect_data_for_pid() will read it @@ -1113,24 +1446,21 @@ void collect_data_for_all_processes(int tbl_pid_stats_fd) read_proc_filesystem(); uint32_t key; - pids = root_of_pids; // global list of all processes running + pids = ebpf_root_of_pids; // global list of all processes running // while (bpf_map_get_next_key(tbl_pid_stats_fd, &key, &next_key) == 0) { while (pids) { key = pids->pid; ebpf_process_stat_t *w = global_process_stats[key]; if (!w) { - w = callocz(1, sizeof(ebpf_process_stat_t)); + w = ebpf_process_stat_get(); global_process_stats[key] = w; } if (bpf_map_lookup_elem(tbl_pid_stats_fd, &key, w)) { // Clean Process structures - freez(w); + ebpf_process_stat_release(w); global_process_stats[key] = NULL; - freez(current_apps_data[key]); - current_apps_data[key] = NULL; - cleanup_variables_from_other_threads(key); pids = pids->next; @@ -1148,7 +1478,7 @@ void collect_data_for_all_processes(int tbl_pid_stats_fd) // this has to be done, before the cleanup // // concentrate everything on the targets - for (pids = root_of_pids; pids; pids = pids->next) + for (pids = ebpf_root_of_pids; pids; pids = pids->next) aggregate_pid_on_target(pids->target, pids, NULL); post_aggregate_targets(apps_groups_root_target); diff --git a/collectors/ebpf.plugin/ebpf_apps.h b/collectors/ebpf.plugin/ebpf_apps.h index 0bea9122..d33442af 100644 --- a/collectors/ebpf.plugin/ebpf_apps.h +++ b/collectors/ebpf.plugin/ebpf_apps.h @@ -3,7 +3,6 @@ #ifndef NETDATA_EBPF_APPS_H #define NETDATA_EBPF_APPS_H 1 -#include "libnetdata/threads/threads.h" #include "libnetdata/locks/locks.h" #include "libnetdata/avl/avl.h" #include "libnetdata/clocks/clocks.h" @@ -34,92 +33,21 @@ #include "ebpf_swap.h" #include "ebpf_vfs.h" -#define MAX_COMPARE_NAME 100 -#define MAX_NAME 100 - -// ---------------------------------------------------------------------------- -// process_pid_stat -// -// Fields read from the kernel ring for a specific PID -// -typedef struct process_pid_stat { - uint64_t pid_tgid; // Unique identifier - uint32_t pid; // process id - - // Count number of calls done for specific function - uint32_t open_call; - uint32_t write_call; - uint32_t writev_call; - uint32_t read_call; - uint32_t readv_call; - uint32_t unlink_call; - uint32_t exit_call; - uint32_t release_call; - uint32_t fork_call; - uint32_t clone_call; - uint32_t close_call; - - // Count number of bytes written or read - uint64_t write_bytes; - uint64_t writev_bytes; - uint64_t readv_bytes; - uint64_t read_bytes; - - // Count number of errors for the specified function - uint32_t open_err; - uint32_t write_err; - uint32_t writev_err; - uint32_t read_err; - uint32_t readv_err; - uint32_t unlink_err; - uint32_t fork_err; - uint32_t clone_err; - uint32_t close_err; -} process_pid_stat_t; - -// ---------------------------------------------------------------------------- -// socket_bandwidth -// -// Fields read from the kernel ring for a specific PID -// -typedef struct socket_bandwidth { - uint64_t first; - uint64_t ct; - uint64_t sent; - uint64_t received; - unsigned char removed; -} socket_bandwidth_t; +#define EBPF_MAX_COMPARE_NAME 100 +#define EBPF_MAX_NAME 100 // ---------------------------------------------------------------------------- // pid_stat // -// structure to store data for each process running -// see: man proc for the description of the fields - -struct pid_fd { - int fd; - -#ifndef __FreeBSD__ - ino_t inode; - char *filename; - uint32_t link_hash; - size_t cache_iterations_counter; - size_t cache_iterations_reset; -#endif -}; - -struct target { - char compare[MAX_COMPARE_NAME + 1]; +struct ebpf_target { + char compare[EBPF_MAX_COMPARE_NAME + 1]; uint32_t comparehash; size_t comparelen; - char id[MAX_NAME + 1]; + char id[EBPF_MAX_NAME + 1]; uint32_t idhash; - char name[MAX_NAME + 1]; - - uid_t uid; - gid_t gid; + char name[EBPF_MAX_NAME + 1]; // Changes made to simplify integration between apps and eBPF. netdata_publish_cachestat_t cachestat; @@ -129,58 +57,9 @@ struct target { netdata_fd_stat_t fd; netdata_publish_shm_t shm; - /* These variables are not necessary for eBPF collector - kernel_uint_t minflt; - kernel_uint_t cminflt; - kernel_uint_t majflt; - kernel_uint_t cmajflt; - kernel_uint_t utime; - kernel_uint_t stime; - kernel_uint_t gtime; - kernel_uint_t cutime; - kernel_uint_t cstime; - kernel_uint_t cgtime; - kernel_uint_t num_threads; - // kernel_uint_t rss; - - kernel_uint_t status_vmsize; - kernel_uint_t status_vmrss; - kernel_uint_t status_vmshared; - kernel_uint_t status_rssfile; - kernel_uint_t status_rssshmem; - kernel_uint_t status_vmswap; - - kernel_uint_t io_logical_bytes_read; - kernel_uint_t io_logical_bytes_written; - // kernel_uint_t io_read_calls; - // kernel_uint_t io_write_calls; - kernel_uint_t io_storage_bytes_read; - kernel_uint_t io_storage_bytes_written; - // kernel_uint_t io_cancelled_write_bytes; - - int *target_fds; - int target_fds_size; - - kernel_uint_t openfiles; - kernel_uint_t openpipes; - kernel_uint_t opensockets; - kernel_uint_t openinotifies; - kernel_uint_t openeventfds; - kernel_uint_t opentimerfds; - kernel_uint_t opensignalfds; - kernel_uint_t openeventpolls; - kernel_uint_t openother; - */ - kernel_uint_t starttime; kernel_uint_t collected_starttime; - /* - kernel_uint_t uptime_min; - kernel_uint_t uptime_sum; - kernel_uint_t uptime_max; - */ - unsigned int processes; // how many processes have been merged to this int exposed; // if set, we have sent this to netdata int hidden; // if set, we set the hidden flag on the dimension @@ -189,20 +68,20 @@ struct target { int starts_with; // if set, the compare string matches only the // beginning of the command - struct pid_on_target *root_pid; // list of aggregated pids for target debugging + struct ebpf_pid_on_target *root_pid; // list of aggregated pids for target debugging - struct target *target; // the one that will be reported to netdata - struct target *next; + struct ebpf_target *target; // the one that will be reported to netdata + struct ebpf_target *next; }; -extern struct target *apps_groups_default_target; -extern struct target *apps_groups_root_target; -extern struct target *users_root_target; -extern struct target *groups_root_target; +extern struct ebpf_target *apps_groups_default_target; +extern struct ebpf_target *apps_groups_root_target; +extern struct ebpf_target *users_root_target; +extern struct ebpf_target *groups_root_target; -struct pid_stat { +struct ebpf_pid_stat { int32_t pid; - char comm[MAX_COMPARE_NAME + 1]; + char comm[EBPF_MAX_COMPARE_NAME + 1]; char *cmdline; uint32_t log_thrown; @@ -210,96 +89,6 @@ struct pid_stat { // char state; int32_t ppid; - // int32_t pgrp; - // int32_t session; - // int32_t tty_nr; - // int32_t tpgid; - // uint64_t flags; - - /* - // these are raw values collected - kernel_uint_t minflt_raw; - kernel_uint_t cminflt_raw; - kernel_uint_t majflt_raw; - kernel_uint_t cmajflt_raw; - kernel_uint_t utime_raw; - kernel_uint_t stime_raw; - kernel_uint_t gtime_raw; // guest_time - kernel_uint_t cutime_raw; - kernel_uint_t cstime_raw; - kernel_uint_t cgtime_raw; // cguest_time - - // these are rates - kernel_uint_t minflt; - kernel_uint_t cminflt; - kernel_uint_t majflt; - kernel_uint_t cmajflt; - kernel_uint_t utime; - kernel_uint_t stime; - kernel_uint_t gtime; - kernel_uint_t cutime; - kernel_uint_t cstime; - kernel_uint_t cgtime; - - // int64_t priority; - // int64_t nice; - int32_t num_threads; - // int64_t itrealvalue; - kernel_uint_t collected_starttime; - // kernel_uint_t vsize; - // kernel_uint_t rss; - // kernel_uint_t rsslim; - // kernel_uint_t starcode; - // kernel_uint_t endcode; - // kernel_uint_t startstack; - // kernel_uint_t kstkesp; - // kernel_uint_t kstkeip; - // uint64_t signal; - // uint64_t blocked; - // uint64_t sigignore; - // uint64_t sigcatch; - // uint64_t wchan; - // uint64_t nswap; - // uint64_t cnswap; - // int32_t exit_signal; - // int32_t processor; - // uint32_t rt_priority; - // uint32_t policy; - // kernel_uint_t delayacct_blkio_ticks; - - uid_t uid; - gid_t gid; - - kernel_uint_t status_vmsize; - kernel_uint_t status_vmrss; - kernel_uint_t status_vmshared; - kernel_uint_t status_rssfile; - kernel_uint_t status_rssshmem; - kernel_uint_t status_vmswap; -#ifndef __FreeBSD__ - ARL_BASE *status_arl; -#endif - - kernel_uint_t io_logical_bytes_read_raw; - kernel_uint_t io_logical_bytes_written_raw; - // kernel_uint_t io_read_calls_raw; - // kernel_uint_t io_write_calls_raw; - kernel_uint_t io_storage_bytes_read_raw; - kernel_uint_t io_storage_bytes_written_raw; - // kernel_uint_t io_cancelled_write_bytes_raw; - - kernel_uint_t io_logical_bytes_read; - kernel_uint_t io_logical_bytes_written; - // kernel_uint_t io_read_calls; - // kernel_uint_t io_write_calls; - kernel_uint_t io_storage_bytes_read; - kernel_uint_t io_storage_bytes_written; - // kernel_uint_t io_cancelled_write_bytes; - */ - - struct pid_fd *fds; // array of fds it uses - size_t fds_size; // the size of the fds array - int children_count; // number of processes directly referencing this unsigned char keep : 1; // 1 when we need to keep this process in memory even after it exited int keeploops; // increases by 1 every time keep is 1 and updated 0 @@ -312,28 +101,21 @@ struct pid_stat { // each process gets a unique number - struct target *target; // app_groups.conf targets - struct target *user_target; // uid based targets - struct target *group_target; // gid based targets + struct ebpf_target *target; // app_groups.conf targets + struct ebpf_target *user_target; // uid based targets + struct ebpf_target *group_target; // gid based targets usec_t stat_collected_usec; usec_t last_stat_collected_usec; - usec_t io_collected_usec; - usec_t last_io_collected_usec; - - kernel_uint_t uptime; - - char *fds_dirname; // the full directory name in /proc/PID/fd - char *stat_filename; char *status_filename; char *io_filename; char *cmdline_filename; - struct pid_stat *parent; - struct pid_stat *prev; - struct pid_stat *next; + struct ebpf_pid_stat *parent; + struct ebpf_pid_stat *prev; + struct ebpf_pid_stat *next; }; // ---------------------------------------------------------------------------- @@ -344,15 +126,15 @@ struct pid_stat { // // - Each entry in /etc/apps_groups.conf creates a target. // - Each user and group used by a process in the system, creates a target. -struct pid_on_target { +struct ebpf_pid_on_target { int32_t pid; - struct pid_on_target *next; + struct ebpf_pid_on_target *next; }; // ---------------------------------------------------------------------------- // Structures used to read information from kernel ring typedef struct ebpf_process_stat { - uint64_t pid_tgid; + uint64_t pid_tgid; // This cannot be removed, because it is used inside kernel ring. uint32_t pid; //Counter @@ -406,16 +188,16 @@ static inline void debug_log_int(const char *fmt, ...) // ---------------------------------------------------------------------------- // Exported variabled and functions // -extern struct pid_stat **all_pids; +extern struct ebpf_pid_stat **ebpf_all_pids; -int ebpf_read_apps_groups_conf(struct target **apps_groups_default_target, - struct target **apps_groups_root_target, - const char *path, - const char *file); +int ebpf_read_apps_groups_conf(struct ebpf_target **apps_groups_default_target, + struct ebpf_target **apps_groups_root_target, + const char *path, + const char *file); -void clean_apps_groups_target(struct target *apps_groups_root_target); +void clean_apps_groups_target(struct ebpf_target *apps_groups_root_target); -size_t zero_all_targets(struct target *root); +size_t zero_all_targets(struct ebpf_target *root); int am_i_running_as_root(); @@ -427,15 +209,74 @@ int get_pid_comm(pid_t pid, size_t n, char *dest); size_t read_processes_statistic_using_pid_on_target(ebpf_process_stat_t **ep, int fd, - struct pid_on_target *pids); + struct ebpf_pid_on_target *pids); -size_t read_bandwidth_statistic_using_pid_on_target(ebpf_bandwidth_t **ep, int fd, struct pid_on_target *pids); +size_t read_bandwidth_statistic_using_pid_on_target(ebpf_bandwidth_t **ep, int fd, struct ebpf_pid_on_target *pids); void collect_data_for_all_processes(int tbl_pid_stats_fd); extern ebpf_process_stat_t **global_process_stats; -extern ebpf_process_publish_apps_t **current_apps_data; extern netdata_publish_cachestat_t **cachestat_pid; extern netdata_publish_dcstat_t **dcstat_pid; +extern netdata_publish_swap_t **swap_pid; +extern netdata_publish_vfs_t **vfs_pid; +extern netdata_fd_stat_t **fd_pid; +extern netdata_publish_shm_t **shm_pid; + +// The default value is at least 32 times smaller than maximum number of PIDs allowed on system, +// this is only possible because we are using ARAL (https://github.com/netdata/netdata/tree/master/libnetdata/aral). +#ifndef NETDATA_EBPF_ALLOC_MAX_PID +# define NETDATA_EBPF_ALLOC_MAX_PID 1024 +#endif +#define NETDATA_EBPF_ALLOC_MIN_ELEMENTS 256 + +// ARAL Sectiion +extern void ebpf_aral_init(void); + +extern ebpf_process_stat_t *ebpf_process_stat_get(void); +extern void ebpf_process_stat_release(ebpf_process_stat_t *stat); + +extern ARAL *ebpf_aral_socket_pid; +void ebpf_socket_aral_init(); +ebpf_socket_publish_apps_t *ebpf_socket_stat_get(void); +void ebpf_socket_release(ebpf_socket_publish_apps_t *stat); + +extern ARAL *ebpf_aral_cachestat_pid; +void ebpf_cachestat_aral_init(); +netdata_publish_cachestat_t *ebpf_publish_cachestat_get(void); +void ebpf_cachestat_release(netdata_publish_cachestat_t *stat); + +extern ARAL *ebpf_aral_dcstat_pid; +void ebpf_dcstat_aral_init(); +netdata_publish_dcstat_t *ebpf_publish_dcstat_get(void); +void ebpf_dcstat_release(netdata_publish_dcstat_t *stat); + +extern ARAL *ebpf_aral_vfs_pid; +void ebpf_vfs_aral_init(); +netdata_publish_vfs_t *ebpf_vfs_get(void); +void ebpf_vfs_release(netdata_publish_vfs_t *stat); + +extern ARAL *ebpf_aral_fd_pid; +void ebpf_fd_aral_init(); +netdata_fd_stat_t *ebpf_fd_stat_get(void); +void ebpf_fd_release(netdata_fd_stat_t *stat); + +extern ARAL *ebpf_aral_shm_pid; +void ebpf_shm_aral_init(); +netdata_publish_shm_t *ebpf_shm_stat_get(void); +void ebpf_shm_release(netdata_publish_shm_t *stat); + +// ARAL Section end + +// Threads integrated with apps +extern ebpf_socket_publish_apps_t **socket_bandwidth_curr; +// Threads integrated with apps + +#include "libnetdata/threads/threads.h" + +// ARAL variables +extern ARAL *ebpf_aral_apps_pid_stat; +extern ARAL *ebpf_aral_process_stat; +#define NETDATA_EBPF_PROC_ARAL_NAME "ebpf_proc_stat" #endif /* NETDATA_EBPF_APPS_H */ diff --git a/collectors/ebpf.plugin/ebpf_cachestat.c b/collectors/ebpf.plugin/ebpf_cachestat.c index b21cc610..b2b006dd 100644 --- a/collectors/ebpf.plugin/ebpf_cachestat.c +++ b/collectors/ebpf.plugin/ebpf_cachestat.c @@ -3,8 +3,6 @@ #include "ebpf.h" #include "ebpf_cachestat.h" -netdata_publish_cachestat_t **cachestat_pid; - static char *cachestat_counter_dimension_name[NETDATA_CACHESTAT_END] = { "ratio", "dirty", "hit", "miss" }; static netdata_syscall_stat_t cachestat_counter_aggregated_data[NETDATA_CACHESTAT_END]; @@ -46,10 +44,6 @@ static char *account_page[NETDATA_CACHESTAT_ACCOUNT_DIRTY_END] ={ "account_page_ "__set_page_dirty", "__folio_mark_dirty" }; #ifdef LIBBPF_MAJOR_VERSION -#include "includes/cachestat.skel.h" // BTF code - -static struct cachestat_bpf *bpf_obj = NULL; - /** * Disable probe * @@ -333,20 +327,14 @@ 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); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING; + em->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); -#ifdef LIBBPF_MAJOR_VERSION - if (bpf_obj) - cachestat_bpf__destroy(bpf_obj); -#endif pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -502,7 +490,7 @@ static void cachestat_fill_pid(uint32_t current_pid, netdata_cachestat_pid_t *pu { netdata_publish_cachestat_t *curr = cachestat_pid[current_pid]; if (!curr) { - curr = callocz(1, sizeof(netdata_publish_cachestat_t)); + curr = ebpf_publish_cachestat_get(); cachestat_pid[current_pid] = curr; cachestat_save_pid_values(curr, publish); @@ -521,7 +509,7 @@ static void read_apps_table() { netdata_cachestat_pid_t *cv = cachestat_vector; uint32_t key; - struct pid_stat *pids = root_of_pids; + struct ebpf_pid_stat *pids = ebpf_root_of_pids; int fd = cachestat_maps[NETDATA_CACHESTAT_PID_STATS].map_fd; size_t length = sizeof(netdata_cachestat_pid_t)*ebpf_nprocs; while (pids) { @@ -589,7 +577,7 @@ static void ebpf_update_cachestat_cgroup() */ void ebpf_cachestat_create_apps_charts(struct ebpf_module *em, void *ptr) { - struct target *root = ptr; + struct ebpf_target *root = ptr; ebpf_create_charts_on_apps(NETDATA_CACHESTAT_HIT_RATIO_CHART, "Hit ratio", EBPF_COMMON_DIMENSION_PERCENTAGE, @@ -694,7 +682,7 @@ static void cachestat_send_global(netdata_publish_cachestat_t *publish) * @param publish output structure. * @param root structure with listed IPs */ -void ebpf_cachestat_sum_pids(netdata_publish_cachestat_t *publish, struct pid_on_target *root) +void ebpf_cachestat_sum_pids(netdata_publish_cachestat_t *publish, struct ebpf_pid_on_target *root) { memcpy(&publish->prev, &publish->current,sizeof(publish->current)); memset(&publish->current, 0, sizeof(publish->current)); @@ -720,9 +708,9 @@ void ebpf_cachestat_sum_pids(netdata_publish_cachestat_t *publish, struct pid_on * * @param root the target list. */ -void ebpf_cache_send_apps_data(struct target *root) +void ebpf_cache_send_apps_data(struct ebpf_target *root) { - struct target *w; + struct ebpf_target *w; collected_number value; write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_CACHESTAT_HIT_RATIO_CHART); @@ -1092,6 +1080,11 @@ static void cachestat_collector(ebpf_module_t *em) if (apps & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) ebpf_cache_send_apps_data(apps_groups_root_target); +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_cachestat_pid) + ebpf_send_data_aral_chart(ebpf_aral_cachestat_pid, em); +#endif + if (cgroups) ebpf_cachestat_send_cgroup_data(update_every); @@ -1167,10 +1160,11 @@ static void ebpf_create_memory_charts(ebpf_module_t *em) */ static void ebpf_cachestat_allocate_global_vectors(int apps) { - if (apps) + if (apps) { cachestat_pid = callocz((size_t)pid_max, sizeof(netdata_publish_cachestat_t *)); - - cachestat_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_cachestat_pid_t)); + ebpf_cachestat_aral_init(); + cachestat_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_cachestat_pid_t)); + } cachestat_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_idx_t)); @@ -1232,11 +1226,11 @@ static int ebpf_cachestat_load_bpf(ebpf_module_t *em) } #ifdef LIBBPF_MAJOR_VERSION else { - bpf_obj = cachestat_bpf__open(); - if (!bpf_obj) + cachestat_bpf_obj = cachestat_bpf__open(); + if (!cachestat_bpf_obj) ret = -1; else - ret = ebpf_cachestat_load_and_attach(bpf_obj, em); + ret = ebpf_cachestat_load_and_attach(cachestat_bpf_obj, em); } #endif @@ -1265,7 +1259,6 @@ void *ebpf_cachestat_thread(void *ptr) ebpf_update_pid_table(&cachestat_maps[NETDATA_CACHESTAT_PID_STATS], em); if (ebpf_cachestat_set_internal_value()) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endcachestat; } @@ -1273,7 +1266,6 @@ void *ebpf_cachestat_thread(void *ptr) ebpf_adjust_thread_load(em, default_btf); #endif if (ebpf_cachestat_load_bpf(em)) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endcachestat; } @@ -1289,7 +1281,13 @@ void *ebpf_cachestat_thread(void *ptr) pthread_mutex_lock(&lock); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); ebpf_create_memory_charts(em); +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_cachestat_pid) + ebpf_statistic_create_aral_chart(NETDATA_EBPF_CACHESTAT_ARAL_NAME, em); +#endif + pthread_mutex_unlock(&lock); cachestat_collector(em); diff --git a/collectors/ebpf.plugin/ebpf_cachestat.h b/collectors/ebpf.plugin/ebpf_cachestat.h index 15b06511..2c1f171c 100644 --- a/collectors/ebpf.plugin/ebpf_cachestat.h +++ b/collectors/ebpf.plugin/ebpf_cachestat.h @@ -33,6 +33,9 @@ #define NETDATA_SYSTEMD_CACHESTAT_HIT_FILE_CONTEXT "services.cachestat_hits" #define NETDATA_SYSTEMD_CACHESTAT_MISS_FILES_CONTEXT "services.cachestat_misses" +// ARAL Name +#define NETDATA_EBPF_CACHESTAT_ARAL_NAME "ebpf_cachestat" + // variables enum cachestat_counters { NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU, @@ -82,6 +85,7 @@ typedef struct netdata_publish_cachestat { } netdata_publish_cachestat_t; void *ebpf_cachestat_thread(void *ptr); +void ebpf_cachestat_release(netdata_publish_cachestat_t *stat); extern struct config cachestat_config; extern netdata_ebpf_targets_t cachestat_targets[]; diff --git a/collectors/ebpf.plugin/ebpf_cgroup.c b/collectors/ebpf.plugin/ebpf_cgroup.c index 42c04536..6d7c555b 100644 --- a/collectors/ebpf.plugin/ebpf_cgroup.c +++ b/collectors/ebpf.plugin/ebpf_cgroup.c @@ -6,6 +6,7 @@ #include "ebpf_cgroup.h" ebpf_cgroup_target_t *ebpf_cgroup_pids = NULL; +static void *ebpf_mapped_memory = NULL; int send_cgroup_chart = 0; // -------------------------------------------------------------------------------------------------------------------- @@ -19,7 +20,7 @@ int send_cgroup_chart = 0; * @param fd file descriptor returned after shm_open was called. * @param length length of the shared memory * - * @return It returns a pointer to the region mapped. + * @return It returns a pointer to the region mapped on success and MAP_FAILED otherwise. */ static inline void *ebpf_cgroup_map_shm_locally(int fd, size_t length) { @@ -36,6 +37,16 @@ static inline void *ebpf_cgroup_map_shm_locally(int fd, size_t length) return value; } +/** + * Unmap Shared Memory + * + * Unmap shared memory used to integrate eBPF and cgroup plugin + */ +void ebpf_unmap_cgroup_shared_memory() +{ + munmap(ebpf_mapped_memory, shm_ebpf_cgroup.header->body_length); +} + /** * Map cgroup shared memory * @@ -56,40 +67,47 @@ void ebpf_map_cgroup_shared_memory() limit_try++; next_try = curr_time + NETDATA_EBPF_CGROUP_NEXT_TRY_SEC; - shm_fd_ebpf_cgroup = shm_open(NETDATA_SHARED_MEMORY_EBPF_CGROUP_NAME, O_RDWR, 0660); if (shm_fd_ebpf_cgroup < 0) { - if (limit_try == NETDATA_EBPF_CGROUP_MAX_TRIES) - error("Shared memory was not initialized, integration between processes won't happen."); + shm_fd_ebpf_cgroup = shm_open(NETDATA_SHARED_MEMORY_EBPF_CGROUP_NAME, O_RDWR, 0660); + if (shm_fd_ebpf_cgroup < 0) { + if (limit_try == NETDATA_EBPF_CGROUP_MAX_TRIES) + error("Shared memory was not initialized, integration between processes won't happen."); - return; + return; + } } // Map only header - shm_ebpf_cgroup.header = (netdata_ebpf_cgroup_shm_header_t *) ebpf_cgroup_map_shm_locally(shm_fd_ebpf_cgroup, - sizeof(netdata_ebpf_cgroup_shm_header_t)); - if (!shm_ebpf_cgroup.header) { - limit_try = NETDATA_EBPF_CGROUP_MAX_TRIES + 1; + void *mapped = (netdata_ebpf_cgroup_shm_header_t *) ebpf_cgroup_map_shm_locally(shm_fd_ebpf_cgroup, + sizeof(netdata_ebpf_cgroup_shm_header_t)); + if (unlikely(mapped == SEM_FAILED)) { return; } + netdata_ebpf_cgroup_shm_header_t *header = mapped; - size_t length = shm_ebpf_cgroup.header->body_length; + size_t length = header->body_length; - munmap(shm_ebpf_cgroup.header, sizeof(netdata_ebpf_cgroup_shm_header_t)); + munmap(header, sizeof(netdata_ebpf_cgroup_shm_header_t)); - shm_ebpf_cgroup.header = (netdata_ebpf_cgroup_shm_header_t *)ebpf_cgroup_map_shm_locally(shm_fd_ebpf_cgroup, length); - if (!shm_ebpf_cgroup.header) { - limit_try = NETDATA_EBPF_CGROUP_MAX_TRIES + 1; + if (length <= ((sizeof(netdata_ebpf_cgroup_shm_header_t) + sizeof(netdata_ebpf_cgroup_shm_body_t)))) { return; } - shm_ebpf_cgroup.body = (netdata_ebpf_cgroup_shm_body_t *) ((char *)shm_ebpf_cgroup.header + - sizeof(netdata_ebpf_cgroup_shm_header_t)); + + ebpf_mapped_memory = (void *)ebpf_cgroup_map_shm_locally(shm_fd_ebpf_cgroup, length); + if (unlikely(ebpf_mapped_memory == MAP_FAILED)) { + return; + } + shm_ebpf_cgroup.header = ebpf_mapped_memory; + shm_ebpf_cgroup.body = ebpf_mapped_memory + sizeof(netdata_ebpf_cgroup_shm_header_t); shm_sem_ebpf_cgroup = sem_open(NETDATA_NAMED_SEMAPHORE_EBPF_CGROUP_NAME, O_CREAT, 0660, 1); if (shm_sem_ebpf_cgroup == SEM_FAILED) { error("Cannot create semaphore, integration between eBPF and cgroup won't happen"); - munmap(shm_ebpf_cgroup.header, length); + limit_try = NETDATA_EBPF_CGROUP_MAX_TRIES + 1; + munmap(ebpf_mapped_memory, length); shm_ebpf_cgroup.header = NULL; + shm_ebpf_cgroup.body = NULL; close(shm_fd_ebpf_cgroup); shm_fd_ebpf_cgroup = -1; shm_unlink(NETDATA_SHARED_MEMORY_EBPF_CGROUP_NAME); @@ -258,32 +276,38 @@ void ebpf_reset_updated_var() void ebpf_parse_cgroup_shm_data() { static int previous = 0; - if (shm_ebpf_cgroup.header) { - sem_wait(shm_sem_ebpf_cgroup); - int i, end = shm_ebpf_cgroup.header->cgroup_root_count; + if (!shm_ebpf_cgroup.header || shm_sem_ebpf_cgroup == SEM_FAILED) + return; - pthread_mutex_lock(&mutex_cgroup_shm); + sem_wait(shm_sem_ebpf_cgroup); + int i, end = shm_ebpf_cgroup.header->cgroup_root_count; + if (end <= 0) { + sem_post(shm_sem_ebpf_cgroup); + return; + } - ebpf_remove_cgroup_target_update_list(); + pthread_mutex_lock(&mutex_cgroup_shm); + ebpf_remove_cgroup_target_update_list(); - ebpf_reset_updated_var(); + ebpf_reset_updated_var(); - for (i = 0; i < end; i++) { - netdata_ebpf_cgroup_shm_body_t *ptr = &shm_ebpf_cgroup.body[i]; - if (ptr->enabled) { - ebpf_cgroup_target_t *ect = ebpf_cgroup_find_or_create(ptr); - ebpf_update_pid_link_list(ect, ptr->path); - } + for (i = 0; i < end; i++) { + netdata_ebpf_cgroup_shm_body_t *ptr = &shm_ebpf_cgroup.body[i]; + if (ptr->enabled) { + ebpf_cgroup_target_t *ect = ebpf_cgroup_find_or_create(ptr); + ebpf_update_pid_link_list(ect, ptr->path); } - send_cgroup_chart = previous != shm_ebpf_cgroup.header->cgroup_root_count; - previous = shm_ebpf_cgroup.header->cgroup_root_count; + } + send_cgroup_chart = previous != shm_ebpf_cgroup.header->cgroup_root_count; + previous = shm_ebpf_cgroup.header->cgroup_root_count; + sem_post(shm_sem_ebpf_cgroup); + pthread_mutex_unlock(&mutex_cgroup_shm); #ifdef NETDATA_DEV_MODE - error("Updating cgroup %d (Previous: %d, Current: %d)", send_cgroup_chart, previous, shm_ebpf_cgroup.header->cgroup_root_count); + info("Updating cgroup %d (Previous: %d, Current: %d)", + send_cgroup_chart, previous, shm_ebpf_cgroup.header->cgroup_root_count); #endif - pthread_mutex_unlock(&mutex_cgroup_shm); - sem_post(shm_sem_ebpf_cgroup); - } + sem_post(shm_sem_ebpf_cgroup); } // -------------------------------------------------------------------------------------------------------------------- @@ -315,3 +339,54 @@ void ebpf_create_charts_on_systemd(char *id, char *title, char *units, char *fam fprintf(stdout, "DIMENSION %s '' %s 1 1\n", w->name, algorithm); } } + +// -------------------------------------------------------------------------------------------------------------------- +// Cgroup main thread + +/** + * CGROUP exit + * + * Clean up the main thread. + * + * @param ptr thread data. + */ +static void ebpf_cgroup_exit(void *ptr) +{ + UNUSED(ptr); +} + +/** + * Cgroup integratin + * + * Thread responsible to call functions responsible to sync data between plugins. + * + * @param ptr It is a NULL value for this thread. + * + * @return It always returns NULL. + */ +void *ebpf_cgroup_integration(void *ptr) +{ + netdata_thread_cleanup_push(ebpf_cgroup_exit, ptr); + + 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(); + else + ebpf_parse_cgroup_shm_data(); + } + } + + netdata_thread_cleanup_pop(1); + return NULL; +} diff --git a/collectors/ebpf.plugin/ebpf_cgroup.h b/collectors/ebpf.plugin/ebpf_cgroup.h index 19da7fca..6620ea10 100644 --- a/collectors/ebpf.plugin/ebpf_cgroup.h +++ b/collectors/ebpf.plugin/ebpf_cgroup.h @@ -64,6 +64,8 @@ void ebpf_map_cgroup_shared_memory(); void ebpf_parse_cgroup_shm_data(); void ebpf_create_charts_on_systemd(char *id, char *title, char *units, char *family, char *charttype, int order, char *algorithm, char *context, char *module, int update_every); +void *ebpf_cgroup_integration(void *ptr); +void ebpf_unmap_cgroup_shared_memory(); extern int send_cgroup_chart; #endif /* NETDATA_EBPF_CGROUP_H */ diff --git a/collectors/ebpf.plugin/ebpf_dcstat.c b/collectors/ebpf.plugin/ebpf_dcstat.c index 75e83214..5f140060 100644 --- a/collectors/ebpf.plugin/ebpf_dcstat.c +++ b/collectors/ebpf.plugin/ebpf_dcstat.c @@ -8,7 +8,6 @@ static netdata_syscall_stat_t dcstat_counter_aggregated_data[NETDATA_DCSTAT_IDX_ static netdata_publish_syscall_t dcstat_counter_publish_aggregated[NETDATA_DCSTAT_IDX_END]; netdata_dcstat_pid_t *dcstat_vector = NULL; -netdata_publish_dcstat_t **dcstat_pid = NULL; static netdata_idx_t dcstat_hash_values[NETDATA_DCSTAT_IDX_END]; static netdata_idx_t *dcstat_values = NULL; @@ -45,10 +44,6 @@ netdata_ebpf_targets_t dc_targets[] = { {.name = "lookup_fast", .mode = EBPF_LOA {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; #ifdef LIBBPF_MAJOR_VERSION -#include "includes/dc.skel.h" // BTF code - -static struct dc_bpf *bpf_obj = NULL; - /** * Disable probe * @@ -294,23 +289,16 @@ void ebpf_dcstat_clean_names() static void ebpf_dcstat_free(ebpf_module_t *em ) { pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING; + em->enabled = NETDATA_THREAD_EBPF_STOPPING; pthread_mutex_unlock(&ebpf_exit_cleanup); freez(dcstat_vector); freez(dcstat_values); - ebpf_cleanup_publish_syscall(dcstat_counter_publish_aggregated); - ebpf_dcstat_clean_names(); -#ifdef LIBBPF_MAJOR_VERSION - if (bpf_obj) - dc_bpf__destroy(bpf_obj); -#endif - pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -342,7 +330,7 @@ static void ebpf_dcstat_exit(void *ptr) */ void ebpf_dcstat_create_apps_charts(struct ebpf_module *em, void *ptr) { - struct target *root = ptr; + struct ebpf_target *root = ptr; ebpf_create_charts_on_apps(NETDATA_DC_HIT_CHART, "Percentage of files inside directory cache", EBPF_COMMON_DIMENSION_PERCENTAGE, @@ -432,7 +420,7 @@ static void dcstat_fill_pid(uint32_t current_pid, netdata_dcstat_pid_t *publish) { netdata_publish_dcstat_t *curr = dcstat_pid[current_pid]; if (!curr) { - curr = callocz(1, sizeof(netdata_publish_dcstat_t)); + curr = ebpf_publish_dcstat_get(); dcstat_pid[current_pid] = curr; } @@ -448,7 +436,7 @@ static void read_apps_table() { netdata_dcstat_pid_t *cv = dcstat_vector; uint32_t key; - struct pid_stat *pids = root_of_pids; + struct ebpf_pid_stat *pids = ebpf_root_of_pids; int fd = dcstat_maps[NETDATA_DCSTAT_PID_STATS].map_fd; size_t length = sizeof(netdata_dcstat_pid_t)*ebpf_nprocs; while (pids) { @@ -540,7 +528,7 @@ static void ebpf_dc_read_global_table() * @param publish output structure. * @param root structure with listed IPs */ -void ebpf_dcstat_sum_pids(netdata_publish_dcstat_t *publish, struct pid_on_target *root) +void ebpf_dcstat_sum_pids(netdata_publish_dcstat_t *publish, struct ebpf_pid_on_target *root) { memset(&publish->curr, 0, sizeof(netdata_dcstat_pid_t)); netdata_dcstat_pid_t *dst = &publish->curr; @@ -563,9 +551,9 @@ void ebpf_dcstat_sum_pids(netdata_publish_dcstat_t *publish, struct pid_on_targe * * @param root the target list. */ -void ebpf_dcache_send_apps_data(struct target *root) +void ebpf_dcache_send_apps_data(struct ebpf_target *root) { - struct target *w; + struct ebpf_target *w; collected_number value; write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_DC_HIT_CHART); @@ -1009,6 +997,11 @@ static void dcstat_collector(ebpf_module_t *em) if (apps & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) ebpf_dcache_send_apps_data(apps_groups_root_target); +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_dcstat_pid) + ebpf_send_data_aral_chart(ebpf_aral_dcstat_pid, em); +#endif + if (cgroups) ebpf_dc_send_cgroup_data(update_every); @@ -1064,10 +1057,12 @@ static void ebpf_create_filesystem_charts(int update_every) */ static void ebpf_dcstat_allocate_global_vectors(int apps) { - if (apps) + if (apps) { + ebpf_dcstat_aral_init(); dcstat_pid = callocz((size_t)pid_max, sizeof(netdata_publish_dcstat_t *)); + dcstat_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_dcstat_pid_t)); + } - dcstat_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_dcstat_pid_t)); dcstat_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_idx_t)); memset(dcstat_counter_aggregated_data, 0, NETDATA_DCSTAT_IDX_END * sizeof(netdata_syscall_stat_t)); @@ -1099,11 +1094,11 @@ static int ebpf_dcstat_load_bpf(ebpf_module_t *em) } #ifdef LIBBPF_MAJOR_VERSION else { - bpf_obj = dc_bpf__open(); - if (!bpf_obj) + dc_bpf_obj = dc_bpf__open(); + if (!dc_bpf_obj) ret = -1; else - ret = ebpf_dc_load_and_attach(bpf_obj, em); + ret = ebpf_dc_load_and_attach(dc_bpf_obj, em); } #endif @@ -1137,7 +1132,6 @@ void *ebpf_dcstat_thread(void *ptr) ebpf_adjust_thread_load(em, default_btf); #endif if (ebpf_dcstat_load_bpf(em)) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto enddcstat; } @@ -1155,6 +1149,12 @@ void *ebpf_dcstat_thread(void *ptr) pthread_mutex_lock(&lock); ebpf_create_filesystem_charts(em->update_every); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_dcstat_pid) + ebpf_statistic_create_aral_chart(NETDATA_EBPF_DCSTAT_ARAL_NAME, em); +#endif + pthread_mutex_unlock(&lock); dcstat_collector(em); diff --git a/collectors/ebpf.plugin/ebpf_dcstat.h b/collectors/ebpf.plugin/ebpf_dcstat.h index 201fc8a0..5c9eed4d 100644 --- a/collectors/ebpf.plugin/ebpf_dcstat.h +++ b/collectors/ebpf.plugin/ebpf_dcstat.h @@ -28,6 +28,9 @@ #define NETDATA_SYSTEMD_DC_NOT_CACHE_CONTEXT "services.dc_not_cache" #define NETDATA_SYSTEMD_DC_NOT_FOUND_CONTEXT "services.dc_not_found" +// ARAL name +#define NETDATA_EBPF_DCSTAT_ARAL_NAME "ebpf_dcstat" + enum directory_cache_indexes { NETDATA_DCSTAT_IDX_RATIO, NETDATA_DCSTAT_IDX_REFERENCE, @@ -75,6 +78,7 @@ typedef struct netdata_publish_dcstat { void *ebpf_dcstat_thread(void *ptr); void ebpf_dcstat_create_apps_charts(struct ebpf_module *em, void *ptr); +void ebpf_dcstat_release(netdata_publish_dcstat_t *stat); extern struct config dcstat_config; extern netdata_ebpf_targets_t dc_targets[]; extern ebpf_local_maps_t dcstat_maps[]; diff --git a/collectors/ebpf.plugin/ebpf_disk.c b/collectors/ebpf.plugin/ebpf_disk.c index 5e7e2599..e1a57944 100644 --- a/collectors/ebpf.plugin/ebpf_disk.c +++ b/collectors/ebpf.plugin/ebpf_disk.c @@ -429,7 +429,7 @@ static void ebpf_cleanup_disk_list() static void ebpf_disk_free(ebpf_module_t *em) { pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING; + em->enabled = NETDATA_THREAD_EBPF_STOPPING; pthread_mutex_unlock(&ebpf_exit_cleanup); ebpf_disk_disable_tracepoints(); @@ -444,7 +444,7 @@ static void ebpf_disk_free(ebpf_module_t *em) ebpf_cleanup_disk_list(); pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -761,25 +761,21 @@ void *ebpf_disk_thread(void *ptr) em->maps = disk_maps; if (ebpf_disk_enable_tracepoints()) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto enddisk; } avl_init_lock(&disk_tree, ebpf_compare_disks); if (read_local_disks()) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto enddisk; } if (pthread_mutex_init(&plot_mutex, NULL)) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; error("Cannot initialize local mutex"); goto enddisk; } em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); if (!em->probe_links) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto enddisk; } @@ -792,6 +788,7 @@ void *ebpf_disk_thread(void *ptr) pthread_mutex_lock(&lock); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, disk_maps); pthread_mutex_unlock(&lock); disk_collector(em); diff --git a/collectors/ebpf.plugin/ebpf_fd.c b/collectors/ebpf.plugin/ebpf_fd.c index 79537066..96da91b0 100644 --- a/collectors/ebpf.plugin/ebpf_fd.c +++ b/collectors/ebpf.plugin/ebpf_fd.c @@ -36,17 +36,12 @@ static netdata_idx_t fd_hash_values[NETDATA_FD_COUNTER]; static netdata_idx_t *fd_values = NULL; netdata_fd_stat_t *fd_vector = NULL; -netdata_fd_stat_t **fd_pid = NULL; netdata_ebpf_targets_t fd_targets[] = { {.name = "open", .mode = EBPF_LOAD_TRAMPOLINE}, {.name = "close", .mode = EBPF_LOAD_TRAMPOLINE}, {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; #ifdef LIBBPF_MAJOR_VERSION -#include "includes/fd.skel.h" // BTF code - -static struct fd_bpf *bpf_obj = NULL; - /** * Disable probe * @@ -364,20 +359,14 @@ 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); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING; + em->enabled = NETDATA_THREAD_EBPF_STOPPING; pthread_mutex_unlock(&ebpf_exit_cleanup); - ebpf_cleanup_publish_syscall(fd_publish_aggregated); freez(fd_values); freez(fd_vector); -#ifdef LIBBPF_MAJOR_VERSION - if (bpf_obj) - fd_bpf__destroy(bpf_obj); -#endif - pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -479,7 +468,7 @@ static void fd_fill_pid(uint32_t current_pid, netdata_fd_stat_t *publish) { netdata_fd_stat_t *curr = fd_pid[current_pid]; if (!curr) { - curr = callocz(1, sizeof(netdata_fd_stat_t)); + curr = ebpf_fd_stat_get(); fd_pid[current_pid] = curr; } @@ -495,7 +484,7 @@ static void read_apps_table() { netdata_fd_stat_t *fv = fd_vector; uint32_t key; - struct pid_stat *pids = root_of_pids; + struct ebpf_pid_stat *pids = ebpf_root_of_pids; int fd = fd_maps[NETDATA_FD_PID_STATS].map_fd; size_t length = sizeof(netdata_fd_stat_t) * ebpf_nprocs; while (pids) { @@ -560,7 +549,7 @@ static void ebpf_update_fd_cgroup() * @param fd the output * @param root list of pids */ -static void ebpf_fd_sum_pids(netdata_fd_stat_t *fd, struct pid_on_target *root) +static void ebpf_fd_sum_pids(netdata_fd_stat_t *fd, struct ebpf_pid_on_target *root) { uint32_t open_call = 0; uint32_t close_call = 0; @@ -593,9 +582,9 @@ static void ebpf_fd_sum_pids(netdata_fd_stat_t *fd, struct pid_on_target *root) * @param em the structure with thread information * @param root the target list. */ -void ebpf_fd_send_apps_data(ebpf_module_t *em, struct target *root) +void ebpf_fd_send_apps_data(ebpf_module_t *em, struct ebpf_target *root) { - struct target *w; + struct ebpf_target *w; for (w = root; w; w = w->next) { if (unlikely(w->exposed && w->processes)) { ebpf_fd_sum_pids(&w->fd, w->root_pid); @@ -685,7 +674,7 @@ static void ebpf_create_specific_fd_charts(char *type, ebpf_module_t *em) NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5400, ebpf_create_global_dimension, &fd_publish_aggregated[NETDATA_FD_SYSCALL_OPEN], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_FD); if (em->mode < MODE_ENTRY) { ebpf_create_chart(type, NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR, "Fails to open files", @@ -695,7 +684,7 @@ static void ebpf_create_specific_fd_charts(char *type, ebpf_module_t *em) ebpf_create_global_dimension, &fd_publish_aggregated[NETDATA_FD_SYSCALL_OPEN], 1, em->update_every, - NETDATA_EBPF_MODULE_NAME_SWAP); + NETDATA_EBPF_MODULE_NAME_FD); } ebpf_create_chart(type, NETDATA_SYSCALL_APPS_FILE_CLOSED, "Files closed", @@ -704,7 +693,7 @@ static void ebpf_create_specific_fd_charts(char *type, ebpf_module_t *em) NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5402, ebpf_create_global_dimension, &fd_publish_aggregated[NETDATA_FD_SYSCALL_CLOSE], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_FD); if (em->mode < MODE_ENTRY) { ebpf_create_chart(type, NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR, "Fails to close files", @@ -714,7 +703,7 @@ static void ebpf_create_specific_fd_charts(char *type, ebpf_module_t *em) ebpf_create_global_dimension, &fd_publish_aggregated[NETDATA_FD_SYSCALL_CLOSE], 1, em->update_every, - NETDATA_EBPF_MODULE_NAME_SWAP); + NETDATA_EBPF_MODULE_NAME_FD); } } @@ -797,28 +786,28 @@ static void ebpf_create_systemd_fd_charts(ebpf_module_t *em) EBPF_COMMON_DIMENSION_CALL, NETDATA_APPS_FILE_CGROUP_GROUP, NETDATA_EBPF_CHART_TYPE_STACKED, 20061, ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX], NETDATA_SYSTEMD_FD_OPEN_CONTEXT, - NETDATA_EBPF_MODULE_NAME_PROCESS, em->update_every); + NETDATA_EBPF_MODULE_NAME_FD, em->update_every); if (em->mode < MODE_ENTRY) { ebpf_create_charts_on_systemd(NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR, "Fails to open files", EBPF_COMMON_DIMENSION_CALL, NETDATA_APPS_FILE_CGROUP_GROUP, NETDATA_EBPF_CHART_TYPE_STACKED, 20062, ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX], NETDATA_SYSTEMD_FD_OPEN_ERR_CONTEXT, - NETDATA_EBPF_MODULE_NAME_PROCESS, em->update_every); + NETDATA_EBPF_MODULE_NAME_FD, em->update_every); } ebpf_create_charts_on_systemd(NETDATA_SYSCALL_APPS_FILE_CLOSED, "Files closed", EBPF_COMMON_DIMENSION_CALL, NETDATA_APPS_FILE_CGROUP_GROUP, NETDATA_EBPF_CHART_TYPE_STACKED, 20063, ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX], NETDATA_SYSTEMD_FD_CLOSE_CONTEXT, - NETDATA_EBPF_MODULE_NAME_PROCESS, em->update_every); + NETDATA_EBPF_MODULE_NAME_FD, em->update_every); if (em->mode < MODE_ENTRY) { ebpf_create_charts_on_systemd(NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR, "Fails to close files", EBPF_COMMON_DIMENSION_CALL, NETDATA_APPS_FILE_CGROUP_GROUP, NETDATA_EBPF_CHART_TYPE_STACKED, 20064, ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX], NETDATA_SYSTEMD_FD_CLOSE_ERR_CONTEXT, - NETDATA_EBPF_MODULE_NAME_PROCESS, em->update_every); + NETDATA_EBPF_MODULE_NAME_FD, em->update_every); } } @@ -939,6 +928,11 @@ static void fd_collector(ebpf_module_t *em) if (apps) read_apps_table(); +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_fd_pid) + ebpf_send_data_aral_chart(ebpf_aral_fd_pid, em); +#endif + if (cgroups) ebpf_update_fd_cgroup(); @@ -972,7 +966,7 @@ static void fd_collector(ebpf_module_t *em) */ void ebpf_fd_create_apps_charts(struct ebpf_module *em, void *ptr) { - struct target *root = ptr; + struct ebpf_target *root = ptr; ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_FILE_OPEN, "Number of open files", EBPF_COMMON_DIMENSION_CALL, @@ -980,7 +974,7 @@ void ebpf_fd_create_apps_charts(struct ebpf_module *em, void *ptr) NETDATA_EBPF_CHART_TYPE_STACKED, 20061, ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX], - root, em->update_every, NETDATA_EBPF_MODULE_NAME_PROCESS); + root, em->update_every, NETDATA_EBPF_MODULE_NAME_FD); if (em->mode < MODE_ENTRY) { ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR, @@ -990,7 +984,7 @@ void ebpf_fd_create_apps_charts(struct ebpf_module *em, void *ptr) NETDATA_EBPF_CHART_TYPE_STACKED, 20062, ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX], - root, em->update_every, NETDATA_EBPF_MODULE_NAME_PROCESS); + root, em->update_every, NETDATA_EBPF_MODULE_NAME_FD); } ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_FILE_CLOSED, @@ -1000,7 +994,7 @@ void ebpf_fd_create_apps_charts(struct ebpf_module *em, void *ptr) NETDATA_EBPF_CHART_TYPE_STACKED, 20063, ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX], - root, em->update_every, NETDATA_EBPF_MODULE_NAME_PROCESS); + root, em->update_every, NETDATA_EBPF_MODULE_NAME_FD); if (em->mode < MODE_ENTRY) { ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR, @@ -1010,7 +1004,7 @@ void ebpf_fd_create_apps_charts(struct ebpf_module *em, void *ptr) NETDATA_EBPF_CHART_TYPE_STACKED, 20064, ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX], - root, em->update_every, NETDATA_EBPF_MODULE_NAME_PROCESS); + root, em->update_every, NETDATA_EBPF_MODULE_NAME_FD); } em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED; @@ -1070,10 +1064,11 @@ static void ebpf_create_fd_global_charts(ebpf_module_t *em) */ static void ebpf_fd_allocate_global_vectors(int apps) { - if (apps) + if (apps) { + ebpf_fd_aral_init(); fd_pid = callocz((size_t)pid_max, sizeof(netdata_fd_stat_t *)); - - fd_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_fd_stat_t)); + fd_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_fd_stat_t)); + } fd_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_idx_t)); } @@ -1092,17 +1087,16 @@ static int ebpf_fd_load_bpf(ebpf_module_t *em) if (em->load & EBPF_LOAD_LEGACY) { em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); if (!em->probe_links) { - em->enabled = CONFIG_BOOLEAN_NO; ret = -1; } } #ifdef LIBBPF_MAJOR_VERSION else { - bpf_obj = fd_bpf__open(); - if (!bpf_obj) + fd_bpf_obj = fd_bpf__open(); + if (!fd_bpf_obj) ret = -1; else - ret = ebpf_fd_load_and_attach(bpf_obj, em); + ret = ebpf_fd_load_and_attach(fd_bpf_obj, em); } #endif @@ -1132,7 +1126,6 @@ void *ebpf_fd_thread(void *ptr) ebpf_adjust_thread_load(em, default_btf); #endif if (ebpf_fd_load_bpf(em)) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endfd; } @@ -1148,6 +1141,12 @@ void *ebpf_fd_thread(void *ptr) pthread_mutex_lock(&lock); ebpf_create_fd_global_charts(em); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_fd_pid) + ebpf_statistic_create_aral_chart(NETDATA_EBPF_FD_ARAL_NAME, em); +#endif + pthread_mutex_unlock(&lock); fd_collector(em); diff --git a/collectors/ebpf.plugin/ebpf_fd.h b/collectors/ebpf.plugin/ebpf_fd.h index e6545d79..85dfd36e 100644 --- a/collectors/ebpf.plugin/ebpf_fd.h +++ b/collectors/ebpf.plugin/ebpf_fd.h @@ -33,6 +33,9 @@ #define NETDATA_SYSTEMD_FD_CLOSE_CONTEXT "services.fd_close" #define NETDATA_SYSTEMD_FD_CLOSE_ERR_CONTEXT "services.fd_close_error" +// ARAL name +#define NETDATA_EBPF_FD_ARAL_NAME "ebpf_fd" + typedef struct netdata_fd_stat { uint32_t open_call; // Open syscalls (open and openat) uint32_t close_call; // Close syscall (close) @@ -80,8 +83,8 @@ enum fd_close_syscall { void *ebpf_fd_thread(void *ptr); void ebpf_fd_create_apps_charts(struct ebpf_module *em, void *ptr); +void ebpf_fd_release(netdata_fd_stat_t *stat); extern struct config fd_config; -extern netdata_fd_stat_t **fd_pid; extern netdata_ebpf_targets_t fd_targets[]; #endif /* NETDATA_EBPF_FD_H */ diff --git a/collectors/ebpf.plugin/ebpf_filesystem.c b/collectors/ebpf.plugin/ebpf_filesystem.c index 5250ed8a..f8b28195 100644 --- a/collectors/ebpf.plugin/ebpf_filesystem.c +++ b/collectors/ebpf.plugin/ebpf_filesystem.c @@ -92,7 +92,7 @@ static void ebpf_obsolete_fs_charts(int update_every) static void ebpf_create_fs_charts(int update_every) { static int order = NETDATA_CHART_PRIO_EBPF_FILESYSTEM_CHARTS; - char chart_name[64], title[256], family[64]; + char chart_name[64], title[256], family[64], ctx[64]; int i; uint32_t test = NETDATA_FILESYSTEM_FLAG_CHART_CREATED|NETDATA_FILESYSTEM_REMOVE_CHARTS; for (i = 0; localfs[i].filesystem; i++) { @@ -110,7 +110,7 @@ static void ebpf_create_fs_charts(int update_every) ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, efp->hread.name, title, EBPF_COMMON_DIMENSION_CALL, family, - NULL, NETDATA_EBPF_CHART_TYPE_STACKED, order, ebpf_create_global_dimension, + "filesystem.read_latency", NETDATA_EBPF_CHART_TYPE_STACKED, order, ebpf_create_global_dimension, filesystem_publish_aggregated, NETDATA_EBPF_HIST_MAX_BINS, update_every, NETDATA_EBPF_MODULE_NAME_FILESYSTEM); order++; @@ -123,7 +123,7 @@ static void ebpf_create_fs_charts(int update_every) ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, efp->hwrite.name, title, EBPF_COMMON_DIMENSION_CALL, family, - NULL, NETDATA_EBPF_CHART_TYPE_STACKED, order, ebpf_create_global_dimension, + "filesystem.write_latency", NETDATA_EBPF_CHART_TYPE_STACKED, order, ebpf_create_global_dimension, filesystem_publish_aggregated, NETDATA_EBPF_HIST_MAX_BINS, update_every, NETDATA_EBPF_MODULE_NAME_FILESYSTEM); order++; @@ -136,7 +136,7 @@ static void ebpf_create_fs_charts(int update_every) ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, efp->hopen.name, title, EBPF_COMMON_DIMENSION_CALL, family, - NULL, NETDATA_EBPF_CHART_TYPE_STACKED, order, ebpf_create_global_dimension, + "filesystem.open_latency", NETDATA_EBPF_CHART_TYPE_STACKED, order, ebpf_create_global_dimension, filesystem_publish_aggregated, NETDATA_EBPF_HIST_MAX_BINS, update_every, NETDATA_EBPF_MODULE_NAME_FILESYSTEM); order++; @@ -144,12 +144,13 @@ static void ebpf_create_fs_charts(int update_every) char *type = (efp->flags & NETDATA_FILESYSTEM_ATTR_CHARTS) ? "attribute" : "sync"; snprintfz(title, 255, "%s latency for each %s request.", efp->filesystem, type); snprintfz(chart_name, 63, "%s_%s_latency", efp->filesystem, type); + snprintfz(ctx, 63, "filesystem.%s_latency", type); efp->hadditional.name = strdupz(chart_name); efp->hadditional.title = strdupz(title); efp->hadditional.order = order; ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, efp->hadditional.name, title, EBPF_COMMON_DIMENSION_CALL, family, - NULL, NETDATA_EBPF_CHART_TYPE_STACKED, order, ebpf_create_global_dimension, + ctx, NETDATA_EBPF_CHART_TYPE_STACKED, order, ebpf_create_global_dimension, filesystem_publish_aggregated, NETDATA_EBPF_HIST_MAX_BINS, update_every, NETDATA_EBPF_MODULE_NAME_FILESYSTEM); order++; @@ -182,6 +183,9 @@ int ebpf_filesystem_initialize_ebpf_data(ebpf_module_t *em) return -1; } efp->flags |= NETDATA_FILESYSTEM_FLAG_HAS_PARTITION; + pthread_mutex_lock(&lock); + ebpf_update_kernel_memory(&plugin_statistics, &fs_maps[i], EBPF_ACTION_STAT_ADD); + pthread_mutex_unlock(&lock); // Nedeed for filesystems like btrfs if ((efp->flags & NETDATA_FILESYSTEM_FILL_ADDRESS_TABLE) && (efp->addresses.function)) { @@ -326,18 +330,16 @@ void ebpf_filesystem_cleanup_ebpf_data() static void ebpf_filesystem_free(ebpf_module_t *em) { pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING; + em->enabled = NETDATA_THREAD_EBPF_STOPPING; pthread_mutex_unlock(&ebpf_exit_cleanup); - ebpf_cleanup_publish_syscall(filesystem_publish_aggregated); - ebpf_filesystem_cleanup_ebpf_data(); if (dimensions) ebpf_histogram_dimension_cleanup(dimensions, NETDATA_EBPF_HIST_MAX_BINS); freez(filesystem_hash_values); pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -567,7 +569,6 @@ void *ebpf_filesystem_thread(void *ptr) if (em->optional) info("Netdata cannot monitor the filesystems used on this host."); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endfilesystem; } diff --git a/collectors/ebpf.plugin/ebpf_hardirq.c b/collectors/ebpf.plugin/ebpf_hardirq.c index 20c4b9d0..b4d49dc0 100644 --- a/collectors/ebpf.plugin/ebpf_hardirq.c +++ b/collectors/ebpf.plugin/ebpf_hardirq.c @@ -129,11 +129,54 @@ static hardirq_static_val_t hardirq_static_vals[] = { // thread will write to netdata agent. static avl_tree_lock hardirq_pub; -// tmp store for dynamic hard IRQ values we get from a per-CPU eBPF map. -static hardirq_ebpf_val_t *hardirq_ebpf_vals = NULL; +/***************************************************************** + * + * ARAL SECTION + * + *****************************************************************/ + +// ARAL vectors used to speed up processing +ARAL *ebpf_aral_hardirq = NULL; + +/** + * eBPF hardirq Aral init + * + * Initiallize array allocator that will be used when integration with apps is enabled. + */ +static inline void ebpf_hardirq_aral_init() +{ + ebpf_aral_hardirq = ebpf_allocate_pid_aral(NETDATA_EBPF_HARDIRQ_ARAL_NAME, sizeof(hardirq_val_t)); +} -// 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; +/** + * eBPF hardirq get + * + * Get a hardirq_val_t entry to be used with a specific IRQ. + * + * @return it returns the address on success. + */ +hardirq_val_t *ebpf_hardirq_get(void) +{ + hardirq_val_t *target = aral_mallocz(ebpf_aral_hardirq); + memset(target, 0, sizeof(hardirq_val_t)); + return target; +} + +/** + * eBPF hardirq release + * + * @param stat Release a target after usage. + */ +void ebpf_hardirq_release(hardirq_val_t *stat) +{ + aral_freez(ebpf_aral_hardirq, stat); +} + +/***************************************************************** + * + * EXIT FUNCTIONS + * + *****************************************************************/ /** * Hardirq Free @@ -144,18 +187,11 @@ static hardirq_ebpf_static_val_t *hardirq_ebpf_static_vals = NULL; */ static void ebpf_hardirq_free(ebpf_module_t *em) { - pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING; - pthread_mutex_unlock(&ebpf_exit_cleanup); - 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; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -200,8 +236,84 @@ static int hardirq_val_cmp(void *a, void *b) } } -static void hardirq_read_latency_map(int mapfd) +/** + * Parse interrupts + * + * Parse /proc/interrupts to get names used in metrics + * + * @param irq_name vector to store data. + * @param irq irq value + * + * @return It returns 0 on success and -1 otherwise + */ +static int hardirq_parse_interrupts(char *irq_name, int irq) { + static procfile *ff = NULL; + static int cpus = -1; + if(unlikely(!ff)) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/interrupts"); + ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT); + } + if(unlikely(!ff)) + return -1; + + ff = procfile_readall(ff); + if(unlikely(!ff)) + return -1; // we return 0, so that we will retry to open it next time + + size_t words = procfile_linewords(ff, 0); + if(unlikely(cpus == -1)) { + uint32_t w; + cpus = 0; + for(w = 0; w < words ; w++) { + if(likely(strncmp(procfile_lineword(ff, 0, w), "CPU", 3) == 0)) + cpus++; + } + } + + size_t lines = procfile_lines(ff), l; + if(unlikely(!lines)) { + collector_error("Cannot read /proc/interrupts, zero lines reported."); + return -1; + } + + for(l = 1; l < lines ;l++) { + words = procfile_linewords(ff, l); + if(unlikely(!words)) continue; + const char *id = procfile_lineword(ff, l, 0); + if (!isdigit(id[0])) + continue; + + int cmp = str2i(id); + if (cmp != irq) + continue; + + if(unlikely((uint32_t)(cpus + 2) < words)) { + const char *name = procfile_lineword(ff, l, words - 1); + // On some motherboards IRQ can have the same name, so we append IRQ id to differentiate. + snprintfz(irq_name, NETDATA_HARDIRQ_NAME_LEN - 1, "%d_%s", irq, name); + } + } + + return 0; +} + +/** + * Read Latency MAP + * + * Read data from kernel ring to user ring. + * + * @param mapfd hash map id. + * + * @return it returns 0 on success and -1 otherwise + */ +static int hardirq_read_latency_map(int mapfd) +{ + static hardirq_ebpf_static_val_t *hardirq_ebpf_vals = NULL; + if (!hardirq_ebpf_vals) + hardirq_ebpf_vals = callocz(ebpf_nprocs + 1, sizeof(hardirq_ebpf_static_val_t)); + hardirq_ebpf_key_t key = {}; hardirq_ebpf_key_t next_key = {}; hardirq_val_t search_v = {}; @@ -234,7 +346,7 @@ static void hardirq_read_latency_map(int mapfd) if (unlikely(v == NULL)) { // latency/name can only be added reliably at a later time. // when they're added, only then will we AVL insert. - v = callocz(1, sizeof(hardirq_val_t)); + v = ebpf_hardirq_get(); v->irq = key.irq; v->dim_exists = false; @@ -246,22 +358,10 @@ static void hardirq_read_latency_map(int mapfd) // 2. the name is unfortunately *not* available on all CPU maps - only // a single map contains the name, so we must find it. we only need // to copy it though if the IRQ is new for us. - bool name_saved = false; uint64_t total_latency = 0; int i; - int end = (running_on_kernel < NETDATA_KERNEL_V4_15) ? 1 : ebpf_nprocs; - for (i = 0; i < end; i++) { + for (i = 0; i < ebpf_nprocs; i++) { total_latency += hardirq_ebpf_vals[i].latency/1000; - - // copy name for new IRQs. - if (v_is_new && !name_saved && hardirq_ebpf_vals[i].name[0] != '\0') { - strncpyz( - v->name, - hardirq_ebpf_vals[i].name, - NETDATA_HARDIRQ_NAME_LEN - ); - name_saved = true; - } } // can now safely publish latency for existing IRQs. @@ -269,6 +369,11 @@ static void hardirq_read_latency_map(int mapfd) // can now safely publish new IRQ. if (v_is_new) { + if (hardirq_parse_interrupts(v->name, v->irq)) { + ebpf_hardirq_release(v); + return -1; + } + avl_t *check = avl_insert_lock(&hardirq_pub, (avl_t *)v); if (check != (avl_t *)v) { error("Internal error, cannot insert the AVL tree."); @@ -277,10 +382,16 @@ static void hardirq_read_latency_map(int mapfd) key = next_key; } + + return 0; } static void hardirq_read_latency_static_map(int mapfd) { + static hardirq_ebpf_static_val_t *hardirq_ebpf_static_vals = NULL; + if (!hardirq_ebpf_static_vals) + hardirq_ebpf_static_vals = callocz(ebpf_nprocs + 1, sizeof(hardirq_ebpf_static_val_t)); + uint32_t i; for (i = 0; i < HARDIRQ_EBPF_STATIC_END; i++) { uint32_t map_i = hardirq_static_vals[i].idx; @@ -302,11 +413,17 @@ static void hardirq_read_latency_static_map(int mapfd) /** * Read eBPF maps for hard IRQ. + * + * @return When it is not possible to parse /proc, it returns -1, on success it returns 0; */ -static void hardirq_reader() +static int hardirq_reader() { - hardirq_read_latency_map(hardirq_maps[HARDIRQ_MAP_LATENCY].map_fd); + if (hardirq_read_latency_map(hardirq_maps[HARDIRQ_MAP_LATENCY].map_fd)) + return -1; + hardirq_read_latency_static_map(hardirq_maps[HARDIRQ_MAP_LATENCY_STATIC].map_fd); + + return 0; } static void hardirq_create_charts(int update_every) @@ -372,25 +489,21 @@ static inline void hardirq_write_static_dims() /** * Main loop for this collector. + * + * @param em the main thread structure. */ static void hardirq_collector(ebpf_module_t *em) { - hardirq_ebpf_vals = callocz( - (running_on_kernel < NETDATA_KERNEL_V4_15) ? 1 : ebpf_nprocs, - sizeof(hardirq_ebpf_val_t) - ); - hardirq_ebpf_static_vals = callocz( - (running_on_kernel < NETDATA_KERNEL_V4_15) ? 1 : ebpf_nprocs, - sizeof(hardirq_ebpf_static_val_t) - ); - + memset(&hardirq_pub, 0, sizeof(hardirq_pub)); avl_init_lock(&hardirq_pub, hardirq_val_cmp); + ebpf_hardirq_aral_init(); // create chart and static dims. pthread_mutex_lock(&lock); hardirq_create_charts(em->update_every); hardirq_create_static_dims(); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); pthread_mutex_unlock(&lock); // loop and read from published data until ebpf plugin is closed. @@ -406,7 +519,9 @@ static void hardirq_collector(ebpf_module_t *em) continue; counter = 0; - hardirq_reader(); + if (hardirq_reader()) + break; + pthread_mutex_lock(&lock); // write dims now for all hitherto discovered IRQs. @@ -437,13 +552,11 @@ void *ebpf_hardirq_thread(void *ptr) em->maps = hardirq_maps; if (ebpf_enable_tracepoints(hardirq_tracepoints) == 0) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endhardirq; } em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); if (!em->probe_links) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endhardirq; } diff --git a/collectors/ebpf.plugin/ebpf_hardirq.h b/collectors/ebpf.plugin/ebpf_hardirq.h index fe38b1bb..52dea1e5 100644 --- a/collectors/ebpf.plugin/ebpf_hardirq.h +++ b/collectors/ebpf.plugin/ebpf_hardirq.h @@ -3,6 +3,9 @@ #ifndef NETDATA_EBPF_HARDIRQ_H #define NETDATA_EBPF_HARDIRQ_H 1 +#include +#include "libnetdata/avl/avl.h" + /***************************************************************** * copied from kernel-collectors repo, with modifications needed * for inclusion here. @@ -15,12 +18,6 @@ typedef struct hardirq_ebpf_key { int irq; } hardirq_ebpf_key_t; -typedef struct hardirq_ebpf_val { - uint64_t latency; - uint64_t ts; - char name[NETDATA_HARDIRQ_NAME_LEN]; -} hardirq_ebpf_val_t; - enum hardirq_ebpf_static { HARDIRQ_EBPF_STATIC_APIC_THERMAL, HARDIRQ_EBPF_STATIC_APIC_THRESHOLD, @@ -46,6 +43,9 @@ typedef struct hardirq_ebpf_static_val { * below this is eBPF plugin-specific code. *****************************************************************/ +// ARAL Name +#define NETDATA_EBPF_HARDIRQ_ARAL_NAME "ebpf_harddirq" + #define NETDATA_EBPF_MODULE_NAME_HARDIRQ "hardirq" #define NETDATA_HARDIRQ_CONFIG_FILE "hardirq.conf" diff --git a/collectors/ebpf.plugin/ebpf_mdflush.c b/collectors/ebpf.plugin/ebpf_mdflush.c index 1a5a7731..fc794e5e 100644 --- a/collectors/ebpf.plugin/ebpf_mdflush.c +++ b/collectors/ebpf.plugin/ebpf_mdflush.c @@ -46,7 +46,7 @@ static void ebpf_mdflush_free(ebpf_module_t *em) { freez(mdflush_ebpf_vals); pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -208,6 +208,7 @@ static void mdflush_collector(ebpf_module_t *em) pthread_mutex_lock(&lock); mdflush_create_charts(update_every); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); pthread_mutex_unlock(&lock); // loop and read from published data until ebpf plugin is closed. @@ -246,24 +247,19 @@ void *ebpf_mdflush_thread(void *ptr) char *md_flush_request = ebpf_find_symbol("md_flush_request"); if (!md_flush_request) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; error("Cannot monitor MD devices, because md is not loaded."); - } - freez(md_flush_request); - - if (em->thread->enabled == NETDATA_THREAD_EBPF_STOPPED) { goto endmdflush; } em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); if (!em->probe_links) { - em->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endmdflush; } mdflush_collector(em); endmdflush: + freez(md_flush_request); ebpf_update_disabled_plugin_stats(em); netdata_thread_cleanup_pop(1); diff --git a/collectors/ebpf.plugin/ebpf_mount.c b/collectors/ebpf.plugin/ebpf_mount.c index e06010b5..a2a4c553 100644 --- a/collectors/ebpf.plugin/ebpf_mount.c +++ b/collectors/ebpf.plugin/ebpf_mount.c @@ -18,8 +18,6 @@ struct config mount_config = { .first_section = NULL, .last_section = NULL, .mut .index = {.avl_tree = { .root = NULL, .compar = appconfig_section_compare }, .rwlock = AVL_LOCK_INITIALIZER } }; -static netdata_idx_t *mount_values = NULL; - static netdata_idx_t mount_hash_values[NETDATA_MOUNT_END]; netdata_ebpf_targets_t mount_targets[] = { {.name = "mount", .mode = EBPF_LOAD_TRAMPOLINE}, @@ -27,10 +25,6 @@ netdata_ebpf_targets_t mount_targets[] = { {.name = "mount", .mode = EBPF_LOAD_T {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; #ifdef LIBBPF_MAJOR_VERSION -#include "includes/mount.skel.h" // BTF code - -static struct mount_bpf *bpf_obj = NULL; - /***************************************************************** * * BTF FUNCTIONS @@ -228,18 +222,7 @@ 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); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING; - pthread_mutex_unlock(&ebpf_exit_cleanup); - - freez(mount_values); - -#ifdef LIBBPF_MAJOR_VERSION - if (bpf_obj) - mount_bpf__destroy(bpf_obj); -#endif - - pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -269,6 +252,10 @@ static void ebpf_mount_exit(void *ptr) */ static void ebpf_mount_read_global_table() { + static netdata_idx_t *mount_values = NULL; + if (!mount_values) + mount_values = callocz((size_t)ebpf_nprocs + 1, sizeof(netdata_idx_t)); + uint32_t idx; netdata_idx_t *val = mount_hash_values; netdata_idx_t *stored = mount_values; @@ -311,7 +298,6 @@ static void ebpf_mount_send_data() */ static void mount_collector(ebpf_module_t *em) { - mount_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_idx_t)); memset(mount_hash_values, 0, sizeof(mount_hash_values)); heartbeat_t hb; @@ -390,17 +376,16 @@ static int ebpf_mount_load_bpf(ebpf_module_t *em) if (em->load & EBPF_LOAD_LEGACY) { em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); if (!em->probe_links) { - em->enabled = CONFIG_BOOLEAN_NO; ret = -1; } } #ifdef LIBBPF_MAJOR_VERSION else { - bpf_obj = mount_bpf__open(); - if (!bpf_obj) + mount_bpf_obj = mount_bpf__open(); + if (!mount_bpf_obj) ret = -1; else - ret = ebpf_mount_load_and_attach(bpf_obj, em); + ret = ebpf_mount_load_and_attach(mount_bpf_obj, em); } #endif @@ -430,7 +415,6 @@ void *ebpf_mount_thread(void *ptr) ebpf_adjust_thread_load(em, default_btf); #endif if (ebpf_mount_load_bpf(em)) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endmount; } @@ -442,6 +426,7 @@ void *ebpf_mount_thread(void *ptr) pthread_mutex_lock(&lock); ebpf_create_mount_charts(em->update_every); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); pthread_mutex_unlock(&lock); mount_collector(em); diff --git a/collectors/ebpf.plugin/ebpf_oomkill.c b/collectors/ebpf.plugin/ebpf_oomkill.c index 82420d54..856c922e 100644 --- a/collectors/ebpf.plugin/ebpf_oomkill.c +++ b/collectors/ebpf.plugin/ebpf_oomkill.c @@ -47,18 +47,18 @@ 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; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } static void oomkill_write_data(int32_t *keys, uint32_t total) { // for each app, see if it was OOM killed. record as 1 if so otherwise 0. - struct target *w; + struct ebpf_target *w; for (w = apps_groups_root_target; w != NULL; w = w->next) { if (likely(w->exposed && w->processes)) { bool was_oomkilled = false; - struct pid_on_target *pids = w->root_pid; + struct ebpf_pid_on_target *pids = w->root_pid; while (pids) { uint32_t j; for (j = 0; j < total; j++) { @@ -299,27 +299,28 @@ static void oomkill_collector(ebpf_module_t *em) int counter = update_every - 1; while (!ebpf_exit_plugin) { (void)heartbeat_next(&hb, USEC_PER_SEC); - if (!ebpf_exit_plugin || ++counter != update_every) + if (ebpf_exit_plugin || ++counter != update_every) continue; counter = 0; - pthread_mutex_lock(&collect_data_mutex); - pthread_mutex_lock(&lock); uint32_t count = oomkill_read_data(keys); - if (cgroups && count) - ebpf_update_oomkill_cgroup(keys, count); + if (!count) + continue; - // write everything from the ebpf map. - if (cgroups) + pthread_mutex_lock(&collect_data_mutex); + pthread_mutex_lock(&lock); + if (cgroups) { + ebpf_update_oomkill_cgroup(keys, count); + // write everything from the ebpf map. ebpf_oomkill_send_cgroup_data(update_every); + } if (em->apps_charts & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) { write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_OOMKILL_CHART); oomkill_write_data(keys, count); write_end_chart(); } - pthread_mutex_unlock(&lock); pthread_mutex_unlock(&collect_data_mutex); } @@ -334,7 +335,7 @@ static void oomkill_collector(ebpf_module_t *em) */ void ebpf_oomkill_create_apps_charts(struct ebpf_module *em, void *ptr) { - struct target *root = ptr; + struct ebpf_target *root = ptr; ebpf_create_charts_on_apps(NETDATA_OOMKILL_CHART, "OOM kills", EBPF_COMMON_DIMENSION_KILLS, @@ -361,37 +362,36 @@ void *ebpf_oomkill_thread(void *ptr) em->maps = oomkill_maps; #define NETDATA_DEFAULT_OOM_DISABLED_MSG "Disabling OOMKILL thread, because" - if (unlikely(!all_pids || !em->apps_charts)) { + if (unlikely(!ebpf_all_pids || !em->apps_charts)) { // When we are not running integration with apps, we won't fill necessary variables for this thread to run, so // we need to disable it. - if (em->thread->enabled) + pthread_mutex_lock(&ebpf_exit_cleanup); + if (em->enabled) info("%s apps integration is completely disabled.", NETDATA_DEFAULT_OOM_DISABLED_MSG); + pthread_mutex_unlock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + goto endoomkill; } else if (running_on_kernel < NETDATA_EBPF_KERNEL_4_14) { - if (em->thread->enabled) + pthread_mutex_lock(&ebpf_exit_cleanup); + if (em->enabled) info("%s kernel does not have necessary tracepoints.", NETDATA_DEFAULT_OOM_DISABLED_MSG); + pthread_mutex_unlock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; - } - - if (em->thread->enabled == NETDATA_THREAD_EBPF_STOPPED) { goto endoomkill; } if (ebpf_enable_tracepoints(oomkill_tracepoints) == 0) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endoomkill; } em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); if (!em->probe_links) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endoomkill; } pthread_mutex_lock(&lock); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); pthread_mutex_unlock(&lock); oomkill_collector(em); diff --git a/collectors/ebpf.plugin/ebpf_process.c b/collectors/ebpf.plugin/ebpf_process.c index 9a191d39..66af4785 100644 --- a/collectors/ebpf.plugin/ebpf_process.c +++ b/collectors/ebpf.plugin/ebpf_process.c @@ -42,9 +42,6 @@ static netdata_idx_t *process_hash_values = NULL; static netdata_syscall_stat_t process_aggregated_data[NETDATA_KEY_PUBLISH_PROCESS_END]; static netdata_publish_syscall_t process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_END]; -ebpf_process_stat_t **global_process_stats = NULL; -ebpf_process_publish_apps_t **current_apps_data = NULL; - int process_enabled = 0; bool publish_internal_metrics = true; @@ -56,6 +53,8 @@ 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 char *memlock_stat = {"memory_locked"}; +static char *hash_table_stat = {"hash_table"}; /***************************************************************** * @@ -138,19 +137,19 @@ static void ebpf_process_send_data(ebpf_module_t *em) * Sum values for pid * * @param root the structure with all available PIDs - * * @param offset the address that we are reading * * @return it returns the sum of all PIDs */ -long long ebpf_process_sum_values_for_pids(struct pid_on_target *root, size_t offset) +long long ebpf_process_sum_values_for_pids(struct ebpf_pid_on_target *root, size_t offset) { long long ret = 0; while (root) { int32_t pid = root->pid; - ebpf_process_publish_apps_t *w = current_apps_data[pid]; + ebpf_process_stat_t *w = global_process_stats[pid]; if (w) { - ret += get_value_from_structure((char *)w, offset); + uint32_t *value = (uint32_t *)((char *)w + offset); + ret += *value; } root = root->next; @@ -166,13 +165,13 @@ long long ebpf_process_sum_values_for_pids(struct pid_on_target *root, size_t of */ void ebpf_process_remove_pids() { - struct pid_stat *pids = root_of_pids; + struct ebpf_pid_stat *pids = ebpf_root_of_pids; int pid_fd = process_maps[NETDATA_PROCESS_PID_TABLE].map_fd; while (pids) { uint32_t pid = pids->pid; ebpf_process_stat_t *w = global_process_stats[pid]; if (w) { - freez(w); + ebpf_process_stat_release(w); global_process_stats[pid] = NULL; bpf_map_delete_elem(pid_fd, &pid); } @@ -186,15 +185,15 @@ void ebpf_process_remove_pids() * * @param root the target list. */ -void ebpf_process_send_apps_data(struct target *root, ebpf_module_t *em) +void ebpf_process_send_apps_data(struct ebpf_target *root, ebpf_module_t *em) { - struct target *w; + struct ebpf_target *w; collected_number value; write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_TASK_PROCESS); for (w = root; w; w = w->next) { if (unlikely(w->exposed && w->processes)) { - value = ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_publish_apps_t, create_process)); + value = ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_stat_t, create_process)); write_chart_dimension(w->name, value); } } @@ -203,7 +202,7 @@ void ebpf_process_send_apps_data(struct target *root, ebpf_module_t *em) write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_TASK_THREAD); for (w = root; w; w = w->next) { if (unlikely(w->exposed && w->processes)) { - value = ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_publish_apps_t, create_thread)); + value = ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_stat_t, create_thread)); write_chart_dimension(w->name, value); } } @@ -212,8 +211,8 @@ void ebpf_process_send_apps_data(struct target *root, ebpf_module_t *em) write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_TASK_EXIT); for (w = root; w; w = w->next) { if (unlikely(w->exposed && w->processes)) { - value = ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_publish_apps_t, - call_do_exit)); + value = ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_stat_t, + exit_call)); write_chart_dimension(w->name, value); } } @@ -222,8 +221,8 @@ void ebpf_process_send_apps_data(struct target *root, ebpf_module_t *em) write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_TASK_CLOSE); for (w = root; w; w = w->next) { if (unlikely(w->exposed && w->processes)) { - value = ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_publish_apps_t, - call_release_task)); + value = ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_stat_t, + release_call)); write_chart_dimension(w->name, value); } } @@ -233,7 +232,7 @@ void ebpf_process_send_apps_data(struct target *root, ebpf_module_t *em) write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_TASK_ERROR); for (w = root; w; w = w->next) { if (unlikely(w->exposed && w->processes)) { - value = ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_publish_apps_t, + value = ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_stat_t, task_err)); write_chart_dimension(w->name, value); } @@ -283,38 +282,6 @@ static void read_hash_global_tables() process_aggregated_data[NETDATA_KEY_PUBLISH_PROCESS_CLONE].ecall = res[NETDATA_KEY_ERROR_SYS_CLONE]; } -/** - * Read the hash table and store data to allocated vectors. - */ -static void ebpf_process_update_apps_data() -{ - struct pid_stat *pids = root_of_pids; - while (pids) { - uint32_t current_pid = pids->pid; - ebpf_process_stat_t *ps = global_process_stats[current_pid]; - if (!ps) { - pids = pids->next; - continue; - } - - ebpf_process_publish_apps_t *cad = current_apps_data[current_pid]; - if (!cad) { - cad = callocz(1, sizeof(ebpf_process_publish_apps_t)); - current_apps_data[current_pid] = cad; - } - - //Read data - cad->call_do_exit = ps->exit_call; - cad->call_release_task = ps->release_call; - cad->create_process = ps->create_process; - cad->create_thread = ps->create_thread; - - cad->task_err = ps->task_err; - - pids = pids->next; - } -} - /** * Update cgroup * @@ -489,6 +456,56 @@ static inline void ebpf_create_statistic_load_chart(ebpf_module_t *em) ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); } +/** + * Create chart for Kernel Memory + * + * Write to standard output current values for allocated memory. + * + * @param em a pointer to the structure with the default values. + */ +static inline void ebpf_create_statistic_kernel_memory(ebpf_module_t *em) +{ + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + NETDATA_EBPF_KERNEL_MEMORY, + "Memory allocated for hash tables.", + "bytes", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_LINE, + NULL, + 140002, + em->update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + + ebpf_write_global_dimension(memlock_stat, + memlock_stat, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); +} + +/** + * Create chart Hash Table + * + * Write to standard output number of hash tables used with this software. + * + * @param em a pointer to the structure with the default values. + */ +static inline void ebpf_create_statistic_hash_tables(ebpf_module_t *em) +{ + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + NETDATA_EBPF_HASH_TABLES_LOADED, + "Number of hash tables loaded.", + "hash tables", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_LINE, + NULL, + 140003, + em->update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + + ebpf_write_global_dimension(hash_table_stat, + hash_table_stat, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); +} + /** * Update Internal Metric variable * @@ -520,6 +537,10 @@ static void ebpf_create_statistic_charts(ebpf_module_t *em) ebpf_create_statistic_thread_chart(em); ebpf_create_statistic_load_chart(em); + + ebpf_create_statistic_kernel_memory(em); + + ebpf_create_statistic_hash_tables(em); } /** @@ -532,7 +553,7 @@ static void ebpf_create_statistic_charts(ebpf_module_t *em) */ void ebpf_process_create_apps_charts(struct ebpf_module *em, void *ptr) { - struct target *root = ptr; + struct ebpf_target *root = ptr; ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_TASK_PROCESS, "Process started", EBPF_COMMON_DIMENSION_CALL, @@ -584,58 +605,6 @@ void ebpf_process_create_apps_charts(struct ebpf_module *em, void *ptr) em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED; } -/** - * Create apps charts - * - * Call ebpf_create_chart to create the charts on apps submenu. - * - * @param root a pointer for the targets. - */ -static void ebpf_create_apps_charts(struct target *root) -{ - if (unlikely(!all_pids)) - return; - - struct target *w; - int newly_added = 0; - - for (w = root; w; w = w->next) { - if (w->target) - continue; - - if (unlikely(w->processes && (debug_enabled || w->debug_enabled))) { - struct pid_on_target *pid_on_target; - - fprintf( - stderr, "ebpf.plugin: target '%s' has aggregated %u process%s:", w->name, w->processes, - (w->processes == 1) ? "" : "es"); - - for (pid_on_target = w->root_pid; pid_on_target; pid_on_target = pid_on_target->next) { - fprintf(stderr, " %d", pid_on_target->pid); - } - - fputc('\n', stderr); - } - - if (!w->exposed && w->processes) { - newly_added++; - w->exposed = 1; - if (debug_enabled || w->debug_enabled) - debug_log_int("%s just added - regenerating charts.", w->name); - } - } - - if (!newly_added) - return; - - int counter; - for (counter = 0; ebpf_modules[counter].thread_name; counter++) { - ebpf_module_t *current = &ebpf_modules[counter]; - if (current->enabled && current->apps_charts && current->apps_routine) - current->apps_routine(current, root); - } -} - /***************************************************************** * * FUNCTIONS TO CLOSE THE THREAD @@ -677,13 +646,13 @@ static void ebpf_process_exit(void *ptr) { ebpf_module_t *em = (ebpf_module_t *)ptr; - ebpf_cleanup_publish_syscall(process_publish_aggregated); freez(process_hash_values); ebpf_process_disable_tracepoints(); pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + process_pid_fd = -1; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -1010,8 +979,7 @@ void ebpf_process_update_cgroup_algorithm() int i; for (i = 0; i < NETDATA_KEY_PUBLISH_PROCESS_END; i++) { netdata_publish_syscall_t *ptr = &process_publish_aggregated[i]; - freez(ptr->algorithm); - ptr->algorithm = strdupz(ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + ptr->algorithm = ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]; } } @@ -1034,6 +1002,14 @@ void ebpf_send_statistic_data() write_chart_dimension(load_event_stat[NETDATA_EBPF_LOAD_STAT_LEGACY], (long long)plugin_statistics.legacy); write_chart_dimension(load_event_stat[NETDATA_EBPF_LOAD_STAT_CORE], (long long)plugin_statistics.core); write_end_chart(); + + write_begin_chart(NETDATA_MONITORING_FAMILY, NETDATA_EBPF_KERNEL_MEMORY); + write_chart_dimension(memlock_stat, (long long)plugin_statistics.memlock_kern); + write_end_chart(); + + write_begin_chart(NETDATA_MONITORING_FAMILY, NETDATA_EBPF_HASH_TABLES_LOADED); + write_chart_dimension(hash_table_stat, (long long)plugin_statistics.hash_tables); + write_end_chart(); } /** @@ -1047,29 +1023,21 @@ static void process_collector(ebpf_module_t *em) heartbeat_init(&hb); int publish_global = em->global_charts; int cgroups = em->cgroup_charts; + pthread_mutex_lock(&ebpf_exit_cleanup); int thread_enabled = em->enabled; + process_pid_fd = process_maps[NETDATA_PROCESS_PID_TABLE].map_fd; + pthread_mutex_unlock(&ebpf_exit_cleanup); if (cgroups) ebpf_process_update_cgroup_algorithm(); - int update_apps_every = (int) EBPF_CFG_UPDATE_APPS_EVERY_DEFAULT; - int pid_fd = process_maps[NETDATA_PROCESS_PID_TABLE].map_fd; int update_every = em->update_every; int counter = update_every - 1; - int update_apps_list = update_apps_every - 1; while (!ebpf_exit_plugin) { usec_t dt = heartbeat_next(&hb, USEC_PER_SEC); (void)dt; if (ebpf_exit_plugin) break; - pthread_mutex_lock(&collect_data_mutex); - if (++update_apps_list == update_apps_every) { - update_apps_list = 0; - cleanup_exited_pids(); - collect_data_for_all_processes(pid_fd); - } - pthread_mutex_unlock(&collect_data_mutex); - if (++counter == update_every) { counter = 0; @@ -1078,12 +1046,7 @@ static void process_collector(ebpf_module_t *em) netdata_apps_integration_flags_t apps_enabled = em->apps_charts; pthread_mutex_lock(&collect_data_mutex); - ebpf_create_apps_charts(apps_groups_root_target); - if (all_pids_count > 0) { - if (apps_enabled) { - ebpf_process_update_apps_data(); - } - + if (ebpf_all_pids_count > 0) { if (cgroups && shm_ebpf_cgroup.header) { ebpf_update_process_cgroup(); } @@ -1092,7 +1055,7 @@ static void process_collector(ebpf_module_t *em) pthread_mutex_lock(&lock); ebpf_send_statistic_data(); - if (thread_enabled) { + if (thread_enabled == NETDATA_THREAD_EBPF_RUNNING) { if (publish_global) { ebpf_process_send_data(em); } @@ -1101,6 +1064,11 @@ static void process_collector(ebpf_module_t *em) ebpf_process_send_apps_data(apps_groups_root_target, em); } +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_process_stat) + ebpf_send_data_aral_chart(ebpf_aral_process_stat, em); +#endif + if (cgroups && shm_ebpf_cgroup.header) { ebpf_process_send_cgroup_data(em); } @@ -1133,7 +1101,6 @@ static void ebpf_process_allocate_global_vectors(size_t length) process_hash_values = callocz(ebpf_nprocs, sizeof(netdata_idx_t)); global_process_stats = callocz((size_t)pid_max, sizeof(ebpf_process_stat_t *)); - current_apps_data = callocz((size_t)pid_max, sizeof(ebpf_process_publish_apps_t *)); } static void change_syscalls() @@ -1213,10 +1180,12 @@ void *ebpf_process_thread(void *ptr) ebpf_module_t *em = (ebpf_module_t *)ptr; em->maps = process_maps; + pthread_mutex_lock(&ebpf_exit_cleanup); if (ebpf_process_enable_tracepoints()) { - em->enabled = em->global_charts = em->apps_charts = em->cgroup_charts = CONFIG_BOOLEAN_NO; + em->enabled = em->global_charts = em->apps_charts = em->cgroup_charts = NETDATA_THREAD_EBPF_STOPPING; } process_enabled = em->enabled; + pthread_mutex_unlock(&ebpf_exit_cleanup); pthread_mutex_lock(&lock); ebpf_process_allocate_global_vectors(NETDATA_KEY_PUBLISH_PROCESS_END); @@ -1226,7 +1195,6 @@ void *ebpf_process_thread(void *ptr) set_local_pointers(); em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); if (!em->probe_links) { - em->enabled = CONFIG_BOOLEAN_NO; pthread_mutex_unlock(&lock); goto endprocess; } @@ -1239,11 +1207,18 @@ void *ebpf_process_thread(void *ptr) process_aggregated_data, process_publish_aggregated, process_dimension_names, process_id_names, algorithms, NETDATA_KEY_PUBLISH_PROCESS_END); - if (process_enabled) { + if (process_enabled == NETDATA_THREAD_EBPF_RUNNING) { ebpf_create_global_charts(em); } ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); + +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_process_stat) + ebpf_statistic_create_aral_chart(NETDATA_EBPF_PROC_ARAL_NAME, em); +#endif + ebpf_create_statistic_charts(em); pthread_mutex_unlock(&lock); @@ -1251,8 +1226,10 @@ void *ebpf_process_thread(void *ptr) process_collector(em); endprocess: - if (!em->enabled) + pthread_mutex_lock(&ebpf_exit_cleanup); + if (em->enabled == NETDATA_THREAD_EBPF_RUNNING) ebpf_update_disabled_plugin_stats(em); + pthread_mutex_unlock(&ebpf_exit_cleanup); netdata_thread_cleanup_pop(1); return NULL; diff --git a/collectors/ebpf.plugin/ebpf_process.h b/collectors/ebpf.plugin/ebpf_process.h index 6fded16f..5f119aea 100644 --- a/collectors/ebpf.plugin/ebpf_process.h +++ b/collectors/ebpf.plugin/ebpf_process.h @@ -85,17 +85,6 @@ typedef enum netdata_publish_process { NETDATA_KEY_PUBLISH_PROCESS_END } netdata_publish_process_t; -typedef struct ebpf_process_publish_apps { - // Number of calls during the last read - uint64_t call_do_exit; - uint64_t call_release_task; - uint64_t create_process; - uint64_t create_thread; - - // Number of errors during the last read - uint64_t task_err; -} ebpf_process_publish_apps_t; - enum ebpf_process_tables { NETDATA_PROCESS_PID_TABLE, NETDATA_PROCESS_GLOBAL_TABLE, diff --git a/collectors/ebpf.plugin/ebpf_shm.c b/collectors/ebpf.plugin/ebpf_shm.c index 4057eff7..f81c0196 100644 --- a/collectors/ebpf.plugin/ebpf_shm.c +++ b/collectors/ebpf.plugin/ebpf_shm.c @@ -12,8 +12,6 @@ netdata_publish_shm_t *shm_vector = NULL; static netdata_idx_t shm_hash_values[NETDATA_SHM_END]; static netdata_idx_t *shm_values = NULL; -netdata_publish_shm_t **shm_pid = NULL; - struct config shm_config = { .first_section = NULL, .last_section = NULL, .mutex = NETDATA_MUTEX_INITIALIZER, @@ -41,10 +39,6 @@ netdata_ebpf_targets_t shm_targets[] = { {.name = "shmget", .mode = EBPF_LOAD_TR {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; #ifdef LIBBPF_MAJOR_VERSION -#include "includes/shm.skel.h" - -static struct shm_bpf *bpf_obj = NULL; - /***************************************************************** * * BTF FUNCTIONS @@ -287,22 +281,11 @@ 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); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING; - pthread_mutex_unlock(&ebpf_exit_cleanup); - - ebpf_cleanup_publish_syscall(shm_publish_aggregated); - freez(shm_vector); freez(shm_values); -#ifdef LIBBPF_MAJOR_VERSION - if (bpf_obj) - shm_bpf__destroy(bpf_obj); -#endif - pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -355,7 +338,7 @@ static void shm_fill_pid(uint32_t current_pid, netdata_publish_shm_t *publish) { netdata_publish_shm_t *curr = shm_pid[current_pid]; if (!curr) { - curr = callocz(1, sizeof(netdata_publish_shm_t)); + curr = ebpf_shm_stat_get( ); shm_pid[current_pid] = curr; } @@ -411,7 +394,7 @@ static void read_apps_table() { netdata_publish_shm_t *cv = shm_vector; uint32_t key; - struct pid_stat *pids = root_of_pids; + struct ebpf_pid_stat *pids = ebpf_root_of_pids; int fd = shm_maps[NETDATA_PID_SHM_TABLE].map_fd; size_t length = sizeof(netdata_publish_shm_t)*ebpf_nprocs; while (pids) { @@ -487,7 +470,7 @@ static void ebpf_shm_read_global_table() /** * Sum values for all targets. */ -static void ebpf_shm_sum_pids(netdata_publish_shm_t *shm, struct pid_on_target *root) +static void ebpf_shm_sum_pids(netdata_publish_shm_t *shm, struct ebpf_pid_on_target *root) { while (root) { int32_t pid = root->pid; @@ -513,9 +496,9 @@ static void ebpf_shm_sum_pids(netdata_publish_shm_t *shm, struct pid_on_target * * * @param root the target list. */ -void ebpf_shm_send_apps_data(struct target *root) +void ebpf_shm_send_apps_data(struct ebpf_target *root) { - struct target *w; + struct ebpf_target *w; for (w = root; w; w = w->next) { if (unlikely(w->exposed && w->processes)) { ebpf_shm_sum_pids(&w->shm, w->root_pid); @@ -873,6 +856,11 @@ static void shm_collector(ebpf_module_t *em) ebpf_shm_send_apps_data(apps_groups_root_target); } +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_shm_pid) + ebpf_send_data_aral_chart(ebpf_aral_shm_pid, em); +#endif + if (cgroups) { ebpf_shm_send_cgroup_data(update_every); } @@ -895,7 +883,7 @@ static void shm_collector(ebpf_module_t *em) */ void ebpf_shm_create_apps_charts(struct ebpf_module *em, void *ptr) { - struct target *root = ptr; + struct ebpf_target *root = ptr; ebpf_create_charts_on_apps(NETDATA_SHMGET_CHART, "Calls to syscall shmget(2).", EBPF_COMMON_DIMENSION_CALL, @@ -945,10 +933,11 @@ void ebpf_shm_create_apps_charts(struct ebpf_module *em, void *ptr) */ static void ebpf_shm_allocate_global_vectors(int apps) { - if (apps) + if (apps) { + ebpf_shm_aral_init(); shm_pid = callocz((size_t)pid_max, sizeof(netdata_publish_shm_t *)); - - shm_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_publish_shm_t)); + shm_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_publish_shm_t)); + } shm_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_idx_t)); @@ -1001,17 +990,16 @@ static int ebpf_shm_load_bpf(ebpf_module_t *em) if (em->load & EBPF_LOAD_LEGACY) { em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); if (!em->probe_links) { - em->enabled = CONFIG_BOOLEAN_NO; ret = -1; } } #ifdef LIBBPF_MAJOR_VERSION else { - bpf_obj = shm_bpf__open(); - if (!bpf_obj) + shm_bpf_obj = shm_bpf__open(); + if (!shm_bpf_obj) ret = -1; else - ret = ebpf_shm_load_and_attach(bpf_obj, em); + ret = ebpf_shm_load_and_attach(shm_bpf_obj, em); } #endif @@ -1041,7 +1029,6 @@ void *ebpf_shm_thread(void *ptr) ebpf_adjust_thread_load(em, default_btf); #endif if (ebpf_shm_load_bpf(em)) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endshm; } @@ -1065,6 +1052,12 @@ void *ebpf_shm_thread(void *ptr) pthread_mutex_lock(&lock); ebpf_create_shm_charts(em->update_every); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_shm_pid) + ebpf_statistic_create_aral_chart(NETDATA_EBPF_SHM_ARAL_NAME, em); +#endif + pthread_mutex_unlock(&lock); shm_collector(em); diff --git a/collectors/ebpf.plugin/ebpf_shm.h b/collectors/ebpf.plugin/ebpf_shm.h index b06a4a5d..f58eaa6c 100644 --- a/collectors/ebpf.plugin/ebpf_shm.h +++ b/collectors/ebpf.plugin/ebpf_shm.h @@ -27,6 +27,9 @@ #define NETDATA_SYSTEMD_SHM_DT_CONTEXT "services.shmdt" #define NETDATA_SYSTEMD_SHM_CTL_CONTEXT "services.shmctl" +// ARAL name +#define NETDATA_EBPF_SHM_ARAL_NAME "ebpf_shm" + typedef struct netdata_publish_shm { uint64_t get; uint64_t at; @@ -50,10 +53,9 @@ enum shm_counters { NETDATA_SHM_END }; -extern netdata_publish_shm_t **shm_pid; - void *ebpf_shm_thread(void *ptr); void ebpf_shm_create_apps_charts(struct ebpf_module *em, void *ptr); +void ebpf_shm_release(netdata_publish_shm_t *stat); extern netdata_ebpf_targets_t shm_targets[]; extern struct config shm_config; diff --git a/collectors/ebpf.plugin/ebpf_socket.c b/collectors/ebpf.plugin/ebpf_socket.c index 1954be71..aebc9ca1 100644 --- a/collectors/ebpf.plugin/ebpf_socket.c +++ b/collectors/ebpf.plugin/ebpf_socket.c @@ -5,6 +5,9 @@ #include "ebpf.h" #include "ebpf_socket.h" +// ---------------------------------------------------------------------------- +// ARAL vectors used to speed up processing + /***************************************************************** * * GLOBAL VARIABLES @@ -58,7 +61,6 @@ static netdata_idx_t *socket_hash_values = NULL; static netdata_syscall_stat_t socket_aggregated_data[NETDATA_MAX_SOCKET_VECTOR]; static netdata_publish_syscall_t socket_publish_aggregated[NETDATA_MAX_SOCKET_VECTOR]; -ebpf_socket_publish_apps_t **socket_bandwidth_curr = NULL; static ebpf_bandwidth_t *bandwidth_vector = NULL; pthread_mutex_t nv_mutex; @@ -97,10 +99,6 @@ struct netdata_static_thread socket_threads = { }; #ifdef LIBBPF_MAJOR_VERSION -#include "includes/socket.skel.h" // BTF code - -static struct socket_bpf *bpf_obj = NULL; - /** * Disable Probe * @@ -454,7 +452,6 @@ static inline void clean_internal_socket_plot(netdata_socket_plot_t *ptr) * Clean socket plot * * Clean the allocated data for inbound and outbound vectors. - */ static void clean_allocated_socket_plot() { if (!network_viewer_opt.enabled) @@ -476,12 +473,12 @@ static void clean_allocated_socket_plot() } clean_internal_socket_plot(&plot[outbound_vectors.last]); } + */ /** * Clean network ports allocated during initialization. * * @param ptr a pointer to the link list. - */ static void clean_network_ports(ebpf_network_viewer_port_list_t *ptr) { if (unlikely(!ptr)) @@ -494,6 +491,7 @@ static void clean_network_ports(ebpf_network_viewer_port_list_t *ptr) ptr = next; } } + */ /** * Clean service names @@ -501,7 +499,6 @@ static void clean_network_ports(ebpf_network_viewer_port_list_t *ptr) * Clean the allocated link list that stores names. * * @param names the link list. - */ static void clean_service_names(ebpf_network_viewer_dim_name_t *names) { if (unlikely(!names)) @@ -514,12 +511,12 @@ static void clean_service_names(ebpf_network_viewer_dim_name_t *names) names = next; } } + */ /** * Clean hostnames * * @param hostnames the hostnames to clean - */ static void clean_hostnames(ebpf_network_viewer_hostname_list_t *hostnames) { if (unlikely(!hostnames)) @@ -533,19 +530,7 @@ static void clean_hostnames(ebpf_network_viewer_hostname_list_t *hostnames) hostnames = next; } } - -/** - * Cleanup publish syscall - * - * @param nps list of structures to clean */ -void ebpf_cleanup_publish_syscall(netdata_publish_syscall_t *nps) -{ - while (nps) { - freez(nps->algorithm); - nps = nps->next; - } -} /** * Clean port Structure @@ -596,15 +581,8 @@ static void clean_ip_structure(ebpf_network_viewer_ip_list_t **clean) */ static void ebpf_socket_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); - - ebpf_cleanup_publish_syscall(socket_publish_aggregated); + /* We can have thousands of sockets to clean, so we are transferring + * for OS the responsibility while we do not use ARAL here freez(socket_hash_values); freez(bandwidth_vector); @@ -616,25 +594,17 @@ static void ebpf_socket_free(ebpf_module_t *em ) clean_port_structure(&listen_ports); - ebpf_modules[EBPF_MODULE_SOCKET_IDX].enabled = 0; - clean_network_ports(network_viewer_opt.included_port); clean_network_ports(network_viewer_opt.excluded_port); clean_service_names(network_viewer_opt.names); clean_hostnames(network_viewer_opt.included_hostnames); clean_hostnames(network_viewer_opt.excluded_hostnames); + */ pthread_mutex_destroy(&nv_mutex); - freez(socket_threads.thread); - -#ifdef LIBBPF_MAJOR_VERSION - if (bpf_obj) - socket_bpf__destroy(bpf_obj); -#endif - pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -648,8 +618,10 @@ static void ebpf_socket_free(ebpf_module_t *em ) static void ebpf_socket_exit(void *ptr) { ebpf_module_t *em = (ebpf_module_t *)ptr; + pthread_mutex_lock(&nv_mutex); if (socket_threads.thread) netdata_thread_cancel(*socket_threads.thread); + pthread_mutex_unlock(&nv_mutex); ebpf_socket_free(em); } @@ -662,8 +634,7 @@ static void ebpf_socket_exit(void *ptr) */ void ebpf_socket_cleanup(void *ptr) { - ebpf_module_t *em = (ebpf_module_t *)ptr; - ebpf_socket_free(em); + UNUSED(ptr); } /***************************************************************** @@ -958,7 +929,7 @@ static void ebpf_socket_send_data(ebpf_module_t *em) * * @return it returns the sum of all PIDs */ -long long ebpf_socket_sum_values_for_pids(struct pid_on_target *root, size_t offset) +long long ebpf_socket_sum_values_for_pids(struct ebpf_pid_on_target *root, size_t offset) { long long ret = 0; while (root) { @@ -980,11 +951,11 @@ long long ebpf_socket_sum_values_for_pids(struct pid_on_target *root, size_t off * @param em the structure with thread information * @param root the target list. */ -void ebpf_socket_send_apps_data(ebpf_module_t *em, struct target *root) +void ebpf_socket_send_apps_data(ebpf_module_t *em, struct ebpf_target *root) { UNUSED(em); - struct target *w; + struct ebpf_target *w; collected_number value; write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_NET_APPS_CONNECTION_TCP_V4); @@ -1217,7 +1188,7 @@ static void ebpf_create_global_charts(ebpf_module_t *em) */ void ebpf_socket_create_apps_charts(struct ebpf_module *em, void *ptr) { - struct target *root = ptr; + struct ebpf_target *root = ptr; int order = 20080; ebpf_create_charts_on_apps(NETDATA_NET_APPS_CONNECTION_TCP_V4, "Calls to tcp_v4_connection", EBPF_COMMON_DIMENSION_CONNECTIONS, @@ -2156,10 +2127,11 @@ void *ebpf_socket_read_hash(void *ptr) heartbeat_init(&hb); int fd_ipv4 = socket_maps[NETDATA_SOCKET_TABLE_IPV4].map_fd; int fd_ipv6 = socket_maps[NETDATA_SOCKET_TABLE_IPV6].map_fd; - while (!ebpf_exit_plugin) { + // This thread is cancelled from another thread + for (;;) { (void)heartbeat_next(&hb, USEC_PER_SEC); if (ebpf_exit_plugin) - continue; + break; pthread_mutex_lock(&nv_mutex); ebpf_read_socket_hash_table(fd_ipv4, AF_INET); @@ -2227,7 +2199,7 @@ void ebpf_socket_fill_publish_apps(uint32_t current_pid, ebpf_bandwidth_t *eb) { ebpf_socket_publish_apps_t *curr = socket_bandwidth_curr[current_pid]; if (!curr) { - curr = callocz(1, sizeof(ebpf_socket_publish_apps_t)); + curr = ebpf_socket_stat_get(); socket_bandwidth_curr[current_pid] = curr; } @@ -2275,7 +2247,7 @@ static void ebpf_socket_update_apps_data() int fd = socket_maps[NETDATA_SOCKET_TABLE_BANDWIDTH].map_fd; ebpf_bandwidth_t *eb = bandwidth_vector; uint32_t key; - struct pid_stat *pids = root_of_pids; + struct ebpf_pid_stat *pids = ebpf_root_of_pids; while (pids) { key = pids->pid; @@ -2794,8 +2766,7 @@ void ebpf_socket_update_cgroup_algorithm() int i; for (i = 0; i < NETDATA_MAX_SOCKET_VECTOR; i++) { netdata_publish_syscall_t *ptr = &socket_publish_aggregated[i]; - freez(ptr->algorithm); - ptr->algorithm = strdupz(ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + ptr->algorithm = ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]; } } @@ -2904,6 +2875,11 @@ static void socket_collector(ebpf_module_t *em) if (socket_apps_enabled & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) ebpf_socket_send_apps_data(em, apps_groups_root_target); +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_socket_pid) + ebpf_send_data_aral_chart(ebpf_aral_socket_pid, em); +#endif + if (cgroups) ebpf_socket_send_cgroup_data(update_every); @@ -2947,10 +2923,11 @@ static void ebpf_socket_allocate_global_vectors(int apps) memset(socket_publish_aggregated, 0 ,NETDATA_MAX_SOCKET_VECTOR * sizeof(netdata_publish_syscall_t)); socket_hash_values = callocz(ebpf_nprocs, sizeof(netdata_idx_t)); - if (apps) + if (apps) { + ebpf_socket_aral_init(); socket_bandwidth_curr = callocz((size_t)pid_max, sizeof(ebpf_socket_publish_apps_t *)); - - bandwidth_vector = callocz((size_t)ebpf_nprocs, sizeof(ebpf_bandwidth_t)); + bandwidth_vector = callocz((size_t)ebpf_nprocs, sizeof(ebpf_bandwidth_t)); + } socket_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_socket_t)); if (network_viewer_opt.enabled) { @@ -3722,7 +3699,7 @@ static void link_hostnames(char *parse) ebpf_network_viewer_hostname_list_t *hostname = callocz(1 , sizeof(ebpf_network_viewer_hostname_list_t)); hostname->value = strdupz(parse); hostname->hash = simple_hash(parse); - hostname->value_pattern = simple_pattern_create(parse, NULL, SIMPLE_PATTERN_EXACT); + hostname->value_pattern = simple_pattern_create(parse, NULL, SIMPLE_PATTERN_EXACT, true); link_hostname((!neg)?&network_viewer_opt.included_hostnames:&network_viewer_opt.excluded_hostnames, hostname); @@ -3888,11 +3865,11 @@ static int ebpf_socket_load_bpf(ebpf_module_t *em) } #ifdef LIBBPF_MAJOR_VERSION else { - bpf_obj = socket_bpf__open(); - if (!bpf_obj) + socket_bpf_obj = socket_bpf__open(); + if (!socket_bpf_obj) ret = -1; else - ret = ebpf_socket_load_and_attach(bpf_obj, em); + ret = ebpf_socket_load_and_attach(socket_bpf_obj, em); } #endif @@ -3922,7 +3899,6 @@ void *ebpf_socket_thread(void *ptr) parse_table_size_options(&socket_config); if (pthread_mutex_init(&nv_mutex, NULL)) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; error("Cannot initialize local mutex"); goto endsocket; } @@ -3945,7 +3921,6 @@ void *ebpf_socket_thread(void *ptr) ebpf_adjust_thread_load(em, default_btf); #endif if (ebpf_socket_load_bpf(em)) { - em->enabled = CONFIG_BOOLEAN_NO; pthread_mutex_unlock(&lock); goto endsocket; } @@ -3964,6 +3939,12 @@ void *ebpf_socket_thread(void *ptr) ebpf_create_global_charts(em); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); + +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_socket_pid) + ebpf_statistic_create_aral_chart(NETDATA_EBPF_SOCKET_ARAL_NAME, em); +#endif pthread_mutex_unlock(&lock); diff --git a/collectors/ebpf.plugin/ebpf_socket.h b/collectors/ebpf.plugin/ebpf_socket.h index 63b1e107..1ba20e65 100644 --- a/collectors/ebpf.plugin/ebpf_socket.h +++ b/collectors/ebpf.plugin/ebpf_socket.h @@ -160,6 +160,9 @@ typedef enum ebpf_socket_idx { #define NETDATA_SERVICES_SOCKET_UDP_RECV_CONTEXT "services.net_udp_recv" #define NETDATA_SERVICES_SOCKET_UDP_SEND_CONTEXT "services.net_udp_send" +// ARAL name +#define NETDATA_EBPF_SOCKET_ARAL_NAME "ebpf_socket" + typedef struct ebpf_socket_publish_apps { // Data read uint64_t bytes_sent; // Bytes sent @@ -364,7 +367,6 @@ void parse_network_viewer_section(struct config *cfg); 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; extern struct config socket_config; extern netdata_ebpf_targets_t socket_targets[]; diff --git a/collectors/ebpf.plugin/ebpf_softirq.c b/collectors/ebpf.plugin/ebpf_softirq.c index 49e9c305..33abbdf5 100644 --- a/collectors/ebpf.plugin/ebpf_softirq.c +++ b/collectors/ebpf.plugin/ebpf_softirq.c @@ -64,7 +64,7 @@ static softirq_ebpf_val_t *softirq_ebpf_vals = NULL; static void ebpf_softirq_free(ebpf_module_t *em) { pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING; + em->enabled = NETDATA_THREAD_EBPF_STOPPING; pthread_mutex_unlock(&ebpf_exit_cleanup); for (int i = 0; softirq_tracepoints[i].class != NULL; i++) { @@ -73,7 +73,7 @@ static void ebpf_softirq_free(ebpf_module_t *em) freez(softirq_ebpf_vals); pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -164,6 +164,7 @@ static void softirq_collector(ebpf_module_t *em) softirq_create_charts(em->update_every); softirq_create_dims(); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); pthread_mutex_unlock(&lock); // loop and read from published data until ebpf plugin is closed. @@ -208,13 +209,11 @@ void *ebpf_softirq_thread(void *ptr) em->maps = softirq_maps; if (ebpf_enable_tracepoints(softirq_tracepoints) == 0) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endsoftirq; } em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); if (!em->probe_links) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endsoftirq; } diff --git a/collectors/ebpf.plugin/ebpf_swap.c b/collectors/ebpf.plugin/ebpf_swap.c index 059efb63..2352470a 100644 --- a/collectors/ebpf.plugin/ebpf_swap.c +++ b/collectors/ebpf.plugin/ebpf_swap.c @@ -7,12 +7,10 @@ static char *swap_dimension_name[NETDATA_SWAP_END] = { "read", "write" }; static netdata_syscall_stat_t swap_aggregated_data[NETDATA_SWAP_END]; static netdata_publish_syscall_t swap_publish_aggregated[NETDATA_SWAP_END]; -netdata_publish_swap_t *swap_vector = NULL; - static netdata_idx_t swap_hash_values[NETDATA_SWAP_END]; static netdata_idx_t *swap_values = NULL; -netdata_publish_swap_t **swap_pid = NULL; +netdata_publish_swap_t *swap_vector = NULL; struct config swap_config = { .first_section = NULL, .last_section = NULL, @@ -39,10 +37,6 @@ netdata_ebpf_targets_t swap_targets[] = { {.name = "swap_readpage", .mode = EBPF {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; #ifdef LIBBPF_MAJOR_VERSION -#include "includes/swap.skel.h" // BTF code - -static struct swap_bpf *bpf_obj = NULL; - /** * Disable probe * @@ -224,21 +218,11 @@ 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); - 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); -#ifdef LIBBPF_MAJOR_VERSION - if (bpf_obj) - swap_bpf__destroy(bpf_obj); -#endif pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -341,7 +325,7 @@ static void read_apps_table() { netdata_publish_swap_t *cv = swap_vector; uint32_t key; - struct pid_stat *pids = root_of_pids; + struct ebpf_pid_stat *pids = ebpf_root_of_pids; int fd = swap_maps[NETDATA_PID_SWAP_TABLE].map_fd; size_t length = sizeof(netdata_publish_swap_t)*ebpf_nprocs; while (pids) { @@ -410,7 +394,7 @@ static void ebpf_swap_read_global_table() * @param swap * @param root */ -static void ebpf_swap_sum_pids(netdata_publish_swap_t *swap, struct pid_on_target *root) +static void ebpf_swap_sum_pids(netdata_publish_swap_t *swap, struct ebpf_pid_on_target *root) { uint64_t local_read = 0; uint64_t local_write = 0; @@ -435,9 +419,9 @@ static void ebpf_swap_sum_pids(netdata_publish_swap_t *swap, struct pid_on_targe * * @param root the target list. */ -void ebpf_swap_send_apps_data(struct target *root) +void ebpf_swap_send_apps_data(struct ebpf_target *root) { - struct target *w; + struct ebpf_target *w; for (w = root; w; w = w->next) { if (unlikely(w->exposed && w->processes)) { ebpf_swap_sum_pids(&w->swap, w->root_pid); @@ -707,7 +691,7 @@ static void swap_collector(ebpf_module_t *em) */ void ebpf_swap_create_apps_charts(struct ebpf_module *em, void *ptr) { - struct target *root = ptr; + struct ebpf_target *root = ptr; ebpf_create_charts_on_apps(NETDATA_MEM_SWAP_READ_CHART, "Calls to function swap_readpage.", EBPF_COMMON_DIMENSION_CALL, @@ -829,7 +813,6 @@ void *ebpf_swap_thread(void *ptr) ebpf_adjust_thread_load(em, default_btf); #endif if (ebpf_swap_load_bpf(em)) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endswap; } @@ -842,6 +825,7 @@ void *ebpf_swap_thread(void *ptr) pthread_mutex_lock(&lock); ebpf_create_swap_charts(em->update_every); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); pthread_mutex_unlock(&lock); swap_collector(em); diff --git a/collectors/ebpf.plugin/ebpf_swap.h b/collectors/ebpf.plugin/ebpf_swap.h index 79182e52..8ca980bf 100644 --- a/collectors/ebpf.plugin/ebpf_swap.h +++ b/collectors/ebpf.plugin/ebpf_swap.h @@ -42,8 +42,6 @@ enum swap_counters { NETDATA_SWAP_END }; -extern netdata_publish_swap_t **swap_pid; - void *ebpf_swap_thread(void *ptr); void ebpf_swap_create_apps_charts(struct ebpf_module *em, void *ptr); diff --git a/collectors/ebpf.plugin/ebpf_sync.c b/collectors/ebpf.plugin/ebpf_sync.c index 7c81c1df..f838b65a 100644 --- a/collectors/ebpf.plugin/ebpf_sync.c +++ b/collectors/ebpf.plugin/ebpf_sync.c @@ -204,16 +204,12 @@ void ebpf_sync_cleanup_objects() */ static void ebpf_sync_free(ebpf_module_t *em) { - pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING; - pthread_mutex_unlock(&ebpf_exit_cleanup); - #ifdef LIBBPF_MAJOR_VERSION ebpf_sync_cleanup_objects(); #endif pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -523,7 +519,6 @@ void *ebpf_sync_thread(void *ptr) ebpf_adjust_thread_load(em, default_btf); #endif if (ebpf_sync_initialize_syscall(em)) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endsync; } diff --git a/collectors/ebpf.plugin/ebpf_vfs.c b/collectors/ebpf.plugin/ebpf_vfs.c index b3c0ba45..e2d87fd5 100644 --- a/collectors/ebpf.plugin/ebpf_vfs.c +++ b/collectors/ebpf.plugin/ebpf_vfs.c @@ -13,7 +13,6 @@ static char *vfs_id_names[NETDATA_KEY_PUBLISH_VFS_END] = { "vfs_unlink", "vfs_re static netdata_idx_t *vfs_hash_values = NULL; static netdata_syscall_stat_t vfs_aggregated_data[NETDATA_KEY_PUBLISH_VFS_END]; static netdata_publish_syscall_t vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_END]; -netdata_publish_vfs_t **vfs_pid = NULL; netdata_publish_vfs_t *vfs_vector = NULL; static ebpf_local_maps_t vfs_maps[] = {{.name = "tbl_vfs_pid", .internal_input = ND_EBPF_DEFAULT_PID_SIZE, @@ -46,10 +45,6 @@ netdata_ebpf_targets_t vfs_targets[] = { {.name = "vfs_write", .mode = EBPF_LOAD {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; #ifdef LIBBPF_MAJOR_VERSION -#include "includes/vfs.skel.h" // BTF code - -static struct vfs_bpf *bpf_obj = NULL; - /** * Disable probe * @@ -397,20 +392,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); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPING; - pthread_mutex_unlock(&ebpf_exit_cleanup); - freez(vfs_hash_values); freez(vfs_vector); -#ifdef LIBBPF_MAJOR_VERSION - if (bpf_obj) - vfs_bpf__destroy(bpf_obj); -#endif - pthread_mutex_lock(&ebpf_exit_cleanup); - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; pthread_mutex_unlock(&ebpf_exit_cleanup); } @@ -540,7 +526,7 @@ static void ebpf_vfs_read_global_table() * @param swap output structure * @param root link list with structure to be used */ -static void ebpf_vfs_sum_pids(netdata_publish_vfs_t *vfs, struct pid_on_target *root) +static void ebpf_vfs_sum_pids(netdata_publish_vfs_t *vfs, struct ebpf_pid_on_target *root) { netdata_publish_vfs_t accumulator; memset(&accumulator, 0, sizeof(accumulator)); @@ -606,9 +592,9 @@ static void ebpf_vfs_sum_pids(netdata_publish_vfs_t *vfs, struct pid_on_target * * @param em the structure with thread information * @param root the target list. */ -void ebpf_vfs_send_apps_data(ebpf_module_t *em, struct target *root) +void ebpf_vfs_send_apps_data(ebpf_module_t *em, struct ebpf_target *root) { - struct target *w; + struct ebpf_target *w; for (w = root; w; w = w->next) { if (unlikely(w->exposed && w->processes)) { ebpf_vfs_sum_pids(&w->vfs, w->root_pid); @@ -775,7 +761,7 @@ static void vfs_fill_pid(uint32_t current_pid, netdata_publish_vfs_t *publish) { netdata_publish_vfs_t *curr = vfs_pid[current_pid]; if (!curr) { - curr = callocz(1, sizeof(netdata_publish_vfs_t)); + curr = ebpf_vfs_get(); vfs_pid[current_pid] = curr; } @@ -787,7 +773,7 @@ static void vfs_fill_pid(uint32_t current_pid, netdata_publish_vfs_t *publish) */ static void ebpf_vfs_read_apps() { - struct pid_stat *pids = root_of_pids; + struct ebpf_pid_stat *pids = ebpf_root_of_pids; netdata_publish_vfs_t *vv = vfs_vector; int fd = vfs_maps[NETDATA_VFS_PID].map_fd; size_t length = sizeof(netdata_publish_vfs_t) * ebpf_nprocs; @@ -926,88 +912,88 @@ static void ebpf_create_specific_vfs_charts(char *type, ebpf_module_t *em) EBPF_COMMON_DIMENSION_CALL, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_UNLINK_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5500, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_UNLINK], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS, "Write to disk", EBPF_COMMON_DIMENSION_CALL, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_WRITE_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5501, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_WRITE], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); if (em->mode < MODE_ENTRY) { ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS_ERROR, "Fails to write", EBPF_COMMON_DIMENSION_CALL, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_WRITE_ERROR_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5502, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_WRITE], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); } ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_READ_CALLS, "Read from disk", EBPF_COMMON_DIMENSION_CALL, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_READ_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5503, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); if (em->mode < MODE_ENTRY) { ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_READ_CALLS_ERROR, "Fails to read", EBPF_COMMON_DIMENSION_CALL, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_READ_ERROR_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5504, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); } ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_WRITE_BYTES, "Bytes written on disk", EBPF_COMMON_DIMENSION_BYTES, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_WRITE_BYTES_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5505, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_WRITE], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_READ_BYTES, "Bytes read from disk", EBPF_COMMON_DIMENSION_BYTES, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_READ_BYTES_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5506, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_FSYNC, "Calls for vfs_fsync", EBPF_COMMON_DIMENSION_CALL, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_FSYNC_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5507, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_FSYNC], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); if (em->mode < MODE_ENTRY) { ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_FSYNC_CALLS_ERROR, "Sync error", EBPF_COMMON_DIMENSION_CALL, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_FSYNC_ERROR_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5508, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_FSYNC], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); } ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_OPEN, "Calls for vfs_open", EBPF_COMMON_DIMENSION_CALL, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_OPEN_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5509, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_OPEN], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); if (em->mode < MODE_ENTRY) { ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_OPEN_CALLS_ERROR, "Open error", EBPF_COMMON_DIMENSION_CALL, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_OPEN_ERROR_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5510, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_OPEN], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); } ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_CREATE, "Calls for vfs_create", EBPF_COMMON_DIMENSION_CALL, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_CREATE_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5511, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_CREATE], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); if (em->mode < MODE_ENTRY) { ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_CREATE_CALLS_ERROR, "Create error", EBPF_COMMON_DIMENSION_CALL, NETDATA_VFS_CGROUP_GROUP, NETDATA_CGROUP_VFS_CREATE_ERROR_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5512, ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_CREATE], - 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); } } @@ -1484,6 +1470,11 @@ static void vfs_collector(ebpf_module_t *em) if (apps) ebpf_vfs_read_apps(); +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_vfs_pid) + ebpf_send_data_aral_chart(ebpf_aral_vfs_pid, em); +#endif + if (cgroups) read_update_vfs_cgroup(); @@ -1683,7 +1674,7 @@ static void ebpf_create_global_charts(ebpf_module_t *em) **/ void ebpf_vfs_create_apps_charts(struct ebpf_module *em, void *ptr) { - struct target *root = ptr; + struct ebpf_target *root = ptr; ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_FILE_DELETED, "Files deleted", @@ -1825,14 +1816,16 @@ void ebpf_vfs_create_apps_charts(struct ebpf_module *em, void *ptr) */ static void ebpf_vfs_allocate_global_vectors(int apps) { + if (apps) { + ebpf_vfs_aral_init(); + vfs_pid = callocz((size_t)pid_max, sizeof(netdata_publish_vfs_t *)); + vfs_vector = callocz(ebpf_nprocs, sizeof(netdata_publish_vfs_t)); + } + memset(vfs_aggregated_data, 0, sizeof(vfs_aggregated_data)); memset(vfs_publish_aggregated, 0, sizeof(vfs_publish_aggregated)); vfs_hash_values = callocz(ebpf_nprocs, sizeof(netdata_idx_t)); - vfs_vector = callocz(ebpf_nprocs, sizeof(netdata_publish_vfs_t)); - - if (apps) - vfs_pid = callocz((size_t)pid_max, sizeof(netdata_publish_vfs_t *)); } /***************************************************************** @@ -1860,11 +1853,11 @@ static int ebpf_vfs_load_bpf(ebpf_module_t *em) } #ifdef LIBBPF_MAJOR_VERSION else { - bpf_obj = vfs_bpf__open(); - if (!bpf_obj) + vfs_bpf_obj = vfs_bpf__open(); + if (!vfs_bpf_obj) ret = -1; else - ret = ebpf_vfs_load_and_attach(bpf_obj, em); + ret = ebpf_vfs_load_and_attach(vfs_bpf_obj, em); } #endif @@ -1895,7 +1888,6 @@ void *ebpf_vfs_thread(void *ptr) ebpf_adjust_thread_load(em, default_btf); #endif if (ebpf_vfs_load_bpf(em)) { - em->thread->enabled = NETDATA_THREAD_EBPF_STOPPED; goto endvfs; } @@ -1910,6 +1902,12 @@ void *ebpf_vfs_thread(void *ptr) pthread_mutex_lock(&lock); ebpf_create_global_charts(em); ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps); +#ifdef NETDATA_DEV_MODE + if (ebpf_aral_vfs_pid) + ebpf_statistic_create_aral_chart(NETDATA_EBPF_VFS_ARAL_NAME, em); +#endif + pthread_mutex_unlock(&lock); vfs_collector(em); diff --git a/collectors/ebpf.plugin/ebpf_vfs.h b/collectors/ebpf.plugin/ebpf_vfs.h index d7fc2672..45a1df4b 100644 --- a/collectors/ebpf.plugin/ebpf_vfs.h +++ b/collectors/ebpf.plugin/ebpf_vfs.h @@ -69,6 +69,9 @@ #define NETDATA_SYSTEMD_VFS_FSYNC_CONTEXT "services.vfs_fsync" #define NETDATA_SYSTEMD_VFS_FSYNC_ERROR_CONTEXT "services.vfs_fsync_error" +// ARAL name +#define NETDATA_EBPF_VFS_ARAL_NAME "ebpf_vfs" + typedef struct netdata_publish_vfs { uint64_t pid_tgid; uint32_t pid; @@ -164,10 +167,9 @@ enum netdata_vfs_calls_name { NETDATA_VFS_END_LIST }; -extern netdata_publish_vfs_t **vfs_pid; - void *ebpf_vfs_thread(void *ptr); void ebpf_vfs_create_apps_charts(struct ebpf_module *em, void *ptr); +void ebpf_vfs_release(netdata_publish_vfs_t *stat); extern netdata_ebpf_targets_t vfs_targets[]; extern struct config vfs_config; diff --git a/collectors/ebpf.plugin/metrics.csv b/collectors/ebpf.plugin/metrics.csv new file mode 100644 index 00000000..5714c976 --- /dev/null +++ b/collectors/ebpf.plugin/metrics.csv @@ -0,0 +1,197 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +cgroup.fd_open,cgroup,open,calls/s,Number of open files,line,,ebpf.plugin,filedescriptor +cgroup.fd_open_error,cgroup,open,calls/s,Fails to open files,line,,ebpf.plugin,filedescriptor +cgroup.fd_closed,cgroup,close,calls/s,Files closed,line,,ebpf.plugin,filedescriptor +cgroup.fd_close_error,cgroup,close,calls/s,Fails to close files,line,,ebpf.plugin,filedescriptor +services.file_open,,a dimension per systemd service,calls/s,Number of open files,stacked,,ebpf.plugin,filedescriptor +services.file_open_error,,a dimension per systemd service,calls/s,Fails to open files,stacked,,ebpf.plugin,filedescriptor +services.file_closed,,a dimension per systemd service,calls/s,Files closed,stacked,,ebpf.plugin,filedescriptor +services.file_close_error,,a dimension per systemd service,calls/s,Fails to close files,stacked,,ebpf.plugin,filedescriptor +apps.file_open,,a dimension per app group,calls/s,Number of open files,stacked,,ebpf.plugin,filedescriptor +apps.file_open_error,,a dimension per app group,calls/s,Fails to open files,stacked,,ebpf.plugin,filedescriptor +apps.file_closed,,a dimension per app group,calls/s,Files closed,stacked,,ebpf.plugin,filedescriptor +apps.file_close_error,,a dimension per app group,calls/s,Fails to close files,stacked,,ebpf.plugin,filedescriptor +filesystem.file_descriptor,,"open, close",calls/s,Open and close calls,line,,ebpf.plugin,filedescriptor +filesystem.file_error,,"open, close",calls/s,Open fails,line,,ebpf.plugin,filedescriptor +system.process_thread,,process,calls/s,Start process,line,,ebpf.plugin,processes +system.process_status,,"process, zombie",difference,Process not closed,line,,ebpf.plugin,processes +system.exit,,process,calls/s,Exit process,line,,ebpf.plugin,processes +system.task_error,,task,calls/s,Fails to create process,line,,ebpf.plugin,processes +apps.process_create,,a dimension per app group,calls/s,Process started,stacked,,ebpf.plugin,processes +apps.thread_create,,a dimension per app group,calls/s,Threads started,stacked,,ebpf.plugin,processes +apps.task_exit,,a dimension per app group,calls/s,Tasks starts exit process,stacked,,ebpf.plugin,processes +apps.task_close,,a dimension per app group,calls/s,Tasks closed,stacked,,ebpf.plugin,processes +apps.task_error,,a dimension per app group,calls/s,Errors to create process or threads,stacked,,ebpf.plugin,processes +cgroup.process_create,cgroup,process,calls/s,Process started,line,,ebpf.plugin,processes +cgroup.thread_create,cgroup,thread,calls/s,Threads started,line,,ebpf.plugin,processes +cgroup.task_exit,cgroup,exit,calls/s,Tasks starts exit process,line,,ebpf.plugin,processes +cgroup.task_close,cgroup,process,calls/s,Tasks closed,line,,ebpf.plugin,processes +cgroup.task_error,cgroup,process,calls/s,Errors to create process or threads,line,,ebpf.plugin,processes +services.process_create,cgroup,a dimension per systemd service,calls/s,Process started,stacked,,ebpf.plugin,processes +services.thread_create,cgroup,a dimension per systemd service,calls/s,Threads started,stacked,,ebpf.plugin,processes +services.task_close,cgroup,a dimension per systemd service,calls/s,Tasks starts exit process,stacked,,ebpf.plugin,processes +services.task_exit,cgroup,a dimension per systemd service,calls/s,Tasks closed,stacked,,ebpf.plugin,processes +services.task_error,cgroup,a dimension per systemd service,calls/s,Errors to create process or threads,stacked,,ebpf.plugin,processes +disk.latency_io,disk,latency,calls/s,Disk latency,stacked,,ebpf.plugin,disk +system.hardirq_latency,,hardirq names,milisecondds,Hardware IRQ latency,stacked,,ebpf.plugin,hardirq +apps.cachestat_ratio,,a dimension per app group,%,Hit ratio,line,,ebpf.plugin,cachestat +apps.cachestat_dirties,,a dimension per app group,page/s,Number of dirty pages,stacked,,ebpf.plugin,cachestat +apps.cachestat_hits,,a dimension per app group,hits/s,Number of accessed files,stacked,,ebpf.plugin,cachestat +apps.cachestat_misses,,a dimension per app group,misses/s,Files out of page cache,stacked,,ebpf.plugin,cachestat +services.cachestat_ratio,,a dimension per systemd service,%,Hit ratio,line,,ebpf.plugin,cachestat +services.cachestat_dirties,,a dimension per systemd service,page/s,Number of dirty pages,line,,ebpf.plugin,cachestat +services.cachestat_hits,,a dimension per systemd service,hits/s,Number of accessed files,line,,ebpf.plugin,cachestat +services.cachestat_misses,,a dimension per systemd service,misses/s,Files out of page cache,line,,ebpf.plugin,cachestat +cgroup.cachestat_ratio,cgroup,ratio,%,Hit ratio,line,,ebpf.plugin,cachestat +cgroup.cachestat_dirties,cgroup,dirty,page/s,Number of dirty pages,line,,ebpf.plugin,cachestat +cgroup.cachestat_hits,cgroup,hit,hits/s,Number of accessed files,line,,ebpf.plugin,cachestat +cgroup.cachestat_misses,cgroup,miss,misses/s,Files out of page cache,line,,ebpf.plugin,cachestat +mem.file_sync,,"fsync, fdatasync",calls/s,Monitor calls for fsync(2) and fdatasync(2).,stacked,,ebpf.plugin,sync +mem.meory_map,,msync,calls/s,Monitor calls for msync(2).,line,,ebpf.plugin,sync +mem.sync,,"sync, syncfs",calls/s,Monitor calls for sync(2) and syncfs(2).,line,,ebpf.plugin,sync +mem.file_segment,,sync_file_range,calls/s,Monitor calls for sync_file_range(2).,line,,ebpf.plugin,sync +mem.cachestat_ratio,,ratio,%,Hit ratio,line,,ebpf.plugin,cachestat +mem.cachestat_dirties,,dirty,page/s,Number of dirty pages,line,,ebpf.plugin,cachestat +mem.cachestat_hits,,hit,hits/s,Number of accessed files,line,,ebpf.plugin,cachestat +mem.cachestat_misses,,miss,misses/s,Files out of page cache,line,,ebpf.plugin,cachestat +mdstat.mdstat_flush,,disk,flushes,MD flushes,stacked,,ebpf.plugin,mdflush +cgroup.swap_read,cgroup,read,calls/s,Calls to function swap_readpage.,line,,ebpf.plugin,swap +cgroup.swap_write,cgroup,write,calls/s,Calls to function swap_writepage.,line,,ebpf.plugin,swap +services.swap_read,,a dimension per systemd service,calls/s,Calls to swap_readpage.,stacked,,ebpf.plugin,swap +services.swap_write,,a dimension per systemd service,calls/s,Calls to function swap_writepage.,stacked,,ebpf.plugin,swap +apps.swap_read_call,,a dimension per app group,calls/s,Calls to function swap_readpage.,stacked,,ebpf.plugin,swap +apps.swap_write_call,,a dimension per app group,calls/s,Calls to function swap_writepage.,stacked,,ebpf.plugin,swap +system.swapcalls,,"write, read",calls/s,Calls to access swap memory,line,,ebpf.plugin,swap +cgroup.oomkills,cgroup,cgroup name,kills,OOM kills. This chart is provided by eBPF plugin.,line,,ebpf.plugin,oomkill +services.oomkills,,a dimension per systemd service,kills,OOM kills. This chart is provided by eBPF plugin.,line,,ebpf.plugin,oomkill +apps.oomkills,,a dimension per app group,kills,OOM kills,stacked,,ebpf.plugin,oomkill +ip.inbound_conn,,connection_tcp,connections/s,Inbound connections.,line,,ebpf.plugin,socket +ip.tcp_outbound_conn,,received,connections/s,TCP outbound connections.,line,,ebpf.plugin,socket +ip.tcp_functions,,"received, send, closed",calls/s,Calls to internal functions,line,,ebpf.plugin,socket +ip.total_tcp_bandwidth,,"received, send",kilobits/s,TCP bandwidth,line,,ebpf.plugin,socket +ip.tcp_error,,"received, send",calls/s,TCP errors,line,,ebpf.plugin,socket +ip.tcp_retransmit,,retransmited,calls/s,Packages retransmitted,line,,ebpf.plugin,socket +ip.udp_functions,,"received, send",calls/s,UDP calls,line,,ebpf.plugin,socket +ip.total_udp_bandwidth,,"received, send",kilobits/s,UDP bandwidth,line,,ebpf.plugin,socket +ip.udp_error,,"received, send",calls/s,UDP errors,line,,ebpf.plugin,socket +apps.outbound_conn_v4,,a dimension per app group,connections/s,Calls to tcp_v4_connection,stacked,,ebpf.plugin,socket +apps.outbound_conn_v6,,a dimension per app group,connections/s,Calls to tcp_v6_connection,stacked,,ebpf.plugin,socket +apps.total_bandwidth_sent,,a dimension per app group,kilobits/s,Bytes sent,stacked,,ebpf.plugin,socket +apps.total_bandwidth_recv,,a dimension per app group,kilobits/s,bytes received,stacked,,ebpf.plugin,socket +apps.bandwidth_tcp_send,,a dimension per app group,calls/s,Calls for tcp_sendmsg,stacked,,ebpf.plugin,socket +apps.bandwidth_tcp_recv,,a dimension per app group,calls/s,Calls for tcp_cleanup_rbuf,stacked,,ebpf.plugin,socket +apps.bandwidth_tcp_retransmit,,a dimension per app group,calls/s,Calls for tcp_retransmit,stacked,,ebpf.plugin,socket +apps.bandwidth_udp_send,,a dimension per app group,calls/s,Calls for udp_sendmsg,stacked,,ebpf.plugin,socket +apps.bandwidth_udp_recv,,a dimension per app group,calls/s,Calls for udp_recvmsg,stacked,,ebpf.plugin,socket +cgroup.net_conn_ipv4,cgroup,connected_v4,connections/s,Calls to tcp_v4_connection,line,,ebpf.plugin,socket +cgroup.net_conn_ipv6,cgroup,connected_v6,connections/s,Calls to tcp_v6_connection,line,,ebpf.plugin,socket +cgroup.net_bytes_recv,cgroup,received,calls/s,Bytes received,line,,ebpf.plugin,socket +cgroup.net_bytes_sent,cgroup,sent,calls/s,Bytes sent,line,,ebpf.plugin,socket +cgroup.net_tcp_recv,cgroup,received,calls/s,Calls to tcp_cleanup_rbuf.,line,,ebpf.plugin,socket +cgroup.net_tcp_send,cgroup,sent,calls/s,Calls to tcp_sendmsg.,line,,ebpf.plugin,socket +cgroup.net_retransmit,cgroup,retransmitted,calls/s,Calls to tcp_retransmit.,line,,ebpf.plugin,socket +cgroup.net_udp_send,cgroup,sent,calls/s,Calls to udp_sendmsg,line,,ebpf.plugin,socket +cgroup.net_udp_recv,cgroup,received,calls/s,Calls to udp_recvmsg,line,,ebpf.plugin,socket +services.net_conn_ipv4,,a dimension per systemd service,connections/s,Calls to tcp_v4_connection,stacked,,ebpf.plugin,socket +services.net_conn_ipv6,,a dimension per systemd service,connections/s,Calls to tcp_v6_connection,stacked,,ebpf.plugin,socket +services.net_bytes_recv,,a dimension per systemd service,kilobits/s,Bytes received,stacked,,ebpf.plugin,socket +services.net_bytes_sent,,a dimension per systemd service,kilobits/s,Bytes sent,stacked,,ebpf.plugin,socket +services.net_tcp_recv,,a dimension per systemd service,calls/s,Calls to tcp_cleanup_rbuf.,stacked,,ebpf.plugin,socket +services.net_tcp_send,,a dimension per systemd service,calls/s,Calls to tcp_sendmsg.,stacked,,ebpf.plugin,socket +services.net_tcp_retransmit,,a dimension per systemd service,calls/s,Calls to tcp_retransmit,stacked,,ebpf.plugin,socket +services.net_udp_send,,a dimension per systemd service,calls/s,Calls to udp_sendmsg,stacked,,ebpf.plugin,socket +services.net_udp_recv,,a dimension per systemd service,calls/s,Calls to udp_recvmsg,stacked,,ebpf.plugin,socket +apps.dc_ratio,,a dimension per app group,%,Percentage of files inside directory cache,line,,ebpf.plugin,dcstat +apps.dc_reference,,a dimension per app group,files,Count file access,stacked,,ebpf.plugin,dcstat +apps.dc_not_cache,,a dimension per app group,files,Files not present inside directory cache,stacked,,ebpf.plugin,dcstat +apps.dc_not_found,,a dimension per app group,files,Files not found,stacked,,ebpf.plugin,dcstat +cgroup.dc_ratio,cgroup,ratio,%,Percentage of files inside directory cache,line,,ebpf.plugin,dcstat +cgroup.dc_reference,cgroup,reference,files,Count file access,line,,ebpf.plugin,dcstat +cgroup.dc_not_cache,cgroup,slow,files,Files not present inside directory cache,line,,ebpf.plugin,dcstat +cgroup.dc_not_found,cgroup,miss,files,Files not found,line,,ebpf.plugin,dcstat +services.dc_ratio,,a dimension per systemd service,%,Percentage of files inside directory cache,line,,ebpf.plugin,dcstat +services.dc_reference,,a dimension per systemd service,files,Count file access,line,,ebpf.plugin,dcstat +services.dc_not_cache,,a dimension per systemd service,files,Files not present inside directory cache,line,,ebpf.plugin,dcstat +services.dc_not_found,,a dimension per systemd service,files,Files not found,line,,ebpf.plugin,dcstat +filesystem.dc_hit_ratio,,ratio,%,Percentage of files inside directory cache,line,,ebpf.plugin,dcstat +filesystem.dc_reference,filesystem,"reference, slow, miss",files,Variables used to calculate hit ratio.,line,,ebpf.plugin,dcstat +filesystem.read_latency,filesystem,latency period,calls/s,ext4 latency for each read request.,stacked,,ebpf.plugin,filesystem +filesystem.write_latency,iilesystem,latency period,calls/s,ext4 latency for each write request.,stacked,,ebpf.plugin,filesystem +filesystem.open_latency,filesystem,latency period,calls/s,ext4 latency for each open request.,stacked,,ebpf.plugin,filesystem +filesystem.sync_latency,filesystem,latency period,calls/s,ext4 latency for each sync request.,stacked,,ebpf.plugin,filesystem +filesystem.attributte_latency,,latency period,calls/s,nfs latency for each attribute request.,stacked,,ebpf.plugin,filesystem +cgroup.shmget,cgroup,get,calls/s,Calls to syscall shmget(2).,line,,ebpf.plugin,shm +cgroup.shmat,cgroup,at,calls/s,Calls to syscall shmat(2).,line,,ebpf.plugin,shm +cgroup.shmdt,cgroup,dt,calls/s,Calls to syscall shmdt(2).,line,,ebpf.plugin,shm +cgroup.shmctl,cgroup,ctl,calls/s,Calls to syscall shmctl(2).,line,,ebpf.plugin,shm +services.shmget,,a dimension per systemd service,calls/s,Calls to syscall shmget(2).,stacked,,ebpf.plugin,shm +services.shmat,,a dimension per systemd service,calls/s,Calls to syscall shmat(2).,stacked,,ebpf.plugin,shm +services.shmdt,,a dimension per systemd service,calls/s,Calls to syscall shmdt(2).,stacked,,ebpf.plugin,shm +services.shmctl,,a dimension per systemd service,calls/s,Calls to syscall shmctl(2).,stacked,,ebpf.plugin,shm +apps.shmget_call,,a dimension per app group,calls/s,Calls to syscall shmget(2).,stacked,,ebpf.plugin,shm +apps.shmat_call,,a dimension per app group,calls/s,Calls to syscall shmat(2).,stacked,,ebpf.plugin,shm +apps.shmdt_call,,a dimension per app group,calls/s,Calls to syscall shmdt(2).,stacked,,ebpf.plugin,shm +apps.shmctl_call,,a dimension per app group,calls/s,Calls to syscall shmctl(2).,stacked,,ebpf.plugin,shm +system.shared_memory_calls,,"get, at, dt, ctl",calls/s,Calls to shared memory system calls,line,,ebpf.plugin,shm +system.softirq_latency,,soft IRQs,miliseconds,Software IRQ latency,stacked,,ebpf.plugin,softirq +mount_points.call,,"mount, umount",calls/s,Calls to mount and umount syscalls,line,,ebpf.plugin,mount +mount_points.error,,"mount, umount",calls/s,Errors to mount and umount file systems,line,,ebpf.plugin,mount +cgroup.vfs_unlink,cgroup,delete,calls/s,Files deleted,line,,ebpf.plugin,vfs +cgroup.vfs_write,cgroup,write,calls/s,Write to disk,line,,ebpf.plugin,vfs +cgroup.vfs_write_error,cgroup,write,calls/s,Fails to write,line,,ebpf.plugin,vfs +cgroup.vfs_read,cgroup,read,calls/s,Read from disk,line,,ebpf.plugin,vfs +cgroup.vfs_read_error,cgroup,read,calls/s,Fails to read,line,,ebpf.plugin,vfs +cgroup.vfs_write_bytes,cgroup,write,bytes/s,Bytes written on disk,line,,ebpf.plugin,vfs +cgroup.vfs_read_bytes,cgroup,read,bytes/s,Bytes read from disk,line,,ebpf.plugin,vfs +cgroup.vfs_fsync,cgroup,fsync,calls/s,Calls for vfs_fsync,line,,ebpf.plugin,vfs +cgroup.vfs_fsync_error,cgroup,fsync,calls/s,Sync error,line,,ebpf.plugin,vfs +cgroup.vfs_open,cgroup,open,calls/s,Calls for vfs_open,line,,ebpf.plugin,vfs +cgroup.vfs_open_error,cgroup,open,calls/s,Open error,line,,ebpf.plugin,vfs +cgroup.vfs_create,cgroup,create,calls/s,Calls for vfs_create,line,,ebpf.plugin,vfs +cgroup.vfs_create_error,cgroup,create,calls/s,Create error,line,,ebpf.plugin,vfs +services.vfs_unlink,,a dimension per systemd service,calls/s,Files deleted,stacked,,ebpf.plugin,vfs +services.vfs_write,,a dimension per systemd service,calls/s,Write to disk,stacked,,ebpf.plugin,vfs +services.vfs_write_error,,a dimension per systemd service,calls/s,Fails to write,stacked,,ebpf.plugin,vfs +services.vfs_read,,a dimension per systemd service,calls/s,Read from disk,stacked,,ebpf.plugin,vfs +services.vfs_read_error,,a dimension per systemd service,calls/s,Fails to read,stacked,,ebpf.plugin,vfs +services.vfs_write_bytes,,a dimension per systemd service,bytes/s,Bytes written on disk,stacked,,ebpf.plugin,vfs +services.vfs_read_bytes,,a dimension per systemd service,bytes/s,Bytes read from disk,stacked,,ebpf.plugin,vfs +services.vfs_fsync,,a dimension per systemd service,calls/s,Calls to vfs_fsync,stacked,,ebpf.plugin,vfs +services.vfs_fsync_error,,a dimension per systemd service,calls/s,Sync error,stacked,,ebpf.plugin,vfs +services.vfs_open,,a dimension per systemd service,calls/s,Calls to vfs_open,stacked,,ebpf.plugin,vfs +services.vfs_open_error,,a dimension per systemd service,calls/s,Open error,stacked,,ebpf.plugin,vfs +services.vfs_create,,a dimension per systemd service,calls/s,Calls to vfs_create,stacked,,ebpf.plugin,vfs +services.vfs_create_error,,a dimension per systemd service,calls/s,Create error,stacked,,ebpf.plugin,vfs +filesystem.vfs_deleted_objects,,delete,calls/s,Remove files,line,,ebpf.plugin,vfs +filesystem.vfs_io,,"read, write",calls/s,Calls to IO,line,,ebpf.plugin,vfs +filesystem.vfs_io_bytes,,"read, write",bytes/s,Bytes written and read,line,,ebpf.plugin,vfs +filesystem.vfs_io_error,,"read, write",calls/s,Fails to write or read,line,,ebpf.plugin,vfs +filesystem.vfs_fsync,,fsync,calls/s,Calls for vfs_fsync,line,,ebpf.plugin,vfs +filesystem.vfs_fsync_error,,fsync,calls/s,Fails to synchronize,line,,ebpf.plugin,vfs +filesystem.vfs_open,,open,calls/s,Calls for vfs_open,line,,ebpf.plugin,vfs +filesystem.vfs_open_error,,open,calls/s,Fails to open a file,line,,ebpf.plugin,vfs +filesystem.vfs_create,,create,calls/s,Calls for vfs_create,line,,ebpf.plugin,vfs +filesystem.vfs_create_error,,create,calls/s,Fails to create a file.,line,,ebpf.plugin,vfs +apps.file_deleted,,a dimension per app group,calls/s,Files deleted,stacked,,ebpf.plugin,vfs +apps.vfs_write_call,,a dimension per app group,calls/s,Write to disk,stacked,,ebpf.plugin,vfs +apps.vfs_write_error,,a dimension per app group,calls/s,Fails to write,stacked,,ebpf.plugin,vfs +apps.vfs_read_call,,a dimension per app group,calls/s,Read from disk,stacked,,ebpf.plugin,vfs +apps.vfs_read_error,,a dimension per app group,calls/s,Fails to read,stacked,,ebpf.plugin,vfs +apps.vfs_write_bytes,,a dimension per app group,bytes/s,Bytes written on disk,stacked,,ebpf.plugin,vfs +apps.vfs_read_bytes,,a dimension per app group,bytes/s,Bytes read on disk,stacked,,ebpf.plugin,vfs +apps.vfs_fsync,,a dimension per app group,calls/s,Calls for vfs_fsync,stacked,,ebpf.plugin,vfs +apps.vfs_fsync_error,,a dimension per app group,calls/s,Sync error,stacked,,ebpf.plugin,vfs +apps.vfs_open,,a dimension per app group,calls/s,Calls for vfs_open,stacked,,ebpf.plugin,vfs +apps.vfs_open_error,,a dimension per app group,calls/s,Open error,stacked,,ebpf.plugin,vfs +apps.vfs_create,,a dimension per app group,calls/s,Calls for vfs_create,stacked,,ebpf.plugin,vfs +apps.vfs_create_error,,a dimension per app group,calls/s,Create error,stacked,,ebpf.plugin,vfs +netdata.ebpf_aral_stat_size,,memory,bytes,Bytes allocated for ARAL.,stacked,,ebpf.plugin,process +netdata.ebpf_aral_stat_alloc,,aral,calls,Calls to allocate memory.,stacked,,ebpf.plugin,process +netdata.ebpf_threads,,"total, running",threads,Threads info,line,,ebpf.plugin,process +netdata.ebpf_load_methods,,"legacy, co-re",methods,Load info,line,,ebpf.plugin,process +netdata.ebpf_kernel_memory,,memory_locked,bytes,Memory allocated for hash tables.,line,,ebpf.plugin,process +netdata.ebpf_hash_tables_count,,hash_table,hash tables,Number of hash tables loaded,line,,ebpf.plugin,process +netdata.ebpf_aral_stat_size,,memory,bytes,Bytes allocated for ARAL,stacked,,ebpf.plugin,process +netdata.ebpf_aral_stat_alloc,,aral,calls,Calls to allocate memory,stacked,,ebpf.plugin,process +netdata.ebpf_aral_stat_size,,memory,bytes,Bytes allocated for ARAL.,stacked,,ebpf.plugin,process +netdata.ebpf_aral_stat_alloc,,aral,calls,Calls to allocate memory,stacked,,ebpf.plugin,process diff --git a/collectors/freebsd.plugin/README.md b/collectors/freebsd.plugin/README.md index 3d37a41f..9c33fccb 100644 --- a/collectors/freebsd.plugin/README.md +++ b/collectors/freebsd.plugin/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/free sidebar_label: "FreeBSD system metrics (freebsd.plugin)" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/System metrics" +learn_rel_path: "Integrations/Monitor/System metrics" --> -# freebsd.plugin +# FreeBSD system metrics (freebsd.plugin) Collects resource usage and performance data on FreeBSD systems diff --git a/collectors/freebsd.plugin/freebsd_devstat.c b/collectors/freebsd.plugin/freebsd_devstat.c index d4180d33..65b8a2d5 100644 --- a/collectors/freebsd.plugin/freebsd_devstat.c +++ b/collectors/freebsd.plugin/freebsd_devstat.c @@ -222,10 +222,10 @@ int do_kern_devstat(int update_every, usec_t dt) { CONFIG_BOOLEAN_AUTO); excluded_disks = simple_pattern_create( - config_get(CONFIG_SECTION_KERN_DEVSTAT, "disable by default disks matching", DEFAULT_EXCLUDED_DISKS) - , NULL - , SIMPLE_PATTERN_EXACT - ); + config_get(CONFIG_SECTION_KERN_DEVSTAT, "disable by default disks matching", DEFAULT_EXCLUDED_DISKS), + NULL, + SIMPLE_PATTERN_EXACT, + true); } if (likely(do_system_io || do_io || do_ops || do_qops || do_util || do_iotime || do_await || do_avagsz || do_svctm)) { diff --git a/collectors/freebsd.plugin/freebsd_getifaddrs.c b/collectors/freebsd.plugin/freebsd_getifaddrs.c index f1e67088..80a20910 100644 --- a/collectors/freebsd.plugin/freebsd_getifaddrs.c +++ b/collectors/freebsd.plugin/freebsd_getifaddrs.c @@ -177,15 +177,15 @@ int do_getifaddrs(int update_every, usec_t dt) { CONFIG_BOOLEAN_AUTO); excluded_interfaces = simple_pattern_create( - config_get(CONFIG_SECTION_GETIFADDRS, "disable by default interfaces matching", DEFAULT_EXCLUDED_INTERFACES) - , NULL - , SIMPLE_PATTERN_EXACT - ); + config_get(CONFIG_SECTION_GETIFADDRS, "disable by default interfaces matching", DEFAULT_EXCLUDED_INTERFACES), + NULL, + SIMPLE_PATTERN_EXACT, + true); physical_interfaces = simple_pattern_create( - config_get(CONFIG_SECTION_GETIFADDRS, "set physical interfaces for system.net", DEFAULT_PHYSICAL_INTERFACES) - , NULL - , SIMPLE_PATTERN_EXACT - ); + config_get(CONFIG_SECTION_GETIFADDRS, "set physical interfaces for system.net", DEFAULT_PHYSICAL_INTERFACES), + NULL, + SIMPLE_PATTERN_EXACT, + true); } if (likely(do_bandwidth_ipv4 || do_bandwidth_ipv6 || do_bandwidth || do_packets || do_errors || do_bandwidth_net || do_packets_net || diff --git a/collectors/freebsd.plugin/freebsd_getmntinfo.c b/collectors/freebsd.plugin/freebsd_getmntinfo.c index d17cddfc..cc0abd90 100644 --- a/collectors/freebsd.plugin/freebsd_getmntinfo.c +++ b/collectors/freebsd.plugin/freebsd_getmntinfo.c @@ -143,18 +143,16 @@ int do_getmntinfo(int update_every, usec_t dt) { do_inodes = config_get_boolean_ondemand(CONFIG_SECTION_GETMNTINFO, "inodes usage for all disks", CONFIG_BOOLEAN_AUTO); excluded_mountpoints = simple_pattern_create( - config_get(CONFIG_SECTION_GETMNTINFO, "exclude space metrics on paths", - DEFAULT_EXCLUDED_PATHS) - , NULL - , SIMPLE_PATTERN_EXACT - ); + config_get(CONFIG_SECTION_GETMNTINFO, "exclude space metrics on paths", DEFAULT_EXCLUDED_PATHS), + NULL, + SIMPLE_PATTERN_EXACT, + true); excluded_filesystems = simple_pattern_create( - config_get(CONFIG_SECTION_GETMNTINFO, "exclude space metrics on filesystems", - DEFAULT_EXCLUDED_FILESYSTEMS) - , NULL - , SIMPLE_PATTERN_EXACT - ); + config_get(CONFIG_SECTION_GETMNTINFO, "exclude space metrics on filesystems", DEFAULT_EXCLUDED_FILESYSTEMS), + NULL, + SIMPLE_PATTERN_EXACT, + true); } if (likely(do_space || do_inodes)) { diff --git a/collectors/freebsd.plugin/freebsd_sysctl.c b/collectors/freebsd.plugin/freebsd_sysctl.c index 035309b7..a154c635 100644 --- a/collectors/freebsd.plugin/freebsd_sysctl.c +++ b/collectors/freebsd.plugin/freebsd_sysctl.c @@ -1618,7 +1618,7 @@ int do_net_isr(int update_every, usec_t dt) { all_softnet_charts[i].netisr_cpuid, NULL, "softnet_stat", - NULL, + "cpu.softnet_stat", "Per CPU netisr statistics", "events/s", "freebsd.plugin", diff --git a/collectors/freebsd.plugin/metrics.csv b/collectors/freebsd.plugin/metrics.csv new file mode 100644 index 00000000..3c02a4c2 --- /dev/null +++ b/collectors/freebsd.plugin/metrics.csv @@ -0,0 +1,112 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +system.load,,"load1, load5, load15",load,"System Load Average",line,,freebsd.plugin,vm.loadavg +system.active_processes,,active,processes,"System Active Processes",line,,freebsd.plugin,vm.vmtotal +system.processes,,"running, blocked",processes,"System Processes",line,,freebsd.plugin,vm.vmtotal +mem.real,,used,MiB,"Total Real Memory In Use",area,,freebsd.plugin,vm.vmtotal +system.cpu,,"nice, system, user, interrupt, idle",percentage,"Total CPU utilization",stacked,,freebsd.plugin,kern.cp_time +cpu.cpu,core,"nice, system, user, interrupt, idle",percentage,"Core utilization",stacked,,freebsd.plugin,kern.cp_time +cpu.temperature,,a dimension per core,Celsius,"Core temperature",line,,freebsd.plugin,dev.cpu.temperature +cpu.scaling_cur_freq,,frequency,MHz,"Current CPU Scaling Frequency",line,,freebsd.plugin,dev.cpu.0.freq +system.intr,,interrupts,interrupts/s,"Total Hardware Interrupts",line,,freebsd.plugin,hw.intrcnt +system.interrupts,,a dimension per interrupt,interrupts/s,"System interrupts",stacked,,freebsd.plugin,hw.intrcnt +system.dev_intr,,interrupts,interrupts/s,"Device Interrupts",line,,freebsd.plugin,vm.stats.sys.v_intr +system.soft_intr,,interrupts,interrupts/s,"Software Interrupts",line,,freebsd.plugin,vm.stats.sys.v_soft +system.ctxt,,switches,context switches/s,"CPU Context Switches",line,,freebsd.plugin,vm.stats.sys.v_swtch +system.forks,,started,processes/s,"Started Processes",line,,freebsd.plugin,vm.stats.sys.v_swtch +system.swap,,"free, used",MiB,"System Swap",stacked,,freebsd.plugin,vm.swap_info +system.ram,,"free, active, inactive, wired, cache, laundry, buffers",MiB,"System RAM",stacked,,freebsd.plugin,system.ram +mem.available,,avail,MiB,"Available RAM for applications",line,,freebsd.plugin,system.ram +system.swapio,,"io, out",KiB/s,"Swap I/O",area,,freebsd.plugin,vm.stats.vm.v_swappgs +mem.pgfaults,,"memory, io_requiring, cow, cow_optimized, in_transit",page faults/s,"Memory Page Faults",line,,freebsd.plugin,vm.stats.vm.v_pgfaults +system.ipc_semaphores,,semaphores,semaphores,"IPC Semaphores",area,,freebsd.plugin,kern.ipc.sem +system.ipc_semaphore_arrays,,arrays,arrays,"IPC Semaphore Arrays",area,,freebsd.plugin,kern.ipc.sem +system.ipc_shared_mem_segs,,segments,segments,"IPC Shared Memory Segments",area,,freebsd.plugin,kern.ipc.shm +system.ipc_shared_mem_size,,allocated,KiB,"IPC Shared Memory Segments Size",area,,freebsd.plugin,kern.ipc.shm +system.ipc_msq_queues,,queues,queues,"Number of IPC Message Queues",area,,freebsd.plugin,kern.ipc.msq +system.ipc_msq_messages,,messages,messages,"Number of Messages in IPC Message Queues",area,,freebsd.plugin,kern.ipc.msq +system.ipc_msq_size,,"allocated, used",bytes,"Size of IPC Message Queues",line,,freebsd.plugin,kern.ipc.msq +system.uptime,,uptime,seconds,"System Uptime",line,,freebsd.plugin,uptime +system.softnet_stat,,"dispatched, hybrid_dispatched, qdrops, queued",events/s,"System softnet_stat",line,,freebsd.plugin,net.isr +cpu.softnet_stat,core,"dispatched, hybrid_dispatched, qdrops, queued",events/s,"Per CPU netisr statistics",line,,freebsd.plugin,net.isr +system.io,,"io, out",KiB/s,"Disk I/O",area,,freebsd.plugin,devstat +disk.io,disk,"reads, writes, frees",KiB/s,"Disk I/O Bandwidth",area,,freebsd.plugin,devstat +disk.ops,disk,"reads, writes, other, frees",operations/s,"Disk Completed I/O Operations",line,,freebsd.plugin,devstat +disk.qops,disk,operations,operations,"Disk Current I/O Operations",line,,freebsd.plugin,devstat +disk.util,disk,utilization,% of time working,"Disk Utilization Time",line,,freebsd.plugin,devstat +disk.iotime,disk,"reads, writes, other, frees",milliseconds/s,"Disk Total I/O Time",line,,freebsd.plugin,devstat +disk.await,disk,"reads, writes, other, frees",milliseconds/operation,"Average Completed I/O Operation Time",line,,freebsd.plugin,devstat +disk.avgsz,disk,"reads, writes, frees",KiB/operation,"Average Completed I/O Operation Bandwidth",area,,freebsd.plugin,devstat +disk.svctm,disk,svctm,milliseconds/operation,"Average Service Time",line,,freebsd.plugin,devstat +ipv4.tcpsock,,connections,active connections,"IPv4 TCP Connections",line,,freebsd.plugin,net.inet.tcp.states +ipv4.tcppackets,,"received, sent",packets/s,"IPv4 TCP Packets",line,,freebsd.plugin,net.inet.tcp.stats +ipv4.tcperrors,,"InErrs, InCsumErrors, RetransSegs",packets/s,"IPv4 TCP Errors",line,,freebsd.plugin,net.inet.tcp.stats +ipv4.tcphandshake,,"EstabResets, ActiveOpens, PassiveOpens, AttemptFails",events/s,"IPv4 TCP Handshake Issues",line,,freebsd.plugin,net.inet.tcp.stats +ipv4.tcpconnaborts,,"baddata, userclosed, nomemory, timeout, linger",connections/s,"TCP Connection Aborts",line,,freebsd.plugin,net.inet.tcp.stats +ipv4.tcpofo,,inqueue,packets/s,"TCP Out-Of-Order Queue",line,,freebsd.plugin,net.inet.tcp.stats +ipv4.tcpsyncookies,,"received, sent, failed",packets/s,"TCP SYN Cookies",line,,freebsd.plugin,net.inet.tcp.stats +ipv4.tcplistenissues,,overflows,packets/s,"TCP Listen Socket Issues",line,,freebsd.plugin,net.inet.tcp.stats +ipv4.ecnpkts,,"InCEPkts, InECT0Pkts, InECT1Pkts, OutECT0Pkts, OutECT1Pkts",packets/s,"IPv4 ECN Statistics",line,,freebsd.plugin,net.inet.tcp.stats +ipv4.udppackets,,"received, sent",packets/s,"IPv4 UDP Packets",line,,freebsd.plugin,net.inet.udp.stats +ipv4.udperrors,,"InErrors, NoPorts, RcvbufErrors, InCsumErrors, IgnoredMulti",events/s,"IPv4 UDP Errors",line,,freebsd.plugin,net.inet.udp.stats +ipv4.icmp,,"received, sent",packets/s,"IPv4 ICMP Packets",line,,freebsd.plugin,net.inet.icmp.stats +ipv4.icmp_errors,,"InErrors, OutErrors, InCsumErrors",packets/s,"IPv4 ICMP Errors",line,,freebsd.plugin,net.inet.icmp.stats +ipv4.icmpmsg,,"InEchoReps, OutEchoReps, InEchos, OutEchos",packets/s,"IPv4 ICMP Messages",line,,freebsd.plugin,net.inet.icmp.stats +ipv4.packets,,"received, sent, forwarded, delivered",packets/s,"IPv4 Packets",line,,freebsd.plugin,net.inet.ip.stats +ipv4.fragsout,,"ok, failed, created",packets/s,"IPv4 Fragments Sent",line,,freebsd.plugin,net.inet.ip.stats +ipv4.fragsin,,"ok, failed, all",packets/s,"IPv4 Fragments Reassembly",line,,freebsd.plugin,net.inet.ip.stats +ipv4.errors,,"InDiscards, OutDiscards, InHdrErrors, OutNoRoutes, InAddrErrors, InUnknownProtos",packets/s,"IPv4 Errors",line,,freebsd.plugin,net.inet.ip.stats +ipv6.packets,,"received, sent, forwarded, delivers",packets/s,"IPv6 Packets",line,,freebsd.plugin,net.inet6.ip6.stats +ipv6.fragsout,,"ok, failed, all",packets/s,"IPv6 Fragments Sent",line,,freebsd.plugin,net.inet6.ip6.stats +ipv6.fragsin,,"ok, failed, timeout, all",packets/s,"IPv6 Fragments Reassembly",line,,freebsd.plugin,net.inet6.ip6.stats +ipv6.errors,,"InDiscards, OutDiscards, InHdrErrors, InAddrErrors, InTruncatedPkts, InNoRoutes, OutNoRoutes",packets/s,"IPv6 Errors",line,,freebsd.plugin,net.inet6.ip6.stats +ipv6.icmp,,"received, sent",messages/s,"IPv6 ICMP Messages",line,,freebsd.plugin,net.inet6.icmp6.stats +ipv6.icmpredir,,"received, sent",redirects/s,"IPv6 ICMP Redirects",line,,freebsd.plugin,net.inet6.icmp6.stats +ipv6.icmperrors,,"InErrors, OutErrors, InCsumErrors, InDestUnreachs, InPktTooBigs, InTimeExcds, InParmProblems, OutDestUnreachs, OutTimeExcds, OutParmProblems",errors/s,"IPv6 ICMP Errors",line,,freebsd.plugin,net.inet6.icmp6.stats +ipv6.icmpechos,,"InEchos, OutEchos, InEchoReplies, OutEchoReplies",messages/s,"IPv6 ICMP Echo",line,,freebsd.plugin,net.inet6.icmp6.stats +ipv6.icmprouter,,"InSolicits, OutSolicits, InAdvertisements, OutAdvertisements",messages/s,"IPv6 Router Messages",line,,freebsd.plugin,net.inet6.icmp6.stats +ipv6.icmpneighbor,,"InSolicits, OutSolicits, InAdvertisements, OutAdvertisements",messages/s,"IPv6 Neighbor Messages",line,,freebsd.plugin,net.inet6.icmp6.stats +ipv6.icmptypes,,"InType1, InType128, InType129, InType136, OutType1, OutType128, OutType129, OutType133, OutType135, OutType143",messages/s,"IPv6 ICMP Types",line,,freebsd.plugin,net.inet6.icmp6.stats +ipfw.mem,,"dynamic, static",bytes,"Memory allocated by rules",stacked,,freebsd.plugin,ipfw +ipfw.packets,,a dimension per static rule,packets/s,"Packets",stacked,,freebsd.plugin,ipfw +ipfw.bytes,,a dimension per static rule,bytes/s,"Bytes",stacked,,freebsd.plugin,ipfw +ipfw.active,,a dimension per dynamic rule,rules,"Active rules",stacked,,freebsd.plugin,ipfw +ipfw.expired,,a dimension per dynamic rule,rules,"Expired rules",stacked,,freebsd.plugin,ipfw +system.net,,"received, sent",kilobits/s,"Network Traffic",area,,freebsd.plugin,getifaddrs +system.packets,,"received, sent, multicast_received, multicast_sent",packets/s,"Network Packets",line,,freebsd.plugin,getifaddrs +system.ipv4,,"received, sent",kilobits/s,"IPv4 Bandwidth",area,,freebsd.plugin,getifaddrs +system.ipv6,,"received, sent",kilobits/s,"IPv6 Bandwidth",area,,freebsd.plugin,getifaddrs +net.net,network device,"received, sent",kilobits/s,"Bandwidth",area,,freebsd.plugin,getifaddrs +net.packets,network device,"received, sent, multicast_received, multicast_sent",packets/s,"Packets",line,,freebsd.plugin,getifaddrs +net.errors,network device,"inbound, outbound",errors/s,"Interface Errors",line,,freebsd.plugin,getifaddrs +net.drops,network device,"inbound, outbound",drops/s,"Interface Drops",line,,freebsd.plugin,getifaddrs +net.events,network device,collisions,events/s,"Network Interface Events",line,,freebsd.plugin,getifaddrs +disk.space,mount point,"avail, used, reserved_for_root",GiB,"Disk Space Usage for {mounted dir} [{mounted filesystem}]",stacked,,freebsd.plugin,getmntinfo +disk.inodes,mount point,"avail, used, reserved_for_root",inodes,"Disk Files (inodes) Usage for {mounted dir} [{mounted filesystem}]",stacked,,freebsd.plugin,getmntinfo +zfs.arc_size,,"arcsz, target, min, max",MiB,"ZFS ARC Size",area,,freebsd.plugin,zfs +zfs.l2_size,,"actual, size",MiB,"ZFS L2 ARC Size",area,,freebsd.plugin,zfs +zfs.reads,,"arc, demand, prefetch, metadata, l2",reads/s,"ZFS Reads",area,,freebsd.plugin,zfs +zfs.bytes,,"read, write",KiB/s,"ZFS ARC L2 Read/Write Rate",area,,freebsd.plugin,zfs +zfs.hits,,"hits, misses",percentage,"ZFS ARC Hits",stacked,,freebsd.plugin,zfs +zfs.hits_rate,,"hits, misses",events/s,"ZFS ARC Hits Rate",stacked,,freebsd.plugin,zfs +zfs.dhits,,"hits, misses",percentage,"ZFS Demand Hits",stacked,,freebsd.plugin,zfs +zfs.dhits_rate,,"hits, misses",events/s,"ZFS Demand Hits Rate",stacked,,freebsd.plugin,zfs +zfs.phits,,"hits, misses",percentage,"ZFS Prefetch Hits",stacked,,freebsd.plugin,zfs +zfs.phits_rate,,"hits, misses",events/s,"ZFS Prefetch Hits Rate",stacked,,freebsd.plugin,zfs +zfs.mhits,,"hits, misses",percentage,"ZFS Metadata Hits",stacked,,freebsd.plugin,zfs +zfs.mhits_rate,,"hits, misses",events/s,"ZFS Metadata Hits Rate",stacked,,freebsd.plugin,zfs +zfs.l2hits,,"hits, misses",percentage,"ZFS L2 Hits",stacked,,freebsd.plugin,zfs +zfs.l2hits_rate,,"hits, misses",events/s,"ZFS L2 Hits Rate",stacked,,freebsd.plugin,zfs +zfs.list_hits,,"mfu, mfu_ghost, mru, mru_ghost",hits/s,"ZFS List Hits",area,,freebsd.plugin,zfs +zfs.arc_size_breakdown,,"recent, frequent",percentage,"ZFS ARC Size Breakdown",stacked,,freebsd.plugin,zfs +zfs.memory_ops,,throttled,operations/s,"ZFS Memory Operations",line,,freebsd.plugin,zfs +zfs.important_ops,,"evict_skip, deleted, mutex_miss, hash_collisions",operations/s,"ZFS Important Operations",line,,freebsd.plugin,zfs +zfs.actual_hits,,"hits, misses",percentage,"ZFS Actual Cache Hits",stacked,,freebsd.plugin,zfs +zfs.actual_hits_rate,,"hits, misses",events/s,"ZFS Actual Cache Hits Rate",stacked,,freebsd.plugin,zfs +zfs.demand_data_hits,,"hits, misses",percentage,"ZFS Data Demand Efficiency",stacked,,freebsd.plugin,zfs +zfs.demand_data_hits_rate,,"hits, misses",events/s,"ZFS Data Demand Efficiency Rate",stacked,,freebsd.plugin,zfs +zfs.prefetch_data_hits,,"hits, misses",percentage,"ZFS Data Prefetch Efficiency",stacked,,freebsd.plugin,zfs +zfs.prefetch_data_hits_rate,,"hits, misses",events/s,"ZFS Data Prefetch Efficiency Rate",stacked,,freebsd.plugin,zfs +zfs.hash_elements,,"current, max",elements,"ZFS ARC Hash Elements",line,,freebsd.plugin,zfs +zfs.hash_chains,,"current, max",chains,"ZFS ARC Hash Chains",line,,freebsd.plugin,zfs +zfs.trim_bytes,,TRIMmed,bytes,"Successfully TRIMmed bytes",line,,freebsd.plugin,zfs +zfs.trim_requests,,"successful, failed, unsupported",requests,"TRIM requests",line,,freebsd.plugin,zfs diff --git a/collectors/freeipmi.plugin/README.md b/collectors/freeipmi.plugin/README.md index e33a9d3b..47decd7f 100644 --- a/collectors/freeipmi.plugin/README.md +++ b/collectors/freeipmi.plugin/README.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/free sidebar_label: "freeipmi.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Devices" +learn_rel_path: "Integrations/Monitor/Devices" --> # freeipmi.plugin diff --git a/collectors/freeipmi.plugin/metrics.csv b/collectors/freeipmi.plugin/metrics.csv new file mode 100644 index 00000000..9d493a53 --- /dev/null +++ b/collectors/freeipmi.plugin/metrics.csv @@ -0,0 +1,10 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +ipmi.sel,,events,events,"IPMI Events",area,,freeipmi.plugin, +ipmi.sensors_states,,"nominal, critical, warning",sensors,"IPMI Sensors State",line,,freeipmi.plugin, +ipmi.temperatures_c,,a dimension per sensor,Celsius,"System Celsius Temperatures read by IPMI",line,,freeipmi.plugin, +ipmi.temperatures_f,,a dimension per sensor,Fahrenheit,"System Celsius Temperatures read by IPMI",line,,freeipmi.plugin, +ipmi.voltages,,a dimension per sensor,Volts,"System Voltages read by IPMI",line,,freeipmi.plugin, +ipmi.amps,,a dimension per sensor,Amps,"System Current read by IPMI",line,,freeipmi.plugin, +ipmi.rpm,,a dimension per sensor,RPM,"System Fans read by IPMI",line,,freeipmi.plugin, +ipmi.watts,,a dimension per sensor,Watts,"System Power read by IPMI",line,,freeipmi.plugin, +ipmi.percent,,a dimension per sensor,%,"System Metrics read by IPMI",line,,freeipmi.plugin, \ No newline at end of file diff --git a/collectors/idlejitter.plugin/README.md b/collectors/idlejitter.plugin/README.md index 1a3d8025..9474a2b9 100644 --- a/collectors/idlejitter.plugin/README.md +++ b/collectors/idlejitter.plugin/README.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/idle sidebar_label: "idlejitter.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/QoS" +learn_rel_path: "Integrations/Monitor/QoS" --> # idlejitter.plugin diff --git a/collectors/idlejitter.plugin/metrics.csv b/collectors/idlejitter.plugin/metrics.csv new file mode 100644 index 00000000..05cc1233 --- /dev/null +++ b/collectors/idlejitter.plugin/metrics.csv @@ -0,0 +1,2 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +system.idlejitter,,"min, max, average","microseconds lost/s","CPU Idle Jitter",line,,idlejitter.plugin, \ No newline at end of file diff --git a/collectors/ioping.plugin/README.md b/collectors/ioping.plugin/README.md index 1ab9238f..73fc35fb 100644 --- a/collectors/ioping.plugin/README.md +++ b/collectors/ioping.plugin/README.md @@ -1,16 +1,6 @@ - - -# ioping.plugin - -The ioping plugin supports monitoring latency for any number of directories/files/devices, -by pinging them with `ioping`. +# Monitor I/O latency using ioping.plugin + +The ioping plugin supports monitoring I/O latency for any number of directories/files/devices, by pinging them with `ioping`. A recent version of `ioping` is required (one that supports option `-N`). The supplied plugin can install it, by running: diff --git a/collectors/ioping.plugin/metrics.csv b/collectors/ioping.plugin/metrics.csv new file mode 100644 index 00000000..040ea856 --- /dev/null +++ b/collectors/ioping.plugin/metrics.csv @@ -0,0 +1,2 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +ioping.latency,disk,latency,microseconds,"Read Latency",line,,ioping.plugin, \ No newline at end of file diff --git a/collectors/macos.plugin/README.md b/collectors/macos.plugin/README.md index 3a3e8a1a..509e22ed 100644 --- a/collectors/macos.plugin/README.md +++ b/collectors/macos.plugin/README.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/maco sidebar_label: "macos.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/System metrics" +learn_rel_path: "Integrations/Monitor/System metrics" --> # macos.plugin diff --git a/collectors/macos.plugin/metrics.csv b/collectors/macos.plugin/metrics.csv new file mode 100644 index 00000000..4fee1706 --- /dev/null +++ b/collectors/macos.plugin/metrics.csv @@ -0,0 +1,51 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +system.cpu,,"user, nice, system, idle",percentage,"Total CPU utilization",stacked,,macos.plugin,mach_smi +system.ram,,"active, wired, throttled, compressor, inactive, purgeable, speculative, free",MiB,"System RAM",stacked,,macos.plugin,mach_smi +system.swapio,,"io, out",KiB/s,"Swap I/O",area,,macos.plugin,mach_smi +mem.pgfaults,,"memory, cow, pagein, pageout, compress, decompress, zero_fill, reactivate, purge",faults/s,"Memory Page Faults",line,,macos.plugin,mach_smi +system.load,,"load1, load5, load15",load,"System Load Average",line,,macos.plugin,sysctl +system.swap,,"free, used",MiB,"System Swap",stacked,,macos.plugin,sysctl +system.ipv4,,"received, sent",kilobits/s,"IPv4 Bandwidth",area,,macos.plugin,sysctl +ipv4.tcppackets,,"received, sent",packets/s,"IPv4 TCP Packets",line,,macos.plugin,sysctl +ipv4.tcperrors,,"InErrs, InCsumErrors, RetransSegs",packets/s,"IPv4 TCP Errors",line,,macos.plugin,sysctl +ipv4.tcphandshake,,"EstabResets, ActiveOpens, PassiveOpens, AttemptFails",events/s,"IPv4 TCP Handshake Issues",line,,macos.plugin,sysctl +ipv4.tcpconnaborts,,"baddata, userclosed, nomemory, timeout",connections/s,"TCP Connection Aborts",line,,macos.plugin,sysctl +ipv4.tcpofo,,inqueue,packets/s,"TCP Out-Of-Order Queue",line,,macos.plugin,sysctl +ipv4.tcpsyncookies,,"received, sent, failed",packets/s,"TCP SYN Cookies",line,,macos.plugin,sysctl +ipv4.ecnpkts,,"CEP, NoECTP",packets/s,"IPv4 ECN Statistics",line,,macos.plugin,sysctl +ipv4.udppackets,,"received, sent",packets/s,"IPv4 UDP Packets",line,,macos.plugin,sysctl +ipv4.udperrors,,"RcvbufErrors, InErrors, NoPorts, InCsumErrors, IgnoredMulti",events/s,"IPv4 UDP Errors",line,,macos.plugin,sysctl +ipv4.icmp,,"received, sent",packets/s,"IPv4 ICMP Packets",line,,macos.plugin,sysctl +ipv4.icmp_errors,,"InErrors, OutErrors, InCsumErrors",packets/s,"IPv4 ICMP Errors",line,,macos.plugin,sysctl +ipv4.icmpmsg,,"InEchoReps, OutEchoReps, InEchos, OutEchos",packets/s,"IPv4 ICMP Messages",line,,macos.plugin,sysctl +ipv4.packets,,"received, sent, forwarded, delivered",packets/s,"IPv4 Packets",line,,macos.plugin,sysctl +ipv4.fragsout,,"ok, failed, created",packets/s,"IPv4 Fragments Sent",line,,macos.plugin,sysctl +ipv4.fragsin,,"ok, failed, all",packets/s,"IPv4 Fragments Reassembly",line,,macos.plugin,sysctl +ipv4.errors,,"InDiscards, OutDiscards, InHdrErrors, OutNoRoutes, InAddrErrors, InUnknownProtos",packets/s,"IPv4 Errors",line,,macos.plugin,sysctl +ipv6.packets,,"received, sent, forwarded, delivers",packets/s,"IPv6 Packets",line,,macos.plugin,sysctl +ipv6.fragsout,,"ok, failed, all",packets/s,"IPv6 Fragments Sent",line,,macos.plugin,sysctl +ipv6.fragsin,,"ok, failed, timeout, all",packets/s,"IPv6 Fragments Reassembly",line,,macos.plugin,sysctl +ipv6.errors,,"InDiscards, OutDiscards, InHdrErrors, InAddrErrors, InTruncatedPkts, InNoRoutes, OutNoRoutes",packets/s,"IPv6 Errors",line,,macos.plugin,sysctl +ipv6.icmp,,"received, sent",messages/s,"IPv6 ICMP Messages",line,,macos.plugin,sysctl +ipv6.icmpredir,,"received, sent",redirects/s,"IPv6 ICMP Redirects",line,,macos.plugin,sysctl +ipv6.icmperrors,,"InErrors, OutErrors, InCsumErrors, InDestUnreachs, InPktTooBigs, InTimeExcds, InParmProblems, OutDestUnreachs, OutTimeExcds, OutParmProblems",errors/s,"IPv6 ICMP Errors",line,,macos.plugin,sysctl +ipv6.icmpechos,,"InEchos, OutEchos, InEchoReplies, OutEchoReplies",messages/s,"IPv6 ICMP Echo",line,,macos.plugin,sysctl +ipv6.icmprouter,,"InSolicits, OutSolicits, InAdvertisements, OutAdvertisements",messages/s,"IPv6 Router Messages",line,,macos.plugin,sysctl +ipv6.icmpneighbor,,"InSolicits, OutSolicits, InAdvertisements, OutAdvertisements",messages/s,"IPv6 Neighbor Messages",line,,macos.plugin,sysctl +ipv6.icmptypes,,"InType1, InType128, InType129, InType136, OutType1, OutType128, OutType129, OutType133, OutType135, OutType143",messages/s,"IPv6 ICMP Types",line,,macos.plugin,sysctl +system.uptime,,uptime,seconds,"System Uptime",line,,macos.plugin,sysctl +disk.io,disk,"read, writes",KiB/s,"Disk I/O Bandwidth",area,,macos.plugin,iokit +disk.ops,disk,"read, writes",operations/s,"Disk Completed I/O Operations",line,,macos.plugin,iokit +disk.util,disk,utilization,% of time working,"Disk Utilization Time",area,,macos.plugin,iokit +disk.iotime,disk,"reads, writes",milliseconds/s,"Disk Total I/O Time",line,,macos.plugin,iokit +disk.await,disk,"reads, writes",milliseconds/operation,"Average Completed I/O Operation Time",line,,macos.plugin,iokit +disk.avgsz,disk,"reads, writes",KiB/operation,"Average Completed I/O Operation Bandwidth",line,,macos.plugin,iokit +disk.svctm,disk,svctm,milliseconds/operation,"Average Service Time",line,,macos.plugin,iokit +system.io,,"in, out",KiB/s,"Disk I/O",area,,macos.plugin,iokit +disk.space,mount point,"avail, used, reserved_for_root",GiB,"Disk Space Usage for {mounted dir} [{mounted filesystem}]",stacked,,macos.plugin,iokit +disk.inodes,mount point,"avail, used, reserved_for_root",inodes,"Disk Files (inodes) Usage for {mounted dir} [{mounted filesystem}]",stacked,,macos.plugin,iokit +net.net,network device,"received, sent",kilobits/s,"Bandwidth",area,,macos.plugin,iokit +net.packets,network device,"received, sent, multicast_received, multicast_sent",packets/s,"Packets",line,,macos.plugin,iokit +net.errors,network device,"inbound, outbound",errors/s,"Interface Errors",line,,macos.plugin,iokit +net.drops,network device,inbound,drops/s,"Interface Drops",line,,macos.plugin,iokit +net.events,network device,"frames, collisions, carrier",events/s,"Network Interface Events",line,,macos.plugin,iokit diff --git a/collectors/nfacct.plugin/README.md b/collectors/nfacct.plugin/README.md index f57625c8..e8502236 100644 --- a/collectors/nfacct.plugin/README.md +++ b/collectors/nfacct.plugin/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/nfac sidebar_label: "Netfilter statistics (nfacct.plugin)" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Networking" +learn_rel_path: "Integrations/Monitor/Networking" --> -# nfacct.plugin +# Monitor Netfilter statistics (nfacct.plugin) `nfacct.plugin` collects Netfilter statistics. diff --git a/collectors/nfacct.plugin/metrics.csv b/collectors/nfacct.plugin/metrics.csv new file mode 100644 index 00000000..7bd00d3f --- /dev/null +++ b/collectors/nfacct.plugin/metrics.csv @@ -0,0 +1,8 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +netfilter.netlink_new,,"new, ignore, invalid",connections/s,"Connection Tracker New Connections",line,,nfacct.plugin, +netfilter.netlink_changes,,"insert, delete, delete_list",changes/s,"Connection Tracker Changes",line,,nfacct.plugin, +netfilter.netlink_search,,"searched, search_restart, found",searches/s,"Connection Tracker Searches",line,,nfacct.plugin, +netfilter.netlink_errors,,"icmp_error, insert_failed, drop, early_drop",events/s,"Connection Tracker Errors",line,,nfacct.plugin, +netfilter.netlink_expect,,"created, deleted, new",expectations/s,"Connection Tracker Expectations",line,,nfacct.plugin, +netfilter.nfacct_packets,,a dimension per nfacct object,packets/s,"Netfilter Accounting Packets",line,,nfacct.plugin, +netfilter.nfacct_bytes,,a dimension per nfacct object,kilobytes/s,"Netfilter Accounting Bandwidth",line,,nfacct.plugin, \ No newline at end of file diff --git a/collectors/perf.plugin/README.md b/collectors/perf.plugin/README.md index 9e114363..e519be9c 100644 --- a/collectors/perf.plugin/README.md +++ b/collectors/perf.plugin/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/perf sidebar_label: "CPU performance statistics (perf.plugin)" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/System metrics" +learn_rel_path: "Integrations/Monitor/System metrics" --> -# perf.plugin +# Monitor CPU performance statistics (perf.plugin) `perf.plugin` collects system-wide CPU performance statistics from Performance Monitoring Units (PMU) using the `perf_event_open()` system call. diff --git a/collectors/perf.plugin/metrics.csv b/collectors/perf.plugin/metrics.csv new file mode 100644 index 00000000..786e0743 --- /dev/null +++ b/collectors/perf.plugin/metrics.csv @@ -0,0 +1,18 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +perf.cpu_cycles,,"cpu, ref_cpu",cycles/s,"CPU cycles",line,,perf.plugin, +perf.instructions,,instructions,instructions/s,"Instructions",line,,perf.plugin, +perf.instructions_per_cycle,,ipc,instructions/cycle,"Instructions per Cycle(IPC)",line,,perf.plugin, +perf.branch_instructions,,"instructions, misses",instructions/s,"Branch instructions",line,,perf.plugin, +perf.cache,,"references, misses",operations/s,"Cache operations",line,,perf.plugin, +perf.bus_cycles,,bus,cycles/s,"Bus cycles",line,,perf.plugin, +perf.stalled_cycles,,"frontend, backend",cycles/s,"Stalled frontend and backend cycles",line,,perf.plugin, +perf.migrations,,migrations,migrations,"CPU migrations",line,,perf.plugin, +perf.alignment_faults,,faults,faults,"Alignment faults",line,,perf.plugin, +perf.emulation_faults,,faults,faults,"Emulation faults",line,,perf.plugin, +perf.l1d_cache,,"read_access, read_misses, write_access, write_misses",events/s,"L1D cache operations",line,,perf.plugin, +perf.l1d_cache_prefetch,,prefetches,prefetches/s,"L1D prefetch cache operations",line,,perf.plugin, +perf.l1i_cache,,"read_access, read_misses",events/s,"L1I cache operations",line,,perf.plugin, +perf.ll_cache,,"read_access, read_misses, write_access, write_misses",events/s,"LL cache operations",line,,perf.plugin, +perf.dtlb_cache,,"read_access, read_misses, write_access, write_misses",events/s,"DTLB cache operations",line,,perf.plugin, +perf.itlb_cache,,"read_access, read_misses",events/s,"ITLB cache operations",line,,perf.plugin, +perf.pbu_cache,,read_access,events/s,"PBU cache operations",line,,perf.plugin, \ No newline at end of file diff --git a/collectors/plugins.d/README.md b/collectors/plugins.d/README.md index 8ad1d3a6..1c3b50cb 100644 --- a/collectors/plugins.d/README.md +++ b/collectors/plugins.d/README.md @@ -1,13 +1,13 @@ -# External plugins overview +# External plugins `plugins.d` is the Netdata internal plugin that collects metrics from external processes, thus allowing Netdata to use **external plugins**. @@ -138,7 +138,7 @@ a single program can produce any number of charts with any number of dimensions Charts can be added any time (not just the beginning). -### command line parameters +### Command line parameters The plugin **MUST** accept just **one** parameter: **the number of seconds it is expected to update the values for its charts**. The value passed by Netdata @@ -149,7 +149,7 @@ The external plugin can overwrite the update frequency. For example, the server request per second updates, but the plugin may ignore it and update its charts every 5 seconds. -### environment variables +### Environment variables There are a few environment variables that are set by `netdata` and are available for the plugin to use. @@ -175,6 +175,83 @@ The plugin should output instructions for Netdata to its output (`stdout`). Sinc `DISABLE` will disable this plugin. This will prevent Netdata from restarting the plugin. You can also exit with the value `1` to have the same effect. +#### HOST_DEFINE + +`HOST_DEFINE` defines a new (or updates an existing) virtual host. + +The template is: + +> HOST_DEFINE machine_guid hostname + +where: + +- `machine_guid` + + uniquely identifies the host, this is what will be needed to add charts to the host. + +- `hostname` + + is the hostname of the virtual host + +#### HOST_LABEL + +`HOST_LABEL` adds a key-value pair to the virtual host labels. It has to be given between `HOST_DEFINE` and `HOST_DEFINE_END`. + +The template is: + +> HOST_LABEL key value + +where: + +- `key` + + uniquely identifies the key of the label + +- `value` + + is the value associated with this key + +There are a few special keys that are used to define the system information of the monitored system: + +- `_cloud_provider_type` +- `_cloud_instance_type` +- `_cloud_instance_region` +- `_os_name` +- `_os_version` +- `_kernel_version` +- `_system_cores` +- `_system_cpu_freq` +- `_system_ram_total` +- `_system_disk_space` +- `_architecture` +- `_virtualization` +- `_container` +- `_container_detection` +- `_virt_detection` +- `_is_k8s_node` +- `_install_type` +- `_prebuilt_arch` +- `_prebuilt_dist` + +#### HOST_DEFINE_END + +`HOST_DEFINE_END` commits the host information, creating a new host entity, or updating an existing one with the same `machine_guid`. + +#### HOST + +`HOST` switches data collection between hosts. + +The template is: + +> HOST machine_guid + +where: + +- `machine_guid` + + is the UUID of the host to switch to. After this command, every other command following it is assumed to be associated with this host. + Setting machine_guid to `localhost` switches data collection to the local host. + #### CHART `CHART` defines a new chart. diff --git a/collectors/plugins.d/plugins_d.c b/collectors/plugins.d/plugins_d.c index 7608f3af..dc13cd2e 100644 --- a/collectors/plugins.d/plugins_d.c +++ b/collectors/plugins.d/plugins_d.c @@ -18,7 +18,7 @@ inline size_t pluginsd_initialize_plugin_directories() } // Parse it and store it to plugin directories - return quoted_strings_splitter(plugins_dir_list, plugin_directories, PLUGINSD_MAX_DIRECTORIES, config_isspace, NULL, NULL, 0); + return quoted_strings_splitter(plugins_dir_list, plugin_directories, PLUGINSD_MAX_DIRECTORIES, config_isspace); } static inline void plugin_set_disabled(struct plugind *cd) { @@ -51,6 +51,8 @@ static void pluginsd_worker_thread_cleanup(void *arg) { struct plugind *cd = (struct plugind *)arg; + worker_unregister(); + netdata_spinlock_lock(&cd->unsafe.spinlock); cd->unsafe.running = false; @@ -62,74 +64,73 @@ static void pluginsd_worker_thread_cleanup(void *arg) netdata_spinlock_unlock(&cd->unsafe.spinlock); if (pid) { - info("data collection thread exiting"); - siginfo_t info; - info("killing child process pid %d", pid); + info("PLUGINSD: 'host:%s', killing data collection child process with pid %d", + rrdhost_hostname(cd->host), pid); + if (killpid(pid) != -1) { - info("waiting for child process pid %d to exit...", pid); + info("PLUGINSD: 'host:%s', waiting for data collection child process pid %d to exit...", + rrdhost_hostname(cd->host), pid); + waitid(P_PID, (id_t)pid, &info, WEXITED); } } } #define SERIAL_FAILURES_THRESHOLD 10 -static void pluginsd_worker_thread_handle_success(struct plugind *cd) -{ +static void pluginsd_worker_thread_handle_success(struct plugind *cd) { if (likely(cd->successful_collections)) { sleep((unsigned int)cd->update_every); return; } 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->unsafe.pid, - plugin_is_enabled(cd) ? "Waiting a bit before starting it again." : "Will not start it again - it is now disabled."); + info("PLUGINSD: 'host:%s', '%s' (pid %d) does not generate useful output but it reports success (exits with 0). %s.", + rrdhost_hostname(cd->host), 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; } if (cd->serial_failures > SERIAL_FAILURES_THRESHOLD) { - 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->unsafe.pid, cd->serial_failures); + error("PLUGINSD: 'host:'%s', '%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.", + rrdhost_hostname(cd->host), cd->fullfilename, cd->unsafe.pid, cd->serial_failures); plugin_set_disabled(cd); return; } } -static void pluginsd_worker_thread_handle_error(struct plugind *cd, int worker_ret_code) -{ +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->unsafe.pid); + info("PLUGINSD: 'host:%s', '%s' (pid %d) was killed with SIGTERM. Disabling it.", + rrdhost_hostname(cd->host), 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->unsafe.pid, worker_ret_code); + error("PLUGINSD: 'host:%s', '%s' (pid %d) exited with error code %d and haven't collected any data. Disabling it.", + rrdhost_hostname(cd->host), cd->fullfilename, 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->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."); + error("PLUGINSD: 'host:%s', '%s' (pid %d) exited with error code %d, but has given useful output in the past (%zu times). %s", + rrdhost_hostname(cd->host), 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; } 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)." - "We tried to restart it %zu times, but it failed to generate data. Disabling it.", - cd->fullfilename, cd->unsafe.pid, worker_ret_code, cd->successful_collections, cd->serial_failures); + error("PLUGINSD: 'host:%s', '%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.", + rrdhost_hostname(cd->host), cd->fullfilename, cd->unsafe.pid, worker_ret_code, + cd->successful_collections, cd->serial_failures); plugin_set_disabled(cd); return; } @@ -137,8 +138,7 @@ static void pluginsd_worker_thread_handle_error(struct plugind *cd, int worker_r #undef SERIAL_FAILURES_THRESHOLD -static 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); @@ -151,14 +151,20 @@ static void *pluginsd_worker_thread(void *arg) while (service_running(SERVICE_COLLECTORS)) { FILE *fp_child_input = NULL; 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); + error("PLUGINSD: 'host:%s', cannot popen(\"%s\", \"r\").", rrdhost_hostname(cd->host), cd->cmd); break; } - 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->unsafe.pid, count); + info("PLUGINSD: 'host:%s' connected to '%s' running on pid %d", + rrdhost_hostname(cd->host), cd->fullfilename, cd->unsafe.pid); + + count = pluginsd_process(cd->host, cd, fp_child_input, fp_child_output, 0); + + info("PLUGINSD: 'host:%s', '%s' (pid %d) disconnected after %zu successful data collections (ENDs).", + rrdhost_hostname(cd->host), cd->fullfilename, cd->unsafe.pid, count); + killpid(cd->unsafe.pid); int worker_ret_code = netdata_pclose(fp_child_input, fp_child_output, cd->unsafe.pid); @@ -172,29 +178,29 @@ static void *pluginsd_worker_thread(void *arg) if (unlikely(!plugin_is_enabled(cd))) break; } - worker_unregister(); netdata_thread_cleanup_pop(1); return NULL; } -static void pluginsd_main_cleanup(void *data) -{ +static void pluginsd_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..."); + info("PLUGINSD: cleaning up..."); struct plugind *cd; for (cd = pluginsd_root; cd; cd = cd->next) { netdata_spinlock_lock(&cd->unsafe.spinlock); if (cd->unsafe.enabled && cd->unsafe.running && cd->unsafe.thread != 0) { - info("stopping plugin thread: %s", cd->id); + info("PLUGINSD: 'host:%s', stopping plugin thread: %s", + rrdhost_hostname(cd->host), cd->id); + netdata_thread_cancel(cd->unsafe.thread); } netdata_spinlock_unlock(&cd->unsafe.spinlock); } - info("cleanup completed."); + info("PLUGINSD: cleanup completed."); static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; worker_unregister(); @@ -282,6 +288,7 @@ 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->host = localhost; cd->unsafe.enabled = enabled; cd->unsafe.running = false; @@ -294,9 +301,7 @@ void *pluginsd_main(void *ptr) config_get(cd->id, "command options", def)); // link it - if (likely(pluginsd_root)) - cd->next = pluginsd_root; - pluginsd_root = cd; + DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(pluginsd_root, cd, prev, next); if (plugin_is_enabled(cd)) { char tag[NETDATA_THREAD_TAG_MAX + 1]; diff --git a/collectors/plugins.d/plugins_d.h b/collectors/plugins.d/plugins_d.h index 35af9fe5..68ed4940 100644 --- a/collectors/plugins.d/plugins_d.h +++ b/collectors/plugins.d/plugins_d.h @@ -34,6 +34,17 @@ #define PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE "RSSTATE" #define PLUGINSD_KEYWORD_REPLAY_END "REND" +#define PLUGINSD_KEYWORD_BEGIN_V2 "BEGIN2" +#define PLUGINSD_KEYWORD_SET_V2 "SET2" +#define PLUGINSD_KEYWORD_END_V2 "END2" + +#define PLUGINSD_KEYWORD_HOST_DEFINE "HOST_DEFINE" +#define PLUGINSD_KEYWORD_HOST_DEFINE_END "HOST_DEFINE_END" +#define PLUGINSD_KEYWORD_HOST_LABEL "HOST_LABEL" +#define PLUGINSD_KEYWORD_HOST "HOST" + +#define PLUGINSD_KEYWORD_EXIT "EXIT" + #define PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT 10 // seconds #define PLUGINSD_LINE_MAX_SSL_READ 512 @@ -56,6 +67,7 @@ struct plugind { size_t serial_failures; // the number of times the plugin started // without collecting values + RRDHOST *host; // the host the plugin collects data for int update_every; // the plugin default data collection frequency struct { @@ -67,7 +79,8 @@ struct plugind { } unsafe; time_t started_t; - uint32_t capabilities; // follows the same principles as streaming capabilities + + struct plugind *prev; struct plugind *next; }; diff --git a/collectors/plugins.d/pluginsd_parser.c b/collectors/plugins.d/pluginsd_parser.c index 2c0f2cbc..28fc0bd4 100644 --- a/collectors/plugins.d/pluginsd_parser.c +++ b/collectors/plugins.d/pluginsd_parser.c @@ -71,20 +71,109 @@ static inline RRDSET *pluginsd_require_chart_from_parent(void *user, const char return st; } -static inline RRDDIM_ACQUIRED *pluginsd_acquire_dimension(RRDHOST *host, RRDSET *st, const char *dimension, const char *cmd) { +static inline RRDSET *pluginsd_get_chart_from_parent(void *user) { + return ((PARSER_USER_OBJECT *) user)->st; +} + +static inline void pluginsd_lock_rrdset_data_collection(void *user) { + PARSER_USER_OBJECT *u = (PARSER_USER_OBJECT *) user; + if(u->st && !u->v2.locked_data_collection) { + netdata_spinlock_lock(&u->st->data_collection_lock); + u->v2.locked_data_collection = true; + } +} + +static inline bool pluginsd_unlock_rrdset_data_collection(void *user) { + PARSER_USER_OBJECT *u = (PARSER_USER_OBJECT *) user; + if(u->st && u->v2.locked_data_collection) { + netdata_spinlock_unlock(&u->st->data_collection_lock); + u->v2.locked_data_collection = false; + return true; + } + + return false; +} + +void pluginsd_rrdset_cleanup(RRDSET *st) { + for(size_t i = 0; i < st->pluginsd.used ; i++) { + if (st->pluginsd.rda[i]) { + rrddim_acquired_release(st->pluginsd.rda[i]); + st->pluginsd.rda[i] = NULL; + } + } + freez(st->pluginsd.rda); + st->pluginsd.rda = NULL; + st->pluginsd.size = 0; + st->pluginsd.used = 0; + st->pluginsd.pos = 0; +} + +static inline void pluginsd_set_chart_from_parent(void *user, RRDSET *st, const char *keyword) { + PARSER_USER_OBJECT *u = (PARSER_USER_OBJECT *) user; + + if(unlikely(pluginsd_unlock_rrdset_data_collection(user))) { + error("PLUGINSD: 'host:%s/chart:%s/' stale data collection lock found during %s; it has been unlocked", + rrdhost_hostname(u->st->rrdhost), rrdset_id(u->st), keyword); + } + + if(unlikely(u->v2.ml_locked)) { + ml_chart_update_end(u->st); + u->v2.ml_locked = false; + + error("PLUGINSD: 'host:%s/chart:%s/' stale ML lock found during %s, it has been unlocked", + rrdhost_hostname(u->st->rrdhost), rrdset_id(u->st), keyword); + } + + if(st) { + size_t dims = dictionary_entries(st->rrddim_root_index); + if(unlikely(st->pluginsd.size < dims)) { + st->pluginsd.rda = reallocz(st->pluginsd.rda, dims * sizeof(RRDDIM_ACQUIRED *)); + st->pluginsd.size = dims; + } + + if(st->pluginsd.pos > st->pluginsd.used && st->pluginsd.pos <= st->pluginsd.size) + st->pluginsd.used = st->pluginsd.pos; + + st->pluginsd.pos = 0; + } + + u->st = st; +} + +static inline RRDDIM *pluginsd_acquire_dimension(RRDHOST *host, RRDSET *st, const char *dimension, const char *cmd) { if (unlikely(!dimension || !*dimension)) { error("PLUGINSD: 'host:%s/chart:%s' got a %s, without a dimension.", rrdhost_hostname(host), rrdset_id(st), cmd); return NULL; } - RRDDIM_ACQUIRED *rda = rrddim_find_and_acquire(st, dimension); + RRDDIM_ACQUIRED *rda; - if (unlikely(!rda)) + if(likely(st->pluginsd.pos < st->pluginsd.used)) { + rda = st->pluginsd.rda[st->pluginsd.pos]; + RRDDIM *rd = rrddim_acquired_to_rrddim(rda); + if (likely(rd && string_strcmp(rd->id, dimension) == 0)) { + st->pluginsd.pos++; + return rd; + } + else { + rrddim_acquired_release(rda); + st->pluginsd.rda[st->pluginsd.pos] = NULL; + } + } + + rda = rrddim_find_and_acquire(st, dimension); + if (unlikely(!rda)) { error("PLUGINSD: 'host:%s/chart:%s/dim:%s' got a %s but dimension does not exist.", rrdhost_hostname(host), rrdset_id(st), dimension, cmd); - return rda; + return NULL; + } + + if(likely(st->pluginsd.pos < st->pluginsd.size)) + st->pluginsd.rda[st->pluginsd.pos++] = rda; + + return rrddim_acquired_to_rrddim(rda); } static inline RRDSET *pluginsd_find_chart(RRDHOST *host, const char *chart, const char *cmd) { @@ -102,8 +191,14 @@ static inline RRDSET *pluginsd_find_chart(RRDHOST *host, const char *chart, cons return st; } -static inline PARSER_RC PLUGINSD_DISABLE_PLUGIN(void *user) { +static inline PARSER_RC PLUGINSD_DISABLE_PLUGIN(void *user, const char *keyword, const char *msg) { ((PARSER_USER_OBJECT *) user)->enabled = 0; + + if(keyword && msg) { + error_limit_static_global_var(erl, 1, 0); + error_limit(&erl, "PLUGINSD: keyword %s: %s", keyword, msg); + } + return PARSER_RC_ERROR; } @@ -113,24 +208,21 @@ PARSER_RC pluginsd_set(char **words, size_t num_words, void *user) char *value = get_word(words, num_words, 2); RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_SET); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); RRDSET *st = pluginsd_require_chart_from_parent(user, PLUGINSD_KEYWORD_SET, PLUGINSD_KEYWORD_CHART); - if(!st) return PLUGINSD_DISABLE_PLUGIN(user); - - RRDDIM_ACQUIRED *rda = pluginsd_acquire_dimension(host, st, dimension, PLUGINSD_KEYWORD_SET); - if(!rda) return PLUGINSD_DISABLE_PLUGIN(user); + if(!st) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); - RRDDIM *rd = rrddim_acquired_to_rrddim(rda); + RRDDIM *rd = pluginsd_acquire_dimension(host, st, dimension, PLUGINSD_KEYWORD_SET); + if(!rd) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); if (unlikely(rrdset_flag_check(st, RRDSET_FLAG_DEBUG))) debug(D_PLUGINSD, "PLUGINSD: 'host:%s/chart:%s/dim:%s' SET is setting value to '%s'", rrdhost_hostname(host), rrdset_id(st), dimension, value && *value ? value : "UNSET"); if (value && *value) - rrddim_set_by_pointer(st, rd, strtoll(value, NULL, 0)); + rrddim_set_by_pointer(st, rd, str2ll_encoded(value)); - rrddim_acquired_release(rda); return PARSER_RC_OK; } @@ -140,12 +232,12 @@ PARSER_RC pluginsd_begin(char **words, size_t num_words, void *user) char *microseconds_txt = get_word(words, num_words, 2); RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_BEGIN); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); RRDSET *st = pluginsd_find_chart(host, id, PLUGINSD_KEYWORD_BEGIN); - if(!st) return PLUGINSD_DISABLE_PLUGIN(user); + if(!st) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); - ((PARSER_USER_OBJECT *)user)->st = st; + pluginsd_set_chart_from_parent(user, st, PLUGINSD_KEYWORD_BEGIN); usec_t microseconds = 0; if (microseconds_txt && *microseconds_txt) { @@ -187,16 +279,16 @@ PARSER_RC pluginsd_end(char **words, size_t num_words, void *user) UNUSED(num_words); RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_END); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); RRDSET *st = pluginsd_require_chart_from_parent(user, PLUGINSD_KEYWORD_END, PLUGINSD_KEYWORD_BEGIN); - if(!st) return PLUGINSD_DISABLE_PLUGIN(user); + if(!st) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); if (unlikely(rrdset_flag_check(st, RRDSET_FLAG_DEBUG))) debug(D_PLUGINSD, "requested an END on chart '%s'", rrdset_id(st)); - ((PARSER_USER_OBJECT *) user)->st = NULL; - ((PARSER_USER_OBJECT *) user)->count++; + pluginsd_set_chart_from_parent(user, NULL, PLUGINSD_KEYWORD_END); + ((PARSER_USER_OBJECT *) user)->data_collections_count++; struct timeval now; now_realtime_timeval(&now); @@ -205,10 +297,151 @@ PARSER_RC pluginsd_end(char **words, size_t num_words, void *user) return PARSER_RC_OK; } +static void pluginsd_host_define_cleanup(void *user) { + PARSER_USER_OBJECT *u = user; + + string_freez(u->host_define.hostname); + dictionary_destroy(u->host_define.rrdlabels); + + u->host_define.hostname = NULL; + u->host_define.rrdlabels = NULL; + u->host_define.parsing_host = false; +} + +static inline bool pluginsd_validate_machine_guid(const char *guid, uuid_t *uuid, char *output) { + if(uuid_parse(guid, *uuid)) + return false; + + uuid_unparse_lower(*uuid, output); + + return true; +} + +static PARSER_RC pluginsd_host_define(char **words, size_t num_words, void *user) { + PARSER_USER_OBJECT *u = user; + + char *guid = get_word(words, num_words, 1); + char *hostname = get_word(words, num_words, 2); + + if(unlikely(!guid || !*guid || !hostname || !*hostname)) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_HOST_DEFINE, "missing parameters"); + + if(unlikely(u->host_define.parsing_host)) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_HOST_DEFINE, + "another host definition is already open - did you send " PLUGINSD_KEYWORD_HOST_DEFINE_END "?"); + + if(!pluginsd_validate_machine_guid(guid, &u->host_define.machine_guid, u->host_define.machine_guid_str)) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_HOST_DEFINE, "cannot parse MACHINE_GUID - is it a valid UUID?"); + + u->host_define.hostname = string_strdupz(hostname); + u->host_define.rrdlabels = rrdlabels_create(); + u->host_define.parsing_host = true; + + return PARSER_RC_OK; +} + +static inline PARSER_RC pluginsd_host_dictionary(char **words, size_t num_words, void *user, DICTIONARY *dict, const char *keyword) { + PARSER_USER_OBJECT *u = user; + + char *name = get_word(words, num_words, 1); + char *value = get_word(words, num_words, 2); + + if(!name || !*name || !value) + return PLUGINSD_DISABLE_PLUGIN(user, keyword, "missing parameters"); + + if(!u->host_define.parsing_host || !dict) + return PLUGINSD_DISABLE_PLUGIN(user, keyword, "host is not defined, send " PLUGINSD_KEYWORD_HOST_DEFINE " before this"); + + rrdlabels_add(dict, name, value, RRDLABEL_SRC_CONFIG); + + return PARSER_RC_OK; +} + +static PARSER_RC pluginsd_host_labels(char **words, size_t num_words, void *user) { + PARSER_USER_OBJECT *u = user; + return pluginsd_host_dictionary(words, num_words, user, u->host_define.rrdlabels, PLUGINSD_KEYWORD_HOST_LABEL); +} + +static PARSER_RC pluginsd_host_define_end(char **words __maybe_unused, size_t num_words __maybe_unused, void *user) { + PARSER_USER_OBJECT *u = user; + + if(!u->host_define.parsing_host) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_HOST_DEFINE_END, "missing initialization, send " PLUGINSD_KEYWORD_HOST_DEFINE " before this"); + + RRDHOST *host = rrdhost_find_or_create( + string2str(u->host_define.hostname), + string2str(u->host_define.hostname), + u->host_define.machine_guid_str, + "Netdata Virtual Host 1.0", + netdata_configured_timezone, + netdata_configured_abbrev_timezone, + netdata_configured_utc_offset, + NULL, + 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, + rrdhost_labels_to_system_info(u->host_define.rrdlabels), + false + ); + + if(host->rrdlabels) { + rrdlabels_migrate_to_these(host->rrdlabels, u->host_define.rrdlabels); + } + else { + host->rrdlabels = u->host_define.rrdlabels; + u->host_define.rrdlabels = NULL; + } + + pluginsd_host_define_cleanup(user); + + u->host = host; + pluginsd_set_chart_from_parent(user, NULL, PLUGINSD_KEYWORD_HOST_DEFINE_END); + + rrdhost_flag_clear(host, RRDHOST_FLAG_ORPHAN); + rrdcontext_host_child_connected(host); + schedule_node_info_update(host); + + return PARSER_RC_OK; +} + +static PARSER_RC pluginsd_host(char **words, size_t num_words, void *user) { + PARSER_USER_OBJECT *u = user; + + char *guid = get_word(words, num_words, 1); + + if(!guid || !*guid || strcmp(guid, "localhost") == 0) { + u->host = localhost; + return PARSER_RC_OK; + } + + uuid_t uuid; + char uuid_str[UUID_STR_LEN]; + if(!pluginsd_validate_machine_guid(guid, &uuid, uuid_str)) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_HOST, "cannot parse MACHINE_GUID - is it a valid UUID?"); + + RRDHOST *host = rrdhost_find_by_guid(uuid_str); + if(unlikely(!host)) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_HOST, "cannot find a host with this machine guid - have you created it?"); + + u->host = host; + + return PARSER_RC_OK; +} + PARSER_RC pluginsd_chart(char **words, size_t num_words, void *user) { RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_CHART); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); char *type = get_word(words, num_words, 1); char *name = get_word(words, num_words, 2); @@ -231,19 +464,14 @@ PARSER_RC pluginsd_chart(char **words, size_t num_words, void *user) } // make sure we have the required variables - if (unlikely((!type || !*type || !id || !*id))) { - error("PLUGINSD: 'host:%s' requested a CHART, without a type.id. Disabling it.", - rrdhost_hostname(host)); - - ((PARSER_USER_OBJECT *) user)->enabled = 0; - return PARSER_RC_ERROR; - } + if (unlikely((!type || !*type || !id || !*id))) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_CHART, "missing parameters"); // parse the name, and make sure it does not include 'type.' if (unlikely(name && *name)) { // when data are streamed from child nodes // name will be type.name - // so we have to remove 'type.' from name too + // so, we have to remove 'type.' from name too size_t len = strlen(type); if (strncmp(type, name, len) == 0 && name[len] == '.') name = &name[len + 1]; @@ -320,7 +548,7 @@ PARSER_RC pluginsd_chart(char **words, size_t num_words, void *user) rrdset_flag_clear(st, RRDSET_FLAG_STORE_FIRST); } } - ((PARSER_USER_OBJECT *)user)->st = st; + pluginsd_set_chart_from_parent(user, st, PLUGINSD_KEYWORD_CHART); return PARSER_RC_OK; } @@ -332,10 +560,10 @@ PARSER_RC pluginsd_chart_definition_end(char **words, size_t num_words, void *us 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); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); RRDSET *st = pluginsd_require_chart_from_parent(user, PLUGINSD_KEYWORD_CHART_DEFINITION_END, PLUGINSD_KEYWORD_CHART); - if(!st) return PLUGINSD_DISABLE_PLUGIN(user); + if(!st) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); 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; @@ -379,33 +607,24 @@ PARSER_RC pluginsd_dimension(char **words, size_t num_words, void *user) char *options = get_word(words, num_words, 6); RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_DIMENSION); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); RRDSET *st = pluginsd_require_chart_from_parent(user, PLUGINSD_KEYWORD_DIMENSION, PLUGINSD_KEYWORD_CHART); - if(!st) return PLUGINSD_DISABLE_PLUGIN(user); + if(!st) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); - if (unlikely(!id)) { - error("PLUGINSD: 'host:%s/chart:%s' got a DIMENSION, without an id. Disabling it.", - rrdhost_hostname(host), st ? rrdset_id(st) : "UNSET"); - return PLUGINSD_DISABLE_PLUGIN(user); - } - - if (unlikely(!st && !((PARSER_USER_OBJECT *) user)->st_exists)) { - error("PLUGINSD: 'host:%s' got a DIMENSION, without a CHART. Disabling it.", - rrdhost_hostname(host)); - return PLUGINSD_DISABLE_PLUGIN(user); - } + if (unlikely(!id)) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_DIMENSION, "missing dimension id"); long multiplier = 1; if (multiplier_s && *multiplier_s) { - multiplier = strtol(multiplier_s, NULL, 0); + multiplier = str2ll_encoded(multiplier_s); if (unlikely(!multiplier)) multiplier = 1; } long divisor = 1; if (likely(divisor_s && *divisor_s)) { - divisor = strtol(divisor_s, NULL, 0); + divisor = str2ll_encoded(divisor_s); if (unlikely(!divisor)) divisor = 1; } @@ -683,7 +902,7 @@ PARSER_RC pluginsd_function_result_begin(char **words, size_t num_words, void *u } else { if(format && *format) - pf->destination_wb->contenttype = functions_format_to_content_type(format); + pf->destination_wb->content_type = functions_format_to_content_type(format); pf->code = code; @@ -712,9 +931,9 @@ PARSER_RC pluginsd_variable(char **words, size_t num_words, void *user) NETDATA_DOUBLE v; RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_VARIABLE); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); - RRDSET *st = ((PARSER_USER_OBJECT *) user)->st; + RRDSET *st = pluginsd_get_chart_from_parent(user); int global = (st) ? 0 : 1; @@ -730,13 +949,8 @@ PARSER_RC pluginsd_variable(char **words, size_t num_words, void *user) } } - if (unlikely(!name || !*name)) { - error("PLUGINSD: 'host:%s/chart:%s' got a VARIABLE without a variable name. Disabling it.", - rrdhost_hostname(host), st ? rrdset_id(st):"UNSET"); - - ((PARSER_USER_OBJECT *)user)->enabled = 0; - return PLUGINSD_DISABLE_PLUGIN(user); - } + if (unlikely(!name || !*name)) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_VARIABLE, "missing variable name"); if (unlikely(!value || !*value)) value = NULL; @@ -750,17 +964,11 @@ PARSER_RC pluginsd_variable(char **words, size_t num_words, void *user) return PARSER_RC_OK; } - if (!global && !st) { - error("PLUGINSD: 'host:%s/chart:%s' cannot update CHART VARIABLE '%s' without a chart", - rrdhost_hostname(host), - st ? rrdset_id(st):"UNSET", - name - ); - return PLUGINSD_DISABLE_PLUGIN(user); - } + if (!global && !st) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_VARIABLE, "no chart is defined and no GLOBAL is given"); char *endptr = NULL; - v = (NETDATA_DOUBLE)str2ndd(value, &endptr); + v = (NETDATA_DOUBLE) str2ndd_encoded(value, &endptr); if (unlikely(endptr && *endptr)) { if (endptr == value) error("PLUGINSD: 'host:%s/chart:%s' the value '%s' of VARIABLE '%s' cannot be parsed as a number", @@ -803,8 +1011,8 @@ PARSER_RC pluginsd_variable(char **words, size_t num_words, void *user) PARSER_RC pluginsd_flush(char **words __maybe_unused, size_t num_words __maybe_unused, void *user) { - debug(D_PLUGINSD, "requested a FLUSH"); - ((PARSER_USER_OBJECT *) user)->st = NULL; + debug(D_PLUGINSD, "requested a " PLUGINSD_KEYWORD_FLUSH); + pluginsd_set_chart_from_parent(user, NULL, PLUGINSD_KEYWORD_FLUSH); ((PARSER_USER_OBJECT *) user)->replay.start_time = 0; ((PARSER_USER_OBJECT *) user)->replay.end_time = 0; ((PARSER_USER_OBJECT *) user)->replay.start_time_ut = 0; @@ -816,7 +1024,7 @@ PARSER_RC pluginsd_disable(char **words __maybe_unused, size_t num_words __maybe { info("PLUGINSD: plugin called DISABLE. Disabling it."); ((PARSER_USER_OBJECT *) user)->enabled = 0; - return PARSER_RC_ERROR; + return PARSER_RC_STOP; } PARSER_RC pluginsd_label(char **words, size_t num_words, void *user) @@ -825,10 +1033,8 @@ PARSER_RC pluginsd_label(char **words, size_t num_words, void *user) const char *label_source = get_word(words, num_words, 2); const char *value = get_word(words, num_words, 3); - if (!name || !label_source || !value) { - error("PLUGINSD: ignoring malformed or empty LABEL command."); - return PLUGINSD_DISABLE_PLUGIN(user); - } + if (!name || !label_source || !value) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_LABEL, "missing parameters"); char *store = (char *)value; bool allocated_store = false; @@ -874,7 +1080,7 @@ PARSER_RC pluginsd_label(char **words, size_t num_words, void *user) PARSER_RC pluginsd_overwrite(char **words __maybe_unused, size_t num_words __maybe_unused, void *user) { RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_OVERWRITE); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); debug(D_PLUGINSD, "requested to OVERWRITE host labels"); @@ -898,11 +1104,12 @@ PARSER_RC pluginsd_clabel(char **words, size_t num_words, void *user) if (!name || !value || !*label_source) { error("Ignoring malformed or empty CHART LABEL command."); - return PLUGINSD_DISABLE_PLUGIN(user); + return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); } if(unlikely(!((PARSER_USER_OBJECT *) user)->chart_rrdlabels_linked_temporarily)) { - ((PARSER_USER_OBJECT *)user)->chart_rrdlabels_linked_temporarily = ((PARSER_USER_OBJECT *)user)->st->rrdlabels; + RRDSET *st = pluginsd_get_chart_from_parent(user); + ((PARSER_USER_OBJECT *)user)->chart_rrdlabels_linked_temporarily = st->rrdlabels; rrdlabels_unmark_all(((PARSER_USER_OBJECT *)user)->chart_rrdlabels_linked_temporarily); } @@ -915,17 +1122,17 @@ PARSER_RC pluginsd_clabel(char **words, size_t num_words, void *user) PARSER_RC pluginsd_clabel_commit(char **words __maybe_unused, size_t num_words __maybe_unused, void *user) { RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_CLABEL_COMMIT); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); RRDSET *st = pluginsd_require_chart_from_parent(user, PLUGINSD_KEYWORD_CLABEL_COMMIT, PLUGINSD_KEYWORD_BEGIN); - if(!st) return PLUGINSD_DISABLE_PLUGIN(user); + if(!st) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); debug(D_PLUGINSD, "requested to commit chart labels"); if(!((PARSER_USER_OBJECT *)user)->chart_rrdlabels_linked_temporarily) { error("PLUGINSD: 'host:%s' got CLABEL_COMMIT, without a CHART or BEGIN. Ignoring it.", rrdhost_hostname(host)); - return PLUGINSD_DISABLE_PLUGIN(user); + return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); } rrdlabels_remove_all_unmarked(((PARSER_USER_OBJECT *)user)->chart_rrdlabels_linked_temporarily); @@ -937,15 +1144,14 @@ PARSER_RC pluginsd_clabel_commit(char **words __maybe_unused, size_t num_words _ return PARSER_RC_OK; } -PARSER_RC pluginsd_replay_rrdset_begin(char **words, size_t num_words, void *user) -{ +PARSER_RC pluginsd_replay_begin(char **words, size_t num_words, void *user) { char *id = get_word(words, num_words, 1); char *start_time_str = get_word(words, num_words, 2); char *end_time_str = get_word(words, num_words, 3); char *child_now_str = get_word(words, num_words, 4); RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_REPLAY_BEGIN); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); RRDSET *st; if (likely(!id || !*id)) @@ -953,17 +1159,17 @@ PARSER_RC pluginsd_replay_rrdset_begin(char **words, size_t num_words, void *use else st = pluginsd_find_chart(host, id, PLUGINSD_KEYWORD_REPLAY_BEGIN); - if(!st) return PLUGINSD_DISABLE_PLUGIN(user); - ((PARSER_USER_OBJECT *) user)->st = st; + if(!st) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); + pluginsd_set_chart_from_parent(user, st, PLUGINSD_KEYWORD_REPLAY_BEGIN); if(start_time_str && end_time_str) { - time_t start_time = (time_t)str2ul(start_time_str); - time_t end_time = (time_t)str2ul(end_time_str); + time_t start_time = (time_t) str2ull_encoded(start_time_str); + time_t end_time = (time_t) str2ull_encoded(end_time_str); time_t wall_clock_time = 0, tolerance; bool wall_clock_comes_from_child; (void)wall_clock_comes_from_child; if(child_now_str) { - wall_clock_time = (time_t)str2ul(child_now_str); + wall_clock_time = (time_t) str2ull_encoded(child_now_str); tolerance = st->update_every + 1; wall_clock_comes_from_child = true; } @@ -1016,7 +1222,9 @@ PARSER_RC pluginsd_replay_rrdset_begin(char **words, size_t num_words, void *use return PARSER_RC_OK; } - error("PLUGINSD REPLAY ERROR: 'host:%s/chart:%s' got a " PLUGINSD_KEYWORD_REPLAY_BEGIN " from %ld to %ld, but timestamps are invalid (now is %ld [%s], tolerance %ld). Ignoring " PLUGINSD_KEYWORD_REPLAY_SET, + error("PLUGINSD REPLAY ERROR: 'host:%s/chart:%s' got a " PLUGINSD_KEYWORD_REPLAY_BEGIN + " from %ld to %ld, but timestamps are invalid " + "(now is %ld [%s], tolerance %ld). Ignoring " PLUGINSD_KEYWORD_REPLAY_SET, rrdhost_hostname(st->rrdhost), rrdset_id(st), start_time, end_time, wall_clock_time, wall_clock_comes_from_child ? "child wall clock" : "parent wall clock", tolerance); } @@ -1033,6 +1241,33 @@ PARSER_RC pluginsd_replay_rrdset_begin(char **words, size_t num_words, void *use return PARSER_RC_OK; } +static inline SN_FLAGS pluginsd_parse_storage_number_flags(const char *flags_str) { + SN_FLAGS flags = SN_FLAG_NONE; + + char c; + while ((c = *flags_str++)) { + switch (c) { + case 'A': + flags |= SN_FLAG_NOT_ANOMALOUS; + break; + + case 'R': + flags |= SN_FLAG_RESET; + break; + + case 'E': + flags = SN_EMPTY_SLOT; + return flags; + + default: + internal_error(true, "Unknown SN_FLAGS flag '%c'", c); + break; + } + } + + return flags; +} + PARSER_RC pluginsd_replay_set(char **words, size_t num_words, void *user) { char *dimension = get_word(words, num_words, 1); @@ -1040,31 +1275,34 @@ PARSER_RC pluginsd_replay_set(char **words, size_t num_words, void *user) char *flags_str = get_word(words, num_words, 3); RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_REPLAY_SET); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); RRDSET *st = pluginsd_require_chart_from_parent(user, PLUGINSD_KEYWORD_REPLAY_SET, PLUGINSD_KEYWORD_REPLAY_BEGIN); - if(!st) return PLUGINSD_DISABLE_PLUGIN(user); + if(!st) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); - if(!((PARSER_USER_OBJECT *) user)->replay.rset_enabled) { + PARSER_USER_OBJECT *u = user; + if(!u->replay.rset_enabled) { error_limit_static_thread_var(erl, 1, 0); - error_limit(&erl, "PLUGINSD: 'host:%s/chart:%s' got a " PLUGINSD_KEYWORD_REPLAY_SET " but it is disabled by " PLUGINSD_KEYWORD_REPLAY_BEGIN " errors", - rrdhost_hostname(host), rrdset_id(st)); + error_limit(&erl, "PLUGINSD: 'host:%s/chart:%s' got a %s but it is disabled by %s errors", + rrdhost_hostname(host), rrdset_id(st), PLUGINSD_KEYWORD_REPLAY_SET, PLUGINSD_KEYWORD_REPLAY_BEGIN); // we have to return OK here return PARSER_RC_OK; } - RRDDIM_ACQUIRED *rda = pluginsd_acquire_dimension(host, st, dimension, PLUGINSD_KEYWORD_REPLAY_SET); - if(!rda) return PLUGINSD_DISABLE_PLUGIN(user); + RRDDIM *rd = pluginsd_acquire_dimension(host, st, dimension, PLUGINSD_KEYWORD_REPLAY_SET); + if(!rd) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); - if (unlikely(!((PARSER_USER_OBJECT *) user)->replay.start_time || !((PARSER_USER_OBJECT *) user)->replay.end_time)) { - error("PLUGINSD: 'host:%s/chart:%s/dim:%s' got a " PLUGINSD_KEYWORD_REPLAY_SET " with invalid timestamps %ld to %ld from a " PLUGINSD_KEYWORD_REPLAY_BEGIN ". Disabling it.", + if (unlikely(!u->replay.start_time || !u->replay.end_time)) { + error("PLUGINSD: 'host:%s/chart:%s/dim:%s' got a %s with invalid timestamps %ld to %ld from a %s. Disabling it.", rrdhost_hostname(host), rrdset_id(st), dimension, - ((PARSER_USER_OBJECT *) user)->replay.start_time, - ((PARSER_USER_OBJECT *) user)->replay.end_time); - return PLUGINSD_DISABLE_PLUGIN(user); + PLUGINSD_KEYWORD_REPLAY_SET, + u->replay.start_time, + u->replay.end_time, + PLUGINSD_KEYWORD_REPLAY_BEGIN); + return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); } if (unlikely(!value_str || !*value_str)) @@ -1074,39 +1312,19 @@ PARSER_RC pluginsd_replay_set(char **words, size_t num_words, void *user) flags_str = ""; if (likely(value_str)) { - RRDDIM *rd = rrddim_acquired_to_rrddim(rda); - RRDDIM_FLAGS rd_flags = rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE | RRDDIM_FLAG_ARCHIVED); if(!(rd_flags & RRDDIM_FLAG_ARCHIVED)) { - NETDATA_DOUBLE value = strtondd(value_str, NULL); - SN_FLAGS flags = SN_FLAG_NONE; - - char c; - while ((c = *flags_str++)) { - switch (c) { - case 'R': - flags |= SN_FLAG_RESET; - break; - - case 'E': - flags |= SN_EMPTY_SLOT; - value = NAN; - break; - - default: - error("unknown flag '%c'", c); - break; - } - } + NETDATA_DOUBLE value = str2ndd_encoded(value_str, NULL); + SN_FLAGS flags = pluginsd_parse_storage_number_flags(flags_str); - if (!netdata_double_isnumber(value)) { + if (!netdata_double_isnumber(value) || (flags == SN_EMPTY_SLOT)) { value = NAN; flags = SN_EMPTY_SLOT; } - rrddim_store_metric(rd, ((PARSER_USER_OBJECT *) user)->replay.end_time_ut, value, flags); - rd->last_collected_time.tv_sec = ((PARSER_USER_OBJECT *) user)->replay.end_time; + rrddim_store_metric(rd, u->replay.end_time_ut, value, flags); + rd->last_collected_time.tv_sec = u->replay.end_time; rd->last_collected_time.tv_usec = 0; rd->collections_counter++; } @@ -1117,7 +1335,6 @@ PARSER_RC pluginsd_replay_set(char **words, size_t num_words, void *user) } } - rrddim_acquired_release(rda); return PARSER_RC_OK; } @@ -1133,26 +1350,25 @@ PARSER_RC pluginsd_replay_rrddim_collection_state(char **words, size_t num_words char *last_stored_value_str = get_word(words, num_words, 5); RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); RRDSET *st = pluginsd_require_chart_from_parent(user, PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE, PLUGINSD_KEYWORD_REPLAY_BEGIN); - if(!st) return PLUGINSD_DISABLE_PLUGIN(user); + if(!st) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); - RRDDIM_ACQUIRED *rda = pluginsd_acquire_dimension(host, st, dimension, PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE); - if(!rda) return PLUGINSD_DISABLE_PLUGIN(user); + RRDDIM *rd = pluginsd_acquire_dimension(host, st, dimension, PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE); + if(!rd) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); - RRDDIM *rd = rrddim_acquired_to_rrddim(rda); usec_t dim_last_collected_ut = (usec_t)rd->last_collected_time.tv_sec * USEC_PER_SEC + (usec_t)rd->last_collected_time.tv_usec; - usec_t last_collected_ut = last_collected_ut_str ? str2ull(last_collected_ut_str) : 0; + usec_t last_collected_ut = last_collected_ut_str ? str2ull_encoded(last_collected_ut_str) : 0; if(last_collected_ut > dim_last_collected_ut) { - rd->last_collected_time.tv_sec = last_collected_ut / USEC_PER_SEC; - rd->last_collected_time.tv_usec = last_collected_ut % USEC_PER_SEC; + rd->last_collected_time.tv_sec = (time_t)(last_collected_ut / USEC_PER_SEC); + rd->last_collected_time.tv_usec = (last_collected_ut % USEC_PER_SEC); } - rd->last_collected_value = last_collected_value_str ? str2ll(last_collected_value_str, NULL) : 0; - rd->last_calculated_value = last_calculated_value_str ? str2ndd(last_calculated_value_str, NULL) : 0; - rd->last_stored_value = last_stored_value_str ? str2ndd(last_stored_value_str, NULL) : 0.0; - rrddim_acquired_release(rda); + rd->last_collected_value = last_collected_value_str ? str2ll_encoded(last_collected_value_str) : 0; + rd->last_calculated_value = last_calculated_value_str ? str2ndd_encoded(last_calculated_value_str, NULL) : 0; + rd->last_stored_value = last_stored_value_str ? str2ndd_encoded(last_stored_value_str, NULL) : 0.0; + return PARSER_RC_OK; } @@ -1165,23 +1381,23 @@ PARSER_RC pluginsd_replay_rrdset_collection_state(char **words, size_t num_words char *last_updated_ut_str = get_word(words, num_words, 2); RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); RRDSET *st = pluginsd_require_chart_from_parent(user, PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE, PLUGINSD_KEYWORD_REPLAY_BEGIN); - if(!st) return PLUGINSD_DISABLE_PLUGIN(user); + if(!st) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); usec_t chart_last_collected_ut = (usec_t)st->last_collected_time.tv_sec * USEC_PER_SEC + (usec_t)st->last_collected_time.tv_usec; - usec_t last_collected_ut = last_collected_ut_str ? str2ull(last_collected_ut_str) : 0; + usec_t last_collected_ut = last_collected_ut_str ? str2ull_encoded(last_collected_ut_str) : 0; if(last_collected_ut > chart_last_collected_ut) { - st->last_collected_time.tv_sec = last_collected_ut / USEC_PER_SEC; - st->last_collected_time.tv_usec = last_collected_ut % USEC_PER_SEC; + st->last_collected_time.tv_sec = (time_t)(last_collected_ut / USEC_PER_SEC); + st->last_collected_time.tv_usec = (last_collected_ut % USEC_PER_SEC); } usec_t chart_last_updated_ut = (usec_t)st->last_updated.tv_sec * USEC_PER_SEC + (usec_t)st->last_updated.tv_usec; - usec_t last_updated_ut = last_updated_ut_str ? str2ull(last_updated_ut_str) : 0; + usec_t last_updated_ut = last_updated_ut_str ? str2ull_encoded(last_updated_ut_str) : 0; if(last_updated_ut > chart_last_updated_ut) { - st->last_updated.tv_sec = last_updated_ut / USEC_PER_SEC; - st->last_updated.tv_usec = last_updated_ut % USEC_PER_SEC; + st->last_updated.tv_sec = (time_t)(last_updated_ut / USEC_PER_SEC); + st->last_updated.tv_usec = (last_updated_ut % USEC_PER_SEC); } st->counter++; @@ -1205,24 +1421,25 @@ PARSER_RC pluginsd_replay_end(char **words, size_t num_words, void *user) const char *last_entry_requested_txt = get_word(words, num_words, 6); const char *child_world_time_txt = get_word(words, num_words, 7); // optional - time_t update_every_child = (time_t)str2ul(update_every_child_txt); - time_t first_entry_child = (time_t)str2ul(first_entry_child_txt); - time_t last_entry_child = (time_t)str2ul(last_entry_child_txt); + time_t update_every_child = (time_t) str2ull_encoded(update_every_child_txt); + time_t first_entry_child = (time_t) str2ull_encoded(first_entry_child_txt); + time_t last_entry_child = (time_t) str2ull_encoded(last_entry_child_txt); bool start_streaming = (strcmp(start_streaming_txt, "true") == 0); - time_t first_entry_requested = (time_t)str2ul(first_entry_requested_txt); - time_t last_entry_requested = (time_t)str2ul(last_entry_requested_txt); + time_t first_entry_requested = (time_t) str2ull_encoded(first_entry_requested_txt); + time_t last_entry_requested = (time_t) str2ull_encoded(last_entry_requested_txt); // the optional child world time - time_t child_world_time = (child_world_time_txt && *child_world_time_txt) ? (time_t)str2ul(child_world_time_txt) : now_realtime_sec(); + time_t child_world_time = (child_world_time_txt && *child_world_time_txt) ? (time_t) str2ull_encoded( + child_world_time_txt) : now_realtime_sec(); PARSER_USER_OBJECT *user_object = user; RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_REPLAY_END); - if(!host) return PLUGINSD_DISABLE_PLUGIN(user); + if(!host) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); RRDSET *st = pluginsd_require_chart_from_parent(user, PLUGINSD_KEYWORD_REPLAY_END, PLUGINSD_KEYWORD_REPLAY_BEGIN); - if(!st) return PLUGINSD_DISABLE_PLUGIN(user); + if(!st) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); #ifdef NETDATA_LOG_REPLICATION_REQUESTS internal_error(true, @@ -1235,8 +1452,7 @@ PARSER_RC pluginsd_replay_end(char **words, size_t num_words, void *user) ); #endif - ((PARSER_USER_OBJECT *) user)->st = NULL; - ((PARSER_USER_OBJECT *) user)->count++; + ((PARSER_USER_OBJECT *) user)->data_collections_count++; if(((PARSER_USER_OBJECT *) user)->replay.rset_enabled && st->rrdhost->receiver) { time_t now = now_realtime_sec(); @@ -1282,11 +1498,16 @@ PARSER_RC pluginsd_replay_end(char **words, size_t num_words, void *user) internal_error(true, "REPLAY ERROR: 'host:%s/chart:%s' got a " PLUGINSD_KEYWORD_REPLAY_END " with enable_streaming = true, but there is no replication in progress for this chart.", rrdhost_hostname(host), rrdset_id(st)); #endif + + pluginsd_set_chart_from_parent(user, NULL, PLUGINSD_KEYWORD_REPLAY_END); + worker_set_metric(WORKER_RECEIVER_JOB_REPLICATION_COMPLETION, 100.0); return PARSER_RC_OK; } + pluginsd_set_chart_from_parent(user, NULL, PLUGINSD_KEYWORD_REPLAY_END); + rrdcontext_updated_retention_rrdset(st); bool ok = replicate_chart_request(send_to_plugin, user_object->parser, host, st, @@ -1295,8 +1516,319 @@ PARSER_RC pluginsd_replay_end(char **words, size_t num_words, void *user) return ok ? PARSER_RC_OK : PARSER_RC_ERROR; } +PARSER_RC pluginsd_begin_v2(char **words, size_t num_words, void *user) { + timing_init(); + + char *id = get_word(words, num_words, 1); + char *update_every_str = get_word(words, num_words, 2); + char *end_time_str = get_word(words, num_words, 3); + char *wall_clock_time_str = get_word(words, num_words, 4); + + if(unlikely(!id || !update_every_str || !end_time_str || !wall_clock_time_str)) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_BEGIN_V2, "missing parameters"); + + RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_BEGIN_V2); + if(unlikely(!host)) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); + + timing_step(TIMING_STEP_BEGIN2_PREPARE); + + RRDSET *st = pluginsd_find_chart(host, id, PLUGINSD_KEYWORD_BEGIN_V2); + if(unlikely(!st)) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); + + pluginsd_set_chart_from_parent(user, st, PLUGINSD_KEYWORD_BEGIN_V2); + + if(unlikely(rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE | RRDSET_FLAG_ARCHIVED))) + rrdset_isnot_obsolete(st); + + timing_step(TIMING_STEP_BEGIN2_FIND_CHART); + + // ------------------------------------------------------------------------ + // parse the parameters + + time_t update_every = (time_t) str2ull_encoded(update_every_str); + time_t end_time = (time_t) str2ull_encoded(end_time_str); + + time_t wall_clock_time; + if(likely(*wall_clock_time_str == '#')) + wall_clock_time = end_time; + else + wall_clock_time = (time_t) str2ull_encoded(wall_clock_time_str); + + if (unlikely(update_every != st->update_every)) + rrdset_set_update_every_s(st, update_every); + + timing_step(TIMING_STEP_BEGIN2_PARSE); + + // ------------------------------------------------------------------------ + // prepare our state + + pluginsd_lock_rrdset_data_collection(user); + + PARSER_USER_OBJECT *u = (PARSER_USER_OBJECT *) user; + u->v2.update_every = update_every; + u->v2.end_time = end_time; + u->v2.wall_clock_time = wall_clock_time; + u->v2.ml_locked = ml_chart_update_begin(st); + + timing_step(TIMING_STEP_BEGIN2_ML); + + // ------------------------------------------------------------------------ + // propagate it forward in v2 + + if(!u->v2.stream_buffer.wb && rrdhost_has_rrdpush_sender_enabled(st->rrdhost)) + u->v2.stream_buffer = rrdset_push_metric_initialize(u->st, wall_clock_time); + + if(u->v2.stream_buffer.v2 && u->v2.stream_buffer.wb) { + // check if receiver and sender have the same number parsing capabilities + bool can_copy = stream_has_capability(u, STREAM_CAP_IEEE754) == stream_has_capability(&u->v2.stream_buffer, STREAM_CAP_IEEE754); + NUMBER_ENCODING encoding = stream_has_capability(&u->v2.stream_buffer, STREAM_CAP_IEEE754) ? NUMBER_ENCODING_BASE64 : NUMBER_ENCODING_HEX; + + BUFFER *wb = u->v2.stream_buffer.wb; + + buffer_need_bytes(wb, 1024); + + if(unlikely(u->v2.stream_buffer.begin_v2_added)) + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_END_V2 "\n", sizeof(PLUGINSD_KEYWORD_END_V2) - 1 + 1); + + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_BEGIN_V2 " '", sizeof(PLUGINSD_KEYWORD_BEGIN_V2) - 1 + 2); + buffer_fast_strcat(wb, rrdset_id(st), string_strlen(st->id)); + buffer_fast_strcat(wb, "' ", 2); + + if(can_copy) + buffer_strcat(wb, update_every_str); + else + buffer_print_uint64_encoded(wb, encoding, update_every); + + buffer_fast_strcat(wb, " ", 1); + + if(can_copy) + buffer_strcat(wb, end_time_str); + else + buffer_print_uint64_encoded(wb, encoding, end_time); + + buffer_fast_strcat(wb, " ", 1); + + if(can_copy) + buffer_strcat(wb, wall_clock_time_str); + else + buffer_print_uint64_encoded(wb, encoding, wall_clock_time); + + buffer_fast_strcat(wb, "\n", 1); + + u->v2.stream_buffer.last_point_end_time_s = end_time; + u->v2.stream_buffer.begin_v2_added = true; + } + + timing_step(TIMING_STEP_BEGIN2_PROPAGATE); + + // ------------------------------------------------------------------------ + // store it + + st->last_collected_time.tv_sec = end_time; + st->last_collected_time.tv_usec = 0; + st->last_updated.tv_sec = end_time; + st->last_updated.tv_usec = 0; + st->counter++; + st->counter_done++; + + // these are only needed for db mode RAM, SAVE, MAP, ALLOC + st->current_entry++; + if(st->current_entry >= st->entries) + st->current_entry -= st->entries; + + timing_step(TIMING_STEP_BEGIN2_STORE); + + return PARSER_RC_OK; +} + +PARSER_RC pluginsd_set_v2(char **words, size_t num_words, void *user) { + timing_init(); + + char *dimension = get_word(words, num_words, 1); + char *collected_str = get_word(words, num_words, 2); + char *value_str = get_word(words, num_words, 3); + char *flags_str = get_word(words, num_words, 4); + + if(unlikely(!dimension || !collected_str || !value_str || !flags_str)) + return PLUGINSD_DISABLE_PLUGIN(user, PLUGINSD_KEYWORD_SET_V2, "missing parameters"); + + PARSER_USER_OBJECT *u = (PARSER_USER_OBJECT *) user; + + RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_SET_V2); + if(unlikely(!host)) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); + + RRDSET *st = pluginsd_require_chart_from_parent(user, PLUGINSD_KEYWORD_SET_V2, PLUGINSD_KEYWORD_BEGIN_V2); + if(unlikely(!st)) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); + + timing_step(TIMING_STEP_SET2_PREPARE); + + RRDDIM *rd = pluginsd_acquire_dimension(host, st, dimension, PLUGINSD_KEYWORD_SET_V2); + if(unlikely(!rd)) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); + + if(unlikely(rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE | RRDDIM_FLAG_ARCHIVED))) + rrddim_isnot_obsolete(st, rd); + + timing_step(TIMING_STEP_SET2_LOOKUP_DIMENSION); + + // ------------------------------------------------------------------------ + // parse the parameters + + collected_number collected_value = (collected_number) str2ll_encoded(collected_str); + + NETDATA_DOUBLE value; + if(*value_str == '#') + value = (NETDATA_DOUBLE)collected_value; + else + value = str2ndd_encoded(value_str, NULL); + + SN_FLAGS flags = pluginsd_parse_storage_number_flags(flags_str); + + timing_step(TIMING_STEP_SET2_PARSE); + + // ------------------------------------------------------------------------ + // check value and ML + + if (unlikely(!netdata_double_isnumber(value) || (flags == SN_EMPTY_SLOT))) { + value = NAN; + flags = SN_EMPTY_SLOT; + + if(u->v2.ml_locked) + ml_dimension_is_anomalous(rd, u->v2.end_time, 0, false); + } + else if(u->v2.ml_locked) { + if (ml_dimension_is_anomalous(rd, u->v2.end_time, value, true)) { + // clear anomaly bit: 0 -> is anomalous, 1 -> not anomalous + flags &= ~((storage_number) SN_FLAG_NOT_ANOMALOUS); + } + else + flags |= SN_FLAG_NOT_ANOMALOUS; + } + + timing_step(TIMING_STEP_SET2_ML); + + // ------------------------------------------------------------------------ + // propagate it forward in v2 + + if(u->v2.stream_buffer.v2 && u->v2.stream_buffer.begin_v2_added && u->v2.stream_buffer.wb) { + // check if receiver and sender have the same number parsing capabilities + bool can_copy = stream_has_capability(u, STREAM_CAP_IEEE754) == stream_has_capability(&u->v2.stream_buffer, STREAM_CAP_IEEE754); + NUMBER_ENCODING integer_encoding = stream_has_capability(&u->v2.stream_buffer, STREAM_CAP_IEEE754) ? NUMBER_ENCODING_BASE64 : NUMBER_ENCODING_HEX; + NUMBER_ENCODING doubles_encoding = stream_has_capability(&u->v2.stream_buffer, STREAM_CAP_IEEE754) ? NUMBER_ENCODING_BASE64 : NUMBER_ENCODING_DECIMAL; + + BUFFER *wb = u->v2.stream_buffer.wb; + buffer_need_bytes(wb, 1024); + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_SET_V2 " '", sizeof(PLUGINSD_KEYWORD_SET_V2) - 1 + 2); + buffer_fast_strcat(wb, rrddim_id(rd), string_strlen(rd->id)); + buffer_fast_strcat(wb, "' ", 2); + if(can_copy) + buffer_strcat(wb, collected_str); + else + buffer_print_int64_encoded(wb, integer_encoding, collected_value); // original v2 had hex + buffer_fast_strcat(wb, " ", 1); + if(can_copy) + buffer_strcat(wb, value_str); + else + buffer_print_netdata_double_encoded(wb, doubles_encoding, value); // original v2 had decimal + buffer_fast_strcat(wb, " ", 1); + buffer_print_sn_flags(wb, flags, true); + buffer_fast_strcat(wb, "\n", 1); + } + + timing_step(TIMING_STEP_SET2_PROPAGATE); + + // ------------------------------------------------------------------------ + // store it + + rrddim_store_metric(rd, u->v2.end_time * USEC_PER_SEC, value, flags); + rd->last_collected_time.tv_sec = u->v2.end_time; + rd->last_collected_time.tv_usec = 0; + rd->last_collected_value = collected_value; + rd->last_stored_value = value; + rd->last_calculated_value = value; + rd->collections_counter++; + rd->updated = true; + + timing_step(TIMING_STEP_SET2_STORE); + + return PARSER_RC_OK; +} + +void pluginsd_cleanup_v2(void *user) { + // this is called when the thread is stopped while processing + pluginsd_set_chart_from_parent(user, NULL, "THREAD CLEANUP"); +} + +PARSER_RC pluginsd_end_v2(char **words __maybe_unused, size_t num_words __maybe_unused, void *user) { + timing_init(); + + RRDHOST *host = pluginsd_require_host_from_parent(user, PLUGINSD_KEYWORD_END_V2); + if(unlikely(!host)) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); + + RRDSET *st = pluginsd_require_chart_from_parent(user, PLUGINSD_KEYWORD_END_V2, PLUGINSD_KEYWORD_BEGIN_V2); + if(unlikely(!st)) return PLUGINSD_DISABLE_PLUGIN(user, NULL, NULL); + + PARSER_USER_OBJECT *u = (PARSER_USER_OBJECT *) user; + u->data_collections_count++; + + timing_step(TIMING_STEP_END2_PREPARE); + + // ------------------------------------------------------------------------ + // propagate the whole chart update in v1 + + if(unlikely(!u->v2.stream_buffer.v2 && !u->v2.stream_buffer.begin_v2_added && u->v2.stream_buffer.wb)) + rrdset_push_metrics_v1(&u->v2.stream_buffer, st); + + timing_step(TIMING_STEP_END2_PUSH_V1); + + // ------------------------------------------------------------------------ + // unblock data collection + + ml_chart_update_end(st); + u->v2.ml_locked = false; + + timing_step(TIMING_STEP_END2_ML); + + pluginsd_unlock_rrdset_data_collection(user); + rrdcontext_collected_rrdset(st); + store_metric_collection_completed(); + + timing_step(TIMING_STEP_END2_RRDSET); + + // ------------------------------------------------------------------------ + // propagate it forward + + rrdset_push_metrics_finished(&u->v2.stream_buffer, st); + + timing_step(TIMING_STEP_END2_PROPAGATE); + + // ------------------------------------------------------------------------ + // cleanup RRDSET / RRDDIM + + RRDDIM *rd; + rrddim_foreach_read(rd, st) { + rd->calculated_value = 0; + rd->collected_value = 0; + rd->updated = false; + } + rrddim_foreach_done(rd); + + // ------------------------------------------------------------------------ + // reset state + + u->v2 = (struct parser_user_object_v2){ 0 }; + + timing_step(TIMING_STEP_END2_STORE); + timing_report(); + + return PARSER_RC_OK; +} + static void pluginsd_process_thread_cleanup(void *ptr) { PARSER *parser = (PARSER *)ptr; + + pluginsd_cleanup_v2(parser->user); + pluginsd_host_define_cleanup(parser->user); + rrd_collector_finished(); parser_destroy(parser); } @@ -1335,7 +1867,10 @@ inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp_plugi }; // fp_plugin_output = our input; fp_plugin_input = our output - PARSER *parser = parser_init(host, &user, fp_plugin_output, fp_plugin_input, -1, PARSER_INPUT_SPLIT, NULL); + PARSER *parser = parser_init(&user, fp_plugin_output, fp_plugin_input, -1, + PARSER_INPUT_SPLIT, NULL); + + pluginsd_keywords_init(parser, PARSER_INIT_PLUGINSD); rrd_collector_started(); @@ -1344,9 +1879,10 @@ inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp_plugi netdata_thread_cleanup_push(pluginsd_process_thread_cleanup, parser); user.parser = parser; + char buffer[PLUGINSD_LINE_MAX + 1]; - while (likely(!parser_next(parser))) { - if (unlikely(!service_running(SERVICE_COLLECTORS) || parser_action(parser, NULL))) + while (likely(!parser_next(parser, buffer, PLUGINSD_LINE_MAX))) { + if (unlikely(!service_running(SERVICE_COLLECTORS) || parser_action(parser, buffer))) break; } @@ -1354,7 +1890,7 @@ inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp_plugi netdata_thread_cleanup_pop(1); cd->unsafe.enabled = user.enabled; - size_t count = user.count; + size_t count = user.data_collections_count; if (likely(count)) { cd->successful_collections += count; @@ -1365,3 +1901,141 @@ inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp_plugi return count; } + +PARSER_RC pluginsd_exit(char **words __maybe_unused, size_t num_words __maybe_unused, void *user __maybe_unused) +{ + info("PLUGINSD: plugin called EXIT."); + return PARSER_RC_STOP; +} + +static void pluginsd_keywords_init_internal(PARSER *parser, PLUGINSD_KEYWORDS types, void (*add_func)(PARSER *parser, char *keyword, keyword_function func)) { + + if (types & PARSER_INIT_PLUGINSD) { + add_func(parser, PLUGINSD_KEYWORD_FLUSH, pluginsd_flush); + add_func(parser, PLUGINSD_KEYWORD_DISABLE, pluginsd_disable); + + add_func(parser, PLUGINSD_KEYWORD_HOST_DEFINE, pluginsd_host_define); + add_func(parser, PLUGINSD_KEYWORD_HOST_DEFINE_END, pluginsd_host_define_end); + add_func(parser, PLUGINSD_KEYWORD_HOST_LABEL, pluginsd_host_labels); + add_func(parser, PLUGINSD_KEYWORD_HOST, pluginsd_host); + + add_func(parser, PLUGINSD_KEYWORD_EXIT, pluginsd_exit); + } + + if (types & (PARSER_INIT_PLUGINSD | PARSER_INIT_STREAMING)) { + // plugins.d plugins and streaming + add_func(parser, PLUGINSD_KEYWORD_CHART, pluginsd_chart); + add_func(parser, PLUGINSD_KEYWORD_DIMENSION, pluginsd_dimension); + add_func(parser, PLUGINSD_KEYWORD_VARIABLE, pluginsd_variable); + add_func(parser, PLUGINSD_KEYWORD_LABEL, pluginsd_label); + add_func(parser, PLUGINSD_KEYWORD_OVERWRITE, pluginsd_overwrite); + add_func(parser, PLUGINSD_KEYWORD_CLABEL_COMMIT, pluginsd_clabel_commit); + add_func(parser, PLUGINSD_KEYWORD_CLABEL, pluginsd_clabel); + add_func(parser, PLUGINSD_KEYWORD_FUNCTION, pluginsd_function); + add_func(parser, PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN, pluginsd_function_result_begin); + + add_func(parser, PLUGINSD_KEYWORD_BEGIN, pluginsd_begin); + add_func(parser, PLUGINSD_KEYWORD_SET, pluginsd_set); + add_func(parser, PLUGINSD_KEYWORD_END, pluginsd_end); + + inflight_functions_init(parser); + } + + if (types & PARSER_INIT_STREAMING) { + add_func(parser, PLUGINSD_KEYWORD_CHART_DEFINITION_END, pluginsd_chart_definition_end); + + // replication + add_func(parser, PLUGINSD_KEYWORD_REPLAY_BEGIN, pluginsd_replay_begin); + add_func(parser, PLUGINSD_KEYWORD_REPLAY_SET, pluginsd_replay_set); + add_func(parser, PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE, pluginsd_replay_rrddim_collection_state); + add_func(parser, PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE, pluginsd_replay_rrdset_collection_state); + add_func(parser, PLUGINSD_KEYWORD_REPLAY_END, pluginsd_replay_end); + + // streaming metrics v2 + add_func(parser, PLUGINSD_KEYWORD_BEGIN_V2, pluginsd_begin_v2); + add_func(parser, PLUGINSD_KEYWORD_SET_V2, pluginsd_set_v2); + add_func(parser, PLUGINSD_KEYWORD_END_V2, pluginsd_end_v2); + } +} + +void pluginsd_keywords_init(PARSER *parser, PLUGINSD_KEYWORDS types) { + pluginsd_keywords_init_internal(parser, types, parser_add_keyword); +} + +struct pluginsd_user_unittest { + size_t size; + const char **hashtable; + uint32_t (*hash)(const char *s); + size_t collisions; +}; + +void pluginsd_keyword_collision_check(PARSER *parser, char *keyword, keyword_function func __maybe_unused) { + struct pluginsd_user_unittest *u = parser->user; + + uint32_t hash = u->hash(keyword); + uint32_t slot = hash % u->size; + + if(u->hashtable[slot]) + u->collisions++; + + u->hashtable[slot] = keyword; +} + +static struct { + const char *name; + uint32_t (*hash)(const char *s); + size_t slots_needed; +} hashers[] = { + { .name = "djb2_hash32(s)", djb2_hash32, .slots_needed = 0, }, + { .name = "fnv1_hash32(s)", fnv1_hash32, .slots_needed = 0, }, + { .name = "fnv1a_hash32(s)", fnv1a_hash32, .slots_needed = 0, }, + { .name = "larson_hash32(s)", larson_hash32, .slots_needed = 0, }, + { .name = "pluginsd_parser_hash32(s)", pluginsd_parser_hash32, .slots_needed = 0, }, + + // terminator + { .name = NULL, NULL, .slots_needed = 0, }, +}; + +int pluginsd_parser_unittest(void) { + PARSER *p; + size_t slots_to_check = 1000; + size_t i, h; + + // check for hashtable collisions + for(h = 0; hashers[h].name ;h++) { + hashers[h].slots_needed = slots_to_check * 1000000; + + for (i = 10; i < slots_to_check; i++) { + struct pluginsd_user_unittest user = { + .hash = hashers[h].hash, + .size = i, + .hashtable = callocz(i, sizeof(const char *)), + .collisions = 0, + }; + + p = parser_init(&user, NULL, NULL, -1, PARSER_INPUT_SPLIT, NULL); + pluginsd_keywords_init_internal(p, PARSER_INIT_PLUGINSD | PARSER_INIT_STREAMING, + pluginsd_keyword_collision_check); + parser_destroy(p); + + freez(user.hashtable); + + if (!user.collisions) { + hashers[h].slots_needed = i; + break; + } + } + } + + for(h = 0; hashers[h].name ;h++) { + if(hashers[h].slots_needed > 1000) + info("PARSER: hash function '%s' cannot be used without collisions under %zu slots", hashers[h].name, slots_to_check); + else + info("PARSER: hash function '%s' needs PARSER_KEYWORDS_HASHTABLE_SIZE (in parser.h) set to %zu", hashers[h].name, hashers[h].slots_needed); + } + + p = parser_init(NULL, NULL, NULL, -1, PARSER_INPUT_SPLIT, NULL); + pluginsd_keywords_init(p, PARSER_INIT_PLUGINSD | PARSER_INIT_STREAMING); + parser_destroy(p); + return 0; +} diff --git a/collectors/plugins.d/pluginsd_parser.h b/collectors/plugins.d/pluginsd_parser.h index e18b43e5..1fdc23a0 100644 --- a/collectors/plugins.d/pluginsd_parser.h +++ b/collectors/plugins.d/pluginsd_parser.h @@ -3,7 +3,12 @@ #ifndef NETDATA_PLUGINSD_PARSER_H #define NETDATA_PLUGINSD_PARSER_H -#include "parser/parser.h" +#include "daemon/common.h" + +typedef enum __attribute__ ((__packed__)) { + PARSER_INIT_PLUGINSD = (1 << 1), + PARSER_INIT_STREAMING = (1 << 2), +} PLUGINSD_KEYWORDS; typedef struct parser_user_object { PARSER *parser; @@ -14,13 +19,20 @@ typedef struct parser_user_object { int trust_durations; DICTIONARY *new_host_labels; DICTIONARY *chart_rrdlabels_linked_temporarily; - size_t count; + size_t data_collections_count; int enabled; - uint8_t st_exists; - uint8_t host_exists; - void *private; // the user can set this for private use + + STREAM_CAPABILITIES capabilities; // receiver capabilities struct { + bool parsing_host; + uuid_t machine_guid; + char machine_guid_str[UUID_STR_LEN]; + STRING *hostname; + DICTIONARY *rrdlabels; + } host_define; + + struct parser_user_object_replay { time_t start_time; time_t end_time; @@ -31,9 +43,20 @@ typedef struct parser_user_object { bool rset_enabled; } replay; + + struct parser_user_object_v2 { + bool locked_data_collection; + RRDSET_STREAM_BUFFER stream_buffer; // sender capabilities in this + time_t update_every; + time_t end_time; + time_t wall_clock_time; + bool ml_locked; + } v2; } PARSER_USER_OBJECT; PARSER_RC pluginsd_function(char **words, size_t num_words, void *user); PARSER_RC pluginsd_function_result_begin(char **words, size_t num_words, void *user); void inflight_functions_init(PARSER *parser); +void pluginsd_keywords_init(PARSER *parser, PLUGINSD_KEYWORDS types); + #endif //NETDATA_PLUGINSD_PARSER_H diff --git a/collectors/proc.plugin/README.md b/collectors/proc.plugin/README.md index f0355060..6c1335a7 100644 --- a/collectors/proc.plugin/README.md +++ b/collectors/proc.plugin/README.md @@ -1,13 +1,10 @@ - - -# proc.plugin +# OS provided metrics (proc.plugin) + +`proc.plugin` 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. + +This plugin is not an external plugin, but one of Netdata's threads. + +In detail, it collects metrics from: - `/proc/net/dev` (all network interfaces for all their values) - `/proc/diskstats` (all disks for all their values) diff --git a/collectors/proc.plugin/ipc.c b/collectors/proc.plugin/ipc.c index adfc15be..b166deba 100644 --- a/collectors/proc.plugin/ipc.c +++ b/collectors/proc.plugin/ipc.c @@ -212,7 +212,7 @@ int ipc_msq_get_info(char *msg_filename, struct message_queue **message_queue_ro // find the id in the linked list or create a new structure int found = 0; - unsigned long long id = str2ull(procfile_lineword(ff, l, 1)); + unsigned long long id = str2ull(procfile_lineword(ff, l, 1), NULL); for(msq = *message_queue_root; msq ; msq = msq->next) { if(unlikely(id == msq->id)) { found = 1; @@ -227,8 +227,8 @@ int ipc_msq_get_info(char *msg_filename, struct message_queue **message_queue_ro msq->id = id; } - msq->messages = str2ull(procfile_lineword(ff, l, 4)); - msq->bytes = str2ull(procfile_lineword(ff, l, 3)); + msq->messages = str2ull(procfile_lineword(ff, l, 4), NULL); + msq->bytes = str2ull(procfile_lineword(ff, l, 3), NULL); msq->found = 1; } @@ -268,7 +268,7 @@ int ipc_shm_get_info(char *shm_filename, struct shm_stats *shm) { } shm->segments++; - shm->bytes += str2ull(procfile_lineword(ff, l, 3)); + shm->bytes += str2ull(procfile_lineword(ff, l, 3), NULL); } return 0; diff --git a/collectors/proc.plugin/metrics.csv b/collectors/proc.plugin/metrics.csv new file mode 100644 index 00000000..ea0d1b36 --- /dev/null +++ b/collectors/proc.plugin/metrics.csv @@ -0,0 +1,271 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +system.cpu,,"guest_nice, guest, steal, softirq, irq, user, system, nice, iowait, idle",percentage,Total CPU utilization,stacked,,proc.plugin,/proc/stat +cpu.cpu,cpu core,"guest_nice, guest, steal, softirq, irq, user, system, nice, iowait, idle",percentage,Core utilization,stacked,cpu,proc.plugin,/proc/stat +system.intr,,interrupts,interrupts/s,CPU Interrupts,line,,proc.plugin,/proc/stat +system.ctxt,,switches,context switches/s,CPU Context Switches,line,,proc.plugin,/proc/stat +system.forks,,started,processes/s,Started Processes,line,,proc.plugin,/proc/stat +system.processes,,"running, blocked",processes,System Processes,line,,proc.plugin,/proc/stat +cpu.core_throttling,,a dimension per cpu core,events/s,Core Thermal Throttling Events,line,,proc.plugin,/proc/stat +cpu.package_throttling,,a dimension per package,events/s,Package Thermal Throttling Events,line,,proc.plugin,/proc/stat +cpu.cpufreq,,a dimension per cpu core,MHz,Current CPU Frequency,line,,proc.plugin,/proc/stat +cpuidle.cpu_cstate_residency_time,cpu core,a dimension per c-state,percentage,C-state residency time,stacked,cpu,proc.plugin,/proc/stat +system.entropy,,entropy,entropy,Available Entropy,line,,proc.plugin,/proc/sys/kernel/random/entropy_avail +system.uptime,,uptime,seconds,System Uptime,line,,proc.plugin,/proc/uptime +system.swapio,,"in, out",KiB/s,Swap I/O,area,,proc.plugin,/proc/vmstat +system.pgpgio,,"in, out",KiB/s,Memory Paged from/to disk,area,,proc.plugin,/proc/vmstat +system.pgfaults,,"minor, major",faults/s,Memory Page Faults,line,,proc.plugin,/proc/vmstat +system.interrupts,,a dimension per device,interrupts/s,System interrupts,stacked,,proc.plugin,/proc/interrupts +cpu.interrupts,cpu core,a dimension per device,interrupts/s,CPU interrupts,stacked,cpu,proc.plugin,/proc/interrupts +system.load,,"load1, load5, load15",load,System Load Average,line,,proc.plugin,/proc/loadavg +system.active_processes,,active,processes,System Active Processes,line,,proc.plugin,/proc/loadavg +system.cpu_some_pressure,,"some10, some60, some300",percentage,"CPU some pressure",line,,proc.plugin,/proc/pressure +system.cpu_some_pressure_stall_time,,time,ms,"CPU some pressure stall time",line,,proc.plugin,/proc/pressure +system.cpu_full_pressure,,"some10, some60, some300",percentage,"CPU full pressure",line,,proc.plugin,/proc/pressure +system.cpu_full_pressure_stall_time,,time,ms,"CPU full pressure stall time",line,,proc.plugin,/proc/pressure +system.memory_some_pressure,,"some10, some60, some300",percentage,"Memory some pressure",line,,proc.plugin,/proc/pressure +system.memory_some_pressure_stall_time,,time,ms,"Memory some pressure stall time",line,,proc.plugin,/proc/pressure +system.memory_full_pressure,,"some10, some60, some300",percentage,"Memory full pressure",line,,proc.plugin,/proc/pressure +system.memory_full_pressure_stall_time,,time,ms,"Memory full pressure stall time",line,,proc.plugin,/proc/pressure +system.io_some_pressure,,"some10, some60, some300",percentage,"I/O some pressure",line,,proc.plugin,/proc/pressure +system.io_some_pressure_stall_time,,time,ms,"I/O some pressure stall time",line,,proc.plugin,/proc/pressure +system.io_full_pressure,,"some10, some60, some300",percentage,"I/O some pressure",line,,proc.plugin,/proc/pressure +system.io_full_pressure_stall_time,,time,ms,"I/O some pressure stall time",line,,proc.plugin,/proc/pressure +system.softirqs,,a dimension per softirq,softirqs/s,System softirqs,stacked,,proc.plugin,/proc/softirqs +cpu.softirqs,cpu core,a dimension per softirq,softirqs/s,CPU softirqs,stacked,cpu,proc.plugin,/proc/softirqs +system.softnet_stat,,"processed, dropped, squeezed, received_rps, flow_limit_count",events/s,System softnet_stat,line,,proc.plugin,/proc/net/softnet_stat +cpu.softnet_stat,cpu core,"processed, dropped, squeezed, received_rps, flow_limit_count",events/s,CPU softnet_stat,line,,proc.plugin,/proc/net/softnet_stat +system.ram,,"free, used, cached, buffers",MiB,System RAM,stacked,,proc.plugin,/proc/meminfo +mem.available,,avail,MiB,Available RAM for applications,area,,proc.plugin,/proc/meminfo +system.swap,,"free, used",MiB,System Swap,stacked,,proc.plugin,/proc/meminfo +mem.hwcorrupt,,HardwareCorrupted,MiB,Corrupted Memory detected by ECC,line,,proc.plugin,/proc/meminfo +mem.commited,,Commited_AS,MiB,Committed (Allocated) Memory,area,,proc.plugin,/proc/meminfo +mem.writeback,,"Dirty, Writeback, FuseWriteback, NfsWriteback, Bounce",MiB,Writeback Memory,line,,proc.plugin,/proc/meminfo +mem.kernel,,"Slab, KernelStack, PageTables, VmallocUsed, Percpu",MiB,Memory Used by Kernel,stacked,,proc.plugin,/proc/meminfo +mem.slab,,"reclaimable, unreclaimable",MiB,Reclaimable Kernel Memory,stacked,,proc.plugin,/proc/meminfo +mem.hugepage,,"free, used, surplus, reserved",MiB,Dedicated HugePages Memory,stacked,,proc.plugin,/proc/meminfo +mem.transparent_hugepages,,"anonymous, shmem",MiB,Transparent HugePages Memory,stacked,,proc.plugin,/proc/meminfo +mem.balloon,,"inflate, deflate, migrate",KiB/s,Memory Ballooning Operations,line,,proc.plugin,/proc/vmstat +mem.zswapio,,"in, out",KiB/s,ZSwap I/O,area,,proc.plugin,/proc/vmstat +mem.ksm_cow,,"swapin, write",KiB/s,KSM Copy On Write Operations,line,,proc.plugin,/proc/vmstat +mem.thp_faults,,"alloc, fallback, fallback_charge",events/s,Transparent Huge Page Fault Allocations,line,,proc.plugin,/proc/vmstat +mem.thp_file,,"alloc, fallback, mapped, fallback_charge",events/s,Transparent Huge Page File Allocations,line,,proc.plugin,/proc/vmstat +mem.thp_zero,,"alloc, failed",events/s,Transparent Huge Zero Page Allocations,line,,proc.plugin,/proc/vmstat +mem.thp_collapse,,"alloc, failed",events/s,Transparent Huge Pages Collapsed by khugepaged,line,,proc.plugin,/proc/vmstat +mem.thp_split,,"split, failed, split_pmd, split_deferred",events/s,Transparent Huge Page Splits,line,,proc.plugin,/proc/vmstat +mem.thp_swapout,,"swapout, fallback",events/s,Transparent Huge Pages Swap Out,line,,proc.plugin,/proc/vmstat +mem.thp_compact,,"success, fail, stall",events/s,Transparent Huge Pages Compaction,line,,proc.plugin,/proc/vmstat +mem.pagetype_global,,a dimension per pagesize,B,System orders available,stacked,,proc.plugin,/proc/pagetypeinfo +mem.pagetype,"node, zone, type",a dimension per pagesize,B,"pagetype_Node{node}_{zone}_{type}",stacked,"node_id, node_zone, node_type",proc.plugin,/proc/pagetypeinfo +mem.oom_kill,,kills,kills/s,Out of Memory Kills,line,,proc.plugin,/proc/vmstat +mem.numa,,"local, foreign, interleave, other, pte_updates, huge_pte_updates, hint_faults, hint_faults_local, pages_migrated",events/s,NUMA events,line,,proc.plugin,/proc/vmstat +mem.ecc_ce,,a dimension per mem controller,errors,ECC Memory Correctable Errors,line,,proc.plugin,/sys/devices/system/edac/mc +mem.ecc_ue,,a dimension per mem controller,errors,ECC Memory Uncorrectable Errors,line,,proc.plugin,/sys/devices/system/edac/mc +mem.numa_nodes,numa node,"hit, miss, local, foreign, interleave, other",events/s,NUMA events,line,numa_node,proc.plugin,/sys/devices/system/node +mem.ksm,,"shared, unshared, sharing, volatile",MiB,Kernel Same Page Merging,stacked,,proc.plugin,/sys/kernel/mm/ksm +mem.ksm_savings,,"savings, offered",MiB,Kernel Same Page Merging Savings,area,,proc.plugin,/sys/kernel/mm/ksm +mem.ksm_ratios,,savings,percentage,Kernel Same Page Merging Effectiveness,line,,proc.plugin,/sys/kernel/mm/ksm +mem.zram_usage,zram device,"compressed, metadata",MiB,ZRAM Memory Usage,area,device,proc.plugin,/sys/block/zram +mem.zram_savings,zram device,"savings, original",MiB,ZRAM Memory Savings,area,device,proc.plugin,/sys/block/zram +mem.zram_ratio,zram device,ratio,ratio,ZRAM Compression Ratio (original to compressed),line,device,proc.plugin,/sys/block/zram +mem.zram_efficiency,zram device,percent,percentage,ZRAM Efficiency,line,device,proc.plugin,/sys/block/zram +system.ipc_semaphores,,semaphores,semaphores,IPC Semaphores,area,,proc.plugin,ipc +system.ipc_semaphore_arrays,,arrays,arrays,IPC Semaphore Arrays,area,,proc.plugin,ipc +system.message_queue_message,,a dimension per queue,messages,IPC Message Queue Number of Messages,stacked,,proc.plugin,ipc +system.message_queue_bytes,,a dimension per queue,bytes,IPC Message Queue Used Bytes,stacked,,proc.plugin,ipc +system.shared_memory_segments,,segments,segments,IPC Shared Memory Number of Segments,stacked,,proc.plugin,ipc +system.shared_memory_bytes,,bytes,bytes,IPC Shared Memory Used Bytes,stacked,,proc.plugin,ipc +system.io,,"in, out",KiB/s,Disk I/O,area,,proc.plugin,/proc/diskstats +disk.io,disk,"reads, writes",KiB/s,Disk I/O Bandwidth,area,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk_ext.io,disk,discards,KiB/s,Amount of Discarded Data,area,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.ops,disk,"reads, writes",operations/s,Disk Completed I/O Operations,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk_ext.ops,disk,"discards, flushes",operations/s,Disk Completed Extended I/O Operations,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.qops,disk,operations,operations,Disk Current I/O Operations,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.backlog,disk,backlog,milliseconds,Disk Backlog,area,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.busy,disk,busy,milliseconds,Disk Busy Time,area,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.util,disk,utilization,% of time working,Disk Utilization Time,area,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.mops,disk,"reads, writes",merged operations/s,Disk Merged Operations,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk_ext.mops,disk,discards,merged operations/s,Disk Merged Discard Operations,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.iotime,disk,"reads, writes",milliseconds/s,Disk Total I/O Time,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk_ext.iotime,disk,"discards, flushes",milliseconds/s,Disk Total I/O Time for Extended Operations,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.await,disk,"reads, writes",milliseconds/operation,Average Completed I/O Operation Time,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk_ext.await,disk,"discards, flushes",milliseconds/operation,Average Completed Extended I/O Operation Time,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.avgsz,disk,"reads, writes",KiB/operation,Average Completed I/O Operation Bandwidth,area,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk_ext.avgsz,disk,discards,KiB/operation,Average Amount of Discarded Data,area,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.svctm,disk,svctm,milliseconds/operation,Average Service Time,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.bcache_cache_alloc,disk,"ununsed, dirty, clean, metadata, undefined",percentage,BCache Cache Allocations,stacked,,proc.plugin,/proc/diskstats +disk.bcache_hit_ratio,disk,"5min, 1hour, 1day, ever",percentage,BCache Cache Hit Ratio,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.bcache_rates,disk,"congested, writeback",KiB/s,BCache Rates,area,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.bcache_size,disk,dirty,MiB,BCache Cache Sizes,area,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.bcache_usage,disk,avail,percentage,BCache Cache Usage,area,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.bcache_cache_read_races,disk,"races, errors",operations/s,BCache Cache Read Races,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.bcache,disk,"hits, misses, collisions, readaheads",operations/s,BCache Cache I/O Operations,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +disk.bcache_bypass,disk,"hits, misses",operations/s,BCache Cache Bypass I/O Operations,line,"device, mount_point, device_type",proc.plugin,/proc/diskstats +md.health,,a dimension per md array,failed disks,Faulty Devices In MD,line,,proc.plugin,/proc/mdstat +md.disks,md array,"inuse, down",disks,Disks Stats,stacked,"device, raid_level",proc.plugin,/proc/mdstat +md.mismatch_cnt,md array,count,unsynchronized blocks,Mismatch Count,line,"device, raid_level",proc.plugin,/proc/mdstat +md.status,md array,"check, resync, recovery, reshape",percent,Current Status,line,"device, raid_level",proc.plugin,/proc/mdstat +md.expected_time_until_operation_finish,md array,finish_in,seconds,Approximate Time Until Finish,line,"device, raid_level",proc.plugin,/proc/mdstat +md.operation_speed,md array,speed,KiB/s,Operation Speed,line,"device, raid_level",proc.plugin,/proc/mdstat +md.nonredundant,md array,available,boolean,Nonredundant Array Availability,line,"device, raid_level",proc.plugin,/proc/mdstat +system.net,,"received, sent",kilobits/s,Physical Network Interfaces Aggregated Bandwidth,area,,proc.plugin,/proc/net/dev +net.net,network device,"received, sent",kilobits/s,Bandwidth,area,"interface_type, device",proc.plugin,/proc/net/dev +net.speed,network device,speed,kilobits/s,Interface Speed,line,"interface_type, device",proc.plugin,/proc/net/dev +net.duplex,network device,"full, half, unknown",state,Interface Duplex State,line,"interface_type, device",proc.plugin,/proc/net/dev +net.operstate,network device,"up, down, notpresent, lowerlayerdown, testing, dormant, unknown",state,Interface Operational State,line,"interface_type, device",proc.plugin,/proc/net/dev +net.carrier,network device,"up, down",state,Interface Physical Link State,line,"interface_type, device",proc.plugin,/proc/net/dev +net.mtu,network device,mtu,octets,Interface MTU,line,"interface_type, device",proc.plugin,/proc/net/dev +net.packets,network device,"received, sent, multicast",packets/s,Packets,line,"interface_type, device",proc.plugin,/proc/net/dev +net.errors,network device,"inbound, outbound",errors/s,Interface Errors,line,"interface_type, device",proc.plugin,/proc/net/dev +net.drops,network device,"inbound, outbound",drops/s,Interface Drops,line,"interface_type, device",proc.plugin,/proc/net/dev +net.fifo,network device,"receive, transmit",errors,Interface FIFO Buffer Errors,line,"interface_type, device",proc.plugin,/proc/net/dev +net.compressed,network device,"received, sent",packets/s,Compressed Packets,line,"interface_type, device",proc.plugin,/proc/net/dev +net.events,network device,"frames, collisions, carrier",events/s,Network Interface Events,line,"interface_type, device",proc.plugin,/proc/net/dev +wireless.status,wireless device,status,status,Internal status reported by interface.,line,,proc.plugin,/proc/net/wireless +wireless.link_quality,wireless device,link_quality,value,"Overall quality of the link. This is an aggregate value, and depends on the driver and hardware.",line,,proc.plugin,/proc/net/wireless +wireless.signal_level,wireless device,signal_level,dBm,"The signal level is the wireless signal power level received by the wireless client. The closer the value is to 0, the stronger the signal.",line,,proc.plugin,/proc/net/wireless +wireless.noise_level,wireless device,noise_level,dBm,"The noise level indicates the amount of background noise in your environment. The closer the value to 0, the greater the noise level.",line,,proc.plugin,/proc/net/wireless +wireless.discarded_packets,wireless device,"nwid, crypt, frag, retry, misc",packets/s,"Packet discarded in the wireless adapter due to wireless specific problems.",line,,proc.plugin,/proc/net/wireless +wireless.missed_beacons,wireless device,missed_beacons,frames/s,Number of missed beacons.,line,,proc.plugin,/proc/net/wireless +ib.bytes,infiniband port,"Received, Sent",kilobits/s,Bandwidth usage,area,,proc.plugin,/sys/class/infiniband +ib.packets,infiniband port,"Received, Sent, Mcast_rcvd, Mcast_sent, Ucast_rcvd, Ucast_sent",packets/s,Packets Statistics,area,,proc.plugin,/sys/class/infiniband +ib.errors,infiniband port,"Pkts_malformated, Pkts_rcvd_discarded, Pkts_sent_discarded, Tick_Wait_to_send, Pkts_missed_resource, Buffer_overrun, Link_Downed, Link_recovered, Link_integrity_err, Link_minor_errors, Pkts_rcvd_with_EBP, Pkts_rcvd_discarded_by_switch, Pkts_sent_discarded_by_switch",errors/s,Error Counters,line,,proc.plugin,/sys/class/infiniband +ib.hwerrors,infiniband port,"Duplicated_packets, Pkt_Seq_Num_gap, Ack_timer_expired, Drop_missing_buffer, Drop_out_of_sequence, NAK_sequence_rcvd, CQE_err_Req, CQE_err_Resp, CQE_Flushed_err_Req, CQE_Flushed_err_Resp, Remote_access_err_Req, Remote_access_err_Resp, Remote_invalid_req, Local_length_err_Resp, RNR_NAK_Packets, CNP_Pkts_ignored, RoCE_ICRC_Errors",errors/s,Hardware Errors,line,,proc.plugin,/sys/class/infiniband +ib.hwpackets,infiniband port,"RoCEv2_Congestion_sent, RoCEv2_Congestion_rcvd, IB_Congestion_handled, ATOMIC_req_rcvd, Connection_req_rcvd, Read_req_rcvd, Write_req_rcvd, RoCE_retrans_adaptive, RoCE_retrans_timeout, RoCE_slow_restart, RoCE_slow_restart_congestion, RoCE_slow_restart_count",packets/s,Hardware Packets Statistics,line,,proc.plugin,/sys/class/infiniband +system.ip,,"received, sent",kilobits/s,IP Bandwidth,area,,proc.plugin,/proc/net/netstat +ip.inerrors,,"noroutes, truncated, checksum",packets/s,IP Input Errors,line,,proc.plugin,/proc/net/netstat +ip.mcast,,"received, sent",kilobits/s,IP Multicast Bandwidth,area,,proc.plugin,/proc/net/netstat +ip.bcast,,"received, sent",kilobits/s,IP Broadcast Bandwidth,area,,proc.plugin,/proc/net/netstat +ip.mcastpkts,,"received, sent",packets/s,IP Multicast Packets,line,,proc.plugin,/proc/net/netstat +ip.bcastpkts,,"received, sent",packets/s,IP Broadcast Packets,line,,proc.plugin,/proc/net/netstat +ip.ecnpkts,,"CEP, NoECTP, ECTP0, ECTP1",packets/s,IP ECN Statistics,line,,proc.plugin,/proc/net/netstat +ip.tcpmemorypressures,,pressures,events/s,TCP Memory Pressures,line,,proc.plugin,/proc/net/netstat +ip.tcpconnaborts,,"baddata, userclosed, nomemory, timeout, linger, failed",connections/s,TCP Connection Aborts,line,,proc.plugin,/proc/net/netstat +ip.tcpreorders,,"timestamp, sack, fack, reno",packets/s,TCP Reordered Packets by Detection Method,line,,proc.plugin,/proc/net/netstat +ip.tcpofo,,"inqueue, dropped, merged, pruned",packets/s,TCP Out-Of-Order Queue,line,,proc.plugin,/proc/net/netstat +ip.tcpsyncookies,,"received, sent, failed",packets/s,TCP SYN Cookies,line,,proc.plugin,/proc/net/netstat +ip.tcp_syn_queue,,"drops, cookies",packets/s,TCP SYN Queue Issues,line,,proc.plugin,/proc/net/netstat +ip.tcp_accept_queue,,"overflows, drops",packets/s,TCP Accept Queue Issues,line,,proc.plugin,/proc/net/netstat +ipv4.packets,,"received, sent, forwarded, delivered",packets/s,IPv4 Packets,line,,proc.plugin,/proc/net/netstat +ipv4.fragsout,,"ok, failed, created",packets/s,IPv4 Fragments Sent,line,,proc.plugin,/proc/net/netstat +ipv4.fragsin,,"ok, failed, all",packets/s,IPv4 Fragments Reassembly,line,,proc.plugin,/proc/net/netstat +ipv4.errors,,"InDiscards, OutDiscards, InHdrErrors, OutNoRoutes, InAddrErrors, InUnknownProtos",packets/s,IPv4 Errors,line,,proc.plugin,/proc/net/netstat +ipv4.icmp,,"received, sent",packets/s,IPv4 ICMP Packets,line,,proc.plugin,/proc/net/netstat +ipv4.icmp_errors,,"InErrors, OutErrors, InCsumErrors",packets/s,IPv4 ICMP Errors,line,,proc.plugin,/proc/net/netstat +ipv4.icmpmsg,,"InEchoReps, OutEchoReps, InDestUnreachs, OutDestUnreachs, InRedirects, OutRedirects, InEchos, OutEchos, InRouterAdvert, OutRouterAdvert, InRouterSelect, OutRouterSelect, InTimeExcds, OutTimeExcds, InParmProbs, OutParmProbs, InTimestamps, OutTimestamps, InTimestampReps, OutTimestampReps",packets/s,IPv4 ICMP Messages,line,,proc.plugin,/proc/net/netstat +ipv4.tcpsock,,connections,active connections,IPv4 TCP Connections,line,,proc.plugin,/proc/net/netstat +ipv4.tcppackets,,"received, sent",packets/s,IPv4 TCP Packets,line,,proc.plugin,/proc/net/netstat +ipv4.tcperrors,,"InErrs, InCsumErrors, RetransSegs",packets/s,IPv4 TCP Errors,line,,proc.plugin,/proc/net/netstat +ipv4.tcpopens,,"active, passive",connections/s,IPv4 TCP Opens,line,,proc.plugin,/proc/net/netstat +ipv4.tcphandshake,,"EstabResets, OutRsts, AttemptFails, SynRetrans",events/s,IPv4 TCP Handshake Issues,line,,proc.plugin,/proc/net/netstat +ipv4.udppackets,,"received, sent",packets/s,IPv4 UDP Packets,line,,proc.plugin,/proc/net/netstat +ipv4.udperrors,,"RcvbufErrors, SndbufErrors, InErrors, NoPorts, InCsumErrors, IgnoredMulti",events/s,IPv4 UDP Errors,line,,proc.plugin,/proc/net/netstat +ipv4.udplite,,"received, sent",packets/s,IPv4 UDPLite Packets,line,,proc.plugin,/proc/net/netstat +ipv4.udplite_errors,,"RcvbufErrors, SndbufErrors, InErrors, NoPorts, InCsumErrors, IgnoredMulti",packets/s,IPv4 UDPLite Errors,line,,proc.plugin,/proc/net/netstat +system.ipv6,,"received, sent",kilobits/s,IPv6 Bandwidth,area,,proc.plugin,/proc/net/netstat +system.ipv6,,"received, sent, forwarded, delivers",packets/s,IPv6 Packets,line,,proc.plugin,/proc/net/netstat +ipv6.fragsout,,"ok, failed, all",packets/s,IPv6 Fragments Sent,line,,proc.plugin,/proc/net/netstat +ipv6.fragsin,,"ok, failed, timeout, all",packets/s,IPv6 Fragments Reassembly,line,,proc.plugin,/proc/net/netstat +ipv6.errors,,"InDiscards, OutDiscards, InHdrErrors, InAddrErrors, InUnknownProtos, InTooBigErrors, InTruncatedPkts, InNoRoutes, OutNoRoutes",packets/s,IPv6 Errors,line,,proc.plugin,/proc/net/netstat +ipv6.udppackets,,"received, sent",packets/s,IPv6 UDP Packets,line,,proc.plugin,/proc/net/netstat +ipv6.udperrors,,"RcvbufErrors, SndbufErrors, InErrors, NoPorts, InCsumErrors, IgnoredMulti",events/s,IPv6 UDP Errors,line,,proc.plugin,/proc/net/netstat +ipv6.udplitepackets,,"received, sent",packets/s,IPv6 UDPlite Packets,line,,proc.plugin,/proc/net/netstat +ipv6.udpliteerrors,,"RcvbufErrors, SndbufErrors, InErrors, NoPorts, InCsumErrors",events/s,IPv6 UDP Lite Errors,line,,proc.plugin,/proc/net/netstat +ipv6.mcast,,"received, sent",kilobits/s,IPv6 Multicast Bandwidth,area,,proc.plugin,/proc/net/netstat +ipv6.bcast,,"received, sent",kilobits/s,IPv6 Broadcast Bandwidth,area,,proc.plugin,/proc/net/netstat +ipv6.mcastpkts,,"received, sent",packets/s,IPv6 Multicast Packets,line,,proc.plugin,/proc/net/netstat +ipv6.icmp,,"received, sent",messages/s,IPv6 ICMP Messages,line,,proc.plugin,/proc/net/netstat +ipv6.icmpredir,,"received, sent",redirects/s,IPv6 ICMP Redirects,line,,proc.plugin,/proc/net/netstat +ipv6.icmperrors,,"InErrors, OutErrors, InCsumErrors, InDestUnreachs, InPktTooBigs, InTimeExcds, InParmProblems, OutDestUnreachs, OutPktTooBigs, OutTimeExcds, OutParmProblems",errors/s,IPv6 ICMP Errors,line,,proc.plugin,/proc/net/netstat +ipv6.icmpechos,,"InEchos, OutEchos, InEchoReplies, OutEchoReplies",messages/s,IPv6 ICMP Echo,line,,proc.plugin,/proc/net/netstat +ipv6.groupmemb,,"InQueries, OutQueries, InResponses, OutResponses, InReductions, OutReductions",messages/s,IPv6 ICMP Group Membership,line,,proc.plugin,/proc/net/netstat +ipv6.icmprouter,,"InSolicits, OutSolicits, InAdvertisements, OutAdvertisements",messages/s,IPv6 Router Messages,line,,proc.plugin,/proc/net/netstat +ipv6.icmpneighbor,,"InSolicits, OutSolicits, InAdvertisements, OutAdvertisements",messages/s,IPv6 Neighbor Messages,line,,proc.plugin,/proc/net/netstat +ipv6.icmpmldv2,,"received, sent",reports/s,IPv6 ICMP MLDv2 Reports,line,,proc.plugin,/proc/net/netstat +ipv6.icmptypes,,"InType1, InType128, InType129, InType136, OutType1, OutType128, OutType129, OutType133, OutType135, OutType143",messages/s,IPv6 ICMP Types,line,,proc.plugin,/proc/net/netstat +ipv6.ect,,"InNoECTPkts, InECT1Pkts, InECT0Pkts, InCEPkts",packets/s,IPv6 ECT Packets,line,,proc.plugin,/proc/net/netstat +ipv6.ect,,"InNoECTPkts, InECT1Pkts, InECT0Pkts, InCEPkts",packets/s,IPv6 ECT Packets,line,,proc.plugin,/proc/net/netstat +ipv4.sockstat_sockets,,used,sockets,IPv4 Sockets Used,line,,proc.plugin,/proc/net/sockstat +ipv4.sockstat_tcp_sockets,,"alloc, orphan, inuse, timewait",sockets,IPv4 TCP Sockets,line,,proc.plugin,/proc/net/sockstat +ipv4.sockstat_tcp_mem,,mem,KiB,IPv4 TCP Sockets Memory,area,,proc.plugin,/proc/net/sockstat +ipv4.sockstat_udp_sockets,,inuse,sockets,IPv4 UDP Sockets,line,,proc.plugin,/proc/net/sockstat +ipv4.sockstat_udp_mem,,mem,sockets,IPv4 UDP Sockets Memory,line,,proc.plugin,/proc/net/sockstat +ipv4.sockstat_udplite_sockets,,inuse,sockets,IPv4 UDPLITE Sockets,line,,proc.plugin,/proc/net/sockstat +ipv4.sockstat_raw_sockets,,inuse,sockets,IPv4 RAW Sockets,line,,proc.plugin,/proc/net/sockstat +ipv4.sockstat_frag_sockets,,inuse,fragments,IPv4 FRAG Sockets,line,,proc.plugin,/proc/net/sockstat +ipv4.sockstat_frag_mem,,mem,KiB,IPv4 FRAG Sockets Memory,area,,proc.plugin,/proc/net/sockstat +ipv6.sockstat6_tcp_sockets,,inuse,sockets,IPv6 TCP Sockets,line,,proc.plugin,/proc/net/sockstat6 +ipv6.sockstat6_udp_sockets,,inuse,sockets,IPv6 UDP Sockets,line,,proc.plugin,/proc/net/sockstat6 +ipv6.sockstat6_udplite_sockets,,inuse,sockets,IPv6 UDPLITE Sockets,line,,proc.plugin,/proc/net/sockstat6 +ipv6.sockstat6_raw_sockets,,inuse,sockets,IPv6 RAW Sockets,line,,proc.plugin,/proc/net/sockstat6 +ipv6.sockstat6_frag_sockets,,inuse,fragments,IPv6 FRAG Sockets,line,,proc.plugin,/proc/net/sockstat6 +ipvs.sockets,,connections,connections/s,IPVS New Connections,line,,proc.plugin,/proc/net/ip_vs_stats +ipvs.packets,,"received, sent",packets/s,IPVS Packets,line,,proc.plugin,/proc/net/ip_vs_stats +ipvs.net,,"received, sent",kilobits/s,IPVS Bandwidth,area,,proc.plugin,/proc/net/ip_vs_stats +nfs.net,,"udp, tcp",operations/s,NFS Client Network,stacked,,proc.plugin,/proc/net/rpc/nfs +nfs.rpc,,"calls, retransmits, auth_refresh",calls/s,NFS Client Remote Procedure Calls Statistics,line,,proc.plugin,/proc/net/rpc/nfs +nfs.proc2,,a dimension per proc2 call,calls/s,NFS v2 Client Remote Procedure Calls,stacked,,proc.plugin,/proc/net/rpc/nfs +nfs.proc3,,a dimension per proc3 call,calls/s,NFS v3 Client Remote Procedure Calls,stacked,,proc.plugin,/proc/net/rpc/nfs +nfs.proc4,,a dimension per proc4 call,calls/s,NFS v4 Client Remote Procedure Calls,stacked,,proc.plugin,/proc/net/rpc/nfs +nfsd.readcache,,"hits, misses, nocache",reads/s,NFS Server Read Cache,stacked,,proc.plugin,/proc/net/rpc/nfsd +nfsd.filehandles,,stale,handles/s,NFS Server File Handles,line,,proc.plugin,/proc/net/rpc/nfsd +nfsd.io,,"read, write",kilobytes/s,NFS Server I/O,area,,proc.plugin,/proc/net/rpc/nfsd +nfsd.threads,,threads,threads,NFS Server Threads,line,,proc.plugin,/proc/net/rpc/nfsd +nfsd.net,,"udp, tcp",packets/s,NFS Server Network Statistics,line,,proc.plugin,/proc/net/rpc/nfsd +nfsd.rpc,,"calls, bad_format, bad_auth",calls/s,NFS Server Remote Procedure Calls Statistics,line,,proc.plugin,/proc/net/rpc/nfsd +nfsd.proc2,,a dimension per proc2 call,calls/s,NFS v2 Server Remote Procedure Calls,stacked,,proc.plugin,/proc/net/rpc/nfsd +nfsd.proc3,,a dimension per proc3 call,calls/s,NFS v3 Server Remote Procedure Calls,stacked,,proc.plugin,/proc/net/rpc/nfsd +nfsd.proc4,,a dimension per proc4 call,calls/s,NFS v4 Server Remote Procedure Calls,stacked,,proc.plugin,/proc/net/rpc/nfsd +nfsd.proc4ops,,a dimension per proc4 operation,operations/s,NFS v4 Server Operations,stacked,,proc.plugin,/proc/net/rpc/nfsd +sctp.established,,established,associations,SCTP current total number of established associations,line,,proc.plugin,/proc/net/sctp/snmp +sctp.transitions,,"active, passive, aborted, shutdown",transitions/s,SCTP Association Transitions,line,,proc.plugin,/proc/net/sctp/snmp +sctp.packets,,"received, sent",packets/s,SCTP Packets,line,,proc.plugin,/proc/net/sctp/snmp +sctp.packet_errors,,"invalid, checksum",packets/s,SCTP Packet Errors,line,,proc.plugin,/proc/net/sctp/snmp +sctp.fragmentation,,"reassembled, fragmented",packets/s,SCTP Fragmentation,line,,proc.plugin,/proc/net/sctp/snmp +netfilter.conntrack_sockets,,connections,active connections,Connection Tracker Connections,line,,proc.plugin,/proc/net/stat/nf_conntrack +netfilter.conntrack_new,,"new, ignore, invalid",connections/s,Connection Tracker New Connections,line,,proc.plugin,/proc/net/stat/nf_conntrack +netfilter.conntrack_changes,,"inserted, deleted, delete_list",changes/s,Connection Tracker Changes,line,,proc.plugin,/proc/net/stat/nf_conntrack +netfilter.conntrack_expect,,"created, deleted, new",expectations/s,Connection Tracker Expectations,line,,proc.plugin,/proc/net/stat/nf_conntrack +netfilter.conntrack_search,,"searched, restarted, found",searches/s,Connection Tracker Searches,line,,proc.plugin,/proc/net/stat/nf_conntrack +netfilter.conntrack_errors,,"icmp_error, error_failed, drop, early_drop",events/s,Connection Tracker Errors,line,,proc.plugin,/proc/net/stat/nf_conntrack +netfilter.synproxy_syn_received,,received,packets/s,SYNPROXY SYN Packets received,line,,proc.plugin,/proc/net/stat/synproxy +netfilter.synproxy_conn_reopened,,reopened,connections/s,SYNPROXY Connections Reopened,line,,proc.plugin,/proc/net/stat/synproxy +netfilter.synproxy_cookies,,"valid, invalid, retransmits",cookies/s,SYNPROXY TCP Cookies,line,,proc.plugin,/proc/net/stat/synproxy +zfspool.state,zfs pool,"online, degraded, faulted, offline, removed, unavail, suspended",boolean,"ZFS pool state",line,pool,proc.plugin,/proc/spl/kstat/zfs +zfs.arc_size,,"arcsz, target, min, max",MiB,"ZFS ARC Size",area,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.l2_size,,"actual, size",MiB,"ZFS L2 ARC Size",area,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.reads,,"arc, demand, prefetch, metadata, l2",reads/s,"ZFS Reads",area,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.bytes,,"read, write",KiB/s,"ZFS ARC L2 Read/Write Rate",area,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.hits,,"hits, misses",percentage,"ZFS ARC Hits",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.hits_rate,,"hits, misses",events/s,"ZFS ARC Hits Rate",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.dhits,,"hits, misses",percentage,"ZFS Demand Hits",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.dhits_rate,,"hits, misses",events/s,"ZFS Demand Hits Rate",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.phits,,"hits, misses",percentage,"ZFS Prefetch Hits",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.phits_rate,,"hits, misses",events/s,"ZFS Prefetch Hits Rate",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.mhits,,"hits, misses",percentage,"ZFS Metadata Hits",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.mhits_rate,,"hits, misses",events/s,"ZFS Metadata Hits Rate",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.l2hits,,"hits, misses",percentage,"ZFS L2 Hits",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.l2hits_rate,,"hits, misses",events/s,"ZFS L2 Hits Rate",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.list_hits,,"mfu, mfu_ghost, mru, mru_ghost",hits/s,"ZFS List Hits",area,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.arc_size_breakdown,,"recent, frequent",percentage,"ZFS ARC Size Breakdown",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.memory_ops,,"direct, throttled, indirect",operations/s,"ZFS Memory Operations",line,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.important_ops,,"evict_skip, deleted, mutex_miss, hash_collisions",operations/s,"ZFS Important Operations",line,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.actual_hits,,"hits, misses",percentage,"ZFS Actual Cache Hits",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.actual_hits_rate,,"hits, misses",events/s,"ZFS Actual Cache Hits Rate",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.demand_data_hits,,"hits, misses",percentage,"ZFS Data Demand Efficiency",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.demand_data_hits_rate,,"hits, misses",events/s,"ZFS Data Demand Efficiency Rate",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.prefetch_data_hits,,"hits, misses",percentage,"ZFS Data Prefetch Efficiency",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.prefetch_data_hits_rate,,"hits, misses",events/s,"ZFS Data Prefetch Efficiency Rate",stacked,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.hash_elements,,"current, max",elements,"ZFS ARC Hash Elements",line,,proc.plugin,/proc/spl/kstat/zfs/arcstats +zfs.hash_chains,,"current, max",chains,"ZFS ARC Hash Chains",line,,proc.plugin,/proc/spl/kstat/zfs/arcstats +btrfs.disk,btrfs filesystem,"unallocated, data_free, data_used, meta_free, meta_used, sys_free, sys_used",MiB,"BTRFS Physical Disk Allocation",stacked,"filesystem_uuid, filesystem_label",proc.plugin,/sys/fs/btrfs +btrfs.data,btrfs filesystem,"free, used",MiB,"BTRFS Data Allocation",stacked,"filesystem_uuid, filesystem_label",proc.plugin,/sys/fs/btrfs +btrfs.metadata,btrfs filesystem,"free, used, reserved",MiB,"BTRFS Metadata Allocation",stacked,"filesystem_uuid, filesystem_label",proc.plugin,/sys/fs/btrfs +btrfs.system,btrfs filesystem,"free, used",MiB,"BTRFS System Allocation",stacked,"filesystem_uuid, filesystem_label",proc.plugin,/sys/fs/btrfs +btrfs.commits,btrfs filesystem,commits,commits,"BTRFS Commits",line,"filesystem_uuid, filesystem_label",proc.plugin,/sys/fs/btrfs +btrfs.commits_perc_time,btrfs filesystem,commits,percentage,"BTRFS Commits Time Share",line,"filesystem_uuid, filesystem_label",proc.plugin,/sys/fs/btrfs +btrfs.commit_timings,btrfs filesystem,"last, max",ms,"BTRFS Commit Timings",line,"filesystem_uuid, filesystem_label",proc.plugin,/sys/fs/btrfs +btrfs.device_errors,btrfs device,"write_errs, read_errs, flush_errs, corruption_errs, generation_errs",errors,"BTRFS Device Errors",line,"device_id, filesystem_uuid, filesystem_label",proc.plugin,/sys/fs/btrfs +powersupply.capacity,power device,capacity,percentage,Battery capacity,line,device,proc.plugin,/sys/class/power_supply +powersupply.charge,power device,"empty_design, empty, now, full, full_design",Ah,Battery charge,line,device,proc.plugin,/sys/class/power_supply +powersupply.energy,power device,"empty_design, empty, now, full, full_design",Wh,Battery energy,line,device,proc.plugin,/sys/class/power_supply +powersupply.voltage,power device,"min_design, min, now, max, max_design",V,Power supply voltage,line,device,proc.plugin,/sys/class/power_supply \ No newline at end of file diff --git a/collectors/proc.plugin/proc_diskstats.c b/collectors/proc.plugin/proc_diskstats.c index b487f291..2a4fe4f8 100644 --- a/collectors/proc.plugin/proc_diskstats.c +++ b/collectors/proc.plugin/proc_diskstats.c @@ -934,16 +934,12 @@ int do_proc_diskstats(int update_every, usec_t dt) { name_disks_by_id = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "name disks by id", name_disks_by_id); preferred_ids = simple_pattern_create( - config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "preferred disk ids", DEFAULT_PREFERRED_IDS) - , NULL - , SIMPLE_PATTERN_EXACT - ); + config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "preferred disk ids", DEFAULT_PREFERRED_IDS), NULL, + SIMPLE_PATTERN_EXACT, true); excluded_disks = simple_pattern_create( - config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "exclude disks", DEFAULT_EXCLUDED_DISKS) - , NULL - , SIMPLE_PATTERN_EXACT - ); + config_get(CONFIG_SECTION_PLUGIN_PROC_DISKSTATS, "exclude disks", DEFAULT_EXCLUDED_DISKS), NULL, + SIMPLE_PATTERN_EXACT, true); } // -------------------------------------------------------------------------- @@ -993,35 +989,35 @@ int do_proc_diskstats(int update_every, usec_t dt) { // # of reads completed # of writes completed // This is the total number of reads or writes completed successfully. - reads = str2ull(procfile_lineword(ff, l, 3)); // rd_ios - writes = str2ull(procfile_lineword(ff, l, 7)); // wr_ios + reads = str2ull(procfile_lineword(ff, l, 3), NULL); // rd_ios + writes = str2ull(procfile_lineword(ff, l, 7), NULL); // wr_ios // # of reads merged # of writes merged // Reads and writes which are adjacent to each other may be merged for // efficiency. Thus two 4K reads may become one 8K read before it is // ultimately handed to the disk, and so it will be counted (and queued) - mreads = str2ull(procfile_lineword(ff, l, 4)); // rd_merges_or_rd_sec - mwrites = str2ull(procfile_lineword(ff, l, 8)); // wr_merges + mreads = str2ull(procfile_lineword(ff, l, 4), NULL); // rd_merges_or_rd_sec + mwrites = str2ull(procfile_lineword(ff, l, 8), NULL); // wr_merges // # of sectors read # of sectors written // This is the total number of sectors read or written successfully. - readsectors = str2ull(procfile_lineword(ff, l, 5)); // rd_sec_or_wr_ios - writesectors = str2ull(procfile_lineword(ff, l, 9)); // wr_sec + readsectors = str2ull(procfile_lineword(ff, l, 5), NULL); // rd_sec_or_wr_ios + writesectors = str2ull(procfile_lineword(ff, l, 9), NULL); // wr_sec // # of milliseconds spent reading # of milliseconds spent writing // This is the total number of milliseconds spent by all reads or writes (as // measured from __make_request() to end_that_request_last()). - readms = str2ull(procfile_lineword(ff, l, 6)); // rd_ticks_or_wr_sec - writems = str2ull(procfile_lineword(ff, l, 10)); // wr_ticks + readms = str2ull(procfile_lineword(ff, l, 6), NULL); // rd_ticks_or_wr_sec + writems = str2ull(procfile_lineword(ff, l, 10), NULL); // wr_ticks // # of I/Os currently in progress // The only field that should go to zero. Incremented as requests are // given to appropriate struct request_queue and decremented as they finish. - queued_ios = str2ull(procfile_lineword(ff, l, 11)); // ios_pgr + queued_ios = str2ull(procfile_lineword(ff, l, 11), NULL); // ios_pgr // # of milliseconds spent doing I/Os // This field increases so long as field queued_ios is nonzero. - busy_ms = str2ull(procfile_lineword(ff, l, 12)); // tot_ticks + busy_ms = str2ull(procfile_lineword(ff, l, 12), NULL); // tot_ticks // weighted # of milliseconds spent doing I/Os // This field is incremented at each I/O start, I/O completion, I/O @@ -1029,27 +1025,27 @@ int do_proc_diskstats(int update_every, usec_t dt) { // (field queued_ios) times the number of milliseconds spent doing I/O since the // last update of this field. This can provide an easy measure of both // I/O completion time and the backlog that may be accumulating. - backlog_ms = str2ull(procfile_lineword(ff, l, 13)); // rq_ticks + backlog_ms = str2ull(procfile_lineword(ff, l, 13), NULL); // rq_ticks if (unlikely(words > 13)) { do_dc_stats = 1; // # of discards completed // This is the total number of discards completed successfully. - discards = str2ull(procfile_lineword(ff, l, 14)); // dc_ios + discards = str2ull(procfile_lineword(ff, l, 14), NULL); // dc_ios // # of discards merged // See the description of mreads/mwrites - mdiscards = str2ull(procfile_lineword(ff, l, 15)); // dc_merges + mdiscards = str2ull(procfile_lineword(ff, l, 15), NULL); // dc_merges // # of sectors discarded // This is the total number of sectors discarded successfully. - discardsectors = str2ull(procfile_lineword(ff, l, 16)); // dc_sec + discardsectors = str2ull(procfile_lineword(ff, l, 16), NULL); // dc_sec // # of milliseconds spent discarding // This is the total number of milliseconds spent by all discards (as // measured from __make_request() to end_that_request_last()). - discardms = str2ull(procfile_lineword(ff, l, 17)); // dc_ticks + discardms = str2ull(procfile_lineword(ff, l, 17), NULL); // dc_ticks } if (unlikely(words > 17)) { @@ -1059,10 +1055,10 @@ int do_proc_diskstats(int update_every, usec_t dt) { // These values increment when an flush I/O request completes. // Block layer combines flush requests and executes at most one at a time. // This counts flush requests executed by disk. Not tracked for partitions. - flushes = str2ull(procfile_lineword(ff, l, 18)); // fl_ios + flushes = str2ull(procfile_lineword(ff, l, 18), NULL); // fl_ios // total wait time for flush requests - flushms = str2ull(procfile_lineword(ff, l, 19)); // fl_ticks + flushms = str2ull(procfile_lineword(ff, l, 19), NULL); // fl_ticks } // -------------------------------------------------------------------------- diff --git a/collectors/proc.plugin/proc_interrupts.c b/collectors/proc.plugin/proc_interrupts.c index 04d8c73a..9a20700a 100644 --- a/collectors/proc.plugin/proc_interrupts.c +++ b/collectors/proc.plugin/proc_interrupts.c @@ -120,7 +120,7 @@ int do_proc_interrupts(int update_every, usec_t dt) { int c; for(c = 0; c < cpus ;c++) { if(likely((c + 1) < (int)words)) - irr->cpu[c].value = str2ull(procfile_lineword(ff, l, (uint32_t)(c + 1))); + irr->cpu[c].value = str2ull(procfile_lineword(ff, l, (uint32_t) (c + 1)), NULL); else irr->cpu[c].value = 0; diff --git a/collectors/proc.plugin/proc_loadavg.c b/collectors/proc.plugin/proc_loadavg.c index e833f69d..106cf908 100644 --- a/collectors/proc.plugin/proc_loadavg.c +++ b/collectors/proc.plugin/proc_loadavg.c @@ -45,7 +45,7 @@ int do_proc_loadavg(int update_every, usec_t dt) { double load15 = strtod(procfile_lineword(ff, 0, 2), NULL); //unsigned long long running_processes = str2ull(procfile_lineword(ff, 0, 3)); - unsigned long long active_processes = str2ull(procfile_lineword(ff, 0, 4)); + unsigned long long active_processes = str2ull(procfile_lineword(ff, 0, 4), NULL); //get system pid_max unsigned long long max_processes = get_system_pid_max(); diff --git a/collectors/proc.plugin/proc_mdstat.c b/collectors/proc.plugin/proc_mdstat.c index d6e87fd2..c3d1793c 100644 --- a/collectors/proc.plugin/proc_mdstat.c +++ b/collectors/proc.plugin/proc_mdstat.c @@ -231,8 +231,8 @@ int do_proc_mdstat(int update_every, usec_t dt) continue; } - raid->inuse_disks = str2ull(str_inuse); - raid->total_disks = str2ull(str_total); + raid->inuse_disks = str2ull(str_inuse, NULL); + raid->total_disks = str2ull(str_total, NULL); raid->failed_disks = raid->total_disks - raid->inuse_disks; } @@ -300,7 +300,7 @@ int do_proc_mdstat(int update_every, usec_t dt) word += 6; // skip leading "speed=" if (likely(s > word)) - raid->speed = str2ull(word); + raid->speed = str2ull(word, NULL); } } diff --git a/collectors/proc.plugin/proc_net_dev.c b/collectors/proc.plugin/proc_net_dev.c index 3ec8783b..9e8127cb 100644 --- a/collectors/proc.plugin/proc_net_dev.c +++ b/collectors/proc.plugin/proc_net_dev.c @@ -725,7 +725,9 @@ int do_proc_net_dev(int update_every, usec_t dt) { do_carrier = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "carrier for all interfaces", CONFIG_BOOLEAN_AUTO); do_mtu = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "mtu for all interfaces", CONFIG_BOOLEAN_AUTO); - disabled_list = simple_pattern_create(config_get(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "disable by default interfaces matching", "lo fireqos* *-ifb fwpr* fwbr* fwln*"), NULL, SIMPLE_PATTERN_EXACT); + disabled_list = simple_pattern_create( + config_get(CONFIG_SECTION_PLUGIN_PROC_NETDEV, "disable by default interfaces matching", + "lo fireqos* *-ifb fwpr* fwbr* fwln*"), NULL, SIMPLE_PATTERN_EXACT, true); } if(unlikely(!ff)) { diff --git a/collectors/proc.plugin/proc_net_rpc_nfs.c b/collectors/proc.plugin/proc_net_rpc_nfs.c index 0ab9d28b..d6547636 100644 --- a/collectors/proc.plugin/proc_net_rpc_nfs.c +++ b/collectors/proc.plugin/proc_net_rpc_nfs.c @@ -187,10 +187,10 @@ int do_proc_net_rpc_nfs(int update_every, usec_t dt) { continue; } - net_count = str2ull(procfile_lineword(ff, l, 1)); - net_udp_count = str2ull(procfile_lineword(ff, l, 2)); - net_tcp_count = str2ull(procfile_lineword(ff, l, 3)); - net_tcp_connections = str2ull(procfile_lineword(ff, l, 4)); + net_count = str2ull(procfile_lineword(ff, l, 1), NULL); + net_udp_count = str2ull(procfile_lineword(ff, l, 2), NULL); + net_tcp_count = str2ull(procfile_lineword(ff, l, 3), NULL); + net_tcp_connections = str2ull(procfile_lineword(ff, l, 4), NULL); unsigned long long sum = net_count + net_udp_count + net_tcp_count + net_tcp_connections; if(sum == 0ULL) do_net = -1; @@ -202,9 +202,9 @@ int do_proc_net_rpc_nfs(int update_every, usec_t dt) { continue; } - rpc_calls = str2ull(procfile_lineword(ff, l, 1)); - rpc_retransmits = str2ull(procfile_lineword(ff, l, 2)); - rpc_auth_refresh = str2ull(procfile_lineword(ff, l, 3)); + rpc_calls = str2ull(procfile_lineword(ff, l, 1), NULL); + rpc_retransmits = str2ull(procfile_lineword(ff, l, 2), NULL); + rpc_auth_refresh = str2ull(procfile_lineword(ff, l, 3), NULL); unsigned long long sum = rpc_calls + rpc_retransmits + rpc_auth_refresh; if(sum == 0ULL) do_rpc = -1; @@ -217,7 +217,7 @@ int do_proc_net_rpc_nfs(int update_every, usec_t dt) { unsigned long long sum = 0; unsigned int i, j; for(i = 0, j = 2; j < words && nfs_proc2_values[i].name[0] ; i++, j++) { - nfs_proc2_values[i].value = str2ull(procfile_lineword(ff, l, j)); + nfs_proc2_values[i].value = str2ull(procfile_lineword(ff, l, j), NULL); nfs_proc2_values[i].present = 1; sum += nfs_proc2_values[i].value; } @@ -238,7 +238,7 @@ int do_proc_net_rpc_nfs(int update_every, usec_t dt) { unsigned long long sum = 0; unsigned int i, j; for(i = 0, j = 2; j < words && nfs_proc3_values[i].name[0] ; i++, j++) { - nfs_proc3_values[i].value = str2ull(procfile_lineword(ff, l, j)); + nfs_proc3_values[i].value = str2ull(procfile_lineword(ff, l, j), NULL); nfs_proc3_values[i].present = 1; sum += nfs_proc3_values[i].value; } @@ -259,7 +259,7 @@ int do_proc_net_rpc_nfs(int update_every, usec_t dt) { unsigned long long sum = 0; unsigned int i, j; for(i = 0, j = 2; j < words && nfs_proc4_values[i].name[0] ; i++, j++) { - nfs_proc4_values[i].value = str2ull(procfile_lineword(ff, l, j)); + nfs_proc4_values[i].value = str2ull(procfile_lineword(ff, l, j), NULL); nfs_proc4_values[i].present = 1; sum += nfs_proc4_values[i].value; } diff --git a/collectors/proc.plugin/proc_net_rpc_nfsd.c b/collectors/proc.plugin/proc_net_rpc_nfsd.c index faa6b5c4..1d9127a0 100644 --- a/collectors/proc.plugin/proc_net_rpc_nfsd.c +++ b/collectors/proc.plugin/proc_net_rpc_nfsd.c @@ -286,9 +286,9 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) { continue; } - rc_hits = str2ull(procfile_lineword(ff, l, 1)); - rc_misses = str2ull(procfile_lineword(ff, l, 2)); - rc_nocache = str2ull(procfile_lineword(ff, l, 3)); + rc_hits = str2ull(procfile_lineword(ff, l, 1), NULL); + rc_misses = str2ull(procfile_lineword(ff, l, 2), NULL); + rc_nocache = str2ull(procfile_lineword(ff, l, 3), NULL); unsigned long long sum = rc_hits + rc_misses + rc_nocache; if(sum == 0ULL) do_rc = -1; @@ -300,7 +300,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) { continue; } - fh_stale = str2ull(procfile_lineword(ff, l, 1)); + fh_stale = str2ull(procfile_lineword(ff, l, 1), NULL); // other file handler metrics were never used and are always zero @@ -313,8 +313,8 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) { continue; } - io_read = str2ull(procfile_lineword(ff, l, 1)); - io_write = str2ull(procfile_lineword(ff, l, 2)); + io_read = str2ull(procfile_lineword(ff, l, 1), NULL); + io_write = str2ull(procfile_lineword(ff, l, 2), NULL); unsigned long long sum = io_read + io_write; if(sum == 0ULL) do_io = -1; @@ -326,7 +326,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) { continue; } - th_threads = str2ull(procfile_lineword(ff, l, 1)); + th_threads = str2ull(procfile_lineword(ff, l, 1), NULL); // thread histogram has been disabled since 2009 (kernel 2.6.30) // https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=8bbfa9f3889b643fc7de82c0c761ef17097f8faf @@ -339,10 +339,10 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) { continue; } - net_count = str2ull(procfile_lineword(ff, l, 1)); - net_udp_count = str2ull(procfile_lineword(ff, l, 2)); - net_tcp_count = str2ull(procfile_lineword(ff, l, 3)); - net_tcp_connections = str2ull(procfile_lineword(ff, l, 4)); + net_count = str2ull(procfile_lineword(ff, l, 1), NULL); + net_udp_count = str2ull(procfile_lineword(ff, l, 2), NULL); + net_tcp_count = str2ull(procfile_lineword(ff, l, 3), NULL); + net_tcp_connections = str2ull(procfile_lineword(ff, l, 4), NULL); unsigned long long sum = net_count + net_udp_count + net_tcp_count + net_tcp_connections; if(sum == 0ULL) do_net = -1; @@ -354,10 +354,10 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) { continue; } - rpc_calls = str2ull(procfile_lineword(ff, l, 1)); - rpc_bad_format = str2ull(procfile_lineword(ff, l, 3)); - rpc_bad_auth = str2ull(procfile_lineword(ff, l, 4)); - rpc_bad_client = str2ull(procfile_lineword(ff, l, 5)); + rpc_calls = str2ull(procfile_lineword(ff, l, 1), NULL); + rpc_bad_format = str2ull(procfile_lineword(ff, l, 3), NULL); + rpc_bad_auth = str2ull(procfile_lineword(ff, l, 4), NULL); + rpc_bad_client = str2ull(procfile_lineword(ff, l, 5), NULL); unsigned long long sum = rpc_calls + rpc_bad_format + rpc_bad_auth + rpc_bad_client; if(sum == 0ULL) do_rpc = -1; @@ -370,7 +370,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) { unsigned long long sum = 0; unsigned int i, j; for(i = 0, j = 2; j < words && nfsd_proc2_values[i].name[0] ; i++, j++) { - nfsd_proc2_values[i].value = str2ull(procfile_lineword(ff, l, j)); + nfsd_proc2_values[i].value = str2ull(procfile_lineword(ff, l, j), NULL); nfsd_proc2_values[i].present = 1; sum += nfsd_proc2_values[i].value; } @@ -391,7 +391,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) { unsigned long long sum = 0; unsigned int i, j; for(i = 0, j = 2; j < words && nfsd_proc3_values[i].name[0] ; i++, j++) { - nfsd_proc3_values[i].value = str2ull(procfile_lineword(ff, l, j)); + nfsd_proc3_values[i].value = str2ull(procfile_lineword(ff, l, j), NULL); nfsd_proc3_values[i].present = 1; sum += nfsd_proc3_values[i].value; } @@ -412,7 +412,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) { unsigned long long sum = 0; unsigned int i, j; for(i = 0, j = 2; j < words && nfsd_proc4_values[i].name[0] ; i++, j++) { - nfsd_proc4_values[i].value = str2ull(procfile_lineword(ff, l, j)); + nfsd_proc4_values[i].value = str2ull(procfile_lineword(ff, l, j), NULL); nfsd_proc4_values[i].present = 1; sum += nfsd_proc4_values[i].value; } @@ -433,7 +433,7 @@ int do_proc_net_rpc_nfsd(int update_every, usec_t dt) { unsigned long long sum = 0; unsigned int i, j; for(i = 0, j = 2; j < words && nfsd4_ops_values[i].name[0] ; i++, j++) { - nfsd4_ops_values[i].value = str2ull(procfile_lineword(ff, l, j)); + nfsd4_ops_values[i].value = str2ull(procfile_lineword(ff, l, j), NULL); nfsd4_ops_values[i].present = 1; sum += nfsd4_ops_values[i].value; } diff --git a/collectors/proc.plugin/proc_pagetypeinfo.c b/collectors/proc.plugin/proc_pagetypeinfo.c index e12c5bff..e5318ce8 100644 --- a/collectors/proc.plugin/proc_pagetypeinfo.c +++ b/collectors/proc.plugin/proc_pagetypeinfo.c @@ -120,10 +120,8 @@ int do_proc_pagetypeinfo(int update_every, usec_t dt) { do_global = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_PAGETYPEINFO, "enable system summary", CONFIG_BOOLEAN_YES); do_detail = config_get_boolean_ondemand(CONFIG_SECTION_PLUGIN_PROC_PAGETYPEINFO, "enable detail per-type", CONFIG_BOOLEAN_AUTO); filter_types = simple_pattern_create( - config_get(CONFIG_SECTION_PLUGIN_PROC_PAGETYPEINFO, "hide charts id matching", "") - , NULL - , SIMPLE_PATTERN_SUFFIX - ); + config_get(CONFIG_SECTION_PLUGIN_PROC_PAGETYPEINFO, "hide charts id matching", ""), NULL, + SIMPLE_PATTERN_SUFFIX, true); pagelines_cnt = 0; @@ -188,7 +186,7 @@ int do_proc_pagetypeinfo(int update_every, usec_t dt) { pgl->type = typename; pgl->zone = zonename; for (o = 0; o < pageorders_cnt; o++) - pgl->free_pages_size[o] = str2uint64_t(procfile_lineword(ff, l, o+6)) * 1 << o; + pgl->free_pages_size[o] = str2uint64_t(procfile_lineword(ff, l, o + 6), NULL) * 1 << o; p++; } @@ -302,7 +300,7 @@ int do_proc_pagetypeinfo(int update_every, usec_t dt) { systemorders[o].size = 0; // Update orders of the current line - pagelines[p].free_pages_size[o] = str2uint64_t(procfile_lineword(ff, l, o+6)) * 1 << o; + pagelines[p].free_pages_size[o] = str2uint64_t(procfile_lineword(ff, l, o + 6), NULL) * 1 << o; // Update sum by order systemorders[o].size += pagelines[p].free_pages_size[o]; diff --git a/collectors/proc.plugin/proc_pressure.c b/collectors/proc.plugin/proc_pressure.c index 80b08d9a..28e4c592 100644 --- a/collectors/proc.plugin/proc_pressure.c +++ b/collectors/proc.plugin/proc_pressure.c @@ -114,7 +114,7 @@ static void proc_pressure_do_resource(procfile *ff, int res_idx, int some) { pcs->total_time.rdtotal = rrddim_add(pcs->total_time.st, "time", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); } - pcs->total_time.value_total = str2ull(procfile_lineword(ff, some ? 0 : 1, 8)) / 1000; + pcs->total_time.value_total = str2ull(procfile_lineword(ff, some ? 0 : 1, 8), NULL) / 1000; } static void proc_pressure_do_resource_some(procfile *ff, int res_idx) { @@ -165,9 +165,16 @@ int do_proc_pressure(int update_every, usec_t dt) { do_some = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_PRESSURE, config_key, CONFIG_BOOLEAN_YES); resources[i].some.enabled = do_some; - snprintfz(config_key, CONFIG_MAX_NAME, "enable %s full pressure", resource_info[i].name); - do_full = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_PRESSURE, config_key, CONFIG_BOOLEAN_YES); - resources[i].full.enabled = do_full; + // Disable CPU full pressure. + // See https://github.com/torvalds/linux/commit/890d550d7dbac7a31ecaa78732aa22be282bb6b8 + if (i == 0) { + do_full = CONFIG_BOOLEAN_NO; + resources[i].full.enabled = do_full; + } else { + snprintfz(config_key, CONFIG_MAX_NAME, "enable %s full pressure", resource_info[i].name); + do_full = config_get_boolean(CONFIG_SECTION_PLUGIN_PROC_PRESSURE, config_key, CONFIG_BOOLEAN_YES); + resources[i].full.enabled = do_full; + } ff = procfile_open(filename, " =", PROCFILE_FLAG_DEFAULT); if (unlikely(!ff)) { diff --git a/collectors/proc.plugin/proc_softirqs.c b/collectors/proc.plugin/proc_softirqs.c index 0d5d8ef9..ccf46cb8 100644 --- a/collectors/proc.plugin/proc_softirqs.c +++ b/collectors/proc.plugin/proc_softirqs.c @@ -113,7 +113,7 @@ int do_proc_softirqs(int update_every, usec_t dt) { int c; for(c = 0; c < cpus ;c++) { if(likely((c + 1) < (int)words)) - irr->cpu[c].value = str2ull(procfile_lineword(ff, l, (uint32_t)(c + 1))); + irr->cpu[c].value = str2ull(procfile_lineword(ff, l, (uint32_t) (c + 1)), NULL); else irr->cpu[c].value = 0; diff --git a/collectors/proc.plugin/proc_spl_kstat_zfs.c b/collectors/proc.plugin/proc_spl_kstat_zfs.c index 0db9970c..428ef0d3 100644 --- a/collectors/proc.plugin/proc_spl_kstat_zfs.c +++ b/collectors/proc.plugin/proc_spl_kstat_zfs.c @@ -216,6 +216,7 @@ struct zfs_pool { RRDDIM *rd_offline; RRDDIM *rd_removed; RRDDIM *rd_unavail; + RRDDIM *rd_suspended; int updated; int disabled; @@ -226,6 +227,7 @@ struct zfs_pool { int offline; int removed; int unavail; + int suspended; }; struct deleted_zfs_pool { @@ -248,6 +250,7 @@ void disable_zfs_pool_state(struct zfs_pool *pool) pool->rd_offline = NULL; pool->rd_removed = NULL; pool->rd_unavail = NULL; + pool->rd_suspended = NULL; pool->disabled = 1; } @@ -285,6 +288,7 @@ 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); + pool->rd_suspended = rrddim_add(pool->st, "suspended", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); rrdlabels_add(pool->st->rrdlabels, "pool", name, RRDLABEL_SRC_AUTO); } @@ -295,6 +299,7 @@ int update_zfs_pool_state_chart(const DICTIONARY_ITEM *item, void *pool_p, void rrddim_set_by_pointer(pool->st, pool->rd_offline, pool->offline); rrddim_set_by_pointer(pool->st, pool->rd_removed, pool->removed); rrddim_set_by_pointer(pool->st, pool->rd_unavail, pool->unavail); + rrddim_set_by_pointer(pool->st, pool->rd_suspended, pool->suspended); rrdset_done(pool->st); } } else { @@ -364,10 +369,10 @@ int do_proc_spl_kstat_zfs_pool_state(int update_every, usec_t dt) pool->offline = 0; pool->removed = 0; pool->unavail = 0; + pool->suspended = 0; char filename[FILENAME_MAX + 1]; - snprintfz( - filename, FILENAME_MAX, "%s%s/%s/state", netdata_configured_host_prefix, dirname, de->d_name); + snprintfz(filename, FILENAME_MAX, "%s/%s/state", dirname, de->d_name); char state[STATE_SIZE + 1]; int ret = read_file(filename, state, STATE_SIZE); @@ -388,6 +393,8 @@ int do_proc_spl_kstat_zfs_pool_state(int update_every, usec_t dt) pool->removed = 1; } else if (!strcmp(state, "UNAVAIL\n")) { pool->unavail = 1; + } else if (!strcmp(state, "SUSPENDED\n")) { + pool->suspended = 1; } else { disable_zfs_pool_state(pool); diff --git a/collectors/proc.plugin/proc_stat.c b/collectors/proc.plugin/proc_stat.c index 2ca7c42e..f0f31935 100644 --- a/collectors/proc.plugin/proc_stat.c +++ b/collectors/proc.plugin/proc_stat.c @@ -182,8 +182,8 @@ static int read_per_core_time_in_state_files(struct cpu_chart *all_cpu_charts, s collector_error("Cannot read time_in_state line. Expected 2 params, read %zu.", words); continue; } - frequency = str2ull(procfile_lineword(tsf->ff, l, 0)); - ticks = str2ull(procfile_lineword(tsf->ff, l, 1)); + frequency = str2ull(procfile_lineword(tsf->ff, l, 0), NULL); + ticks = str2ull(procfile_lineword(tsf->ff, l, 1), NULL); // It is assumed that frequencies are static and sorted ticks_since_last = ticks - tsf->last_ticks[l].ticks; @@ -330,7 +330,7 @@ static int read_schedstat(char *schedstat_filename, struct per_core_cpuidle_char cpuidle_charts_len = cores_found; } - cpuidle_charts[core].active_time = str2ull(procfile_lineword(ff, l, 7)) / 1000; + cpuidle_charts[core].active_time = str2ull(procfile_lineword(ff, l, 7), NULL) / 1000; } } @@ -597,19 +597,19 @@ int do_proc_stat(int update_every, usec_t dt) { unsigned long long user = 0, nice = 0, system = 0, idle = 0, iowait = 0, irq = 0, softirq = 0, steal = 0, guest = 0, guest_nice = 0; id = row_key; - user = str2ull(procfile_lineword(ff, l, 1)); - nice = str2ull(procfile_lineword(ff, l, 2)); - system = str2ull(procfile_lineword(ff, l, 3)); - idle = str2ull(procfile_lineword(ff, l, 4)); - iowait = str2ull(procfile_lineword(ff, l, 5)); - irq = str2ull(procfile_lineword(ff, l, 6)); - softirq = str2ull(procfile_lineword(ff, l, 7)); - steal = str2ull(procfile_lineword(ff, l, 8)); - - guest = str2ull(procfile_lineword(ff, l, 9)); + user = str2ull(procfile_lineword(ff, l, 1), NULL); + nice = str2ull(procfile_lineword(ff, l, 2), NULL); + system = str2ull(procfile_lineword(ff, l, 3), NULL); + idle = str2ull(procfile_lineword(ff, l, 4), NULL); + iowait = str2ull(procfile_lineword(ff, l, 5), NULL); + irq = str2ull(procfile_lineword(ff, l, 6), NULL); + softirq = str2ull(procfile_lineword(ff, l, 7), NULL); + steal = str2ull(procfile_lineword(ff, l, 8), NULL); + + guest = str2ull(procfile_lineword(ff, l, 9), NULL); user -= guest; - guest_nice = str2ull(procfile_lineword(ff, l, 10)); + guest_nice = str2ull(procfile_lineword(ff, l, 10), NULL); nice -= guest_nice; char *title, *type, *context, *family; @@ -739,7 +739,7 @@ int do_proc_stat(int update_every, usec_t dt) { if(likely(do_interrupts)) { static RRDSET *st_intr = NULL; static RRDDIM *rd_interrupts = NULL; - unsigned long long value = str2ull(procfile_lineword(ff, l, 1)); + unsigned long long value = str2ull(procfile_lineword(ff, l, 1), NULL); if(unlikely(!st_intr)) { st_intr = rrdset_create_localhost( @@ -770,7 +770,7 @@ int do_proc_stat(int update_every, usec_t dt) { if(likely(do_context)) { static RRDSET *st_ctxt = NULL; static RRDDIM *rd_switches = NULL; - unsigned long long value = str2ull(procfile_lineword(ff, l, 1)); + unsigned long long value = str2ull(procfile_lineword(ff, l, 1), NULL); if(unlikely(!st_ctxt)) { st_ctxt = rrdset_create_localhost( @@ -796,13 +796,13 @@ int do_proc_stat(int update_every, usec_t dt) { } } else if(unlikely(hash == hash_processes && !processes && strcmp(row_key, "processes") == 0)) { - processes = str2ull(procfile_lineword(ff, l, 1)); + processes = str2ull(procfile_lineword(ff, l, 1), NULL); } else if(unlikely(hash == hash_procs_running && !running && strcmp(row_key, "procs_running") == 0)) { - running = str2ull(procfile_lineword(ff, l, 1)); + running = str2ull(procfile_lineword(ff, l, 1), NULL); } else if(unlikely(hash == hash_procs_blocked && !blocked && strcmp(row_key, "procs_blocked") == 0)) { - blocked = str2ull(procfile_lineword(ff, l, 1)); + blocked = str2ull(procfile_lineword(ff, l, 1), NULL); } } diff --git a/collectors/proc.plugin/proc_sys_kernel_random_entropy_avail.c b/collectors/proc.plugin/proc_sys_kernel_random_entropy_avail.c index a04d4303..b32597bc 100644 --- a/collectors/proc.plugin/proc_sys_kernel_random_entropy_avail.c +++ b/collectors/proc.plugin/proc_sys_kernel_random_entropy_avail.c @@ -17,7 +17,7 @@ int do_proc_sys_kernel_random_entropy_avail(int update_every, usec_t dt) { ff = procfile_readall(ff); if(unlikely(!ff)) return 0; // we return 0, so that we will retry to open it next time - unsigned long long entropy = str2ull(procfile_lineword(ff, 0, 0)); + unsigned long long entropy = str2ull(procfile_lineword(ff, 0, 0), NULL); static RRDSET *st = NULL; static RRDDIM *rd = NULL; diff --git a/collectors/proc.plugin/proc_vmstat.c b/collectors/proc.plugin/proc_vmstat.c index 638d1690..ca56e900 100644 --- a/collectors/proc.plugin/proc_vmstat.c +++ b/collectors/proc.plugin/proc_vmstat.c @@ -10,7 +10,7 @@ int do_proc_vmstat(int update_every, usec_t dt) { (void)dt; static procfile *ff = NULL; - static int do_swapio = -1, do_io = -1, do_pgfaults = -1, do_oom_kill = -1, do_numa = -1; + static int do_swapio = -1, do_io = -1, do_pgfaults = -1, do_oom_kill = -1, do_numa = -1, do_thp = -1, do_zswapio = -1, do_balloon = -1, do_ksm = -1; static int has_numa = -1; static ARL_BASE *arl_base = NULL; @@ -31,6 +31,103 @@ int do_proc_vmstat(int update_every, usec_t dt) { static unsigned long long pswpout = 0ULL; static unsigned long long oom_kill = 0ULL; + // THP page migration +// static unsigned long long pgmigrate_success = 0ULL; +// static unsigned long long pgmigrate_fail = 0ULL; +// static unsigned long long thp_migration_success = 0ULL; +// static unsigned long long thp_migration_fail = 0ULL; +// static unsigned long long thp_migration_split = 0ULL; + + // Compaction cost model + // https://lore.kernel.org/lkml/20121022080525.GB2198@suse.de/ +// static unsigned long long compact_migrate_scanned = 0ULL; +// static unsigned long long compact_free_scanned = 0ULL; +// static unsigned long long compact_isolated = 0ULL; + + // THP defragmentation + static unsigned long long compact_stall = 0ULL; // incremented when an application stalls allocating THP + static unsigned long long compact_fail = 0ULL; // defragmentation events that failed + static unsigned long long compact_success = 0ULL; // defragmentation events that succeeded + + // ? +// static unsigned long long compact_daemon_wake = 0ULL; +// static unsigned long long compact_daemon_migrate_scanned = 0ULL; +// static unsigned long long compact_daemon_free_scanned = 0ULL; + + // ? +// static unsigned long long htlb_buddy_alloc_success = 0ULL; +// static unsigned long long htlb_buddy_alloc_fail = 0ULL; + + // ? +// static unsigned long long cma_alloc_success = 0ULL; +// static unsigned long long cma_alloc_fail = 0ULL; + + // ? +// static unsigned long long unevictable_pgs_culled = 0ULL; +// static unsigned long long unevictable_pgs_scanned = 0ULL; +// static unsigned long long unevictable_pgs_rescued = 0ULL; +// static unsigned long long unevictable_pgs_mlocked = 0ULL; +// static unsigned long long unevictable_pgs_munlocked = 0ULL; +// static unsigned long long unevictable_pgs_cleared = 0ULL; +// static unsigned long long unevictable_pgs_stranded = 0ULL; + + // THP handling of page faults + static unsigned long long thp_fault_alloc = 0ULL; // is incremented every time a huge page is successfully allocated to handle a page fault. This applies to both the first time a page is faulted and for COW faults. + static unsigned long long thp_fault_fallback = 0ULL; // is incremented if a page fault fails to allocate a huge page and instead falls back to using small pages. + static unsigned long long thp_fault_fallback_charge = 0ULL; // is incremented if a page fault fails to charge a huge page and instead falls back to using small pages even though the allocation was successful. + + // khugepaged collapsing of small pages into huge pages + static unsigned long long thp_collapse_alloc = 0ULL; // is incremented by khugepaged when it has found a range of pages to collapse into one huge page and has successfully allocated a new huge page to store the data. + static unsigned long long thp_collapse_alloc_failed = 0ULL; // is incremented if khugepaged found a range of pages that should be collapsed into one huge page but failed the allocation. + + // THP handling of file allocations + static unsigned long long thp_file_alloc = 0ULL; // is incremented every time a file huge page is successfully allocated + static unsigned long long thp_file_fallback = 0ULL; // is incremented if a file huge page is attempted to be allocated but fails and instead falls back to using small pages + static unsigned long long thp_file_fallback_charge = 0ULL; // is incremented if a file huge page cannot be charged and instead falls back to using small pages even though the allocation was successful + static unsigned long long thp_file_mapped = 0ULL; // is incremented every time a file huge page is mapped into user address space + + // THP splitting of huge pages into small pages + static unsigned long long thp_split_page = 0ULL; + static unsigned long long thp_split_page_failed = 0ULL; + static unsigned long long thp_deferred_split_page = 0ULL; // is incremented when a huge page is put onto split queue. This happens when a huge page is partially unmapped and splitting it would free up some memory. Pages on split queue are going to be split under memory pressure + static unsigned long long thp_split_pmd = 0ULL; // is incremented every time a PMD split into table of PTEs. This can happen, for instance, when application calls mprotect() or munmap() on part of huge page. It doesn’t split huge page, only page table entry + + // ? +// static unsigned long long thp_scan_exceed_none_pte = 0ULL; +// static unsigned long long thp_scan_exceed_swap_pte = 0ULL; +// static unsigned long long thp_scan_exceed_share_pte = 0ULL; +// static unsigned long long thp_split_pud = 0ULL; + + // THP Zero Huge Page + static unsigned long long thp_zero_page_alloc = 0ULL; // is incremented every time a huge zero page used for thp is successfully allocated. Note, it doesn’t count every map of the huge zero page, only its allocation + static unsigned long long thp_zero_page_alloc_failed = 0ULL; // is incremented if kernel fails to allocate huge zero page and falls back to using small pages + + // THP Swap Out + static unsigned long long thp_swpout = 0ULL; // is incremented every time a huge page is swapout in one piece without splitting + static unsigned long long thp_swpout_fallback = 0ULL; // is incremented if a huge page has to be split before swapout. Usually because failed to allocate some continuous swap space for the huge page + + // memory ballooning + // Current size of balloon is (balloon_inflate - balloon_deflate) pages + static unsigned long long balloon_inflate = 0ULL; + static unsigned long long balloon_deflate = 0ULL; + static unsigned long long balloon_migrate = 0ULL; + + // ? +// static unsigned long long swap_ra = 0ULL; +// static unsigned long long swap_ra_hit = 0ULL; + + static unsigned long long ksm_swpin_copy = 0ULL; // is incremented every time a KSM page is copied when swapping in + static unsigned long long cow_ksm = 0ULL; // is incremented every time a KSM page triggers copy on write (COW) when users try to write to a KSM page, we have to make a copy + + // zswap + static unsigned long long zswpin = 0ULL; + static unsigned long long zswpout = 0ULL; + + // ? +// static unsigned long long direct_map_level2_splits = 0ULL; +// static unsigned long long direct_map_level3_splits = 0ULL; +// static unsigned long long nr_unstable = 0ULL; + if(unlikely(!ff)) { char filename[FILENAME_MAX + 1]; snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/vmstat"); @@ -49,7 +146,10 @@ int do_proc_vmstat(int update_every, usec_t dt) { do_pgfaults = config_get_boolean("plugin:proc:/proc/vmstat", "memory page faults", CONFIG_BOOLEAN_YES); do_oom_kill = config_get_boolean("plugin:proc:/proc/vmstat", "out of memory kills", CONFIG_BOOLEAN_AUTO); do_numa = config_get_boolean_ondemand("plugin:proc:/proc/vmstat", "system-wide numa metric summary", CONFIG_BOOLEAN_AUTO); - + do_thp = config_get_boolean_ondemand("plugin:proc:/proc/vmstat", "transparent huge pages", CONFIG_BOOLEAN_AUTO); + do_zswapio = config_get_boolean_ondemand("plugin:proc:/proc/vmstat", "zswap i/o", CONFIG_BOOLEAN_AUTO); + do_balloon = config_get_boolean_ondemand("plugin:proc:/proc/vmstat", "memory ballooning", CONFIG_BOOLEAN_AUTO); + do_ksm = config_get_boolean_ondemand("plugin:proc:/proc/vmstat", "kernel same memory", CONFIG_BOOLEAN_AUTO); arl_base = arl_create("vmstat", NULL, 60); arl_expect(arl_base, "pgfault", &pgfault); @@ -94,6 +194,56 @@ int do_proc_vmstat(int update_every, usec_t dt) { has_numa = 0; do_numa = CONFIG_BOOLEAN_NO; } + + if(do_thp == CONFIG_BOOLEAN_YES || do_thp == CONFIG_BOOLEAN_AUTO) { +// arl_expect(arl_base, "pgmigrate_success", &pgmigrate_success); +// arl_expect(arl_base, "pgmigrate_fail", &pgmigrate_fail); +// arl_expect(arl_base, "thp_migration_success", &thp_migration_success); +// arl_expect(arl_base, "thp_migration_fail", &thp_migration_fail); +// arl_expect(arl_base, "thp_migration_split", &thp_migration_split); +// arl_expect(arl_base, "compact_migrate_scanned", &compact_migrate_scanned); +// arl_expect(arl_base, "compact_free_scanned", &compact_free_scanned); +// arl_expect(arl_base, "compact_isolated", &compact_isolated); + arl_expect(arl_base, "compact_stall", &compact_stall); + arl_expect(arl_base, "compact_fail", &compact_fail); + arl_expect(arl_base, "compact_success", &compact_success); +// arl_expect(arl_base, "compact_daemon_wake", &compact_daemon_wake); +// arl_expect(arl_base, "compact_daemon_migrate_scanned", &compact_daemon_migrate_scanned); +// arl_expect(arl_base, "compact_daemon_free_scanned", &compact_daemon_free_scanned); + arl_expect(arl_base, "thp_fault_alloc", &thp_fault_alloc); + arl_expect(arl_base, "thp_fault_fallback", &thp_fault_fallback); + arl_expect(arl_base, "thp_fault_fallback_charge", &thp_fault_fallback_charge); + arl_expect(arl_base, "thp_collapse_alloc", &thp_collapse_alloc); + arl_expect(arl_base, "thp_collapse_alloc_failed", &thp_collapse_alloc_failed); + arl_expect(arl_base, "thp_file_alloc", &thp_file_alloc); + arl_expect(arl_base, "thp_file_fallback", &thp_file_fallback); + arl_expect(arl_base, "thp_file_fallback_charge", &thp_file_fallback_charge); + arl_expect(arl_base, "thp_file_mapped", &thp_file_mapped); + arl_expect(arl_base, "thp_split_page", &thp_split_page); + arl_expect(arl_base, "thp_split_page_failed", &thp_split_page_failed); + arl_expect(arl_base, "thp_deferred_split_page", &thp_deferred_split_page); + arl_expect(arl_base, "thp_split_pmd", &thp_split_pmd); + arl_expect(arl_base, "thp_zero_page_alloc", &thp_zero_page_alloc); + arl_expect(arl_base, "thp_zero_page_alloc_failed", &thp_zero_page_alloc_failed); + arl_expect(arl_base, "thp_swpout", &thp_swpout); + arl_expect(arl_base, "thp_swpout_fallback", &thp_swpout_fallback); + } + + if(do_balloon == CONFIG_BOOLEAN_YES || do_balloon == CONFIG_BOOLEAN_AUTO) { + arl_expect(arl_base, "balloon_inflate", &balloon_inflate); + arl_expect(arl_base, "balloon_deflate", &balloon_deflate); + arl_expect(arl_base, "balloon_migrate", &balloon_migrate); + } + + if(do_ksm == CONFIG_BOOLEAN_YES || do_ksm == CONFIG_BOOLEAN_AUTO) { + arl_expect(arl_base, "ksm_swpin_copy", &ksm_swpin_copy); + arl_expect(arl_base, "cow_ksm", &cow_ksm); + } + + if(do_zswapio == CONFIG_BOOLEAN_YES || do_zswapio == CONFIG_BOOLEAN_AUTO) { + arl_expect(arl_base, "zswpin", &zswpin); + arl_expect(arl_base, "zswpout", &zswpout); + } } arl_begin(arl_base); @@ -306,6 +456,355 @@ int do_proc_vmstat(int update_every, usec_t dt) { rrdset_done(st_numa); } + // -------------------------------------------------------------------- + + if(do_balloon == CONFIG_BOOLEAN_YES || (do_balloon == CONFIG_BOOLEAN_AUTO && (balloon_inflate || balloon_deflate || + balloon_migrate || netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) { + do_balloon = CONFIG_BOOLEAN_YES; + + static RRDSET *st_balloon = NULL; + static RRDDIM *rd_inflate = NULL, *rd_deflate = NULL, *rd_migrate = NULL; + + if(unlikely(!st_balloon)) { + st_balloon = rrdset_create_localhost( + "mem" + , "balloon" + , NULL + , "balloon" + , NULL + , "Memory Ballooning Operations" + , "KiB/s" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_VMSTAT_NAME + , NETDATA_CHART_PRIO_MEM_BALLOON + , update_every + , RRDSET_TYPE_LINE + ); + + rd_inflate = rrddim_add(st_balloon, "inflate", NULL, sysconf(_SC_PAGESIZE), 1024, RRD_ALGORITHM_INCREMENTAL); + rd_deflate = rrddim_add(st_balloon, "deflate", NULL, -sysconf(_SC_PAGESIZE), 1024, RRD_ALGORITHM_INCREMENTAL); + rd_migrate = rrddim_add(st_balloon, "migrate", NULL, sysconf(_SC_PAGESIZE), 1024, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_balloon, rd_inflate, balloon_inflate); + rrddim_set_by_pointer(st_balloon, rd_deflate, balloon_deflate); + rrddim_set_by_pointer(st_balloon, rd_migrate, balloon_migrate); + + rrdset_done(st_balloon); + } + + // -------------------------------------------------------------------- + + if(do_zswapio == CONFIG_BOOLEAN_YES || (do_zswapio == CONFIG_BOOLEAN_AUTO && + (zswpin || zswpout || + netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) { + do_zswapio = CONFIG_BOOLEAN_YES; + + static RRDSET *st_zswapio = NULL; + static RRDDIM *rd_in = NULL, *rd_out = NULL; + + if(unlikely(!st_zswapio)) { + st_zswapio = rrdset_create_localhost( + "system" + , "zswapio" + , NULL + , "zswap" + , NULL + , "ZSwap I/O" + , "KiB/s" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_VMSTAT_NAME + , NETDATA_CHART_PRIO_SYSTEM_ZSWAPIO + , update_every + , RRDSET_TYPE_AREA + ); + + rd_in = rrddim_add(st_zswapio, "in", NULL, sysconf(_SC_PAGESIZE), 1024, RRD_ALGORITHM_INCREMENTAL); + rd_out = rrddim_add(st_zswapio, "out", NULL, -sysconf(_SC_PAGESIZE), 1024, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_zswapio, rd_in, zswpin); + rrddim_set_by_pointer(st_zswapio, rd_out, zswpout); + rrdset_done(st_zswapio); + } + + // -------------------------------------------------------------------- + + if(do_ksm == CONFIG_BOOLEAN_YES || (do_ksm == CONFIG_BOOLEAN_AUTO && + (cow_ksm || ksm_swpin_copy || + netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) { + do_ksm = CONFIG_BOOLEAN_YES; + + static RRDSET *st_ksm_cow = NULL; + static RRDDIM *rd_swapin = NULL, *rd_write = NULL; + + if(unlikely(!st_ksm_cow)) { + st_ksm_cow = rrdset_create_localhost( + "mem" + , "ksm_cow" + , NULL + , "ksm" + , NULL + , "KSM Copy On Write Operations" + , "KiB/s" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_VMSTAT_NAME + , NETDATA_CHART_PRIO_MEM_KSM_COW + , update_every + , RRDSET_TYPE_LINE + ); + + rd_swapin = rrddim_add(st_ksm_cow, "swapin", NULL, sysconf(_SC_PAGESIZE), 1024, RRD_ALGORITHM_INCREMENTAL); + rd_write = rrddim_add(st_ksm_cow, "write", NULL, sysconf(_SC_PAGESIZE), 1024, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_ksm_cow, rd_swapin, ksm_swpin_copy); + rrddim_set_by_pointer(st_ksm_cow, rd_write, cow_ksm); + + rrdset_done(st_ksm_cow); + } + + // -------------------------------------------------------------------- + + if(do_thp == CONFIG_BOOLEAN_YES || do_thp == CONFIG_BOOLEAN_AUTO) { + + if(do_thp == CONFIG_BOOLEAN_YES || (do_thp == CONFIG_BOOLEAN_AUTO && + (netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES || thp_fault_alloc || thp_fault_fallback || thp_fault_fallback_charge))) { + + static RRDSET *st_thp_fault = NULL; + static RRDDIM *rd_alloc = NULL, *rd_fallback = NULL, *rd_fallback_charge = NULL; + + if(unlikely(!st_thp_fault)) { + st_thp_fault = rrdset_create_localhost( + "mem" + , "thp_faults" + , NULL + , "hugepages" + , NULL + , "Transparent Huge Page Fault Allocations" + , "events/s" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_VMSTAT_NAME + , NETDATA_CHART_PRIO_MEM_HUGEPAGES_FAULTS + , update_every + , RRDSET_TYPE_LINE + ); + + rd_alloc = rrddim_add(st_thp_fault, "alloc", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_fallback = rrddim_add(st_thp_fault, "fallback", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_fallback_charge = rrddim_add(st_thp_fault, "fallback_charge", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_thp_fault, rd_alloc, thp_fault_alloc); + rrddim_set_by_pointer(st_thp_fault, rd_fallback, thp_fault_fallback); + rrddim_set_by_pointer(st_thp_fault, rd_fallback_charge, thp_fault_fallback_charge); + + rrdset_done(st_thp_fault); + } + + if(do_thp == CONFIG_BOOLEAN_YES || (do_thp == CONFIG_BOOLEAN_AUTO && + (netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES || thp_fault_alloc || thp_fault_fallback || thp_fault_fallback_charge || thp_file_mapped))) { + + static RRDSET *st_thp_file = NULL; + static RRDDIM *rd_alloc = NULL, *rd_fallback = NULL, *rd_fallback_charge = NULL, *rd_mapped = NULL; + + if(unlikely(!st_thp_file)) { + st_thp_file = rrdset_create_localhost( + "mem" + , "thp_file" + , NULL + , "hugepages" + , NULL + , "Transparent Huge Page File Allocations" + , "events/s" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_VMSTAT_NAME + , NETDATA_CHART_PRIO_MEM_HUGEPAGES_FILE + , update_every + , RRDSET_TYPE_LINE + ); + + rd_alloc = rrddim_add(st_thp_file, "alloc", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_fallback = rrddim_add(st_thp_file, "fallback", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_mapped = rrddim_add(st_thp_file, "mapped", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_fallback_charge = rrddim_add(st_thp_file, "fallback_charge", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_thp_file, rd_alloc, thp_file_alloc); + rrddim_set_by_pointer(st_thp_file, rd_fallback, thp_file_fallback); + rrddim_set_by_pointer(st_thp_file, rd_mapped, thp_file_fallback_charge); + rrddim_set_by_pointer(st_thp_file, rd_fallback_charge, thp_file_fallback_charge); + + rrdset_done(st_thp_file); + } + + if(do_thp == CONFIG_BOOLEAN_YES || (do_thp == CONFIG_BOOLEAN_AUTO && + (netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES || thp_zero_page_alloc || thp_zero_page_alloc_failed))) { + + static RRDSET *st_thp_zero = NULL; + static RRDDIM *rd_alloc = NULL, *rd_failed = NULL; + + if(unlikely(!st_thp_zero)) { + st_thp_zero = rrdset_create_localhost( + "mem" + , "thp_zero" + , NULL + , "hugepages" + , NULL + , "Transparent Huge Zero Page Allocations" + , "events/s" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_VMSTAT_NAME + , NETDATA_CHART_PRIO_MEM_HUGEPAGES_ZERO + , update_every + , RRDSET_TYPE_LINE + ); + + rd_alloc = rrddim_add(st_thp_zero, "alloc", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_failed = rrddim_add(st_thp_zero, "failed", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_thp_zero, rd_alloc, thp_zero_page_alloc); + rrddim_set_by_pointer(st_thp_zero, rd_failed, thp_zero_page_alloc_failed); + + rrdset_done(st_thp_zero); + } + + if(do_thp == CONFIG_BOOLEAN_YES || (do_thp == CONFIG_BOOLEAN_AUTO && + (netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES || thp_collapse_alloc || thp_collapse_alloc_failed))) { + + static RRDSET *st_khugepaged = NULL; + static RRDDIM *rd_alloc = NULL, *rd_failed = NULL; + + if(unlikely(!st_khugepaged)) { + st_khugepaged = rrdset_create_localhost( + "mem" + , "thp_collapse" + , NULL + , "hugepages" + , NULL + , "Transparent Huge Pages Collapsed by khugepaged" + , "events/s" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_VMSTAT_NAME + , NETDATA_CHART_PRIO_MEM_HUGEPAGES_KHUGEPAGED + , update_every + , RRDSET_TYPE_LINE + ); + + rd_alloc = rrddim_add(st_khugepaged, "alloc", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_failed = rrddim_add(st_khugepaged, "failed", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_khugepaged, rd_alloc, thp_collapse_alloc); + rrddim_set_by_pointer(st_khugepaged, rd_failed, thp_collapse_alloc_failed); + + rrdset_done(st_khugepaged); + } + + if(do_thp == CONFIG_BOOLEAN_YES || (do_thp == CONFIG_BOOLEAN_AUTO && + (netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES || thp_split_page || thp_split_page_failed || thp_deferred_split_page || thp_split_pmd))) { + + static RRDSET *st_thp_split = NULL; + static RRDDIM *rd_split = NULL, *rd_failed = NULL, *rd_deferred_split = NULL, *rd_split_pmd = NULL; + + if(unlikely(!st_thp_split)) { + st_thp_split = rrdset_create_localhost( + "mem" + , "thp_split" + , NULL + , "hugepages" + , NULL + , "Transparent Huge Page Splits" + , "events/s" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_VMSTAT_NAME + , NETDATA_CHART_PRIO_MEM_HUGEPAGES_SPLITS + , update_every + , RRDSET_TYPE_LINE + ); + + rd_split = rrddim_add(st_thp_split, "split", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_failed = rrddim_add(st_thp_split, "failed", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_split_pmd = rrddim_add(st_thp_split, "split_pmd", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_deferred_split = rrddim_add(st_thp_split, "split_deferred", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_thp_split, rd_split, thp_split_page); + rrddim_set_by_pointer(st_thp_split, rd_failed, thp_split_page_failed); + rrddim_set_by_pointer(st_thp_split, rd_split_pmd, thp_split_pmd); + rrddim_set_by_pointer(st_thp_split, rd_deferred_split, thp_deferred_split_page); + + rrdset_done(st_thp_split); + } + + if(do_thp == CONFIG_BOOLEAN_YES || (do_thp == CONFIG_BOOLEAN_AUTO && + (netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES || thp_swpout || thp_swpout_fallback))) { + + static RRDSET *st_tmp_swapout = NULL; + static RRDDIM *rd_swapout = NULL, *rd_fallback = NULL; + + if(unlikely(!st_tmp_swapout)) { + st_tmp_swapout = rrdset_create_localhost( + "mem" + , "thp_swapout" + , NULL + , "hugepages" + , NULL + , "Transparent Huge Pages Swap Out" + , "events/s" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_VMSTAT_NAME + , NETDATA_CHART_PRIO_MEM_HUGEPAGES_SWAPOUT + , update_every + , RRDSET_TYPE_LINE + ); + + rd_swapout = rrddim_add(st_tmp_swapout, "swapout", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_fallback = rrddim_add(st_tmp_swapout, "fallback", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_tmp_swapout, rd_swapout, thp_swpout); + rrddim_set_by_pointer(st_tmp_swapout, rd_fallback, thp_swpout_fallback); + + rrdset_done(st_tmp_swapout); + } + + if(do_thp == CONFIG_BOOLEAN_YES || (do_thp == CONFIG_BOOLEAN_AUTO && + (netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES || compact_stall || compact_fail || compact_success))) { + + static RRDSET *st_thp_compact = NULL; + static RRDDIM *rd_success = NULL, *rd_fail = NULL, *rd_stall = NULL; + + if(unlikely(!st_thp_compact)) { + st_thp_compact = rrdset_create_localhost( + "mem" + , "thp_compact" + , NULL + , "hugepages" + , NULL + , "Transparent Huge Pages Compaction" + , "events/s" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_VMSTAT_NAME + , NETDATA_CHART_PRIO_MEM_HUGEPAGES_COMPACT + , update_every + , RRDSET_TYPE_LINE + ); + + rd_success = rrddim_add(st_thp_compact, "success", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_fail = rrddim_add(st_thp_compact, "fail", NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL); + rd_stall = rrddim_add(st_thp_compact, "stall", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); + } + + rrddim_set_by_pointer(st_thp_compact, rd_success, compact_success); + rrddim_set_by_pointer(st_thp_compact, rd_fail, compact_fail); + rrddim_set_by_pointer(st_thp_compact, rd_stall, compact_stall); + + rrdset_done(st_thp_compact); + } + } + return 0; } diff --git a/collectors/proc.plugin/sys_block_zram.c b/collectors/proc.plugin/sys_block_zram.c index 1be725b1..f9166ace 100644 --- a/collectors/proc.plugin/sys_block_zram.c +++ b/collectors/proc.plugin/sys_block_zram.c @@ -130,18 +130,20 @@ static inline void init_rrd(const char *name, ZRAM_DEVICE *d, int update_every) static int init_devices(DICTIONARY *devices, unsigned int zram_id, int update_every) { int count = 0; - DIR *dir = opendir("/dev"); struct dirent *de; struct stat st; - char filename[FILENAME_MAX + 1]; procfile *ff = NULL; ZRAM_DEVICE device; + char filename[FILENAME_MAX + 1]; + + snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/dev"); + DIR *dir = opendir(filename); if (unlikely(!dir)) return 0; while ((de = readdir(dir))) { - snprintfz(filename, FILENAME_MAX, "/dev/%s", de->d_name); + snprintfz(filename, FILENAME_MAX, "%s/dev/%s", netdata_configured_host_prefix, de->d_name); if (unlikely(stat(filename, &st) != 0)) { collector_error("ZRAM : Unable to stat %s: %s", filename, strerror(errno)); @@ -150,7 +152,7 @@ static int init_devices(DICTIONARY *devices, unsigned int zram_id, int update_ev if (major(st.st_rdev) == zram_id) { collector_info("ZRAM : Found device %s", filename); - snprintfz(filename, FILENAME_MAX, "/sys/block/%s/mm_stat", de->d_name); + snprintfz(filename, FILENAME_MAX, "%s/sys/block/%s/mm_stat", netdata_configured_host_prefix, de->d_name); ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT); if (ff == NULL) { @@ -191,13 +193,13 @@ static inline int read_mm_stat(procfile *ff, MM_STAT *stats) { return -1; } - stats->orig_data_size = str2ull(procfile_word(ff, 0)); - stats->compr_data_size = str2ull(procfile_word(ff, 1)); - stats->mem_used_total = str2ull(procfile_word(ff, 2)); - stats->mem_limit = str2ull(procfile_word(ff, 3)); - stats->mem_used_max = str2ull(procfile_word(ff, 4)); - stats->same_pages = str2ull(procfile_word(ff, 5)); - stats->pages_compacted = str2ull(procfile_word(ff, 6)); + stats->orig_data_size = str2ull(procfile_word(ff, 0), NULL); + stats->compr_data_size = str2ull(procfile_word(ff, 1), NULL); + stats->mem_used_total = str2ull(procfile_word(ff, 2), NULL); + stats->mem_limit = str2ull(procfile_word(ff, 3), NULL); + stats->mem_used_max = str2ull(procfile_word(ff, 4), NULL); + stats->same_pages = str2ull(procfile_word(ff, 5), NULL); + stats->pages_compacted = str2ull(procfile_word(ff, 6), NULL); return 0; } @@ -249,10 +251,14 @@ int do_sys_block_zram(int update_every, usec_t dt) { if (unlikely(!initialized)) { initialized = 1; - ff = procfile_open("/proc/devices", " \t:", PROCFILE_FLAG_DEFAULT); + + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/devices"); + + ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT); if (ff == NULL) { - collector_error("Cannot read /proc/devices"); + collector_error("Cannot read %s", filename); return 1; } ff = procfile_readall(ff); diff --git a/collectors/proc.plugin/sys_class_infiniband.c b/collectors/proc.plugin/sys_class_infiniband.c index 5f5e5323..f0b7f9a5 100644 --- a/collectors/proc.plugin/sys_class_infiniband.c +++ b/collectors/proc.plugin/sys_class_infiniband.c @@ -327,8 +327,9 @@ int do_sys_class_infiniband(int update_every, usec_t dt) enable_only_active = config_get_boolean_ondemand( CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "monitor only active ports", CONFIG_BOOLEAN_AUTO); disabled_list = simple_pattern_create( - config_get(CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "disable by default interfaces matching", ""), NULL, - SIMPLE_PATTERN_EXACT); + config_get(CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "disable by default interfaces matching", ""), + NULL, + SIMPLE_PATTERN_EXACT, true); dt_to_refresh_ports = config_get_number(CONFIG_SECTION_PLUGIN_SYS_CLASS_INFINIBAND, "refresh ports state every seconds", 30) * @@ -475,8 +476,8 @@ int do_sys_class_infiniband(int update_every, usec_t dt) char *buffer_width = strstr(buffer_rate, "("); buffer_width++; // str2ull will stop on first non-decimal value - p->speed = str2ull(buffer_rate); - p->width = str2ull(buffer_width); + p->speed = str2ull(buffer_rate, NULL); + p->width = str2ull(buffer_width, NULL); } if (!p->discovered) diff --git a/collectors/proc.plugin/sys_class_power_supply.c b/collectors/proc.plugin/sys_class_power_supply.c index ec36a295..8687ecb5 100644 --- a/collectors/proc.plugin/sys_class_power_supply.c +++ b/collectors/proc.plugin/sys_class_power_supply.c @@ -263,7 +263,7 @@ int do_sys_class_power_supply(int update_every, usec_t dt) { } else { buffer[r] = '\0'; - ps->capacity->value = str2ull(buffer); + ps->capacity->value = str2ull(buffer, NULL); if(unlikely(!keep_fds_open)) { close(ps->capacity->fd); @@ -307,7 +307,7 @@ int do_sys_class_power_supply(int update_every, usec_t dt) { break; } buffer[r] = '\0'; - pd->value = str2ull(buffer); + pd->value = str2ull(buffer, NULL); if(unlikely(!keep_fds_open)) { close(pd->fd); diff --git a/collectors/proc.plugin/sys_devices_system_edac_mc.c b/collectors/proc.plugin/sys_devices_system_edac_mc.c index fe825096..fdb6b51e 100644 --- a/collectors/proc.plugin/sys_devices_system_edac_mc.c +++ b/collectors/proc.plugin/sys_devices_system_edac_mc.c @@ -97,7 +97,7 @@ int do_proc_sys_devices_system_edac_mc(int update_every, usec_t dt) { if(unlikely(!m->ce_ff || procfile_lines(m->ce_ff) < 1 || procfile_linewords(m->ce_ff, 0) < 1)) continue; - m->ce_count = str2ull(procfile_lineword(m->ce_ff, 0, 0)); + m->ce_count = str2ull(procfile_lineword(m->ce_ff, 0, 0), NULL); ce_sum += m->ce_count; m->ce_updated = 1; } @@ -119,7 +119,7 @@ int do_proc_sys_devices_system_edac_mc(int update_every, usec_t dt) { if(unlikely(!m->ue_ff || procfile_lines(m->ue_ff) < 1 || procfile_linewords(m->ue_ff, 0) < 1)) continue; - m->ue_count = str2ull(procfile_lineword(m->ue_ff, 0, 0)); + m->ue_count = str2ull(procfile_lineword(m->ue_ff, 0, 0), NULL); ue_sum += m->ue_count; m->ue_updated = 1; } diff --git a/collectors/proc.plugin/sys_devices_system_node.c b/collectors/proc.plugin/sys_devices_system_node.c index 068d739d..d6db94a2 100644 --- a/collectors/proc.plugin/sys_devices_system_node.c +++ b/collectors/proc.plugin/sys_devices_system_node.c @@ -105,7 +105,7 @@ int do_proc_sys_devices_system_node(int update_every, usec_t dt) { , m->name , NULL , "numa" - , NULL + , "mem.numa_nodes" , "NUMA events" , "events/s" , PLUGIN_PROC_NAME diff --git a/collectors/proc.plugin/sys_fs_btrfs.c b/collectors/proc.plugin/sys_fs_btrfs.c index 6abfd785..da89411b 100644 --- a/collectors/proc.plugin/sys_fs_btrfs.c +++ b/collectors/proc.plugin/sys_fs_btrfs.c @@ -10,13 +10,31 @@ typedef struct btrfs_disk { int exists; char *size_filename; - char *hw_sector_size_filename; unsigned long long size; - unsigned long long hw_sector_size; struct btrfs_disk *next; } BTRFS_DISK; +typedef struct btrfs_device { + int id; + int exists; + + char *error_stats_filename; + RRDSET *st_error_stats; + RRDDIM *rd_write_errs; + RRDDIM *rd_read_errs; + RRDDIM *rd_flush_errs; + RRDDIM *rd_corruption_errs; + RRDDIM *rd_generation_errs; + collected_number write_errs; + collected_number read_errs; + collected_number flush_errs; + collected_number corruption_errs; + collected_number generation_errs; + + struct btrfs_device *next; +} BTRFS_DEVICE; + typedef struct btrfs_node { int exists; int logged_error; @@ -26,10 +44,6 @@ typedef struct btrfs_node { char *label; - // unsigned long long int sectorsize; - // unsigned long long int nodesize; - // unsigned long long int quota_override; - #define declare_btrfs_allocation_section_field(SECTION, FIELD) \ char *allocation_ ## SECTION ## _ ## FIELD ## _filename; \ unsigned long long int allocation_ ## SECTION ## _ ## FIELD; @@ -75,17 +89,130 @@ typedef struct btrfs_node { declare_btrfs_allocation_section_field(system, disk_total) declare_btrfs_allocation_section_field(system, disk_used) + // -------------------------------------------------------------------- + // commit stats + + char *commit_stats_filename; + + RRDSET *st_commits; + RRDDIM *rd_commits; + long long commits_total; + collected_number commits_new; + + RRDSET *st_commits_percentage_time; + RRDDIM *rd_commits_percentage_time; + long long commit_timings_total; + long long commits_percentage_time; + + RRDSET *st_commit_timings; + RRDDIM *rd_commit_timings_last; + RRDDIM *rd_commit_timings_max; + collected_number commit_timings_last; + collected_number commit_timings_max; + BTRFS_DISK *disks; + BTRFS_DEVICE *devices; + struct btrfs_node *next; } BTRFS_NODE; static BTRFS_NODE *nodes = NULL; +static inline int collect_btrfs_error_stats(BTRFS_DEVICE *device){ + char buffer[120 + 1]; + + int ret = read_file(device->error_stats_filename, buffer, 120); + if(unlikely(ret)) { + collector_error("BTRFS: failed to read '%s'", device->error_stats_filename); + device->write_errs = 0; + device->read_errs = 0; + device->flush_errs = 0; + device->corruption_errs = 0; + device->generation_errs = 0; + return ret; + } + + char *p = buffer; + while(p){ + char *val = strsep_skip_consecutive_separators(&p, "\n"); + if(unlikely(!val || !*val)) break; + char *key = strsep_skip_consecutive_separators(&val, " "); + + if(!strcmp(key, "write_errs")) device->write_errs = str2ull(val, NULL); + else if(!strcmp(key, "read_errs")) device->read_errs = str2ull(val, NULL); + else if(!strcmp(key, "flush_errs")) device->flush_errs = str2ull(val, NULL); + else if(!strcmp(key, "corruption_errs")) device->corruption_errs = str2ull(val, NULL); + else if(!strcmp(key, "generation_errs")) device->generation_errs = str2ull(val, NULL); + } + return 0; +} + +static inline int collect_btrfs_commits_stats(BTRFS_NODE *node, int update_every){ + char buffer[120 + 1]; + + int ret = read_file(node->commit_stats_filename, buffer, 120); + if(unlikely(ret)) { + collector_error("BTRFS: failed to read '%s'", node->commit_stats_filename); + node->commits_total = 0; + node->commits_new = 0; + node->commit_timings_last = 0; + node->commit_timings_max = 0; + node->commit_timings_total = 0; + node->commits_percentage_time = 0; + + return ret; + } + + char *p = buffer; + while(p){ + char *val = strsep_skip_consecutive_separators(&p, "\n"); + if(unlikely(!val || !*val)) break; + char *key = strsep_skip_consecutive_separators(&val, " "); + + if(!strcmp(key, "commits")){ + long long commits_total_new = str2ull(val, NULL); + if(likely(node->commits_total)){ + if((node->commits_new = commits_total_new - node->commits_total)) + node->commits_total = commits_total_new; + } else node->commits_total = commits_total_new; + } + else if(!strcmp(key, "last_commit_ms")) node->commit_timings_last = str2ull(val, NULL); + else if(!strcmp(key, "max_commit_ms")) node->commit_timings_max = str2ull(val, NULL); + else if(!strcmp(key, "total_commit_ms")) { + long long commit_timings_total_new = str2ull(val, NULL); + if(likely(node->commit_timings_total)){ + long time_delta = commit_timings_total_new - node->commit_timings_total; + if(time_delta){ + node->commits_percentage_time = time_delta * 10 / update_every; + node->commit_timings_total = commit_timings_total_new; + } else node->commits_percentage_time = 0; + + } else node->commit_timings_total = commit_timings_total_new; + } + } + return 0; +} + +static inline void btrfs_free_commits_stats(BTRFS_NODE *node){ + if(node->st_commits){ + rrdset_is_obsolete(node->st_commits); + rrdset_is_obsolete(node->st_commit_timings); + } + freez(node->commit_stats_filename); + node->commit_stats_filename = NULL; +} + static inline void btrfs_free_disk(BTRFS_DISK *d) { freez(d->name); freez(d->size_filename); - freez(d->hw_sector_size_filename); + freez(d); +} + +static inline void btrfs_free_device(BTRFS_DEVICE *d) { + if(d->st_error_stats) + rrdset_is_obsolete(d->st_error_stats); + freez(d->error_stats_filename); freez(d); } @@ -113,12 +240,20 @@ static inline void btrfs_free_node(BTRFS_NODE *node) { freez(node->allocation_system_bytes_used_filename); freez(node->allocation_system_total_bytes_filename); + btrfs_free_commits_stats(node); + while(node->disks) { BTRFS_DISK *d = node->disks; node->disks = node->disks->next; btrfs_free_disk(d); } + while(node->devices) { + BTRFS_DEVICE *d = node->devices; + node->devices = node->devices->next; + btrfs_free_device(d); + } + freez(node->label); freez(node->id); freez(node); @@ -175,19 +310,6 @@ static inline int find_btrfs_disks(BTRFS_NODE *node, const char *path) { snprintfz(filename, FILENAME_MAX, "%s/%s/size", path, de->d_name); d->size_filename = strdupz(filename); - // for bcache - snprintfz(filename, FILENAME_MAX, "%s/%s/bcache/../queue/hw_sector_size", path, de->d_name); - struct stat sb; - if(stat(filename, &sb) == -1) { - // for disks - snprintfz(filename, FILENAME_MAX, "%s/%s/queue/hw_sector_size", path, de->d_name); - if(stat(filename, &sb) == -1) - // for partitions - snprintfz(filename, FILENAME_MAX, "%s/%s/../queue/hw_sector_size", path, de->d_name); - } - - d->hw_sector_size_filename = strdupz(filename); - // link it d->next = node->disks; node->disks = d; @@ -205,13 +327,11 @@ static inline int find_btrfs_disks(BTRFS_NODE *node, const char *path) { continue; } - if(read_single_number_file(d->hw_sector_size_filename, &d->hw_sector_size) != 0) { - collector_error("BTRFS: failed to read '%s'", d->hw_sector_size_filename); - d->exists = 0; - continue; - } - - node->all_disks_total += d->size * d->hw_sector_size; + // /sys/block//size is in fixed-size sectors of 512 bytes + // https://github.com/torvalds/linux/blob/v6.2/block/genhd.c#L946-L950 + // https://github.com/torvalds/linux/blob/v6.2/include/linux/types.h#L120-L121 + // (also see #3481, #3483) + node->all_disks_total += d->size * 512; } closedir(dir); @@ -245,8 +365,106 @@ static inline int find_btrfs_disks(BTRFS_NODE *node, const char *path) { return 0; } +static inline int find_btrfs_devices(BTRFS_NODE *node, const char *path) { + char filename[FILENAME_MAX + 1]; + + BTRFS_DEVICE *d; + for(d = node->devices ; d ; d = d->next) + d->exists = 0; + + DIR *dir = opendir(path); + if (!dir) { + if(!node->logged_error) { + collector_error("BTRFS: Cannot open directory '%s'.", path); + node->logged_error = 1; + } + return 1; + } + node->logged_error = 0; + + struct dirent *de = NULL; + while ((de = readdir(dir))) { + if (de->d_type != DT_DIR + || !strcmp(de->d_name, ".") + || !strcmp(de->d_name, "..") + ) { + // collector_info("BTRFS: ignoring '%s'", de->d_name); + continue; + } + + collector_info("BTRFS: device found '%s'", de->d_name); + + // -------------------------------------------------------------------- + // search for it + + for(d = node->devices ; d ; d = d->next) { + if(str2ll(de->d_name, NULL) == d->id){ + collector_info("BTRFS: existing device id '%d'", d->id); + break; + } + } + + // -------------------------------------------------------------------- + // did we find it? + + if(!d) { + d = callocz(sizeof(BTRFS_DEVICE), 1); + + d->id = str2ll(de->d_name, NULL); + collector_info("BTRFS: new device with id '%d'", d->id); -static inline int find_all_btrfs_pools(const char *path) { + snprintfz(filename, FILENAME_MAX, "%s/%d/error_stats", path, d->id); + d->error_stats_filename = strdupz(filename); + collector_info("BTRFS: error_stats_filename '%s'", filename); + + // link it + d->next = node->devices; + node->devices = d; + } + + d->exists = 1; + + + // -------------------------------------------------------------------- + // update the values + + if(unlikely(collect_btrfs_error_stats(d))) + d->exists = 0; // 'd' will be garbaged collected in loop below + } + closedir(dir); + + // ------------------------------------------------------------------------ + // cleanup + + BTRFS_DEVICE *last = NULL; + d = node->devices; + + while(d) { + if(unlikely(!d->exists)) { + if(unlikely(node->devices == d)) { + node->devices = d->next; + btrfs_free_device(d); + d = node->devices; + last = NULL; + } + else { + last->next = d->next; + btrfs_free_device(d); + d = last->next; + } + + continue; + } + + last = d; + d = d->next; + } + + return 0; +} + + +static inline int find_all_btrfs_pools(const char *path, int update_every) { static int logged_error = 0; char filename[FILENAME_MAX + 1]; @@ -292,6 +510,10 @@ static inline int find_all_btrfs_pools(const char *path) { snprintfz(filename, FILENAME_MAX, "%s/%s/devices", path, de->d_name); find_btrfs_disks(node, filename); + // update devices + snprintfz(filename, FILENAME_MAX, "%s/%s/devinfo", path, de->d_name); + find_btrfs_devices(node, filename); + continue; } @@ -324,27 +546,6 @@ static inline int find_all_btrfs_pools(const char *path) { node->label = strdupz(node->id); } - //snprintfz(filename, FILENAME_MAX, "%s/%s/sectorsize", path, de->d_name); - //if(read_single_number_file(filename, &node->sectorsize) != 0) { - // 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) { - // 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) { - // collector_error("BTRFS: failed to read '%s'", filename); - // btrfs_free_node(node); - // continue; - //} - // -------------------------------------------------------------------- // macros to simplify our life @@ -399,6 +600,15 @@ static inline int find_all_btrfs_pools(const char *path) { init_btrfs_allocation_section_field(system, disk_total); init_btrfs_allocation_section_field(system, disk_used); + // -------------------------------------------------------------------- + // commit stats + + snprintfz(filename, FILENAME_MAX, "%s/%s/commit_stats", path, de->d_name); + if(!node->commit_stats_filename) node->commit_stats_filename = strdupz(filename); + if(unlikely(collect_btrfs_commits_stats(node, update_every))){ + collector_error("BTRFS: failed to collect commit stats for '%s'", node->id); + btrfs_free_commits_stats(node); + } // -------------------------------------------------------------------- // find all disks related to this node @@ -407,6 +617,11 @@ static inline int find_all_btrfs_pools(const char *path) { snprintfz(filename, FILENAME_MAX, "%s/%s/devices", path, de->d_name); find_btrfs_disks(node, filename); + // -------------------------------------------------------------------- + // find all devices related to this node + + snprintfz(filename, FILENAME_MAX, "%s/%s/devinfo", path, de->d_name); + find_btrfs_devices(node, filename); // -------------------------------------------------------------------- // link it @@ -449,8 +664,8 @@ static inline int find_all_btrfs_pools(const char *path) { } static void add_labels_to_btrfs(BTRFS_NODE *n, RRDSET *st) { - rrdlabels_add(st->rrdlabels, "device", n->id, RRDLABEL_SRC_AUTO); - rrdlabels_add(st->rrdlabels, "device_label", n->label, RRDLABEL_SRC_AUTO); + rrdlabels_add(st->rrdlabels, "filesystem_uuid", n->id, RRDLABEL_SRC_AUTO); + rrdlabels_add(st->rrdlabels, "filesystem_label", n->label, RRDLABEL_SRC_AUTO); } int do_sys_fs_btrfs(int update_every, usec_t dt) { @@ -458,7 +673,9 @@ int do_sys_fs_btrfs(int update_every, usec_t dt) { , do_allocation_disks = CONFIG_BOOLEAN_AUTO , do_allocation_system = CONFIG_BOOLEAN_AUTO , do_allocation_data = CONFIG_BOOLEAN_AUTO - , do_allocation_metadata = CONFIG_BOOLEAN_AUTO; + , do_allocation_metadata = CONFIG_BOOLEAN_AUTO + , do_commit_stats = CONFIG_BOOLEAN_AUTO + , do_error_stats = CONFIG_BOOLEAN_AUTO; static usec_t refresh_delta = 0, refresh_every = 60 * USEC_PER_SEC; static char *btrfs_path = NULL; @@ -479,12 +696,14 @@ int do_sys_fs_btrfs(int update_every, usec_t dt) { do_allocation_data = config_get_boolean_ondemand("plugin:proc:/sys/fs/btrfs", "data allocation", do_allocation_data); do_allocation_metadata = config_get_boolean_ondemand("plugin:proc:/sys/fs/btrfs", "metadata allocation", do_allocation_metadata); do_allocation_system = config_get_boolean_ondemand("plugin:proc:/sys/fs/btrfs", "system allocation", do_allocation_system); + do_commit_stats = config_get_boolean_ondemand("plugin:proc:/sys/fs/btrfs", "commit stats", do_commit_stats); + do_error_stats = config_get_boolean_ondemand("plugin:proc:/sys/fs/btrfs", "error stats", do_error_stats); } refresh_delta += dt; if(refresh_delta >= refresh_every) { refresh_delta = 0; - find_all_btrfs_pools(btrfs_path); + find_all_btrfs_pools(btrfs_path, update_every); } BTRFS_NODE *node; @@ -544,6 +763,25 @@ int do_sys_fs_btrfs(int update_every, usec_t dt) { } } + if(do_commit_stats != CONFIG_BOOLEAN_NO && node->commit_stats_filename) { + if (unlikely(collect_btrfs_commits_stats(node, update_every))) { + collector_error("BTRFS: failed to collect commit stats for '%s'", node->id); + btrfs_free_commits_stats(node); + } + } + + if(do_error_stats != CONFIG_BOOLEAN_NO) { + for(BTRFS_DEVICE *d = node->devices ; d ; d = d->next) { + if(unlikely(collect_btrfs_error_stats(d))){ + collector_error("BTRFS: failed to collect error stats for '%s', devid:'%d'", node->id, d->id); + /* make it refresh btrfs at the next iteration, + * btrfs_free_device(d) will be called in + * find_btrfs_devices() as part of the garbage collection */ + refresh_delta = refresh_every; + } + } + } + // -------------------------------------------------------------------- // allocation/disks @@ -555,9 +793,9 @@ int do_sys_fs_btrfs(int update_every, usec_t dt) { if(unlikely(!node->st_allocation_disks)) { char id[RRD_ID_LENGTH_MAX + 1], name[RRD_ID_LENGTH_MAX + 1], title[200 + 1]; - snprintf(id, RRD_ID_LENGTH_MAX, "disk_%s", node->id); - snprintf(name, RRD_ID_LENGTH_MAX, "disk_%s", node->label); - snprintf(title, 200, "BTRFS Physical Disk Allocation"); + snprintfz(id, RRD_ID_LENGTH_MAX, "disk_%s", node->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "disk_%s", node->label); + snprintfz(title, 200, "BTRFS Physical Disk Allocation"); netdata_fix_chart_id(id); netdata_fix_chart_name(name); @@ -614,9 +852,9 @@ int do_sys_fs_btrfs(int update_every, usec_t dt) { if(unlikely(!node->st_allocation_data)) { char id[RRD_ID_LENGTH_MAX + 1], name[RRD_ID_LENGTH_MAX + 1], title[200 + 1]; - snprintf(id, RRD_ID_LENGTH_MAX, "data_%s", node->id); - snprintf(name, RRD_ID_LENGTH_MAX, "data_%s", node->label); - snprintf(title, 200, "BTRFS Data Allocation"); + snprintfz(id, RRD_ID_LENGTH_MAX, "data_%s", node->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "data_%s", node->label); + snprintfz(title, 200, "BTRFS Data Allocation"); netdata_fix_chart_id(id); netdata_fix_chart_name(name); @@ -658,9 +896,9 @@ int do_sys_fs_btrfs(int update_every, usec_t dt) { if(unlikely(!node->st_allocation_metadata)) { char id[RRD_ID_LENGTH_MAX + 1], name[RRD_ID_LENGTH_MAX + 1], title[200 + 1]; - snprintf(id, RRD_ID_LENGTH_MAX, "metadata_%s", node->id); - snprintf(name, RRD_ID_LENGTH_MAX, "metadata_%s", node->label); - snprintf(title, 200, "BTRFS Metadata Allocation"); + snprintfz(id, RRD_ID_LENGTH_MAX, "metadata_%s", node->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "metadata_%s", node->label); + snprintfz(title, 200, "BTRFS Metadata Allocation"); netdata_fix_chart_id(id); netdata_fix_chart_name(name); @@ -704,9 +942,9 @@ int do_sys_fs_btrfs(int update_every, usec_t dt) { if(unlikely(!node->st_allocation_system)) { char id[RRD_ID_LENGTH_MAX + 1], name[RRD_ID_LENGTH_MAX + 1], title[200 + 1]; - snprintf(id, RRD_ID_LENGTH_MAX, "system_%s", node->id); - snprintf(name, RRD_ID_LENGTH_MAX, "system_%s", node->label); - snprintf(title, 200, "BTRFS System Allocation"); + snprintfz(id, RRD_ID_LENGTH_MAX, "system_%s", node->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "system_%s", node->label); + snprintfz(title, 200, "BTRFS System Allocation"); netdata_fix_chart_id(id); netdata_fix_chart_name(name); @@ -736,6 +974,180 @@ int do_sys_fs_btrfs(int update_every, usec_t dt) { rrddim_set_by_pointer(node->st_allocation_system, node->rd_allocation_system_used, node->allocation_system_bytes_used); rrdset_done(node->st_allocation_system); } + + // -------------------------------------------------------------------- + // commit_stats + + if(do_commit_stats == CONFIG_BOOLEAN_YES || (do_commit_stats == CONFIG_BOOLEAN_AUTO && + (node->commits_total || + netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) { + do_commit_stats = CONFIG_BOOLEAN_YES; + + if(unlikely(!node->st_commits)) { + char id[RRD_ID_LENGTH_MAX + 1], name[RRD_ID_LENGTH_MAX + 1], title[200 + 1]; + + snprintfz(id, RRD_ID_LENGTH_MAX, "commits_%s", node->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "commits_%s", node->label); + snprintfz(title, 200, "BTRFS Commits"); + + netdata_fix_chart_id(id); + netdata_fix_chart_name(name); + + node->st_commits = rrdset_create_localhost( + "btrfs" + , id + , name + , node->label + , "btrfs.commits" + , title + , "commits" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_BTRFS_NAME + , NETDATA_CHART_PRIO_BTRFS_COMMITS + , update_every + , RRDSET_TYPE_LINE + ); + + node->rd_commits = rrddim_add(node->st_commits, "commits", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + add_labels_to_btrfs(node, node->st_commits); + } + + rrddim_set_by_pointer(node->st_commits, node->rd_commits, node->commits_new); + rrdset_done(node->st_commits); + + if(unlikely(!node->st_commits_percentage_time)) { + char id[RRD_ID_LENGTH_MAX + 1], name[RRD_ID_LENGTH_MAX + 1], title[200 + 1]; + + snprintfz(id, RRD_ID_LENGTH_MAX, "commits_perc_time_%s", node->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "commits_perc_time_%s", node->label); + snprintfz(title, 200, "BTRFS Commits Time Share"); + + netdata_fix_chart_id(id); + netdata_fix_chart_name(name); + + node->st_commits_percentage_time = rrdset_create_localhost( + "btrfs" + , id + , name + , node->label + , "btrfs.commits_perc_time" + , title + , "percentage" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_BTRFS_NAME + , NETDATA_CHART_PRIO_BTRFS_COMMITS_PERC_TIME + , update_every + , RRDSET_TYPE_LINE + ); + + node->rd_commits_percentage_time = rrddim_add(node->st_commits_percentage_time, "commits", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); + + add_labels_to_btrfs(node, node->st_commits_percentage_time); + } + + rrddim_set_by_pointer(node->st_commits_percentage_time, node->rd_commits_percentage_time, node->commits_percentage_time); + rrdset_done(node->st_commits_percentage_time); + + + if(unlikely(!node->st_commit_timings)) { + char id[RRD_ID_LENGTH_MAX + 1], name[RRD_ID_LENGTH_MAX + 1], title[200 + 1]; + + snprintfz(id, RRD_ID_LENGTH_MAX, "commit_timings_%s", node->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "commit_timings_%s", node->label); + snprintfz(title, 200, "BTRFS Commit Timings"); + + netdata_fix_chart_id(id); + netdata_fix_chart_name(name); + + node->st_commit_timings = rrdset_create_localhost( + "btrfs" + , id + , name + , node->label + , "btrfs.commit_timings" + , title + , "ms" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_BTRFS_NAME + , NETDATA_CHART_PRIO_BTRFS_COMMIT_TIMINGS + , update_every + , RRDSET_TYPE_LINE + ); + + node->rd_commit_timings_last = rrddim_add(node->st_commit_timings, "last", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + node->rd_commit_timings_max = rrddim_add(node->st_commit_timings, "max", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + add_labels_to_btrfs(node, node->st_commit_timings); + } + + rrddim_set_by_pointer(node->st_commit_timings, node->rd_commit_timings_last, node->commit_timings_last); + rrddim_set_by_pointer(node->st_commit_timings, node->rd_commit_timings_max, node->commit_timings_max); + rrdset_done(node->st_commit_timings); + } + + // -------------------------------------------------------------------- + // error_stats per device + + if(do_error_stats == CONFIG_BOOLEAN_YES || (do_error_stats == CONFIG_BOOLEAN_AUTO && + (node->devices || + netdata_zero_metrics_enabled == CONFIG_BOOLEAN_YES))) { + do_error_stats = CONFIG_BOOLEAN_YES; + + for(BTRFS_DEVICE *d = node->devices ; d ; d = d->next) { + + if(unlikely(!d->st_error_stats)) { + char id[RRD_ID_LENGTH_MAX + 1], name[RRD_ID_LENGTH_MAX + 1], title[200 + 1]; + + snprintfz(id, RRD_ID_LENGTH_MAX, "device_errors_dev%d_%s", d->id, node->id); + snprintfz(name, RRD_ID_LENGTH_MAX, "device_errors_dev%d_%s", d->id, node->label); + snprintfz(title, 200, "BTRFS Device Errors"); + + netdata_fix_chart_id(id); + netdata_fix_chart_name(name); + + d->st_error_stats = rrdset_create_localhost( + "btrfs" + , id + , name + , node->label + , "btrfs.device_errors" + , title + , "errors" + , PLUGIN_PROC_NAME + , PLUGIN_PROC_MODULE_BTRFS_NAME + , NETDATA_CHART_PRIO_BTRFS_ERRORS + , update_every + , RRDSET_TYPE_LINE + ); + + char rd_id[RRD_ID_LENGTH_MAX + 1]; + snprintfz(rd_id, RRD_ID_LENGTH_MAX, "write_errs"); + d->rd_write_errs = rrddim_add(d->st_error_stats, rd_id, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + snprintfz(rd_id, RRD_ID_LENGTH_MAX, "read_errs"); + d->rd_read_errs = rrddim_add(d->st_error_stats, rd_id, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + snprintfz(rd_id, RRD_ID_LENGTH_MAX, "flush_errs"); + d->rd_flush_errs = rrddim_add(d->st_error_stats, rd_id, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + snprintfz(rd_id, RRD_ID_LENGTH_MAX, "corruption_errs"); + d->rd_corruption_errs = rrddim_add(d->st_error_stats, rd_id, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + snprintfz(rd_id, RRD_ID_LENGTH_MAX, "generation_errs"); + d->rd_generation_errs = rrddim_add(d->st_error_stats, rd_id, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + + char dev_id[5]; + snprintfz(dev_id, 4, "%d", d->id); + rrdlabels_add(d->st_error_stats->rrdlabels, "device_id", dev_id, RRDLABEL_SRC_AUTO); + add_labels_to_btrfs(node, d->st_error_stats); + } + + rrddim_set_by_pointer(d->st_error_stats, d->rd_write_errs, d->write_errs); + rrddim_set_by_pointer(d->st_error_stats, d->rd_read_errs, d->read_errs); + rrddim_set_by_pointer(d->st_error_stats, d->rd_flush_errs, d->flush_errs); + rrddim_set_by_pointer(d->st_error_stats, d->rd_corruption_errs, d->corruption_errs); + rrddim_set_by_pointer(d->st_error_stats, d->rd_generation_errs, d->generation_errs); + + rrdset_done(d->st_error_stats); + } + } } return 0; diff --git a/collectors/proc.plugin/sys_kernel_mm_ksm.c b/collectors/proc.plugin/sys_kernel_mm_ksm.c index e586d555..45f1ac33 100644 --- a/collectors/proc.plugin/sys_kernel_mm_ksm.c +++ b/collectors/proc.plugin/sys_kernel_mm_ksm.c @@ -68,19 +68,19 @@ int do_sys_kernel_mm_ksm(int update_every, usec_t dt) { ff_pages_shared = procfile_readall(ff_pages_shared); if(unlikely(!ff_pages_shared)) return 0; // we return 0, so that we will retry to open it next time - pages_shared = str2ull(procfile_lineword(ff_pages_shared, 0, 0)); + pages_shared = str2ull(procfile_lineword(ff_pages_shared, 0, 0), NULL); ff_pages_sharing = procfile_readall(ff_pages_sharing); if(unlikely(!ff_pages_sharing)) return 0; // we return 0, so that we will retry to open it next time - pages_sharing = str2ull(procfile_lineword(ff_pages_sharing, 0, 0)); + pages_sharing = str2ull(procfile_lineword(ff_pages_sharing, 0, 0), NULL); ff_pages_unshared = procfile_readall(ff_pages_unshared); if(unlikely(!ff_pages_unshared)) return 0; // we return 0, so that we will retry to open it next time - pages_unshared = str2ull(procfile_lineword(ff_pages_unshared, 0, 0)); + pages_unshared = str2ull(procfile_lineword(ff_pages_unshared, 0, 0), NULL); ff_pages_volatile = procfile_readall(ff_pages_volatile); if(unlikely(!ff_pages_volatile)) return 0; // we return 0, so that we will retry to open it next time - pages_volatile = str2ull(procfile_lineword(ff_pages_volatile, 0, 0)); + pages_volatile = str2ull(procfile_lineword(ff_pages_volatile, 0, 0), NULL); //ff_pages_to_scan = procfile_readall(ff_pages_to_scan); //if(unlikely(!ff_pages_to_scan)) return 0; // we return 0, so that we will retry to open it next time diff --git a/collectors/python.d.plugin/Makefile.am b/collectors/python.d.plugin/Makefile.am index 6ea7b21b..ca49c1c0 100644 --- a/collectors/python.d.plugin/Makefile.am +++ b/collectors/python.d.plugin/Makefile.am @@ -65,14 +65,11 @@ include memcached/Makefile.inc include monit/Makefile.inc include nvidia_smi/Makefile.inc include nsd/Makefile.inc -include ntpd/Makefile.inc include openldap/Makefile.inc include oracledb/Makefile.inc include pandas/Makefile.inc include postfix/Makefile.inc -include proxysql/Makefile.inc include puppet/Makefile.inc -include rabbitmq/Makefile.inc include rethinkdbs/Makefile.inc include retroshare/Makefile.inc include riakkv/Makefile.inc diff --git a/collectors/python.d.plugin/README.md b/collectors/python.d.plugin/README.md index b6d658fa..569543d1 100644 --- a/collectors/python.d.plugin/README.md +++ b/collectors/python.d.plugin/README.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "python.d.plugin" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/Collectors" +learn_rel_path: "Developers/External plugins/python.d.plugin" --> # python.d.plugin @@ -74,201 +74,4 @@ Where `[module]` is the directory name under ' -- '\<' - -For additional security it uses python `subprocess.Popen` (without `shell=True` option) to execute command. Command can be specified with absolute or relative name. When using relative name, it will try to find `command` in `PATH` environment variable as well as in `/sbin` and `/usr/sbin`. - -`_get_raw_data` returns list of decoded lines returned by `command`. - -### UrlService - -_Examples: `apache`, `nginx`, `tomcat`_ - -_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`. - -If data is grabbed by accessing service via HTTP protocol, this class can be used. It can handle HTTP Basic Auth when specified with `user` and `pass` credentials. - -Please note that the config file can use different variables according to the specification of each module. - -`_get_raw_data` returns list of utf-8 decoded strings (lines). - -### SocketService - -_Examples: `dovecot`, `redis`_ - -_Variables from config file_: `unix_socket`, `host`, `port`, `request`. - -Object will try execute `request` using either `unix_socket` or TCP/IP socket with combination of `host` and `port`. This can access unix sockets with SOCK_STREAM or SOCK_DGRAM protocols and TCP/IP sockets in version 4 and 6 with SOCK_STREAM setting. - -Sockets are accessed in non-blocking mode with 15 second timeout. - -After every execution of `_get_raw_data` socket is closed, to prevent this module needs to set `_keep_alive` variable to `True` and implement custom `_check_raw_data` method. - -`_check_raw_data` should take raw data and return `True` if all data is received otherwise it should return `False`. Also it should do it in fast and efficient way. - -## Pull Request Checklist for Python Plugins - -This is a generic checklist for submitting a new Python plugin for Netdata. It is by no means comprehensive. - -At minimum, to be buildable and testable, the PR needs to include: - -- The module itself, following proper naming conventions: `collectors/python.d.plugin//.chart.py` -- A README.md file for the plugin under `collectors/python.d.plugin/`. -- The configuration file for the module: `collectors/python.d.plugin//.conf`. Python config files are in YAML format, and should include comments describing what options are present. The instructions are also needed in the configuration section of the README.md -- A basic configuration for the plugin in the appropriate global config file: `collectors/python.d.plugin/python.d.conf`, which is also in YAML format. Either add a line that reads `# : yes` if the module is to be enabled by default, or one that reads `: no` if it is to be disabled by default. -- A makefile for the plugin at `collectors/python.d.plugin//Makefile.inc`. Check an existing plugin for what this should look like. -- A line in `collectors/python.d.plugin/Makefile.am` including the above-mentioned makefile. Place it with the other plugin includes (please keep the includes sorted alphabetically). -- Optionally, chart information in `web/gui/dashboard_info.js`. This generally involves specifying a name and icon for the section, and may include descriptions for the section or individual charts. -- Optionally, some default alarm configurations for your collector in `health/health.d/.conf` and a line adding `.conf` in `health/Makefile.am`. - - +See [develop a custom collector in Python](https://github.com/netdata/netdata/edit/master/docs/guides/python-collector.md). diff --git a/collectors/python.d.plugin/adaptec_raid/README.md b/collectors/python.d.plugin/adaptec_raid/README.md index 90ef8fa3..41d5b62e 100644 --- a/collectors/python.d.plugin/adaptec_raid/README.md +++ b/collectors/python.d.plugin/adaptec_raid/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Adaptec RAID" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Hardware" +learn_rel_path: "Integrations/Monitor/Hardware" --> -# Adaptec RAID controller monitoring with Netdata +# Adaptec RAID controller collector Collects logical and physical devices metrics using `arcconf` command-line utility. @@ -78,6 +78,26 @@ sudo ./edit-config python.d/adaptec_raid.conf ![image](https://user-images.githubusercontent.com/22274335/47278133-6d306680-d601-11e8-87c2-cc9c0f42d686.png) ---- + +### Troubleshooting + +To troubleshoot issues with the `adaptec_raid` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `adaptec_raid` module in debug mode: + +```bash +./python.d.plugin adaptec_raid debug trace +``` + diff --git a/collectors/python.d.plugin/adaptec_raid/metrics.csv b/collectors/python.d.plugin/adaptec_raid/metrics.csv new file mode 100644 index 00000000..1462940c --- /dev/null +++ b/collectors/python.d.plugin/adaptec_raid/metrics.csv @@ -0,0 +1,5 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +adaptec_raid.ld_status,,a dimension per logical device,bool,Status of logical devices (1: Failed or Degraded),line,,python.d.plugin,adaptec_raid +adaptec_raid.pd_state,,a dimension per physical device,bool,State of physical devices (1: not Online),line,,python.d.plugin,adaptec_raid +adaptec_raid.smart_warnings,,a dimension per physical device,count,S.M.A.R.T warnings,line,,python.d.plugin,adaptec_raid +adaptec_raid.temperature,,a dimension per physical device,celsius,Temperature,line,,python.d.plugin,adaptec_raid diff --git a/collectors/python.d.plugin/alarms/README.md b/collectors/python.d.plugin/alarms/README.md index 4804bd0d..0f956b29 100644 --- a/collectors/python.d.plugin/alarms/README.md +++ b/collectors/python.d.plugin/alarms/README.md @@ -1,14 +1,14 @@ -# Alarms - graphing Netdata alarm states over time +# Alarms -This collector creates an 'Alarms' menu with one line plot showing alarm states over time. Alarm states are mapped to integer values according to the below default mapping. Any alarm status types not in this mapping will be ignored (Note: This mapping can be changed by editing the `status_map` in the `alarms.conf` file). If you would like to learn more about the different alarm statuses check out the docs [here](https://learn.netdata.cloud/docs/agent/health/reference#alarm-statuses). +This collector creates an 'Alarms' menu with one line plot showing alarm states over time. Alarm states are mapped to integer values according to the below default mapping. Any alarm status types not in this mapping will be ignored (Note: This mapping can be changed by editing the `status_map` in the `alarms.conf` file). If you would like to learn more about the different alarm statuses check out the docs [here](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#alarm-statuses). ``` { @@ -67,3 +67,23 @@ local: ``` It will default to pulling all alarms at each time step from the Netdata rest api at `http://127.0.0.1:19999/api/v1/alarms?all` +### Troubleshooting + +To troubleshoot issues with the `alarms` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `alarms` module in debug mode: + +```bash +./python.d.plugin alarms debug trace +``` + diff --git a/collectors/python.d.plugin/alarms/metrics.csv b/collectors/python.d.plugin/alarms/metrics.csv new file mode 100644 index 00000000..1c28a836 --- /dev/null +++ b/collectors/python.d.plugin/alarms/metrics.csv @@ -0,0 +1,3 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +alarms.status,,a dimension per alarm,status,Alarms ({status mapping}),line,,python.d.plugin,alarms +alarms.status,,a dimension per alarm,value,Alarm Values,line,,python.d.plugin,alarms diff --git a/collectors/python.d.plugin/am2320/README.md b/collectors/python.d.plugin/am2320/README.md index 070e8eb3..b8a6acb0 100644 --- a/collectors/python.d.plugin/am2320/README.md +++ b/collectors/python.d.plugin/am2320/README.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "AM2320" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Remotes/Devices" +learn_rel_path: "Integrations/Monitor/Remotes/Devices" --> # AM2320 sensor monitoring with netdata @@ -54,3 +54,23 @@ Software install: - restart the netdata service. - check the dashboard. +### Troubleshooting + +To troubleshoot issues with the `am2320` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `am2320` module in debug mode: + +```bash +./python.d.plugin am2320 debug trace +``` + diff --git a/collectors/python.d.plugin/am2320/metrics.csv b/collectors/python.d.plugin/am2320/metrics.csv new file mode 100644 index 00000000..0f3b79f2 --- /dev/null +++ b/collectors/python.d.plugin/am2320/metrics.csv @@ -0,0 +1,3 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +am2320.temperature,,temperature,celsius,Temperature,line,,python.d.plugin,am2320 +am2320.humidity,,humidity,percentage,Relative Humidity,line,,python.d.plugin,am2320 diff --git a/collectors/python.d.plugin/anomalies/README.md b/collectors/python.d.plugin/anomalies/README.md index 7c59275f..80f50537 100644 --- a/collectors/python.d.plugin/anomalies/README.md +++ b/collectors/python.d.plugin/anomalies/README.md @@ -4,14 +4,13 @@ description: "Use ML-driven anomaly detection to narrow your focus to only affec 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" +learn_status: "Published" +learn_rel_path: "Integrations/Monitor/Anything" --> # Anomaly detection with 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. +**Note**: Check out the [Netdata Anomaly Advisor](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.md) 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. @@ -84,7 +83,7 @@ sudo ./edit-config python.d/anomalies.conf The default configuration should look something like this. Here you can see each parameter (with sane defaults) and some information about each one and what it does. ```conf -# ---------------------------------------------------------------------- +# - # JOBS (data collection sources) # Pull data from local Netdata node. diff --git a/collectors/python.d.plugin/anomalies/anomalies.chart.py b/collectors/python.d.plugin/anomalies/anomalies.chart.py index 8ca3df68..24e84cc1 100644 --- a/collectors/python.d.plugin/anomalies/anomalies.chart.py +++ b/collectors/python.d.plugin/anomalies/anomalies.chart.py @@ -58,8 +58,7 @@ class Service(SimpleService): self.collected_dims = {'probability': set(), 'anomaly': set()} def check(self): - python_version = float('{}.{}'.format(sys.version_info[0], sys.version_info[1])) - if python_version < 3.6: + if not (sys.version_info[0] >= 3 and sys.version_info[1] >= 6): self.error("anomalies collector only works with Python>=3.6") if len(self.host_charts_dict[self.host]) > 0: _ = get_allmetrics_async(host_charts_dict=self.host_charts_dict, protocol=self.protocol, user=self.username, pwd=self.password) diff --git a/collectors/python.d.plugin/anomalies/metrics.csv b/collectors/python.d.plugin/anomalies/metrics.csv new file mode 100644 index 00000000..847d9d1d --- /dev/null +++ b/collectors/python.d.plugin/anomalies/metrics.csv @@ -0,0 +1,3 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +anomalies.probability,,a dimension per probability,probability,Anomaly Probability,line,,python.d.plugin,anomalies +anomalies.anomaly,,a dimension per anomaly,count,Anomaly,stacked,,python.d.plugin,anomalies diff --git a/collectors/python.d.plugin/beanstalk/README.md b/collectors/python.d.plugin/beanstalk/README.md index 7e7f30de..c86ca354 100644 --- a/collectors/python.d.plugin/beanstalk/README.md +++ b/collectors/python.d.plugin/beanstalk/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Beanstalk" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Message brokers" +learn_rel_path: "Integrations/Monitor/Message brokers" --> -# Beanstalk monitoring with Netdata +# Beanstalk collector Provides server and tube-level statistics. @@ -131,6 +131,26 @@ port : 11300 If no configuration is given, module will attempt to connect to beanstalkd on `127.0.0.1:11300` address ---- + +### Troubleshooting + +To troubleshoot issues with the `beanstalk` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `beanstalk` module in debug mode: + +```bash +./python.d.plugin beanstalk debug trace +``` + diff --git a/collectors/python.d.plugin/beanstalk/metrics.csv b/collectors/python.d.plugin/beanstalk/metrics.csv new file mode 100644 index 00000000..fe0219d1 --- /dev/null +++ b/collectors/python.d.plugin/beanstalk/metrics.csv @@ -0,0 +1,15 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +beanstalk.cpu_usage,,"user, system",cpu time,Cpu Usage,area,,python.d.plugin,beanstalk +beanstalk.jobs_rate,,"total, timeouts",jobs/s,Jobs Rate,line,,python.d.plugin,beanstalk +beanstalk.connections_rate,,connections,connections/s,Connections Rate,area,,python.d.plugin,beanstalk +beanstalk.commands_rate,,"put, peek, peek-ready, peek-delayed, peek-buried, reserve, use, watch, ignore, delete, bury, kick, stats, stats-job, stats-tube, list-tubes, list-tube-used, list-tubes-watched, pause-tube",commands/s,Commands Rate,stacked,,python.d.plugin,beanstalk +beanstalk.connections_rate,,tubes,tubes,Current Tubes,area,,python.d.plugin,beanstalk +beanstalk.current_jobs,,"urgent, ready, reserved, delayed, buried",jobs,Current Jobs,stacked,,python.d.plugin,beanstalk +beanstalk.current_connections,,"written, producers, workers, waiting",connections,Current Connections,line,,python.d.plugin,beanstalk +beanstalk.binlog,,"written, migrated",records/s,Binlog,line,,python.d.plugin,beanstalk +beanstalk.uptime,,uptime,seconds,seconds,line,,python.d.plugin,beanstalk +beanstalk.jobs_rate,tube,jobs,jobs/s,Jobs Rate,area,,python.d.plugin,beanstalk +beanstalk.jobs,tube,"urgent, ready, reserved, delayed, buried",jobs,Jobs,stacked,,python.d.plugin,beanstalk +beanstalk.connections,tube,"using, waiting, watching",connections,Connections,stacked,,python.d.plugin,beanstalk +beanstalk.commands,tube,"deletes, pauses",commands/s,Commands,stacked,,python.d.plugin,beanstalk +beanstalk.pause,tube,"since, left",seconds,Pause,stacked,,python.d.plugin,beanstalk diff --git a/collectors/python.d.plugin/bind_rndc/README.md b/collectors/python.d.plugin/bind_rndc/README.md index e8700188..aa173f38 100644 --- a/collectors/python.d.plugin/bind_rndc/README.md +++ b/collectors/python.d.plugin/bind_rndc/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "ISC Bind" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Webapps" +learn_rel_path: "Integrations/Monitor/Webapps" --> -# ISC Bind monitoring with Netdata +# ISC Bind collector Collects Name server summary performance statistics using `rndc` tool. @@ -77,6 +77,26 @@ local: If no configuration is given, module will attempt to read named.stats file at `/var/log/bind/named.stats` ---- + +### Troubleshooting + +To troubleshoot issues with the `bind_rndc` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `bind_rndc` module in debug mode: + +```bash +./python.d.plugin bind_rndc debug trace +``` + diff --git a/collectors/python.d.plugin/bind_rndc/metrics.csv b/collectors/python.d.plugin/bind_rndc/metrics.csv new file mode 100644 index 00000000..3b073309 --- /dev/null +++ b/collectors/python.d.plugin/bind_rndc/metrics.csv @@ -0,0 +1,5 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +bind_rndc.name_server_statistics,,"requests, rejected_queries, success, failure, responses, duplicate, recursion, nxrrset, nxdomain, non_auth_answer, auth_answer, dropped_queries",stats,Name Server Statistics,line,,python.d.plugin,bind_rndc +bind_rndc.incoming_queries,,a dimension per incoming query type,queries,Incoming queries,line,,python.d.plugin,bind_rndc +bind_rndc.outgoing_queries,,a dimension per outgoing query type,queries,Outgoing queries,line,,python.d.plugin,bind_rndc +bind_rndc.stats_size,,stats_size,MiB,Named Stats File Size,line,,python.d.plugin,bind_rndc diff --git a/collectors/python.d.plugin/boinc/README.md b/collectors/python.d.plugin/boinc/README.md index 149d37ca..ea439775 100644 --- a/collectors/python.d.plugin/boinc/README.md +++ b/collectors/python.d.plugin/boinc/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "BOINC" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Distributed computing" +learn_rel_path: "Integrations/Monitor/Distributed computing" --> -# BOINC monitoring with Netdata +# BOINC collector Monitors task counts for the Berkeley Open Infrastructure Networking Computing (BOINC) distributed computing client using the same RPC interface that the BOINC monitoring GUI does. @@ -39,6 +39,26 @@ remote: password: some-password ``` ---- + +### Troubleshooting + +To troubleshoot issues with the `boinc` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `boinc` module in debug mode: + +```bash +./python.d.plugin boinc debug trace +``` + diff --git a/collectors/python.d.plugin/boinc/metrics.csv b/collectors/python.d.plugin/boinc/metrics.csv new file mode 100644 index 00000000..98c6e866 --- /dev/null +++ b/collectors/python.d.plugin/boinc/metrics.csv @@ -0,0 +1,5 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +boinc.tasks,,"Total, Active",tasks,Overall Tasks,line,,python.d.plugin,boinc +boinc.states,,"New, Downloading, Ready to Run, Compute Errors, Uploading, Uploaded, Aborted, Failed Uploads",tasks,Tasks per State,line,,python.d.plugin,boinc +boinc.sched,,"Uninitialized, Preempted, Scheduled",tasks,Tasks per Scheduler State,line,,python.d.plugin,boinc +boinc.process,,"Uninitialized, Executing, Suspended, Aborted, Quit, Copy Pending",tasks,Tasks per Process State,line,,python.d.plugin,boinc diff --git a/collectors/python.d.plugin/ceph/README.md b/collectors/python.d.plugin/ceph/README.md index e7d0f51e..555491ad 100644 --- a/collectors/python.d.plugin/ceph/README.md +++ b/collectors/python.d.plugin/ceph/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "CEPH" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Storage" +learn_rel_path: "Integrations/Monitor/Storage" --> -# CEPH monitoring with Netdata +# CEPH collector Monitors the ceph cluster usage and consumption data of a server, and produces: @@ -46,6 +46,26 @@ local: keyring_file: '/etc/ceph/ceph.client.admin.keyring' ``` ---- + +### Troubleshooting + +To troubleshoot issues with the `ceph` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `ceph` module in debug mode: + +```bash +./python.d.plugin ceph debug trace +``` + diff --git a/collectors/python.d.plugin/ceph/ceph.chart.py b/collectors/python.d.plugin/ceph/ceph.chart.py index 494eef45..4bcbe197 100644 --- a/collectors/python.d.plugin/ceph/ceph.chart.py +++ b/collectors/python.d.plugin/ceph/ceph.chart.py @@ -331,7 +331,7 @@ class Service(SimpleService): return json.loads(self.cluster.mon_command(json.dumps({ 'prefix': 'df', 'format': 'json' - }), '')[1].decode('utf-8')) + }), b'')[1].decode('utf-8')) def _get_osd_df(self): """ @@ -341,7 +341,7 @@ class Service(SimpleService): return json.loads(self.cluster.mon_command(json.dumps({ 'prefix': 'osd df', 'format': 'json' - }), '')[1].decode('utf-8').replace('-nan', '"-nan"')) + }), b'')[1].decode('utf-8').replace('-nan', '"-nan"')) def _get_osd_perf(self): """ @@ -351,7 +351,7 @@ class Service(SimpleService): return json.loads(self.cluster.mon_command(json.dumps({ 'prefix': 'osd perf', 'format': 'json' - }), '')[1].decode('utf-8')) + }), b'')[1].decode('utf-8')) def _get_osd_pool_stats(self): """ @@ -363,7 +363,7 @@ class Service(SimpleService): return json.loads(self.cluster.mon_command(json.dumps({ 'prefix': 'osd pool stats', 'format': 'json' - }), '')[1].decode('utf-8')) + }), b'')[1].decode('utf-8')) def get_osd_perf_infos(osd_perf): diff --git a/collectors/python.d.plugin/ceph/metrics.csv b/collectors/python.d.plugin/ceph/metrics.csv new file mode 100644 index 00000000..e64f2cf5 --- /dev/null +++ b/collectors/python.d.plugin/ceph/metrics.csv @@ -0,0 +1,16 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +ceph.general_usage,,"avail, used",KiB,Ceph General Space,stacked,,python.d.plugin,ceph +ceph.general_objects,,cluster,objects,Ceph General Objects,area,,python.d.plugin,ceph +ceph.general_bytes,,"read, write",KiB/s,Ceph General Read/Write Data/s,area,,python.d.plugin,ceph +ceph.general_operations,,"read, write",operations,Ceph General Read/Write Operations/s,area,,python.d.plugin,ceph +ceph.general_latency,,"apply, commit",milliseconds,Ceph General Apply/Commit latency,area,,python.d.plugin,ceph +ceph.pool_usage,,a dimension per Ceph Pool,KiB,Ceph Pools,line,,python.d.plugin,ceph +ceph.pool_objects,,a dimension per Ceph Pool,objects,Ceph Pools,line,,python.d.plugin,ceph +ceph.pool_read_bytes,,a dimension per Ceph Pool,KiB/s,Ceph Read Pool Data/s,area,,python.d.plugin,ceph +ceph.pool_write_bytes,,a dimension per Ceph Pool,KiB/s,Ceph Write Pool Data/s,area,,python.d.plugin,ceph +ceph.pool_read_operations,,a dimension per Ceph Pool,operations,Ceph Read Pool Operations/s,area,,python.d.plugin,ceph +ceph.pool_write_operations,,a dimension per Ceph Pool,operations,Ceph Write Pool Operations/s,area,,python.d.plugin,ceph +ceph.osd_usage,,a dimension per Ceph OSD,KiB,Ceph OSDs,line,,python.d.plugin,ceph +ceph.osd_size,,a dimension per Ceph OSD,KiB,Ceph OSDs size,line,,python.d.plugin,ceph +ceph.apply_latency,,a dimension per Ceph OSD,milliseconds,Ceph OSDs apply latency,line,,python.d.plugin,ceph +ceph.commit_latency,,a dimension per Ceph OSD,milliseconds,Ceph OSDs commit latency,line,,python.d.plugin,ceph diff --git a/collectors/python.d.plugin/changefinder/README.md b/collectors/python.d.plugin/changefinder/README.md index 326a69dd..0e9bab88 100644 --- a/collectors/python.d.plugin/changefinder/README.md +++ b/collectors/python.d.plugin/changefinder/README.md @@ -5,10 +5,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "changefinder" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/QoS" +learn_rel_path: "Integrations/Monitor/QoS" --> -# Online changepoint detection with Netdata +# Online change point detection with Netdata This collector uses the Python [changefinder](https://github.com/shunsukeaihara/changefinder) library to perform [online](https://en.wikipedia.org/wiki/Online_machine_learning) [changepoint detection](https://en.wikipedia.org/wiki/Change_detection) @@ -108,7 +108,7 @@ The default configuration should look something like this. Here you can see each information about each one and what it does. ```yaml -# ---------------------------------------------------------------------- +# - # JOBS (data collection sources) # Pull data from local Netdata node. @@ -219,3 +219,23 @@ sudo su -s /bin/bash netdata - Novelty and outlier detection in the [scikit-learn documentation](https://scikit-learn.org/stable/modules/outlier_detection.html). +### Troubleshooting + +To troubleshoot issues with the `changefinder` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `changefinder` module in debug mode: + +```bash +./python.d.plugin changefinder debug trace +``` + diff --git a/collectors/python.d.plugin/changefinder/changefinder.chart.py b/collectors/python.d.plugin/changefinder/changefinder.chart.py index c18e5600..2a69cd9f 100644 --- a/collectors/python.d.plugin/changefinder/changefinder.chart.py +++ b/collectors/python.d.plugin/changefinder/changefinder.chart.py @@ -22,11 +22,11 @@ ORDER = [ CHARTS = { 'scores': { - 'options': [None, 'ChangeFinder', 'score', 'Scores', 'scores', 'line'], + 'options': [None, 'ChangeFinder', 'score', 'Scores', 'changefinder.scores', 'line'], 'lines': [] }, 'flags': { - 'options': [None, 'ChangeFinder', 'flag', 'Flags', 'flags', 'stacked'], + 'options': [None, 'ChangeFinder', 'flag', 'Flags', 'changefinder.flags', 'stacked'], 'lines': [] } } diff --git a/collectors/python.d.plugin/changefinder/metrics.csv b/collectors/python.d.plugin/changefinder/metrics.csv new file mode 100644 index 00000000..ecad582b --- /dev/null +++ b/collectors/python.d.plugin/changefinder/metrics.csv @@ -0,0 +1,3 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +changefinder.scores,,a dimension per chart,score,ChangeFinder,line,,python.d.plugin,changefinder +changefinder.flags,,a dimension per chart,flag,ChangeFinder,stacked,,python.d.plugin,changefinder diff --git a/collectors/python.d.plugin/dovecot/README.md b/collectors/python.d.plugin/dovecot/README.md index 358f1ba8..2397b747 100644 --- a/collectors/python.d.plugin/dovecot/README.md +++ b/collectors/python.d.plugin/dovecot/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Dovecot" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Webapps" +learn_rel_path: "Integrations/Monitor/Webapps" --> -# Dovecot monitoring with Netdata +# Dovecot collector Provides statistics information from Dovecot server. @@ -103,6 +103,26 @@ localsocket: If no configuration is given, module will attempt to connect to dovecot using unix socket localized in `/var/run/dovecot/stats` ---- + +### Troubleshooting + +To troubleshoot issues with the `dovecot` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `dovecot` module in debug mode: + +```bash +./python.d.plugin dovecot debug trace +``` + diff --git a/collectors/python.d.plugin/dovecot/metrics.csv b/collectors/python.d.plugin/dovecot/metrics.csv new file mode 100644 index 00000000..dbffd0b3 --- /dev/null +++ b/collectors/python.d.plugin/dovecot/metrics.csv @@ -0,0 +1,13 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +dovecot.sessions,,active sessions,number,Dovecot Active Sessions,line,,python.d.plugin,dovecot +dovecot.logins,,logins,number,Dovecot Logins,line,,python.d.plugin,dovecot +dovecot.commands,,commands,commands,Dovecot Commands,line,,python.d.plugin,dovecot +dovecot.faults,,"minor, major",faults,Dovecot Page Faults,line,,python.d.plugin,dovecot +dovecot.context_switches,,"voluntary, involuntary",switches,Dovecot Context Switches,line,,python.d.plugin,dovecot +dovecot.io,,"read, write",KiB/s,Dovecot Disk I/O,area,,python.d.plugin,dovecot +dovecot.net,,"read, write",kilobits/s,Dovecot Network Bandwidth,area,,python.d.plugin,dovecot +dovecot.syscalls,,"read, write",syscalls/s,Dovecot Number of SysCalls,line,,python.d.plugin,dovecot +dovecot.lookup,,"path, attr",number/s,Dovecot Lookups,stacked,,python.d.plugin,dovecot +dovecot.cache,,hits,hits/s,Dovecot Cache Hits,line,,python.d.plugin,dovecot +dovecot.auth,,"ok, failed",attempts,Dovecot Authentications,stacked,,python.d.plugin,dovecot +dovecot.auth_cache,,"hit, miss",number,Dovecot Authentication Cache,stacked,,python.d.plugin,dovecot diff --git a/collectors/python.d.plugin/example/README.md b/collectors/python.d.plugin/example/README.md index 7e6d2b91..63ec7a29 100644 --- a/collectors/python.d.plugin/example/README.md +++ b/collectors/python.d.plugin/example/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Example module in Python" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Mock Collectors" +learn_rel_path: "Integrations/Monitor/Mock Collectors" --> -# Example +# Example module in Python You can add custom data collectors using Python. @@ -16,3 +16,23 @@ Netdata provides an [example python data collection module](https://github.com/n 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. +### Troubleshooting + +To troubleshoot issues with the `example` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `example` module in debug mode: + +```bash +./python.d.plugin example debug trace +``` + diff --git a/collectors/python.d.plugin/exim/README.md b/collectors/python.d.plugin/exim/README.md index a9c66c05..bc00ab7c 100644 --- a/collectors/python.d.plugin/exim/README.md +++ b/collectors/python.d.plugin/exim/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Exim" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Webapps" +learn_rel_path: "Integrations/Monitor/Webapps" --> -# Exim monitoring with Netdata +# Exim collector Simple module executing `exim -bpc` to grab exim queue. This command can take a lot of time to finish its execution thus it is not recommended to run it every second. @@ -39,6 +39,26 @@ It produces only one chart: Configuration is not needed. ---- + +### Troubleshooting + +To troubleshoot issues with the `exim` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `exim` module in debug mode: + +```bash +./python.d.plugin exim debug trace +``` + diff --git a/collectors/python.d.plugin/exim/metrics.csv b/collectors/python.d.plugin/exim/metrics.csv new file mode 100644 index 00000000..8e6cc0c2 --- /dev/null +++ b/collectors/python.d.plugin/exim/metrics.csv @@ -0,0 +1,2 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +exim.qemails,,emails,emails,Exim Queue Emails,line,,python.d.plugin,exim diff --git a/collectors/python.d.plugin/fail2ban/README.md b/collectors/python.d.plugin/fail2ban/README.md index 6b2c6bba..41276d5f 100644 --- a/collectors/python.d.plugin/fail2ban/README.md +++ b/collectors/python.d.plugin/fail2ban/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Fail2ban" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Apps" +learn_rel_path: "Integrations/Monitor/Apps" --> -# Fail2ban monitoring with Netdata +# Fail2ban collector Monitors the fail2ban log file to show all bans for all active jails. @@ -80,6 +80,26 @@ local: If no configuration is given, module will attempt to read log file at `/var/log/fail2ban.log` and conf file at `/etc/fail2ban/jail.local`. If conf file is not found default jail is `ssh`. ---- + +### Troubleshooting + +To troubleshoot issues with the `fail2ban` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `fail2ban` module in debug mode: + +```bash +./python.d.plugin fail2ban debug trace +``` + diff --git a/collectors/python.d.plugin/fail2ban/metrics.csv b/collectors/python.d.plugin/fail2ban/metrics.csv new file mode 100644 index 00000000..13ef80f4 --- /dev/null +++ b/collectors/python.d.plugin/fail2ban/metrics.csv @@ -0,0 +1,4 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +fail2ban.faile_attempts,,a dimension per jail,attempts/s,Failed attempts,line,,python.d.plugin,fail2ban +fail2ban.bans,,a dimension per jail,bans/s,Bans,line,,python.d.plugin,fail2ban +fail2ban.banned_ips,,a dimension per jail,ips,Banned IP addresses (since the last restart of netdata),line,,python.d.plugin,fail2ban diff --git a/collectors/python.d.plugin/gearman/README.md b/collectors/python.d.plugin/gearman/README.md index 9ac53cb8..329c3472 100644 --- a/collectors/python.d.plugin/gearman/README.md +++ b/collectors/python.d.plugin/gearman/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Gearman" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Distributed computing" +learn_rel_path: "Integrations/Monitor/Distributed computing" --> -# Gearman monitoring with Netdata +# Gearman collector Monitors Gearman worker statistics. A chart is shown for each job as well as one showing a summary of all workers. @@ -51,3 +51,23 @@ localhost: When no configuration file is found, module tries to connect to TCP/IP socket: `localhost:4730`. +### Troubleshooting + +To troubleshoot issues with the `gearman` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `gearman` module in debug mode: + +```bash +./python.d.plugin gearman debug trace +``` + diff --git a/collectors/python.d.plugin/gearman/metrics.csv b/collectors/python.d.plugin/gearman/metrics.csv new file mode 100644 index 00000000..0592e75d --- /dev/null +++ b/collectors/python.d.plugin/gearman/metrics.csv @@ -0,0 +1,3 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +gearman.total_jobs,,"Pending, Running",Jobs,Total Jobs,line,,python.d.plugin,gearman +gearman.single_job,gearman job,"Pending, Idle, Runnning",Jobs,{job_name},stacked,,python.d.plugin,gearman diff --git a/collectors/python.d.plugin/go_expvar/README.md b/collectors/python.d.plugin/go_expvar/README.md index ff786e7c..f86fa6d0 100644 --- a/collectors/python.d.plugin/go_expvar/README.md +++ b/collectors/python.d.plugin/go_expvar/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Go applications" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Application Performance Monitoring" +learn_rel_path: "Integrations/Monitor/Application Performance Monitoring" --> -# Go applications monitoring with Netdata +# Go applications collector Monitors Go application that exposes its metrics with the use of `expvar` package from the Go standard library. The package produces charts for Go runtime memory statistics and optionally any number of custom charts. @@ -320,3 +320,23 @@ The images below show how do the final charts in Netdata look. ![Custom charts](https://cloud.githubusercontent.com/assets/15180106/26762051/62ae915e-493b-11e7-8518-bd25a3886650.png) +### Troubleshooting + +To troubleshoot issues with the `go_expvar` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `go_expvar` module in debug mode: + +```bash +./python.d.plugin go_expvar debug trace +``` + diff --git a/collectors/python.d.plugin/go_expvar/metrics.csv b/collectors/python.d.plugin/go_expvar/metrics.csv new file mode 100644 index 00000000..5d96ff75 --- /dev/null +++ b/collectors/python.d.plugin/go_expvar/metrics.csv @@ -0,0 +1,8 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +expvar.memstats.heap,,"alloc, inuse",KiB,memory: size of heap memory structures,line,,python.d.plugin,go_expvar +expvar.memstats.stack,,inuse,KiB,memory: size of stack memory structures,line,,python.d.plugin,go_expvar +expvar.memstats.mspan,,inuse,KiB,memory: size of mspan memory structures,line,,python.d.plugin,go_expvar +expvar.memstats.mcache,,inuse,KiB,memory: size of mcache memory structures,line,,python.d.plugin,go_expvar +expvar.memstats.live_objects,,live,objects,memory: number of live objects,line,,python.d.plugin,go_expvar +expvar.memstats.sys,,sys,KiB,memory: size of reserved virtual address space,line,,python.d.plugin,go_expvar +expvar.memstats.gc_pauses,,avg,ns,memory: average duration of GC pauses,line,,python.d.plugin,go_expvar diff --git a/collectors/python.d.plugin/haproxy/README.md b/collectors/python.d.plugin/haproxy/README.md index 1aa1a214..2fa203f6 100644 --- a/collectors/python.d.plugin/haproxy/README.md +++ b/collectors/python.d.plugin/haproxy/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "haproxy-python.d.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Webapps" +learn_rel_path: "Integrations/Monitor/Webapps" --> -# HAProxy monitoring with Netdata +# HAProxy collector Monitors frontend and backend metrics such as bytes in, bytes out, sessions current, sessions in queue current. And health metrics such as backend servers status (server check should be used). @@ -67,4 +67,24 @@ via_socket: If no configuration is given, module will fail to run. ---- + +### Troubleshooting + +To troubleshoot issues with the `haproxy` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `haproxy` module in debug mode: + +```bash +./python.d.plugin haproxy debug trace +``` + diff --git a/collectors/python.d.plugin/haproxy/metrics.csv b/collectors/python.d.plugin/haproxy/metrics.csv new file mode 100644 index 00000000..7c92c566 --- /dev/null +++ b/collectors/python.d.plugin/haproxy/metrics.csv @@ -0,0 +1,31 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +haproxy_f.bin,,a dimension per frontend server,KiB/s,Kilobytes In,line,,python.d.plugin,haproxy +haproxy_f.bout,,a dimension per frontend server,KiB/s,Kilobytes Out,line,,python.d.plugin,haproxy +haproxy_f.scur,,a dimension per frontend server,sessions,Sessions Active,line,,python.d.plugin,haproxy +haproxy_f.qcur,,a dimension per frontend server,sessions,Session In Queue,line,,python.d.plugin,haproxy +haproxy_f.hrsp_1xx,,a dimension per frontend server,responses/s,HTTP responses with 1xx code,line,,python.d.plugin,haproxy +haproxy_f.hrsp_2xx,,a dimension per frontend server,responses/s,HTTP responses with 2xx code,line,,python.d.plugin,haproxy +haproxy_f.hrsp_3xx,,a dimension per frontend server,responses/s,HTTP responses with 3xx code,line,,python.d.plugin,haproxy +haproxy_f.hrsp_4xx,,a dimension per frontend server,responses/s,HTTP responses with 4xx code,line,,python.d.plugin,haproxy +haproxy_f.hrsp_5xx,,a dimension per frontend server,responses/s,HTTP responses with 5xx code,line,,python.d.plugin,haproxy +haproxy_f.hrsp_other,,a dimension per frontend server,responses/s,HTTP responses with other codes (protocol error),line,,python.d.plugin,haproxy +haproxy_f.hrsp_total,,a dimension per frontend server,responses,HTTP responses,line,,python.d.plugin,haproxy +haproxy_b.bin,,a dimension per backend server,KiB/s,Kilobytes In,line,,python.d.plugin,haproxy +haproxy_b.bout,,a dimension per backend server,KiB/s,Kilobytes Out,line,,python.d.plugin,haproxy +haproxy_b.scur,,a dimension per backend server,sessions,Sessions Active,line,,python.d.plugin,haproxy +haproxy_b.qcur,,a dimension per backend server,sessions,Sessions In Queue,line,,python.d.plugin,haproxy +haproxy_b.hrsp_1xx,,a dimension per backend server,responses/s,HTTP responses with 1xx code,line,,python.d.plugin,haproxy +haproxy_b.hrsp_2xx,,a dimension per backend server,responses/s,HTTP responses with 2xx code,line,,python.d.plugin,haproxy +haproxy_b.hrsp_3xx,,a dimension per backend server,responses/s,HTTP responses with 3xx code,line,,python.d.plugin,haproxy +haproxy_b.hrsp_4xx,,a dimension per backend server,responses/s,HTTP responses with 4xx code,line,,python.d.plugin,haproxy +haproxy_b.hrsp_5xx,,a dimension per backend server,responses/s,HTTP responses with 5xx code,line,,python.d.plugin,haproxy +haproxy_b.hrsp_other,,a dimension per backend server,responses/s,HTTP responses with other codes (protocol error),line,,python.d.plugin,haproxy +haproxy_b.hrsp_total,,a dimension per backend server,responses/s,HTTP responses (total),line,,python.d.plugin,haproxy +haproxy_b.qtime,,a dimension per backend server,milliseconds,The average queue time over the 1024 last requests,line,,python.d.plugin,haproxy +haproxy_b.ctime,,a dimension per backend server,milliseconds,The average connect time over the 1024 last requests,line,,python.d.plugin,haproxy +haproxy_b.rtime,,a dimension per backend server,milliseconds,The average response time over the 1024 last requests,line,,python.d.plugin,haproxy +haproxy_b.ttime,,a dimension per backend server,milliseconds,The average total session time over the 1024 last requests,line,,python.d.plugin,haproxy +haproxy_hs.down,,a dimension per backend server,failed servers,Backend Servers In DOWN State,line,,python.d.plugin,haproxy +haproxy_hs.up,,a dimension per backend server,health servers,Backend Servers In UP State,line,,python.d.plugin,haproxy +haproxy_hb.down,,a dimension per backend server,boolean,Is Backend Failed?,line,,python.d.plugin,haproxy +haproxy.idle,,idle,percentage,The Ratio Of Polling Time Vs Total Time,line,,python.d.plugin,haproxy diff --git a/collectors/python.d.plugin/hddtemp/README.md b/collectors/python.d.plugin/hddtemp/README.md index 6a253b5b..b42da734 100644 --- a/collectors/python.d.plugin/hddtemp/README.md +++ b/collectors/python.d.plugin/hddtemp/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Hard drive temperature" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Hardware" +learn_rel_path: "Integrations/Monitor/Hardware" --> -# Hard drive temperature monitoring with Netdata +# Hard drive temperature collector Monitors disk temperatures from one or more `hddtemp` daemons. @@ -36,6 +36,26 @@ port: 7634 If no configuration is given, module will attempt to connect to hddtemp daemon on `127.0.0.1:7634` address ---- + +### Troubleshooting + +To troubleshoot issues with the `hddtemp` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `hddtemp` module in debug mode: + +```bash +./python.d.plugin hddtemp debug trace +``` + diff --git a/collectors/python.d.plugin/hddtemp/metrics.csv b/collectors/python.d.plugin/hddtemp/metrics.csv new file mode 100644 index 00000000..c3a858db --- /dev/null +++ b/collectors/python.d.plugin/hddtemp/metrics.csv @@ -0,0 +1,2 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +hddtemp.temperatures,,a dimension per disk,Celsius,Disk Temperatures,line,,python.d.plugin,hddtemp diff --git a/collectors/python.d.plugin/hpssa/README.md b/collectors/python.d.plugin/hpssa/README.md index 72dc7803..12b25047 100644 --- a/collectors/python.d.plugin/hpssa/README.md +++ b/collectors/python.d.plugin/hpssa/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "HP Smart Storage Arrays" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Storage" +learn_rel_path: "Integrations/Monitor/Storage" --> -# HP Smart Storage Arrays monitoring with Netdata +# HP Smart Storage Arrays collector Monitors controller, cache module, logical and physical drive state and temperature using `ssacli` tool. @@ -84,3 +84,23 @@ ssacli_path: /usr/sbin/ssacli Save the file and restart the Netdata 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. +### Troubleshooting + +To troubleshoot issues with the `hpssa` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `hpssa` module in debug mode: + +```bash +./python.d.plugin hpssa debug trace +``` + diff --git a/collectors/python.d.plugin/hpssa/metrics.csv b/collectors/python.d.plugin/hpssa/metrics.csv new file mode 100644 index 00000000..126ba5da --- /dev/null +++ b/collectors/python.d.plugin/hpssa/metrics.csv @@ -0,0 +1,6 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +hpssa.ctrl_status,,"ctrl_{adapter slot}_status, cache_{adapter slot}_status, battery_{adapter slot}_status per adapter",Status,"Status 1 is OK, Status 0 is not OK",line,,python.d.plugin,hpssa +hpssa.ctrl_temperature,,"ctrl_{adapter slot}_temperature, cache_{adapter slot}_temperature per adapter",Celsius,Temperature,line,,python.d.plugin,hpssa +hpssa.ld_status,,a dimension per logical drive,Status,"Status 1 is OK, Status 0 is not OK",line,,python.d.plugin,hpssa +hpssa.pd_status,,a dimension per physical drive,Status,"Status 1 is OK, Status 0 is not OK",line,,python.d.plugin,hpssa +hpssa.pd_temperature,,a dimension per physical drive,Celsius,Temperature,line,,python.d.plugin,hpssa diff --git a/collectors/python.d.plugin/icecast/README.md b/collectors/python.d.plugin/icecast/README.md index 6fca34ba..25bbf738 100644 --- a/collectors/python.d.plugin/icecast/README.md +++ b/collectors/python.d.plugin/icecast/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Icecast" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Networking" +learn_rel_path: "Integrations/Monitor/Networking" --> -# Icecast monitoring with Netdata +# Icecast collector Monitors the number of listeners for active sources. @@ -42,6 +42,26 @@ remote: Without configuration, module attempts to connect to `http://localhost:8443/status-json.xsl` ---- + +### Troubleshooting + +To troubleshoot issues with the `icecast` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `icecast` module in debug mode: + +```bash +./python.d.plugin icecast debug trace +``` + diff --git a/collectors/python.d.plugin/icecast/metrics.csv b/collectors/python.d.plugin/icecast/metrics.csv new file mode 100644 index 00000000..e05c0504 --- /dev/null +++ b/collectors/python.d.plugin/icecast/metrics.csv @@ -0,0 +1,2 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +icecast.listeners,,a dimension for each active source,listeners,Number Of Listeners,line,,python.d.plugin,icecast diff --git a/collectors/python.d.plugin/ipfs/README.md b/collectors/python.d.plugin/ipfs/README.md index 8f5e53b1..c990ae34 100644 --- a/collectors/python.d.plugin/ipfs/README.md +++ b/collectors/python.d.plugin/ipfs/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "IPFS" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Storage" +learn_rel_path: "Integrations/Monitor/Storage" --> -# IPFS monitoring with Netdata +# IPFS collector Collects [`IPFS`](https://ipfs.io) basic information like file system bandwidth, peers and repo metrics. @@ -30,7 +30,7 @@ cd /etc/netdata # Replace this path with your Netdata config directory, if dif sudo ./edit-config python.d/ipfs.conf ``` ---- + Calls to the following endpoints are disabled due to `IPFS` bugs: @@ -49,6 +49,26 @@ remote: url: 'http://203.0.113.10::5001' ``` ---- + +### Troubleshooting + +To troubleshoot issues with the `ipfs` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `ipfs` module in debug mode: + +```bash +./python.d.plugin ipfs debug trace +``` + diff --git a/collectors/python.d.plugin/ipfs/metrics.csv b/collectors/python.d.plugin/ipfs/metrics.csv new file mode 100644 index 00000000..33dd43c9 --- /dev/null +++ b/collectors/python.d.plugin/ipfs/metrics.csv @@ -0,0 +1,5 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +ipfs.bandwidth,,"in, out",kilobits/s,IPFS Bandwidth,line,,python.d.plugin,ipfs +ipfs.peers,,peers,peers,IPFS Peers,line,,python.d.plugin,ipfs +ipfs.repo_size,,"avail, size",GiB,IPFS Repo Size,area,,python.d.plugin,ipfs +ipfs.repo_objects,,"objects, pinned, recursive_pins",objects,IPFS Repo Objects,line,,python.d.plugin,ipfs diff --git a/collectors/python.d.plugin/litespeed/README.md b/collectors/python.d.plugin/litespeed/README.md index b9bad463..1ad5ad42 100644 --- a/collectors/python.d.plugin/litespeed/README.md +++ b/collectors/python.d.plugin/litespeed/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "LiteSpeed" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Application Performance Monitoring" +learn_rel_path: "Integrations/Monitor/Application Performance Monitoring" --> -# LiteSpeed monitoring with Netdata +# LiteSpeed collector Collects web server performance metrics for network, connection, requests, and cache. @@ -70,6 +70,26 @@ local: If no configuration is given, module will use "/tmp/lshttpd/". ---- + +### Troubleshooting + +To troubleshoot issues with the `litespeed` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `litespeed` module in debug mode: + +```bash +./python.d.plugin litespeed debug trace +``` + diff --git a/collectors/python.d.plugin/litespeed/metrics.csv b/collectors/python.d.plugin/litespeed/metrics.csv new file mode 100644 index 00000000..56e50e42 --- /dev/null +++ b/collectors/python.d.plugin/litespeed/metrics.csv @@ -0,0 +1,10 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +litespeed.net_throughput,,"in, out",kilobits/s,Network Throughput HTTP,area,,python.d.plugin,litespeed +litespeed.net_throughput,,"in, out",kilobits/s,Network Throughput HTTPS,area,,python.d.plugin,litespeed +litespeed.connections,,"free, used",conns,Connections HTTP,stacked,,python.d.plugin,litespeed +litespeed.connections,,"free, used",conns,Connections HTTPS,stacked,,python.d.plugin,litespeed +litespeed.requests,,requests,requests/s,Requests,line,,python.d.plugin,litespeed +litespeed.requests_processing,,processing,requests,Requests In Processing,line,,python.d.plugin,litespeed +litespeed.cache,,hits,hits/s,Public Cache Hits,line,,python.d.plugin,litespeed +litespeed.cache,,hits,hits/s,Private Cache Hits,line,,python.d.plugin,litespeed +litespeed.static,,hits,hits/s,Static Hits,line,,python.d.plugin,litespeed diff --git a/collectors/python.d.plugin/megacli/README.md b/collectors/python.d.plugin/megacli/README.md index 3900de38..1af4d0ea 100644 --- a/collectors/python.d.plugin/megacli/README.md +++ b/collectors/python.d.plugin/megacli/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "MegaRAID controllers" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Devices" +learn_rel_path: "Integrations/Monitor/Devices" --> -# MegaRAID controller monitoring with Netdata +# MegaRAID controller collector Collects adapter, physical drives and battery stats using `megacli` command-line tool. @@ -87,3 +87,23 @@ Save the file and restart the Netdata Agent with `sudo systemctl restart netdata method](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for your system. +### Troubleshooting + +To troubleshoot issues with the `megacli` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `megacli` module in debug mode: + +```bash +./python.d.plugin megacli debug trace +``` + diff --git a/collectors/python.d.plugin/megacli/metrics.csv b/collectors/python.d.plugin/megacli/metrics.csv new file mode 100644 index 00000000..6d7b00bf --- /dev/null +++ b/collectors/python.d.plugin/megacli/metrics.csv @@ -0,0 +1,6 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +megacli.adapter_degraded,,a dimension per adapter,is degraded,Adapter State,line,,python.d.plugin,megacli +megacli.pd_media_error,,a dimension per physical drive,errors/s,Physical Drives Media Errors,line,,python.d.plugin,megacli +megacli.pd_predictive_failure,,a dimension per physical drive,failures/s,Physical Drives Predictive Failures,line,,python.d.plugin,megacli +megacli.bbu_relative_charge,battery,adapter {battery id},percentage,Relative State of Charge,line,,python.d.plugin,megacli +megacli.bbu_cycle_count,battery,adapter {battery id},cycle count,Cycle Count,line,,python.d.plugin,megacli diff --git a/collectors/python.d.plugin/memcached/README.md b/collectors/python.d.plugin/memcached/README.md index 4158ab19..612bd49d 100644 --- a/collectors/python.d.plugin/memcached/README.md +++ b/collectors/python.d.plugin/memcached/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Memcached" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Databases" +learn_rel_path: "Integrations/Monitor/Databases" --> -# Memcached monitoring with Netdata +# Memcached collector Collects memory-caching system performance metrics. It reads server response to stats command ([stats interface](https://github.com/memcached/memcached/wiki/Commands#stats)). @@ -97,6 +97,26 @@ localtcpip: If no configuration is given, module will attempt to connect to memcached instance on `127.0.0.1:11211` address. ---- + +### Troubleshooting + +To troubleshoot issues with the `memcached` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `memcached` module in debug mode: + +```bash +./python.d.plugin memcached debug trace +``` + diff --git a/collectors/python.d.plugin/memcached/memcached.chart.py b/collectors/python.d.plugin/memcached/memcached.chart.py index bb656a2d..adb9560b 100644 --- a/collectors/python.d.plugin/memcached/memcached.chart.py +++ b/collectors/python.d.plugin/memcached/memcached.chart.py @@ -53,40 +53,40 @@ CHARTS = { ] }, 'evicted_reclaimed': { - 'options': [None, 'Items', 'items', 'items', 'memcached.evicted_reclaimed', 'line'], + 'options': [None, 'Evicted and Reclaimed Items', 'items', 'items', 'memcached.evicted_reclaimed', 'line'], 'lines': [ ['reclaimed', 'reclaimed', 'absolute'], ['evictions', 'evicted', 'absolute'] ] }, 'get': { - 'options': [None, 'Requests', 'requests', 'get ops', 'memcached.get', 'stacked'], + 'options': [None, 'Get Requests', 'requests', 'get ops', 'memcached.get', 'stacked'], 'lines': [ ['get_hits', 'hits', 'percent-of-absolute-row'], ['get_misses', 'misses', 'percent-of-absolute-row'] ] }, 'get_rate': { - 'options': [None, 'Rate', 'requests/s', 'get ops', 'memcached.get_rate', 'line'], + 'options': [None, 'Get Request Rate', 'requests/s', 'get ops', 'memcached.get_rate', 'line'], 'lines': [ ['cmd_get', 'rate', 'incremental'] ] }, 'set_rate': { - 'options': [None, 'Rate', 'requests/s', 'set ops', 'memcached.set_rate', 'line'], + 'options': [None, 'Set Request Rate', 'requests/s', 'set ops', 'memcached.set_rate', 'line'], 'lines': [ ['cmd_set', 'rate', 'incremental'] ] }, 'delete': { - 'options': [None, 'Requests', 'requests', 'delete ops', 'memcached.delete', 'stacked'], + 'options': [None, 'Delete Requests', 'requests', 'delete ops', 'memcached.delete', 'stacked'], 'lines': [ ['delete_hits', 'hits', 'percent-of-absolute-row'], ['delete_misses', 'misses', 'percent-of-absolute-row'], ] }, 'cas': { - 'options': [None, 'Requests', 'requests', 'check and set ops', 'memcached.cas', 'stacked'], + 'options': [None, 'Check and Set Requests', 'requests', 'check and set ops', 'memcached.cas', 'stacked'], 'lines': [ ['cas_hits', 'hits', 'percent-of-absolute-row'], ['cas_misses', 'misses', 'percent-of-absolute-row'], @@ -94,28 +94,28 @@ CHARTS = { ] }, 'increment': { - 'options': [None, 'Requests', 'requests', 'increment ops', 'memcached.increment', 'stacked'], + 'options': [None, 'Increment Requests', 'requests', 'increment ops', 'memcached.increment', 'stacked'], 'lines': [ ['incr_hits', 'hits', 'percent-of-absolute-row'], ['incr_misses', 'misses', 'percent-of-absolute-row'] ] }, 'decrement': { - 'options': [None, 'Requests', 'requests', 'decrement ops', 'memcached.decrement', 'stacked'], + 'options': [None, 'Decrement Requests', 'requests', 'decrement ops', 'memcached.decrement', 'stacked'], 'lines': [ ['decr_hits', 'hits', 'percent-of-absolute-row'], ['decr_misses', 'misses', 'percent-of-absolute-row'] ] }, 'touch': { - 'options': [None, 'Requests', 'requests', 'touch ops', 'memcached.touch', 'stacked'], + 'options': [None, 'Touch Requests', 'requests', 'touch ops', 'memcached.touch', 'stacked'], 'lines': [ ['touch_hits', 'hits', 'percent-of-absolute-row'], ['touch_misses', 'misses', 'percent-of-absolute-row'] ] }, 'touch_rate': { - 'options': [None, 'Rate', 'requests/s', 'touch ops', 'memcached.touch_rate', 'line'], + 'options': [None, 'Touch Request Rate', 'requests/s', 'touch ops', 'memcached.touch_rate', 'line'], 'lines': [ ['cmd_touch', 'rate', 'incremental'] ] diff --git a/collectors/python.d.plugin/memcached/metrics.csv b/collectors/python.d.plugin/memcached/metrics.csv new file mode 100644 index 00000000..c7362075 --- /dev/null +++ b/collectors/python.d.plugin/memcached/metrics.csv @@ -0,0 +1,15 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +memcached.cache,,"available, used",MiB,Cache Size,stacked,,python.d.plugin,memcached +memcached.net,,"in, out",kilobits/s,Network,area,,python.d.plugin,memcached +memcached.connections,,"current, rejected, total",connections/s,Connections,line,,python.d.plugin,memcached +memcached.items,,"current,total",items,Items,line,,python.d.plugin,memcached +memcached.evicted_reclaimed,,"reclaimed, evicted", items,Evicted and Reclaimed Items,line,,python.d.plugin,memcached +memcached.get,,"hints, misses",requests,Get Requests,stacked,,python.d.plugin,memcached +memcached.get_rate,,rate,requests/s,Get Request Rate,line,,python.d.plugin,memcached +memcached.set_rate,,rate,requests/s,Set Request Rate,line,,python.d.plugin,memcached +memcached.delete,,"hits, misses",requests,Delete Requests,stacked,,python.d.plugin,memcached +memcached.cas,,"hits, misses, bad value",requests,Check and Set Requests,stacked,,python.d.plugin,memcached +memcached.increment,,"hits, misses",requests,Increment Requests,stacked,,python.d.plugin,memcached +memcached.decrement,,"hits, misses",requests,Decrement Requests,stacked,,python.d.plugin,memcached +memcached.touch,,"hits, misses",requests,Touch Requests,stacked,,python.d.plugin,memcached +memcached.touch_rate,,rate,requests/s,Touch Request Rate,line,,python.d.plugin,memcached diff --git a/collectors/python.d.plugin/monit/README.md b/collectors/python.d.plugin/monit/README.md index 816143eb..f762de0d 100644 --- a/collectors/python.d.plugin/monit/README.md +++ b/collectors/python.d.plugin/monit/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Monit" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Storage" +learn_rel_path: "Integrations/Monitor/Storage" --> -# Monit monitoring with Netdata +# Monit collector 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. @@ -53,6 +53,26 @@ local: If no configuration is given, module will attempt to connect to monit as `http://localhost:2812`. ---- + +### Troubleshooting + +To troubleshoot issues with the `monit` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `monit` module in debug mode: + +```bash +./python.d.plugin monit debug trace +``` + diff --git a/collectors/python.d.plugin/monit/metrics.csv b/collectors/python.d.plugin/monit/metrics.csv new file mode 100644 index 00000000..1981a07e --- /dev/null +++ b/collectors/python.d.plugin/monit/metrics.csv @@ -0,0 +1,13 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +monit.filesystems,,a dimension per target,filesystems,Filesystems,line,,python.d.plugin,monit +monit.directories,,a dimension per target,directories,Directories,line,,python.d.plugin,monit +monit.files,,a dimension per target,files,Files,line,,python.d.plugin,monit +monit.fifos,,a dimension per target,pipes,Pipes (fifo),line,,python.d.plugin,monit +monit.programs,,a dimension per target,programs,Programs statuses,line,,python.d.plugin,monit +monit.services,,a dimension per target,processes,Processes statuses,line,,python.d.plugin,monit +monit.process_uptime,,a dimension per target,seconds,Processes uptime,line,,python.d.plugin,monit +monit.process_threads,,a dimension per target,threads,Processes threads,line,,python.d.plugin,monit +monit.process_childrens,,a dimension per target,children,Child processes,line,,python.d.plugin,monit +monit.hosts,,a dimension per target,hosts,Hosts,line,,python.d.plugin,monit +monit.host_latency,,a dimension per target,milliseconds,Hosts latency,line,,python.d.plugin,monit +monit.networks,,a dimension per target,interfaces,Network interfaces and addresses,line,,python.d.plugin,monit diff --git a/collectors/python.d.plugin/monit/monit.chart.py b/collectors/python.d.plugin/monit/monit.chart.py index bfc18234..5d926961 100644 --- a/collectors/python.d.plugin/monit/monit.chart.py +++ b/collectors/python.d.plugin/monit/monit.chart.py @@ -99,7 +99,7 @@ CHARTS = { 'lines': [] }, 'process_children': { - 'options': ['processes childrens', 'Child processes', 'childrens', 'applications', + 'options': ['processes childrens', 'Child processes', 'children', 'applications', 'monit.process_childrens', 'line'], 'lines': [] }, diff --git a/collectors/python.d.plugin/nsd/README.md b/collectors/python.d.plugin/nsd/README.md index f99726c3..ccc4e712 100644 --- a/collectors/python.d.plugin/nsd/README.md +++ b/collectors/python.d.plugin/nsd/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "NSD" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Networking" +learn_rel_path: "Integrations/Monitor/Networking" --> -# NSD monitoring with Netdata +# NSD collector Uses the `nsd-control stats_noreset` command to provide `nsd` statistics. @@ -66,6 +66,26 @@ It produces: Configuration is not needed. ---- + +### Troubleshooting + +To troubleshoot issues with the `nsd` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `nsd` module in debug mode: + +```bash +./python.d.plugin nsd debug trace +``` + diff --git a/collectors/python.d.plugin/nsd/metrics.csv b/collectors/python.d.plugin/nsd/metrics.csv new file mode 100644 index 00000000..b82812bf --- /dev/null +++ b/collectors/python.d.plugin/nsd/metrics.csv @@ -0,0 +1,7 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +nsd.queries,,queries,queries/s,queries,line,,python.d.plugin,nsd +nsd.zones,,"master, slave",zones,zones,stacked,,python.d.plugin,nsd +nsd.protocols,,"udp, udp6, tcp, tcp6",queries/s,protocol,stacked,,python.d.plugin,nsd +nsd.type,,"A, NS, CNAME, SOA, PTR, HINFO, MX, NAPTR, TXT, AAAA, SRV, ANY",queries/s,query type,stacked,,python.d.plugin,nsd +nsd.transfer,,"NOTIFY, AXFR",queries/s,transfer,stacked,,python.d.plugin,nsd +nsd.rcode,,"NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP, REFUSED, YXDOMAIN",queries/s,return code,stacked,,python.d.plugin,nsd diff --git a/collectors/python.d.plugin/ntpd/Makefile.inc b/collectors/python.d.plugin/ntpd/Makefile.inc deleted file mode 100644 index 81210eba..00000000 --- a/collectors/python.d.plugin/ntpd/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 += ntpd/ntpd.chart.py -dist_pythonconfig_DATA += ntpd/ntpd.conf - -# do not install these files, but include them in the distribution -dist_noinst_DATA += ntpd/README.md ntpd/Makefile.inc - diff --git a/collectors/python.d.plugin/ntpd/README.md b/collectors/python.d.plugin/ntpd/README.md deleted file mode 100644 index 8ae923da..00000000 --- a/collectors/python.d.plugin/ntpd/README.md +++ /dev/null @@ -1,14 +0,0 @@ - - -# NTP daemon monitoring with Netdata - -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 deleted file mode 100644 index 077124b4..00000000 --- a/collectors/python.d.plugin/ntpd/ntpd.chart.py +++ /dev/null @@ -1,387 +0,0 @@ -# -*- coding: utf-8 -*- -# Description: ntpd netdata python.d module -# Author: Sven Mäder (rda0) -# Author: Ilya Mashchenko (ilyam8) -# SPDX-License-Identifier: GPL-3.0-or-later - -import re -import struct - -from bases.FrameworkServices.SocketService import SocketService - -disabled_by_default = True - -# NTP Control Message Protocol constants -MODE = 6 -HEADER_FORMAT = '!BBHHHHH' -HEADER_LEN = 12 -OPCODES = { - 'readstat': 1, - 'readvar': 2 -} - -# Maximal dimension precision -PRECISION = 1000000 - -# Static charts -ORDER = [ - 'sys_offset', - 'sys_jitter', - 'sys_frequency', - 'sys_wander', - 'sys_rootdelay', - 'sys_rootdisp', - 'sys_stratum', - 'sys_tc', - 'sys_precision', - 'peer_offset', - 'peer_delay', - 'peer_dispersion', - 'peer_jitter', - 'peer_xleave', - 'peer_rootdelay', - 'peer_rootdisp', - 'peer_stratum', - 'peer_hmode', - 'peer_pmode', - 'peer_hpoll', - 'peer_ppoll', - 'peer_precision' -] - -CHARTS = { - 'sys_offset': { - 'options': [None, 'Combined offset of server relative to this host', 'milliseconds', - 'system', 'ntpd.sys_offset', 'area'], - 'lines': [ - ['offset', 'offset', 'absolute', 1, PRECISION] - ] - }, - 'sys_jitter': { - 'options': [None, 'Combined system jitter and clock jitter', 'milliseconds', - 'system', 'ntpd.sys_jitter', 'line'], - 'lines': [ - ['sys_jitter', 'system', 'absolute', 1, PRECISION], - ['clk_jitter', 'clock', 'absolute', 1, PRECISION] - ] - }, - 'sys_frequency': { - 'options': [None, 'Frequency offset relative to hardware clock', 'ppm', 'system', 'ntpd.sys_frequency', 'area'], - 'lines': [ - ['frequency', 'frequency', 'absolute', 1, PRECISION] - ] - }, - 'sys_wander': { - 'options': [None, 'Clock frequency wander', 'ppm', 'system', 'ntpd.sys_wander', 'area'], - 'lines': [ - ['clk_wander', 'clock', 'absolute', 1, PRECISION] - ] - }, - 'sys_rootdelay': { - 'options': [None, 'Total roundtrip delay to the primary reference clock', 'milliseconds', 'system', - 'ntpd.sys_rootdelay', 'area'], - 'lines': [ - ['rootdelay', 'delay', 'absolute', 1, PRECISION] - ] - }, - 'sys_rootdisp': { - 'options': [None, 'Total root dispersion to the primary reference clock', 'milliseconds', 'system', - 'ntpd.sys_rootdisp', 'area'], - 'lines': [ - ['rootdisp', 'dispersion', 'absolute', 1, PRECISION] - ] - }, - 'sys_stratum': { - 'options': [None, 'Stratum (1-15)', 'stratum', 'system', 'ntpd.sys_stratum', 'line'], - 'lines': [ - ['stratum', 'stratum', 'absolute', 1, PRECISION] - ] - }, - 'sys_tc': { - 'options': [None, 'Time constant and poll exponent (3-17)', 'log2 s', 'system', 'ntpd.sys_tc', 'line'], - 'lines': [ - ['tc', 'current', 'absolute', 1, PRECISION], - ['mintc', 'minimum', 'absolute', 1, PRECISION] - ] - }, - 'sys_precision': { - 'options': [None, 'Precision', 'log2 s', 'system', 'ntpd.sys_precision', 'line'], - 'lines': [ - ['precision', 'precision', 'absolute', 1, PRECISION] - ] - } -} - -PEER_CHARTS = { - 'peer_offset': { - 'options': [None, 'Filter offset', 'milliseconds', 'peers', 'ntpd.peer_offset', 'line'], - 'lines': [] - }, - 'peer_delay': { - 'options': [None, 'Filter delay', 'milliseconds', 'peers', 'ntpd.peer_delay', 'line'], - 'lines': [] - }, - 'peer_dispersion': { - 'options': [None, 'Filter dispersion', 'milliseconds', 'peers', 'ntpd.peer_dispersion', 'line'], - 'lines': [] - }, - 'peer_jitter': { - 'options': [None, 'Filter jitter', 'milliseconds', 'peers', 'ntpd.peer_jitter', 'line'], - 'lines': [] - }, - 'peer_xleave': { - 'options': [None, 'Interleave delay', 'milliseconds', 'peers', 'ntpd.peer_xleave', 'line'], - 'lines': [] - }, - 'peer_rootdelay': { - 'options': [None, 'Total roundtrip delay to the primary reference clock', 'milliseconds', 'peers', - 'ntpd.peer_rootdelay', 'line'], - 'lines': [] - }, - 'peer_rootdisp': { - 'options': [None, 'Total root dispersion to the primary reference clock', 'ms', 'peers', - 'ntpd.peer_rootdisp', 'line'], - 'lines': [] - }, - 'peer_stratum': { - 'options': [None, 'Stratum (1-15)', 'stratum', 'peers', 'ntpd.peer_stratum', 'line'], - 'lines': [] - }, - 'peer_hmode': { - 'options': [None, 'Host mode (1-6)', 'hmode', 'peers', 'ntpd.peer_hmode', 'line'], - 'lines': [] - }, - 'peer_pmode': { - 'options': [None, 'Peer mode (1-5)', 'pmode', 'peers', 'ntpd.peer_pmode', 'line'], - 'lines': [] - }, - 'peer_hpoll': { - 'options': [None, 'Host poll exponent', 'log2 s', 'peers', 'ntpd.peer_hpoll', 'line'], - 'lines': [] - }, - 'peer_ppoll': { - 'options': [None, 'Peer poll exponent', 'log2 s', 'peers', 'ntpd.peer_ppoll', 'line'], - 'lines': [] - }, - 'peer_precision': { - 'options': [None, 'Precision', 'log2 s', 'peers', 'ntpd.peer_precision', 'line'], - 'lines': [] - } -} - - -class Base: - regex = re.compile(r'([a-z_]+)=((?:-)?[0-9]+(?:\.[0-9]+)?)') - - @staticmethod - def get_header(associd=0, operation='readvar'): - """ - Constructs the NTP Control Message header: - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - |LI | VN |Mode |R|E|M| OpCode | Sequence Number | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Status | Association ID | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Offset | Count | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - """ - version = 2 - sequence = 1 - status = 0 - offset = 0 - count = 0 - header = struct.pack(HEADER_FORMAT, (version << 3 | MODE), OPCODES[operation], - sequence, status, associd, offset, count) - return header - - -class System(Base): - def __init__(self): - self.request = self.get_header() - - def get_data(self, raw): - """ - Extracts key=value pairs with float/integer from ntp response packet data. - """ - data = dict() - for key, value in self.regex.findall(raw): - data[key] = float(value) * PRECISION - return data - - -class Peer(Base): - def __init__(self, idx, name): - self.id = idx - self.real_name = name - self.name = name.replace('.', '_') - self.request = self.get_header(self.id) - - def get_data(self, raw): - """ - Extracts key=value pairs with float/integer from ntp response packet data. - """ - data = dict() - for key, value in self.regex.findall(raw): - dimension = '_'.join([self.name, key]) - data[dimension] = float(value) * PRECISION - return data - - -class Service(SocketService): - def __init__(self, configuration=None, name=None): - SocketService.__init__(self, configuration=configuration, name=name) - self.order = list(ORDER) - self.definitions = dict(CHARTS) - self.port = 'ntp' - self.dgram_socket = True - self.system = System() - self.peers = dict() - self.request = str() - self.retries = 0 - self.show_peers = self.configuration.get('show_peers', False) - self.peer_rescan = self.configuration.get('peer_rescan', 60) - if self.show_peers: - self.definitions.update(PEER_CHARTS) - - def check(self): - """ - Checks if we can get valid systemvars. - If not, returns None to disable module. - """ - self._parse_config() - - peer_filter = self.configuration.get('peer_filter', r'127\..*') - try: - self.peer_filter = re.compile(r'^((0\.0\.0\.0)|({0}))$'.format(peer_filter)) - except re.error as error: - self.error('Compile pattern error (peer_filter) : {0}'.format(error)) - return None - - self.request = self.system.request - raw_systemvars = self._get_raw_data() - - if not self.system.get_data(raw_systemvars): - return None - - return True - - def get_data(self): - """ - Gets systemvars data on each update. - Gets peervars data for all peers on each update. - """ - data = dict() - - self.request = self.system.request - raw = self._get_raw_data() - if not raw: - return None - - data.update(self.system.get_data(raw)) - - if not self.show_peers: - return data - - if not self.peers or self.runs_counter % self.peer_rescan == 0 or self.retries > 8: - self.find_new_peers() - - for peer in self.peers.values(): - self.request = peer.request - peer_data = peer.get_data(self._get_raw_data()) - if peer_data: - data.update(peer_data) - else: - self.retries += 1 - - return data - - def find_new_peers(self): - new_peers = dict((p.real_name, p) for p in self.get_peers()) - if new_peers: - - peers_to_remove = set(self.peers) - set(new_peers) - peers_to_add = set(new_peers) - set(self.peers) - - for peer_name in peers_to_remove: - self.hide_old_peer_from_charts(self.peers[peer_name]) - del self.peers[peer_name] - - for peer_name in peers_to_add: - self.add_new_peer_to_charts(new_peers[peer_name]) - - self.peers.update(new_peers) - self.retries = 0 - - def add_new_peer_to_charts(self, peer): - for chart_id in set(self.charts.charts) & set(PEER_CHARTS): - dim_id = peer.name + chart_id[4:] - if dim_id not in self.charts[chart_id]: - self.charts[chart_id].add_dimension([dim_id, peer.real_name, 'absolute', 1, PRECISION]) - else: - self.charts[chart_id].hide_dimension(dim_id, reverse=True) - - def hide_old_peer_from_charts(self, peer): - for chart_id in set(self.charts.charts) & set(PEER_CHARTS): - dim_id = peer.name + chart_id[4:] - self.charts[chart_id].hide_dimension(dim_id) - - def get_peers(self): - self.request = Base.get_header(operation='readstat') - - raw_data = self._get_raw_data(raw=True) - if not raw_data: - return list() - - peer_ids = self.get_peer_ids(raw_data) - if not peer_ids: - return list() - - new_peers = list() - for peer_id in peer_ids: - self.request = Base.get_header(peer_id) - raw_peer_data = self._get_raw_data() - if not raw_peer_data: - continue - srcadr = re.search(r'(srcadr)=([^,]+)', raw_peer_data) - if not srcadr: - continue - srcadr = srcadr.group(2) - if self.peer_filter.search(srcadr): - continue - stratum = re.search(r'(stratum)=([^,]+)', raw_peer_data) - if not stratum: - continue - if int(stratum.group(2)) > 15: - continue - - new_peer = Peer(idx=peer_id, name=srcadr) - new_peers.append(new_peer) - return new_peers - - def get_peer_ids(self, res): - """ - Unpack the NTP Control Message header - Get data length from header - Get list of association ids returned in the readstat response - """ - - try: - count = struct.unpack(HEADER_FORMAT, res[:HEADER_LEN])[6] - except struct.error as error: - self.error('error unpacking header: {0}'.format(error)) - return None - if not count: - self.error('empty data field in NTP control packet') - return None - - data_end = HEADER_LEN + count - data = res[HEADER_LEN:data_end] - data_format = ''.join(['!', 'H' * int(count / 2)]) - try: - peer_ids = list(struct.unpack(data_format, data))[::2] - except struct.error as error: - self.error('error unpacking data: {0}'.format(error)) - return None - return peer_ids diff --git a/collectors/python.d.plugin/ntpd/ntpd.conf b/collectors/python.d.plugin/ntpd/ntpd.conf deleted file mode 100644 index 80bd468d..00000000 --- a/collectors/python.d.plugin/ntpd/ntpd.conf +++ /dev/null @@ -1,89 +0,0 @@ -# netdata python.d.plugin configuration for ntpd -# -# 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 - -# ---------------------------------------------------------------------- -# 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 -# -# Additionally to the above, ntp also supports the following: -# -# host: 'localhost' # the host to query -# port: '123' # the UDP port where `ntpd` listens -# show_peers: no # use `yes` to show peer charts. enabling this -# # option is recommended only for debugging, as -# # it could possibly imply memory leaks if the -# # peers change frequently. -# peer_filter: '127\..*' # regex to exclude peers -# # by default local peers are hidden -# # use `''` to show all peers. -# peer_rescan: 60 # interval (>0) to check for new/changed peers -# # use `1` to check on every update -# -# ---------------------------------------------------------------------- -# AUTO-DETECTION JOBS -# only one of them will run (they have the same name) - -localhost: - name: 'local' - host: 'localhost' - port: '123' - show_peers: no - -localhost_ipv4: - name: 'local' - host: '127.0.0.1' - port: '123' - show_peers: no - -localhost_ipv6: - name: 'local' - host: '::1' - port: '123' - show_peers: no diff --git a/collectors/python.d.plugin/nvidia_smi/README.md b/collectors/python.d.plugin/nvidia_smi/README.md index ce5473c2..7d45289a 100644 --- a/collectors/python.d.plugin/nvidia_smi/README.md +++ b/collectors/python.d.plugin/nvidia_smi/README.md @@ -4,16 +4,13 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "nvidia_smi-python.d.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Devices" +learn_rel_path: "Integrations/Monitor/Devices" --> -# Nvidia GPU monitoring with Netdata +# Nvidia GPU collector 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://github.com/netdata/netdata/blob/master/packaging/docker/README.md). - - ## Requirements and Notes - You must have the `nvidia-smi` tool installed and your NVIDIA GPU(s) must support the tool. Mostly the newer high end models used for AI / ML and Crypto or Pro range, read more about [nvidia_smi](https://developer.nvidia.com/nvidia-system-management-interface). @@ -67,3 +64,94 @@ exclude_zero_memory_users : yes ``` +### Troubleshooting + +To troubleshoot issues with the `nvidia_smi` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `nvidia_smi` module in debug mode: + +```bash +./python.d.plugin nvidia_smi debug trace +``` + +## Docker + +GPU monitoring in a docker container is possible with [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) installed on the host system, and `gcompat` added to the `NETDATA_EXTRA_APK_PACKAGES` environment variable. + +Sample `docker-compose.yml` +```yaml +version: '3' +services: + netdata: + image: netdata/netdata + container_name: netdata + hostname: example.com # set to fqdn of host + ports: + - 19999:19999 + restart: unless-stopped + cap_add: + - SYS_PTRACE + security_opt: + - apparmor:unconfined + environment: + - NETDATA_EXTRA_APK_PACKAGES=gcompat + volumes: + - netdataconfig:/etc/netdata + - netdatalib:/var/lib/netdata + - netdatacache:/var/cache/netdata + - /etc/passwd:/host/etc/passwd:ro + - /etc/group:/host/etc/group:ro + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /etc/os-release:/host/etc/os-release:ro + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + +volumes: + netdataconfig: + netdatalib: + netdatacache: +``` + +Sample `docker run` +```yaml +docker run -d --name=netdata \ + -p 19999:19999 \ + -e NETDATA_EXTRA_APK_PACKAGES=gcompat \ + -v netdataconfig:/etc/netdata \ + -v netdatalib:/var/lib/netdata \ + -v netdatacache:/var/cache/netdata \ + -v /etc/passwd:/host/etc/passwd:ro \ + -v /etc/group:/host/etc/group:ro \ + -v /proc:/host/proc:ro \ + -v /sys:/host/sys:ro \ + -v /etc/os-release:/host/etc/os-release:ro \ + --restart unless-stopped \ + --cap-add SYS_PTRACE \ + --security-opt apparmor=unconfined \ + --gpus all \ + netdata/netdata +``` + +### Docker Troubleshooting +To troubleshoot `nvidia-smi` in a docker container, first confirm that `nvidia-smi` is working on the host system. If that is working correctly, run `docker exec -it netdata nvidia-smi` to confirm it's working within the docker container. If `nvidia-smi` is fuctioning both inside and outside of the container, confirm that `nvidia-smi: yes` is uncommented in `python.d.conf`. +```bash +docker exec -it netdata bash +cd /etc/netdata +./edit-config python.d.conf +``` diff --git a/collectors/python.d.plugin/nvidia_smi/metrics.csv b/collectors/python.d.plugin/nvidia_smi/metrics.csv new file mode 100644 index 00000000..683ea565 --- /dev/null +++ b/collectors/python.d.plugin/nvidia_smi/metrics.csv @@ -0,0 +1,16 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +nvidia_smi.pci_bandwidth,GPU,"rx, tx",KiB/s,PCI Express Bandwidth Utilization,area,,python.d.plugin,nvidia_smi +nvidia_smi.pci_bandwidth_percent,GPU,"rx_percent, tx_percent",percentage,PCI Express Bandwidth Percent,area,,python.d.plugin,nvidia_smi +nvidia_smi.fan_speed,GPU,speed,percentage,Fan Speed,line,,python.d.plugin,nvidia_smi +nvidia_smi.gpu_utilization,GPU,utilization,percentage,GPU Utilization,line,,python.d.plugin,nvidia_smi +nvidia_smi.mem_utilization,GPU,utilization,percentage,Memory Bandwidth Utilization,line,,python.d.plugin,nvidia_smi +nvidia_smi.encoder_utilization,GPU,"encoder, decoder",percentage,Encoder/Decoder Utilization,line,,python.d.plugin,nvidia_smi +nvidia_smi.memory_allocated,GPU,"free, used",MiB,Memory Usage,stacked,,python.d.plugin,nvidia_smi +nvidia_smi.bar1_memory_usage,GPU,"free, used",MiB,Bar1 Memory Usage,stacked,,python.d.plugin,nvidia_smi +nvidia_smi.temperature,GPU,temp,celsius,Temperature,line,,python.d.plugin,nvidia_smi +nvidia_smi.clocks,GPU,"graphics, video, sm, mem",MHz,Clock Frequencies,line,,python.d.plugin,nvidia_smi +nvidia_smi.power,GPU,power,Watts,Power Utilization,line,,python.d.plugin,nvidia_smi +nvidia_smi.power_state,GPU,a dimension per {power_state},state,Power State,line,,python.d.plugin,nvidia_smi +nvidia_smi.processes_mem,GPU,a dimension per process,MiB,Memory Used by Each Process,stacked,,python.d.plugin,nvidia_smi +nvidia_smi.user_mem,GPU,a dimension per user,MiB,Memory Used by Each User,stacked,,python.d.plugin,nvidia_smi +nvidia_smi.user_num,GPU,users,num,Number of User on GPU,line,,python.d.plugin,nvidia_smi diff --git a/collectors/python.d.plugin/openldap/README.md b/collectors/python.d.plugin/openldap/README.md index 4f29bbb4..eddf40b2 100644 --- a/collectors/python.d.plugin/openldap/README.md +++ b/collectors/python.d.plugin/openldap/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "OpenLDAP" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Networking" +learn_rel_path: "Integrations/Monitor/Networking" --> -# OpenLDAP monitoring with Netdata +# OpenLDAP collector Provides statistics information from openldap (slapd) server. Statistics are taken from LDAP monitoring interface. Manual page, slapd-monitor(5) is available. @@ -77,6 +77,26 @@ openldap: port : 389 ``` ---- + +### Troubleshooting + +To troubleshoot issues with the `openldap` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `openldap` module in debug mode: + +```bash +./python.d.plugin openldap debug trace +``` + diff --git a/collectors/python.d.plugin/openldap/metrics.csv b/collectors/python.d.plugin/openldap/metrics.csv new file mode 100644 index 00000000..0386b889 --- /dev/null +++ b/collectors/python.d.plugin/openldap/metrics.csv @@ -0,0 +1,8 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +openldap.total_connections,,connections,connections/s,Total Connections,line,,python.d.plugin,openldap +openldap.traffic_stats,,sent,KiB/s,Traffic,line,,python.d.plugin,openldap +openldap.operations_status,,"completed, initiated",ops/s,Operations Status,line,,python.d.plugin,openldap +openldap.referrals,,sent,referrals/s,Referrals,line,,python.d.plugin,openldap +openldap.entries,,sent,entries/s,Entries,line,,python.d.plugin,openldap +openldap.ldap_operations,,"bind, search, unbind, add, delete, modify, compare",ops/s,Operations,line,,python.d.plugin,openldap +openldap.waiters,,"write, read",waiters/s,Waiters,line,,python.d.plugin,openldap diff --git a/collectors/python.d.plugin/oracledb/README.md b/collectors/python.d.plugin/oracledb/README.md index 78f807d6..722c77b7 100644 --- a/collectors/python.d.plugin/oracledb/README.md +++ b/collectors/python.d.plugin/oracledb/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "OracleDB" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Databases" +learn_rel_path: "Integrations/Monitor/Databases" --> -# OracleDB monitoring with Netdata +# OracleDB collector Monitors the performance and health metrics of the Oracle database. @@ -98,3 +98,23 @@ remote: All parameters are required. Without them module will fail to start. +### Troubleshooting + +To troubleshoot issues with the `oracledb` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `oracledb` module in debug mode: + +```bash +./python.d.plugin oracledb debug trace +``` + diff --git a/collectors/python.d.plugin/oracledb/metrics.csv b/collectors/python.d.plugin/oracledb/metrics.csv new file mode 100644 index 00000000..126c5c4c --- /dev/null +++ b/collectors/python.d.plugin/oracledb/metrics.csv @@ -0,0 +1,23 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +oracledb.session_count,,"total, active",sessions,Session Count,line,,python.d.plugin,oracledb +oracledb.session_limit_usage,,usage,%,Session Limit Usage,area,,python.d.plugin,oracledb +oracledb.logons,,logons,events/s,Logons,area,,python.d.plugin,oracledb +oracledb.physical_disk_read_writes,,"reads, writes",events/s,Physical Disk Reads/Writes,area,,python.d.plugin,oracledb +oracledb.sorts_on_disks,,sorts,events/s,Sorts On Disk,line,,python.d.plugin,oracledb +oracledb.full_table_scans,,full table scans,events/s,Full Table Scans,line,,python.d.plugin,oracledb +oracledb.database_wait_time_ratio,,wait time ratio,%,Database Wait Time Ratio,line,,python.d.plugin,oracledb +oracledb.shared_pool_free_memory,,free memory,%,Shared Pool Free Memory,line,,python.d.plugin,oracledb +oracledb.in_memory_sorts_ratio,,in-memory sorts,%,In-Memory Sorts Ratio,line,,python.d.plugin,oracledb +oracledb.sql_service_response_time,,time,seconds,SQL Service Response Time,line,,python.d.plugin,oracledb +oracledb.user_rollbacks,,rollbacks,events/s,User Rollbacks,line,,python.d.plugin,oracledb +oracledb.enqueue_timeouts,,enqueue timeouts,events/s,Enqueue Timeouts,line,,python.d.plugin,oracledb +oracledb.cache_hit_ration,,"buffer, cursor, library, row",%,Cache Hit Ratio,stacked,,python.d.plugin,oracledb +oracledb.global_cache_blocks,,"corrupted, lost",events/s,Global Cache Blocks Events,area,,python.d.plugin,oracledb +oracledb.activity,,"parse count, execute count, user commits, user rollbacks",events/s,Activities,stacked,,python.d.plugin,oracledb +oracledb.wait_time,,"application, configuration, administrative, concurrency, commit, network, user I/O, system I/O, scheduler, other",ms,Wait Time,stacked,,python.d.plugin,oracledb +oracledb.tablespace_size,,a dimension per active tablespace,KiB,Size,line,,python.d.plugin,oracledb +oracledb.tablespace_usage,,a dimension per active tablespace,KiB,Usage,line,,python.d.plugin,oracledb +oracledb.tablespace_usage_in_percent,,a dimension per active tablespace,%,Usage,line,,python.d.plugin,oracledb +oracledb.allocated_size,,a dimension per active tablespace,B,Size,line,,python.d.plugin,oracledb +oracledb.allocated_usage,,a dimension per active tablespace,B,Usage,line,,python.d.plugin,oracledb +oracledb.allocated_usage_in_percent,,a dimension per active tablespace,%,Usage,line,,python.d.plugin,oracledb diff --git a/collectors/python.d.plugin/pandas/README.md b/collectors/python.d.plugin/pandas/README.md index 14154947..19b11d5b 100644 --- a/collectors/python.d.plugin/pandas/README.md +++ b/collectors/python.d.plugin/pandas/README.md @@ -1,16 +1,15 @@ - - -# Pandas Netdata Collector +# Ingest structured data (Pandas) Pandas -A python collector using [pandas](https://pandas.pydata.org/) to pull data and do pandas based -preprocessing before feeding to Netdata. +[Pandas](https://pandas.pydata.org/) is a de-facto standard in reading and processing most types of structured data in Python. +If you have metrics appearing in a CSV, JSON, XML, HTML, or [other supported format](https://pandas.pydata.org/docs/user_guide/io.html), +either locally or via some HTTP endpoint, you can easily ingest and present those metrics in Netdata, by leveraging the Pandas collector. + +The collector uses [pandas](https://pandas.pydata.org/) to pull data and do pandas-based +preprocessing, before feeding to Netdata. ## Requirements @@ -20,6 +19,12 @@ This collector depends on some Python (Python 3 only) packages that can usually sudo pip install pandas requests ``` +Note: If you would like to use [`pandas.read_sql`](https://pandas.pydata.org/docs/reference/api/pandas.read_sql.html) to query a database, you will need to install the below packages as well. + +```bash +sudo pip install 'sqlalchemy<2.0' psycopg2-binary +``` + ## Configuration Below is an example configuration to query some json weather data from [Open-Meteo](https://open-meteo.com), @@ -66,12 +71,11 @@ temperature: `chart_configs` is a list of dictionary objects where each one defines the sequence of `df_steps` to be run using [`pandas`](https://pandas.pydata.org/), and the `name`, `title` etc to define the -[CHART variables](https://learn.netdata.cloud/docs/agent/collectors/python.d.plugin#global-variables-order-and-chart) +[CHART variables](https://github.com/netdata/netdata/blob/master/docs/guides/python-collector.md#create-charts) that will control how the results will look in netdata. The example configuration above would result in a `data` dictionary like the below being collected by Netdata -at each time step. They keys in this dictionary will be the -[dimension](https://learn.netdata.cloud/docs/agent/web#dimensions) names on the chart. +at each time step. They keys in this dictionary will be the "dimensions" of the chart. ```javascript {'athens_max': 26.2, 'athens_mean': 19.45952380952381, 'athens_min': 12.2, 'berlin_max': 17.4, 'berlin_mean': 10.764285714285714, 'berlin_min': 5.7, 'dublin_max': 15.3, 'dublin_mean': 12.008928571428571, 'dublin_min': 6.6, 'london_max': 18.9, 'london_mean': 12.510714285714286, 'london_min': 5.2, 'paris_max': 19.4, 'paris_mean': 12.054166666666665, 'paris_min': 4.8} diff --git a/collectors/python.d.plugin/pandas/pandas.chart.py b/collectors/python.d.plugin/pandas/pandas.chart.py index 8eb4452f..7977bcb3 100644 --- a/collectors/python.d.plugin/pandas/pandas.chart.py +++ b/collectors/python.d.plugin/pandas/pandas.chart.py @@ -3,6 +3,7 @@ # Author: Andrew Maguire (andrewm4894) # SPDX-License-Identifier: GPL-3.0-or-later +import os import pandas as pd try: @@ -11,6 +12,12 @@ try: except ImportError: HAS_REQUESTS = False +try: + from sqlalchemy import create_engine + HAS_SQLALCHEMY = True +except ImportError: + HAS_SQLALCHEMY = False + from bases.FrameworkServices.SimpleService import SimpleService ORDER = [] @@ -46,7 +53,10 @@ class Service(SimpleService): """ensure charts and dims all configured and that we can get data""" if not HAS_REQUESTS: - self.warn('requests library could not be imported') + self.warning('requests library could not be imported') + + if not HAS_SQLALCHEMY: + self.warning('sqlalchemy library could not be imported') if not self.chart_configs: self.error('chart_configs must be defined') diff --git a/collectors/python.d.plugin/pandas/pandas.conf b/collectors/python.d.plugin/pandas/pandas.conf index 6684af9d..ca523ed3 100644 --- a/collectors/python.d.plugin/pandas/pandas.conf +++ b/collectors/python.d.plugin/pandas/pandas.conf @@ -188,4 +188,26 @@ update_every: 5 # df_steps: > # pd.read_xml('http://metwdb-openaccess.ichec.ie/metno-wdb2ts/locationforecast?lat=54.7210798611;long=-8.7237392806', xpath='./product/time[1]/location/temperature', parser='etree')| # df.rename(columns={'value': 'dublin'})| -# df[['dublin']]| \ No newline at end of file +# df[['dublin']]| + +# example showing a read_sql from a postgres database using sqlalchemy. +# note: example assumes a running postgress db on localhost with a netdata users and password netdata. +# sql: +# name: "sql" +# update_every: 5 +# chart_configs: +# - name: "sql" +# title: "SQL Example" +# family: "sql.example" +# context: "example" +# type: "line" +# units: "percent" +# df_steps: > +# pd.read_sql_query( +# sql='\ +# select \ +# random()*100 as metric_1, \ +# random()*100 as metric_2 \ +# ', +# con=create_engine('postgresql://localhost/postgres?user=netdata&password=netdata') +# ); diff --git a/collectors/python.d.plugin/postfix/README.md b/collectors/python.d.plugin/postfix/README.md index 8d646ad5..ba556549 100644 --- a/collectors/python.d.plugin/postfix/README.md +++ b/collectors/python.d.plugin/postfix/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Postfix" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Webapps" +learn_rel_path: "Integrations/Monitor/Webapps" --> -# Postfix monitoring with Netdata +# Postfix collector Monitors MTA email queue statistics using [postqueue](http://www.postfix.org/postqueue.1.html) tool. @@ -37,3 +37,23 @@ It produces only two charts: ## Configuration Configuration is not needed. +### Troubleshooting + +To troubleshoot issues with the `postfix` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `postfix` module in debug mode: + +```bash +./python.d.plugin postfix debug trace +``` + diff --git a/collectors/python.d.plugin/postfix/metrics.csv b/collectors/python.d.plugin/postfix/metrics.csv new file mode 100644 index 00000000..696f6ad3 --- /dev/null +++ b/collectors/python.d.plugin/postfix/metrics.csv @@ -0,0 +1,3 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +postfix.qemails,,emails,emails,Postfix Queue Emails,line,,python.d.plugin,postfix +postfix.qsize,,size,KiB,Postfix Queue Emails Size,area,,python.d.plugin,postfix diff --git a/collectors/python.d.plugin/proxysql/Makefile.inc b/collectors/python.d.plugin/proxysql/Makefile.inc deleted file mode 100644 index 66be372c..00000000 --- a/collectors/python.d.plugin/proxysql/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 += proxysql/proxysql.chart.py -dist_pythonconfig_DATA += proxysql/proxysql.conf - -# do not install these files, but include them in the distribution -dist_noinst_DATA += proxysql/README.md proxysql/Makefile.inc - diff --git a/collectors/python.d.plugin/proxysql/README.md b/collectors/python.d.plugin/proxysql/README.md deleted file mode 100644 index d6c626b5..00000000 --- a/collectors/python.d.plugin/proxysql/README.md +++ /dev/null @@ -1,14 +0,0 @@ - - -# ProxySQL monitoring with Netdata - -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 deleted file mode 100644 index 7e06b7bd..00000000 --- a/collectors/python.d.plugin/proxysql/proxysql.chart.py +++ /dev/null @@ -1,354 +0,0 @@ -# -*- coding: utf-8 -*- -# Description: Proxysql netdata python.d module -# Author: Ali Borhani (alibo) -# SPDX-License-Identifier: GPL-3.0+ - -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)) - - -# https://github.com/sysown/proxysql/blob/master/doc/admin_tables.md#stats_mysql_global -QUERY_GLOBAL = query( - "stats_mysql_global", - "Variable_Name", - "Variable_Value" -) - -# https://github.com/sysown/proxysql/blob/master/doc/admin_tables.md#stats_mysql_connection_pool -QUERY_CONNECTION_POOL = query( - "stats_mysql_connection_pool", - "hostgroup", - "srv_host", - "srv_port", - "status", - "ConnUsed", - "ConnFree", - "ConnOK", - "ConnERR", - "Queries", - "Bytes_data_sent", - "Bytes_data_recv", - "Latency_us" -) - -# https://github.com/sysown/proxysql/blob/master/doc/admin_tables.md#stats_mysql_commands_counters -QUERY_COMMANDS = query( - "stats_mysql_commands_counters", - "Command", - "Total_Time_us", - "Total_cnt", - "cnt_100us", - "cnt_500us", - "cnt_1ms", - "cnt_5ms", - "cnt_10ms", - "cnt_50ms", - "cnt_100ms", - "cnt_500ms", - "cnt_1s", - "cnt_5s", - "cnt_10s", - "cnt_INFs" -) - -GLOBAL_STATS = [ - 'client_connections_aborted', - 'client_connections_connected', - 'client_connections_created', - 'client_connections_non_idle', - 'proxysql_uptime', - 'questions', - 'slow_queries' -] - -CONNECTION_POOL_STATS = [ - 'status', - 'connused', - 'connfree', - 'connok', - 'connerr', - 'queries', - 'bytes_data_sent', - 'bytes_data_recv', - 'latency_us' -] - -ORDER = [ - 'connections', - 'active_transactions', - 'questions', - 'pool_overall_net', - 'commands_count', - 'commands_duration', - 'pool_status', - 'pool_net', - 'pool_queries', - 'pool_latency', - 'pool_connection_used', - 'pool_connection_free', - 'pool_connection_ok', - 'pool_connection_error' -] - -HISTOGRAM_ORDER = [ - '100us', - '500us', - '1ms', - '5ms', - '10ms', - '50ms', - '100ms', - '500ms', - '1s', - '5s', - '10s', - 'inf' -] - -STATUS = { - "ONLINE": 1, - "SHUNNED": 2, - "OFFLINE_SOFT": 3, - "OFFLINE_HARD": 4 -} - -CHARTS = { - 'pool_status': { - 'options': [None, 'ProxySQL Backend Status', 'status', 'status', 'proxysql.pool_status', 'line'], - 'lines': [] - }, - 'pool_net': { - 'options': [None, 'ProxySQL Backend Bandwidth', 'kilobits/s', 'bandwidth', 'proxysql.pool_net', 'area'], - 'lines': [] - }, - 'pool_overall_net': { - 'options': [None, 'ProxySQL Backend Overall Bandwidth', 'kilobits/s', 'overall_bandwidth', - 'proxysql.pool_overall_net', 'area'], - 'lines': [ - ['bytes_data_recv', 'in', 'incremental', 8, 1000], - ['bytes_data_sent', 'out', 'incremental', -8, 1000] - ] - }, - 'questions': { - 'options': [None, 'ProxySQL Frontend Questions', 'questions/s', 'questions', 'proxysql.questions', 'line'], - 'lines': [ - ['questions', 'questions', 'incremental'], - ['slow_queries', 'slow_queries', 'incremental'] - ] - }, - 'pool_queries': { - 'options': [None, 'ProxySQL Backend Queries', 'queries/s', 'queries', 'proxysql.queries', 'line'], - 'lines': [] - }, - 'active_transactions': { - 'options': [None, 'ProxySQL Frontend Active Transactions', 'transactions/s', 'active_transactions', - 'proxysql.active_transactions', 'line'], - 'lines': [ - ['active_transactions', 'active_transactions', 'absolute'] - ] - }, - 'pool_latency': { - 'options': [None, 'ProxySQL Backend Latency', 'milliseconds', 'latency', 'proxysql.latency', 'line'], - 'lines': [] - }, - 'connections': { - 'options': [None, 'ProxySQL Frontend Connections', 'connections/s', 'connections', 'proxysql.connections', - 'line'], - 'lines': [ - ['client_connections_connected', 'connected', 'absolute'], - ['client_connections_created', 'created', 'incremental'], - ['client_connections_aborted', 'aborted', 'incremental'], - ['client_connections_non_idle', 'non_idle', 'absolute'] - ] - }, - 'pool_connection_used': { - 'options': [None, 'ProxySQL Used Connections', 'connections', 'pool_connections', - 'proxysql.pool_used_connections', 'line'], - 'lines': [] - }, - 'pool_connection_free': { - 'options': [None, 'ProxySQL Free Connections', 'connections', 'pool_connections', - 'proxysql.pool_free_connections', 'line'], - 'lines': [] - }, - 'pool_connection_ok': { - 'options': [None, 'ProxySQL Established Connections', 'connections', 'pool_connections', - 'proxysql.pool_ok_connections', 'line'], - 'lines': [] - }, - 'pool_connection_error': { - 'options': [None, 'ProxySQL Error Connections', 'connections', 'pool_connections', - 'proxysql.pool_error_connections', 'line'], - 'lines': [] - }, - 'commands_count': { - 'options': [None, 'ProxySQL Commands', 'commands', 'commands', 'proxysql.commands_count', 'line'], - 'lines': [] - }, - 'commands_duration': { - 'options': [None, 'ProxySQL Commands Duration', 'milliseconds', 'commands', 'proxysql.commands_duration', - 'line'], - 'lines': [] - } -} - - -class Service(MySQLService): - def __init__(self, configuration=None, name=None): - MySQLService.__init__(self, configuration=configuration, name=name) - self.order = ORDER - self.definitions = CHARTS - self.queries = dict( - global_status=QUERY_GLOBAL, - connection_pool_status=QUERY_CONNECTION_POOL, - commands_status=QUERY_COMMANDS - ) - - def _get_data(self): - raw_data = self._get_raw_data(description=True) - - if not raw_data: - return None - - to_netdata = dict() - - if 'global_status' in raw_data: - global_status = dict(raw_data['global_status'][0]) - for key in global_status: - if key.lower() in GLOBAL_STATS: - to_netdata[key.lower()] = global_status[key] - - if 'connection_pool_status' in raw_data: - - to_netdata['bytes_data_recv'] = 0 - to_netdata['bytes_data_sent'] = 0 - - for record in raw_data['connection_pool_status'][0]: - backend = self.generate_backend(record) - name = self.generate_backend_name(backend) - - for key in backend: - if key in CONNECTION_POOL_STATS: - if key == 'status': - backend[key] = self.convert_status(backend[key]) - - if len(self.charts) > 0: - if (name + '_status') not in self.charts['pool_status']: - self.add_backend_dimensions(name) - - to_netdata["{0}_{1}".format(name, key)] = backend[key] - - if key == 'bytes_data_recv': - to_netdata['bytes_data_recv'] += int(backend[key]) - - if key == 'bytes_data_sent': - to_netdata['bytes_data_sent'] += int(backend[key]) - - if 'commands_status' in raw_data: - for record in raw_data['commands_status'][0]: - cmd = self.generate_command_stats(record) - name = cmd['name'] - - if len(self.charts) > 0: - if (name + '_count') not in self.charts['commands_count']: - self.add_command_dimensions(name) - self.add_histogram_chart(cmd) - - to_netdata[name + '_count'] = cmd['count'] - to_netdata[name + '_duration'] = cmd['duration'] - for histogram in cmd['histogram']: - dimId = 'commands_histogram_{0}_{1}'.format(name, histogram) - to_netdata[dimId] = cmd['histogram'][histogram] - - return to_netdata or None - - def add_backend_dimensions(self, name): - self.charts['pool_status'].add_dimension([name + '_status', name, 'absolute']) - self.charts['pool_net'].add_dimension([name + '_bytes_data_recv', 'from_' + name, 'incremental', 8, 1024]) - self.charts['pool_net'].add_dimension([name + '_bytes_data_sent', 'to_' + name, 'incremental', -8, 1024]) - self.charts['pool_queries'].add_dimension([name + '_queries', name, 'incremental']) - self.charts['pool_latency'].add_dimension([name + '_latency_us', name, 'absolute', 1, 1000]) - self.charts['pool_connection_used'].add_dimension([name + '_connused', name, 'absolute']) - self.charts['pool_connection_free'].add_dimension([name + '_connfree', name, 'absolute']) - self.charts['pool_connection_ok'].add_dimension([name + '_connok', name, 'incremental']) - self.charts['pool_connection_error'].add_dimension([name + '_connerr', name, 'incremental']) - - def add_command_dimensions(self, cmd): - self.charts['commands_count'].add_dimension([cmd + '_count', cmd, 'incremental']) - self.charts['commands_duration'].add_dimension([cmd + '_duration', cmd, 'incremental', 1, 1000]) - - def add_histogram_chart(self, cmd): - chart = self.charts.add_chart(self.histogram_chart(cmd)) - - for histogram in HISTOGRAM_ORDER: - dimId = 'commands_histogram_{0}_{1}'.format(cmd['name'], histogram) - chart.add_dimension([dimId, histogram, 'incremental']) - - @staticmethod - def histogram_chart(cmd): - return [ - 'commands_histogram_' + cmd['name'], - None, - 'ProxySQL {0} Command Histogram'.format(cmd['name'].title()), - 'commands', - 'commands_histogram', - 'proxysql.commands_histogram_' + cmd['name'], - 'stacked' - ] - - @staticmethod - def generate_backend(data): - return { - 'hostgroup': data[0], - 'srv_host': data[1], - 'srv_port': data[2], - 'status': data[3], - 'connused': data[4], - 'connfree': data[5], - 'connok': data[6], - 'connerr': data[7], - 'queries': data[8], - 'bytes_data_sent': data[9], - 'bytes_data_recv': data[10], - 'latency_us': data[11] - } - - @staticmethod - def generate_command_stats(data): - return { - 'name': data[0].lower(), - 'duration': data[1], - 'count': data[2], - 'histogram': { - '100us': data[3], - '500us': data[4], - '1ms': data[5], - '5ms': data[6], - '10ms': data[7], - '50ms': data[8], - '100ms': data[9], - '500ms': data[10], - '1s': data[11], - '5s': data[12], - '10s': data[13], - 'inf': data[14] - } - } - - @staticmethod - def generate_backend_name(backend): - hostgroup = backend['hostgroup'].replace(' ', '_').lower() - host = backend['srv_host'].replace('.', '_') - - return "{0}_{1}_{2}".format(hostgroup, host, backend['srv_port']) - - @staticmethod - def convert_status(status): - if status in STATUS: - return STATUS[status] - return -1 diff --git a/collectors/python.d.plugin/proxysql/proxysql.conf b/collectors/python.d.plugin/proxysql/proxysql.conf deleted file mode 100644 index 3c503a89..00000000 --- a/collectors/python.d.plugin/proxysql/proxysql.conf +++ /dev/null @@ -1,116 +0,0 @@ -# netdata python.d.plugin configuration for ProxySQL -# -# 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, proxysql also supports the following: -# -# host: 'IP or HOSTNAME' # the host to connect to -# port: PORT # the port to connect to -# -# in all cases, the following can also be set: -# -# user: 'username' # the proxysql username to use -# pass: 'password' # the proxysql password to use -# - -# AUTO-DETECTION JOBS -# only one of them will run (they have the same name) - -tcp: - name : 'local' - user : 'stats' - pass : 'stats' - host : 'localhost' - port : '6032' - -tcpipv4: - name : 'local' - user : 'stats' - pass : 'stats' - host : '127.0.0.1' - port : '6032' - -tcpipv6: - name : 'local' - user : 'stats' - pass : 'stats' - host : '::1' - port : '6032' - -tcp_admin: - name : 'local' - user : 'admin' - pass : 'admin' - host : 'localhost' - port : '6032' - -tcpipv4_admin: - name : 'local' - user : 'admin' - pass : 'admin' - host : '127.0.0.1' - port : '6032' - -tcpipv6_admin: - name : 'local' - user : 'admin' - pass : 'admin' - host : '::1' - port : '6032' diff --git a/collectors/python.d.plugin/puppet/README.md b/collectors/python.d.plugin/puppet/README.md index 8b98b8a2..3b0c55b9 100644 --- a/collectors/python.d.plugin/puppet/README.md +++ b/collectors/python.d.plugin/puppet/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Puppet" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Provisioning tools" +learn_rel_path: "Integrations/Monitor/Provisioning tools" --> -# Puppet monitoring with Netdata +# Puppet collector Monitor status of Puppet Server and Puppet DB. @@ -65,6 +65,26 @@ When no configuration is given, module uses `https://fqdn.example.com:8140`. - Secure PuppetDB config may require client certificate. Not applies to default PuppetDB configuration though. ---- + +### Troubleshooting + +To troubleshoot issues with the `puppet` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `puppet` module in debug mode: + +```bash +./python.d.plugin puppet debug trace +``` + diff --git a/collectors/python.d.plugin/puppet/metrics.csv b/collectors/python.d.plugin/puppet/metrics.csv new file mode 100644 index 00000000..1ec99e10 --- /dev/null +++ b/collectors/python.d.plugin/puppet/metrics.csv @@ -0,0 +1,5 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +puppet.jvm,,"committed, used",MiB,JVM Heap,area,,python.d.plugin,puppet +puppet.jvm,,"committed, used",MiB,JVM Non-Heap,area,,python.d.plugin,puppet +puppet.cpu,,"execution, GC",percentage,CPU usage,stacked,,python.d.plugin,puppet +puppet.fdopen,,used,descriptors,File Descriptors,line,,python.d.plugin,puppet diff --git a/collectors/python.d.plugin/python.d.conf b/collectors/python.d.plugin/python.d.conf index 41385dac..3953ce2b 100644 --- a/collectors/python.d.plugin/python.d.conf +++ b/collectors/python.d.plugin/python.d.conf @@ -56,14 +56,11 @@ hpssa: no # monit: yes # nvidia_smi: yes # nsd: yes -# ntpd: yes # openldap: yes # oracledb: yes # pandas: yes # postfix: yes -# proxysql: yes # puppet: yes -# rabbitmq: yes # rethinkdbs: yes # retroshare: yes # riakkv: yes diff --git a/collectors/python.d.plugin/rabbitmq/Makefile.inc b/collectors/python.d.plugin/rabbitmq/Makefile.inc deleted file mode 100644 index 7e67ef51..00000000 --- a/collectors/python.d.plugin/rabbitmq/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 += rabbitmq/rabbitmq.chart.py -dist_pythonconfig_DATA += rabbitmq/rabbitmq.conf - -# do not install these files, but include them in the distribution -dist_noinst_DATA += rabbitmq/README.md rabbitmq/Makefile.inc - diff --git a/collectors/python.d.plugin/rabbitmq/README.md b/collectors/python.d.plugin/rabbitmq/README.md deleted file mode 100644 index 19df6569..00000000 --- a/collectors/python.d.plugin/rabbitmq/README.md +++ /dev/null @@ -1,141 +0,0 @@ - - -# RabbitMQ monitoring with Netdata - -Collects message broker global and per virtual host metrics. - - -Following charts are drawn: - -1. **Queued Messages** - - - ready - - unacknowledged - -2. **Message Rates** - - - ack - - redelivered - - deliver - - publish - -3. **Global Counts** - - - channels - - consumers - - connections - - queues - - exchanges - -4. **File Descriptors** - - - used descriptors - -5. **Socket Descriptors** - - - used descriptors - -6. **Erlang processes** - - - used processes - -7. **Erlang run queue** - - - Erlang run queue - -8. **Memory** - - - free memory in megabytes - -9. **Disk Space** - - - free disk space in gigabytes - - -Per Vhost charts: - -1. **Vhost Messages** - - - ack - - confirm - - deliver - - get - - get_no_ack - - publish - - redeliver - - return_unroutable - -2. Per Queue charts: - - 1. **Queued Messages** - - - messages - - paged_out - - persistent - - ready - - unacknowledged - - 2. **Queue Messages stats** - - - ack - - confirm - - deliver - - get - - get_no_ack - - publish - - redeliver - - return_unroutable - -## Configuration - -Edit the `python.d/rabbitmq.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 -sudo ./edit-config python.d/rabbitmq.conf -``` - -When no configuration file is found, module tries to connect to: `localhost:15672`. - -```yaml -socket: - name : 'local' - host : '127.0.0.1' - port : 15672 - user : 'guest' - pass : 'guest' -``` - ---- - -### Per-Queue Chart configuration - -RabbitMQ users with the "monitoring" tag cannot see all queue data. You'll need a user with read permissions. -To create a dedicated user for netdata: - -```bash -rabbitmqctl add_user netdata ChangeThisSuperSecretPassword -rabbitmqctl set_permissions netdata "^$" "^$" ".*" -``` - -See [set_permissions](https://www.rabbitmq.com/rabbitmqctl.8.html#set_permissions) for details. - -Once the user is set up, add `collect_queues_metrics: yes` to your `rabbitmq.conf`: - -```yaml -local: - name : 'local' - host : '127.0.0.1' - port : 15672 - user : 'netdata' - pass : 'ChangeThisSuperSecretPassword' - collect_queues_metrics : 'yes' -``` diff --git a/collectors/python.d.plugin/rabbitmq/rabbitmq.chart.py b/collectors/python.d.plugin/rabbitmq/rabbitmq.chart.py deleted file mode 100644 index 866b777f..00000000 --- a/collectors/python.d.plugin/rabbitmq/rabbitmq.chart.py +++ /dev/null @@ -1,443 +0,0 @@ -# -*- coding: utf-8 -*- -# Description: rabbitmq netdata python.d module -# Author: ilyam8 -# SPDX-License-Identifier: GPL-3.0-or-later - -from json import loads - -from bases.FrameworkServices.UrlService import UrlService - -API_NODE = 'api/nodes' -API_OVERVIEW = 'api/overview' -API_QUEUES = 'api/queues' -API_VHOSTS = 'api/vhosts' - -NODE_STATS = [ - 'fd_used', - 'mem_used', - 'sockets_used', - 'proc_used', - 'disk_free', - 'run_queue' -] - -OVERVIEW_STATS = [ - 'object_totals.channels', - 'object_totals.consumers', - 'object_totals.connections', - 'object_totals.queues', - 'object_totals.exchanges', - 'queue_totals.messages_ready', - 'queue_totals.messages_unacknowledged', - 'message_stats.ack', - 'message_stats.redeliver', - 'message_stats.deliver', - 'message_stats.publish', - 'churn_rates.connection_created_details.rate', - 'churn_rates.connection_closed_details.rate', - 'churn_rates.channel_created_details.rate', - 'churn_rates.channel_closed_details.rate', - 'churn_rates.queue_created_details.rate', - 'churn_rates.queue_declared_details.rate', - 'churn_rates.queue_deleted_details.rate' -] - -QUEUE_STATS = [ - 'messages', - 'messages_paged_out', - 'messages_persistent', - 'messages_ready', - 'messages_unacknowledged', - 'message_stats.ack', - 'message_stats.confirm', - 'message_stats.deliver', - 'message_stats.get', - 'message_stats.get_no_ack', - 'message_stats.publish', - 'message_stats.redeliver', - 'message_stats.return_unroutable', -] - -VHOST_MESSAGE_STATS = [ - 'message_stats.ack', - 'message_stats.confirm', - 'message_stats.deliver', - 'message_stats.get', - 'message_stats.get_no_ack', - 'message_stats.publish', - 'message_stats.redeliver', - 'message_stats.return_unroutable', -] - -ORDER = [ - 'queued_messages', - 'connection_churn_rates', - 'channel_churn_rates', - 'queue_churn_rates', - 'message_rates', - 'global_counts', - 'file_descriptors', - 'socket_descriptors', - 'erlang_processes', - 'erlang_run_queue', - 'memory', - 'disk_space' -] - -CHARTS = { - 'file_descriptors': { - 'options': [None, 'File Descriptors', 'descriptors', 'overview', 'rabbitmq.file_descriptors', 'line'], - 'lines': [ - ['fd_used', 'used', 'absolute'] - ] - }, - 'memory': { - 'options': [None, 'Memory', 'MiB', 'overview', 'rabbitmq.memory', 'area'], - 'lines': [ - ['mem_used', 'used', 'absolute', 1, 1 << 20] - ] - }, - 'disk_space': { - 'options': [None, 'Disk Space', 'GiB', 'overview', 'rabbitmq.disk_space', 'area'], - 'lines': [ - ['disk_free', 'free', 'absolute', 1, 1 << 30] - ] - }, - 'socket_descriptors': { - 'options': [None, 'Socket Descriptors', 'descriptors', 'overview', 'rabbitmq.sockets', 'line'], - 'lines': [ - ['sockets_used', 'used', 'absolute'] - ] - }, - 'erlang_processes': { - 'options': [None, 'Erlang Processes', 'processes', 'overview', 'rabbitmq.processes', 'line'], - 'lines': [ - ['proc_used', 'used', 'absolute'] - ] - }, - 'erlang_run_queue': { - 'options': [None, 'Erlang Run Queue', 'processes', 'overview', 'rabbitmq.erlang_run_queue', 'line'], - 'lines': [ - ['run_queue', 'length', 'absolute'] - ] - }, - 'global_counts': { - 'options': [None, 'Global Counts', 'counts', 'overview', 'rabbitmq.global_counts', 'line'], - 'lines': [ - ['object_totals_channels', 'channels', 'absolute'], - ['object_totals_consumers', 'consumers', 'absolute'], - ['object_totals_connections', 'connections', 'absolute'], - ['object_totals_queues', 'queues', 'absolute'], - ['object_totals_exchanges', 'exchanges', 'absolute'] - ] - }, - 'connection_churn_rates': { - 'options': [None, 'Connection Churn Rates', 'operations/s', 'overview', 'rabbitmq.connection_churn_rates', 'line'], - 'lines': [ - ['churn_rates_connection_created_details_rate', 'created', 'absolute'], - ['churn_rates_connection_closed_details_rate', 'closed', 'absolute'] - ] - }, - 'channel_churn_rates': { - 'options': [None, 'Channel Churn Rates', 'operations/s', 'overview', 'rabbitmq.channel_churn_rates', 'line'], - 'lines': [ - ['churn_rates_channel_created_details_rate', 'created', 'absolute'], - ['churn_rates_channel_closed_details_rate', 'closed', 'absolute'] - ] - }, - 'queue_churn_rates': { - 'options': [None, 'Queue Churn Rates', 'operations/s', 'overview', 'rabbitmq.queue_churn_rates', 'line'], - 'lines': [ - ['churn_rates_queue_created_details_rate', 'created', 'absolute'], - ['churn_rates_queue_declared_details_rate', 'declared', 'absolute'], - ['churn_rates_queue_deleted_details_rate', 'deleted', 'absolute'] - ] - }, - 'queued_messages': { - 'options': [None, 'Queued Messages', 'messages', 'overview', 'rabbitmq.queued_messages', 'stacked'], - 'lines': [ - ['queue_totals_messages_ready', 'ready', 'absolute'], - ['queue_totals_messages_unacknowledged', 'unacknowledged', 'absolute'] - ] - }, - 'message_rates': { - 'options': [None, 'Message Rates', 'messages/s', 'overview', 'rabbitmq.message_rates', 'line'], - 'lines': [ - ['message_stats_ack', 'ack', 'incremental'], - ['message_stats_redeliver', 'redeliver', 'incremental'], - ['message_stats_deliver', 'deliver', 'incremental'], - ['message_stats_publish', 'publish', 'incremental'] - ] - } -} - - -def vhost_chart_template(name): - order = [ - 'vhost_{0}_message_stats'.format(name), - ] - family = 'vhost {0}'.format(name) - - charts = { - order[0]: { - 'options': [ - None, 'Vhost "{0}" Messages'.format(name), 'messages/s', family, 'rabbitmq.vhost_messages', 'stacked'], - 'lines': [ - ['vhost_{0}_message_stats_ack'.format(name), 'ack', 'incremental'], - ['vhost_{0}_message_stats_confirm'.format(name), 'confirm', 'incremental'], - ['vhost_{0}_message_stats_deliver'.format(name), 'deliver', 'incremental'], - ['vhost_{0}_message_stats_get'.format(name), 'get', 'incremental'], - ['vhost_{0}_message_stats_get_no_ack'.format(name), 'get_no_ack', 'incremental'], - ['vhost_{0}_message_stats_publish'.format(name), 'publish', 'incremental'], - ['vhost_{0}_message_stats_redeliver'.format(name), 'redeliver', 'incremental'], - ['vhost_{0}_message_stats_return_unroutable'.format(name), 'return_unroutable', 'incremental'], - ] - }, - } - - return order, charts - -def queue_chart_template(queue_id): - vhost, name = queue_id - order = [ - 'vhost_{0}_queue_{1}_queued_message'.format(vhost, name), - 'vhost_{0}_queue_{1}_messages_stats'.format(vhost, name), - ] - family = 'vhost {0}'.format(vhost) - - charts = { - order[0]: { - 'options': [ - None, 'Queue "{0}" in "{1}" queued messages'.format(name, vhost), 'messages', family, 'rabbitmq.queue_messages', 'line'], - 'lines': [ - ['vhost_{0}_queue_{1}_messages'.format(vhost, name), 'messages', 'absolute'], - ['vhost_{0}_queue_{1}_messages_paged_out'.format(vhost, name), 'paged_out', 'absolute'], - ['vhost_{0}_queue_{1}_messages_persistent'.format(vhost, name), 'persistent', 'absolute'], - ['vhost_{0}_queue_{1}_messages_ready'.format(vhost, name), 'ready', 'absolute'], - ['vhost_{0}_queue_{1}_messages_unacknowledged'.format(vhost, name), 'unack', 'absolute'], - ] - }, - order[1]: { - 'options': [ - None, 'Queue "{0}" in "{1}" messages stats'.format(name, vhost), 'messages/s', family, 'rabbitmq.queue_messages_stats', 'line'], - 'lines': [ - ['vhost_{0}_queue_{1}_message_stats_ack'.format(vhost, name), 'ack', 'incremental'], - ['vhost_{0}_queue_{1}_message_stats_confirm'.format(vhost, name), 'confirm', 'incremental'], - ['vhost_{0}_queue_{1}_message_stats_deliver'.format(vhost, name), 'deliver', 'incremental'], - ['vhost_{0}_queue_{1}_message_stats_get'.format(vhost, name), 'get', 'incremental'], - ['vhost_{0}_queue_{1}_message_stats_get_no_ack'.format(vhost, name), 'get_no_ack', 'incremental'], - ['vhost_{0}_queue_{1}_message_stats_publish'.format(vhost, name), 'publish', 'incremental'], - ['vhost_{0}_queue_{1}_message_stats_redeliver'.format(vhost, name), 'redeliver', 'incremental'], - ['vhost_{0}_queue_{1}_message_stats_return_unroutable'.format(vhost, name), 'return_unroutable', 'incremental'], - ] - }, - } - - return order, charts - - -class VhostStatsBuilder: - def __init__(self): - self.stats = None - - def set(self, raw_stats): - self.stats = raw_stats - - def name(self): - return self.stats['name'] - - def has_msg_stats(self): - return bool(self.stats.get('message_stats')) - - def msg_stats(self): - name = self.name() - stats = fetch_data(raw_data=self.stats, metrics=VHOST_MESSAGE_STATS) - return dict(('vhost_{0}_{1}'.format(name, k), v) for k, v in stats.items()) - -class QueueStatsBuilder: - def __init__(self): - self.stats = None - - def set(self, raw_stats): - self.stats = raw_stats - - def id(self): - return self.stats['vhost'], self.stats['name'] - - def queue_stats(self): - vhost, name = self.id() - stats = fetch_data(raw_data=self.stats, metrics=QUEUE_STATS) - return dict(('vhost_{0}_queue_{1}_{2}'.format(vhost, name, k), v) for k, v in stats.items()) - - -class Service(UrlService): - def __init__(self, configuration=None, name=None): - UrlService.__init__(self, configuration=configuration, name=name) - self.order = ORDER - self.definitions = CHARTS - self.url = '{0}://{1}:{2}'.format( - configuration.get('scheme', 'http'), - configuration.get('host', '127.0.0.1'), - configuration.get('port', 15672), - ) - self.node_name = str() - self.vhost = VhostStatsBuilder() - self.collected_vhosts = set() - self.collect_queues_metrics = configuration.get('collect_queues_metrics', False) - self.debug("collect_queues_metrics is {0}".format("enabled" if self.collect_queues_metrics else "disabled")) - if self.collect_queues_metrics: - self.queue = QueueStatsBuilder() - self.collected_queues = set() - - def _get_data(self): - data = dict() - - stats = self.get_overview_stats() - if not stats: - return None - - data.update(stats) - - stats = self.get_nodes_stats() - if not stats: - return None - - data.update(stats) - - stats = self.get_vhosts_stats() - if stats: - data.update(stats) - - if self.collect_queues_metrics: - stats = self.get_queues_stats() - if stats: - data.update(stats) - - return data or None - - def get_overview_stats(self): - url = '{0}/{1}'.format(self.url, API_OVERVIEW) - self.debug("doing http request to '{0}'".format(url)) - raw = self._get_raw_data(url) - if not raw: - return None - - data = loads(raw) - self.node_name = data['node'] - self.debug("found node name: '{0}'".format(self.node_name)) - - stats = fetch_data(raw_data=data, metrics=OVERVIEW_STATS) - self.debug("number of metrics: {0}".format(len(stats))) - return stats - - def get_nodes_stats(self): - if self.node_name == "": - self.error("trying to get node stats, but node name is not set") - return None - - url = '{0}/{1}/{2}'.format(self.url, API_NODE, self.node_name) - self.debug("doing http request to '{0}'".format(url)) - raw = self._get_raw_data(url) - if not raw: - return None - - data = loads(raw) - stats = fetch_data(raw_data=data, metrics=NODE_STATS) - handle_disabled_disk_monitoring(stats) - self.debug("number of metrics: {0}".format(len(stats))) - return stats - - def get_vhosts_stats(self): - url = '{0}/{1}'.format(self.url, API_VHOSTS) - self.debug("doing http request to '{0}'".format(url)) - raw = self._get_raw_data(url) - if not raw: - return None - - data = dict() - vhosts = loads(raw) - charts_initialized = len(self.charts) > 0 - - for vhost in vhosts: - self.vhost.set(vhost) - if not self.vhost.has_msg_stats(): - continue - - if charts_initialized and self.vhost.name() not in self.collected_vhosts: - self.collected_vhosts.add(self.vhost.name()) - self.add_vhost_charts(self.vhost.name()) - - data.update(self.vhost.msg_stats()) - - self.debug("number of vhosts: {0}, metrics: {1}".format(len(vhosts), len(data))) - return data - - def get_queues_stats(self): - url = '{0}/{1}'.format(self.url, API_QUEUES) - self.debug("doing http request to '{0}'".format(url)) - raw = self._get_raw_data(url) - if not raw: - return None - - data = dict() - queues = loads(raw) - charts_initialized = len(self.charts) > 0 - - for queue in queues: - self.queue.set(queue) - if self.queue.id()[0] not in self.collected_vhosts: - continue - - if charts_initialized and self.queue.id() not in self.collected_queues: - self.collected_queues.add(self.queue.id()) - self.add_queue_charts(self.queue.id()) - - data.update(self.queue.queue_stats()) - - self.debug("number of queues: {0}, metrics: {1}".format(len(queues), len(data))) - return data - - def add_vhost_charts(self, vhost_name): - order, charts = vhost_chart_template(vhost_name) - - for chart_name in order: - params = [chart_name] + charts[chart_name]['options'] - dimensions = charts[chart_name]['lines'] - - new_chart = self.charts.add_chart(params) - for dimension in dimensions: - new_chart.add_dimension(dimension) - - def add_queue_charts(self, queue_id): - order, charts = queue_chart_template(queue_id) - - for chart_name in order: - params = [chart_name] + charts[chart_name]['options'] - dimensions = charts[chart_name]['lines'] - - new_chart = self.charts.add_chart(params) - for dimension in dimensions: - new_chart.add_dimension(dimension) - - -def fetch_data(raw_data, metrics): - data = dict() - for metric in metrics: - value = raw_data - metrics_list = metric.split('.') - try: - for m in metrics_list: - value = value[m] - except (KeyError, TypeError): - continue - data['_'.join(metrics_list)] = value - - return data - - -def handle_disabled_disk_monitoring(node_stats): - # https://github.com/netdata/netdata/issues/7218 - # can be "disk_free": "disk_free_monitoring_disabled" - v = node_stats.get('disk_free') - if v and not isinstance(v, int): - del node_stats['disk_free'] diff --git a/collectors/python.d.plugin/rabbitmq/rabbitmq.conf b/collectors/python.d.plugin/rabbitmq/rabbitmq.conf deleted file mode 100644 index 47d47a1b..00000000 --- a/collectors/python.d.plugin/rabbitmq/rabbitmq.conf +++ /dev/null @@ -1,86 +0,0 @@ -# netdata python.d.plugin configuration for rabbitmq -# -# 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, rabbitmq plugin also supports the following: -# -# host: 'ipaddress' # Server ip address or hostname. Default: 127.0.0.1 -# port: 'port' # Rabbitmq port. Default: 15672 -# scheme: 'scheme' # http or https. Default: http -# -# if the URL is password protected, the following are supported: -# -# user: 'username' -# pass: 'password' -# -# Rabbitmq plugin can also collect stats per vhost per queues, which is disabled -# by default. Please note that enabling this can induced a serious overhead on -# both netdata and rabbitmq if a look of queues are configured and used. -# -# collect_queues_metrics: 'yes/no' -# -# ---------------------------------------------------------------------- -# AUTO-DETECTION JOBS -# only one of them will run (they have the same name) -# -local: - host: '127.0.0.1' - user: 'guest' - pass: 'guest' diff --git a/collectors/python.d.plugin/rethinkdbs/README.md b/collectors/python.d.plugin/rethinkdbs/README.md index 578c1c0b..527ce4c3 100644 --- a/collectors/python.d.plugin/rethinkdbs/README.md +++ b/collectors/python.d.plugin/rethinkdbs/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "RethinkDB" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Databases" +learn_rel_path: "Integrations/Monitor/Databases" --> -# RethinkDB monitoring with Netdata +# RethinkDB collector Collects database server and cluster statistics. @@ -52,6 +52,26 @@ localhost: When no configuration file is found, module tries to connect to `127.0.0.1:28015`. ---- + +### Troubleshooting + +To troubleshoot issues with the `rethinkdbs` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `rethinkdbs` module in debug mode: + +```bash +./python.d.plugin rethinkdbs debug trace +``` + diff --git a/collectors/python.d.plugin/rethinkdbs/metrics.csv b/collectors/python.d.plugin/rethinkdbs/metrics.csv new file mode 100644 index 00000000..2eb1eb7a --- /dev/null +++ b/collectors/python.d.plugin/rethinkdbs/metrics.csv @@ -0,0 +1,9 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +rethinkdb.cluster_connected_servers,,"connected, missing",servers,Connected Servers,stacked,,python.d.plugin,rethinkdbs +rethinkdb.cluster_clients_active,,active,clients,Active Clients,line,,python.d.plugin,rethinkdbs +rethinkdb.cluster_queries,,queries,queries/s,Queries,line,,python.d.plugin,rethinkdbs +rethinkdb.cluster_documents,,"reads, writes",documents/s,Documents,line,,python.d.plugin,rethinkdbs +rethinkdb.client_connections,database server,connections,connections,Client Connections,line,,python.d.plugin,rethinkdbs +rethinkdb.clients_active,database server,active,clients,Active Clients,line,,python.d.plugin,rethinkdbs +rethinkdb.queries,database server,queries,queries/s,Queries,line,,python.d.plugin,rethinkdbs +rethinkdb.documents,database server,"reads, writes",documents/s,Documents,line,,python.d.plugin,rethinkdbs diff --git a/collectors/python.d.plugin/retroshare/README.md b/collectors/python.d.plugin/retroshare/README.md index 142b7d5b..b7f2fcb1 100644 --- a/collectors/python.d.plugin/retroshare/README.md +++ b/collectors/python.d.plugin/retroshare/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "RetroShare" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Apm" +learn_rel_path: "Integrations/Monitor/Apm" --> -# RetroShare monitoring with Netdata +# RetroShare collector Monitors application bandwidth, peers and DHT metrics. @@ -45,6 +45,26 @@ remote: user : "user" password : "pass" ``` ---- + +### Troubleshooting + +To troubleshoot issues with the `retroshare` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `retroshare` module in debug mode: + +```bash +./python.d.plugin retroshare debug trace +``` + diff --git a/collectors/python.d.plugin/retroshare/metrics.csv b/collectors/python.d.plugin/retroshare/metrics.csv new file mode 100644 index 00000000..35a0a48c --- /dev/null +++ b/collectors/python.d.plugin/retroshare/metrics.csv @@ -0,0 +1,4 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +retroshare.bandwidth,,"Upload, Download",kilobits/s,RetroShare Bandwidth,area,,python.d.plugin,retroshare +retroshare.peers,,"All friends, Connected friends",peers,RetroShare Peers,line,,python.d.plugin,retroshare +retroshare.dht,,"DHT nodes estimated, RS nodes estimated",peers,Retroshare DHT,line,,python.d.plugin,retroshare diff --git a/collectors/python.d.plugin/riakkv/README.md b/collectors/python.d.plugin/riakkv/README.md index 5e533a41..e822c551 100644 --- a/collectors/python.d.plugin/riakkv/README.md +++ b/collectors/python.d.plugin/riakkv/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Riak KV" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Databases" +learn_rel_path: "Integrations/Monitor/Databases" --> -# Riak KV monitoring with Netdata +# Riak KV collector Collects database stats from `/stats` endpoint. @@ -127,3 +127,23 @@ With no explicit configuration given, the module will attempt to connect to The default update frequency for the plugin is set to 2 seconds as Riak internally updates the metrics every second. If we were to update the metrics every second, the resulting graph would contain odd jitter. +### Troubleshooting + +To troubleshoot issues with the `riakkv` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `riakkv` module in debug mode: + +```bash +./python.d.plugin riakkv debug trace +``` + diff --git a/collectors/python.d.plugin/riakkv/metrics.csv b/collectors/python.d.plugin/riakkv/metrics.csv new file mode 100644 index 00000000..fbac7603 --- /dev/null +++ b/collectors/python.d.plugin/riakkv/metrics.csv @@ -0,0 +1,26 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +riak.kv.throughput,,"gets, puts",operations/s,Reads & writes coordinated by this node,line,,python.d.plugin,riakkv +riak.dt.vnode_updates,,"counters, sets, maps",operations/s,Update operations coordinated by local vnodes by data type,line,,python.d.plugin,riakkv +riak.search,,queries,queries/s,Search queries on the node,line,,python.d.plugin,riakkv +riak.search.documents,,indexed,documents/s,Documents indexed by search,line,,python.d.plugin,riakkv +riak.consistent.operations,,"gets, puts",operations/s,Consistent node operations,line,,python.d.plugin,riakkv +riak.kv.latency.get,,"mean, median, 95, 99, 100",ms,Time between reception of a client GET request and subsequent response to client,line,,python.d.plugin,riakkv +riak.kv.latency.put,,"mean, median, 95, 99, 100",ms,Time between reception of a client PUT request and subsequent response to client,line,,python.d.plugin,riakkv +riak.dt.latency.counter_merge,,"mean, median, 95, 99, 100",ms,Time it takes to perform an Update Counter operation,line,,python.d.plugin,riakkv +riak.dt.latency.set_merge,,"mean, median, 95, 99, 100",ms,Time it takes to perform an Update Set operation,line,,python.d.plugin,riakkv +riak.dt.latency.map_merge,,"mean, median, 95, 99, 100",ms,Time it takes to perform an Update Map operation,line,,python.d.plugin,riakkv +riak.search.latency.query,,"median, min, 95, 99, 999, max",ms,Search query latency,line,,python.d.plugin,riakkv +riak.search.latency.index,,"median, min, 95, 99, 999, max",ms,Time it takes Search to index a new document,line,,python.d.plugin,riakkv +riak.consistent.latency.get,,"mean, median, 95, 99, 100",ms,Strongly consistent read latency,line,,python.d.plugin,riakkv +riak.consistent.latency.put,,"mean, median, 95, 99, 100",ms,Strongly consistent write latency,line,,python.d.plugin,riakkv +riak.vm,,processes,total,Total processes running in the Erlang VM,line,,python.d.plugin,riakkv +riak.vm.memory.processes,,"allocated, used",MB,Memory allocated & used by Erlang processes,line,,python.d.plugin,riakkv +riak.kv.siblings_encountered.get,,"mean, median, 95, 99, 100",siblings,Number of siblings encountered during GET operations by this node during the past minute,line,,python.d.plugin,riakkv +riak.kv.objsize.get,,"mean, median, 95, 99, 100",KB,Object size encountered by this node during the past minute,line,,python.d.plugin,riakkv +riak.search.vnodeq_size,,"mean, median, 95, 99, 100",messages,Number of unprocessed messages in the vnode message queues of Search on this node in the past minute,line,,python.d.plugin,riakkv +riak.search.index,,errors,errors,Number of document index errors encountered by Search,line,,python.d.plugin,riakkv +riak.core.protobuf_connections,,active,connections,Protocol buffer connections by status,line,,python.d.plugin,riakkv +riak.core.repairs,,read,repairs,Number of repair operations this node has coordinated,line,,python.d.plugin,riakkv +riak.core.fsm_active,,"get, put, secondary index, list keys",fsms,Active finite state machines by kind,line,,python.d.plugin,riakkv +riak.core.fsm_rejected,,"get, put",fsms,Finite state machines being rejected by Sidejobs overload protection,line,,python.d.plugin,riakkv +riak.search.index,,"bad_entry, extract_fail",writes,Number of writes to Search failed due to bad data format by reason,line,,python.d.plugin,riakkv diff --git a/collectors/python.d.plugin/samba/README.md b/collectors/python.d.plugin/samba/README.md index 41ae1c5b..8fe133fd 100644 --- a/collectors/python.d.plugin/samba/README.md +++ b/collectors/python.d.plugin/samba/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Samba" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Apps" +learn_rel_path: "Integrations/Monitor/Apps" --> -# Samba monitoring with Netdata +# Samba collector Monitors the performance metrics of Samba file sharing using `smbstatus` command-line tool. @@ -119,6 +119,26 @@ cd /etc/netdata # Replace this path with your Netdata config directory, if dif sudo ./edit-config python.d/samba.conf ``` ---- + +### Troubleshooting + +To troubleshoot issues with the `samba` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `samba` module in debug mode: + +```bash +./python.d.plugin samba debug trace +``` + diff --git a/collectors/python.d.plugin/samba/metrics.csv b/collectors/python.d.plugin/samba/metrics.csv new file mode 100644 index 00000000..600181f6 --- /dev/null +++ b/collectors/python.d.plugin/samba/metrics.csv @@ -0,0 +1,8 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +syscall.rw,,"sendfile, recvfile",KiB/s,R/Ws,area,,python.d.plugin,samba +smb2.rw,,"readout, writein, readin, writeout",KiB/s,R/Ws,area,,python.d.plugin,samba +smb2.create_close,,"create, close",operations/s,Create/Close,line,,python.d.plugin,samba +smb2.get_set_info,,"getinfo, setinfo",operations/s,Info,line,,python.d.plugin,samba +smb2.find,,find,operations/s,Find,line,,python.d.plugin,samba +smb2.notify,,notify,operations/s,Notify,line,,python.d.plugin,samba +smb2.sm_counters,,"tcon, negprot, tdis, cancel, logoff, flush, lock, keepalive, break, sessetup",count,Lesser Ops,stacked,,python.d.plugin,samba diff --git a/collectors/python.d.plugin/sensors/README.md b/collectors/python.d.plugin/sensors/README.md index f5f43585..7ee31bd6 100644 --- a/collectors/python.d.plugin/sensors/README.md +++ b/collectors/python.d.plugin/sensors/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "sensors-python.d.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Devices" +learn_rel_path: "Integrations/Monitor/Devices" --> -# Linux machine sensors monitoring with Netdata +# Linux machine sensors collector Reads system sensors information (temperature, voltage, electric current, power, etc.). @@ -25,12 +25,31 @@ sudo ./edit-config python.d/sensors.conf ### possible issues -There have been reports from users that on certain servers, ACPI ring buffer errors are printed by the kernel (`dmesg`) when ACPI sensors are being accessed. -We are tracking such cases in issue [#827](https://github.com/netdata/netdata/issues/827). -Please join this discussion for help. +There have been reports from users that on certain servers, ACPI ring buffer errors are printed by the kernel (`dmesg`) +when ACPI sensors are being accessed. 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://github.com/netdata/netdata/blob/master/collectors/charts.d.plugin/sensors/README.md) +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) ---- +### Troubleshooting + +To troubleshoot issues with the `sensors` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `sensors` module in debug mode: + +```bash +./python.d.plugin sensors debug trace +``` diff --git a/collectors/python.d.plugin/sensors/metrics.csv b/collectors/python.d.plugin/sensors/metrics.csv new file mode 100644 index 00000000..d49e1938 --- /dev/null +++ b/collectors/python.d.plugin/sensors/metrics.csv @@ -0,0 +1,8 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +sensors.temperature,chip,a dimension per sensor,Celsius,Temperature,line,,python.d.plugin,sensors +sensors.voltage,chip,a dimension per sensor,Volts,Voltage,line,,python.d.plugin,sensors +sensors.current,chip,a dimension per sensor,Ampere,Current,line,,python.d.plugin,sensors +sensors.power,chip,a dimension per sensor,Watt,Power,line,,python.d.plugin,sensors +sensors.fan,chip,a dimension per sensor,Rotations/min,Fans speed,line,,python.d.plugin,sensors +sensors.energy,chip,a dimension per sensor,Joule,Energy,line,,python.d.plugin,sensors +sensors.humidity,chip,a dimension per sensor,Percent,Humidity,line,,python.d.plugin,sensors diff --git a/collectors/python.d.plugin/smartd_log/README.md b/collectors/python.d.plugin/smartd_log/README.md index 7c1e845f..e79348b0 100644 --- a/collectors/python.d.plugin/smartd_log/README.md +++ b/collectors/python.d.plugin/smartd_log/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "S.M.A.R.T. attributes" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Devices" +learn_rel_path: "Integrations/Monitor/Devices" --> -# Storage devices monitoring with Netdata +# Storage devices collector Monitors `smartd` log files to collect HDD/SSD S.M.A.R.T attributes. @@ -123,6 +123,26 @@ local: If no configuration is given, module will attempt to read log files in `/var/log/smartd/` directory. ---- + +### Troubleshooting + +To troubleshoot issues with the `smartd_log` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `smartd_log` module in debug mode: + +```bash +./python.d.plugin smartd_log debug trace +``` + diff --git a/collectors/python.d.plugin/smartd_log/metrics.csv b/collectors/python.d.plugin/smartd_log/metrics.csv new file mode 100644 index 00000000..7dcc703c --- /dev/null +++ b/collectors/python.d.plugin/smartd_log/metrics.csv @@ -0,0 +1,36 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +smartd_log.read_error_rate,,a dimension per device,value,Read Error Rate,line,,python.d.plugin,smartd_log +smartd_log.seek_error_rate,,a dimension per device,value,Seek Error Rate,line,,python.d.plugin,smartd_log +smartd_log.soft_read_error_rate,,a dimension per device,errors,Soft Read Error Rate,line,,python.d.plugin,smartd_log +smartd_log.write_error_rate,,a dimension per device,value,Write Error Rate,line,,python.d.plugin,smartd_log +smartd_log.read_total_err_corrected,,a dimension per device,errors,Read Error Corrected,line,,python.d.plugin,smartd_log +smartd_log.read_total_unc_errors,,a dimension per device,errors,Read Error Uncorrected,line,,python.d.plugin,smartd_log +smartd_log.write_total_err_corrected,,a dimension per device,errors,Write Error Corrected,line,,python.d.plugin,smartd_log +smartd_log.write_total_unc_errors,,a dimension per device,errors,Write Error Uncorrected,line,,python.d.plugin,smartd_log +smartd_log.verify_total_err_corrected,,a dimension per device,errors,Verify Error Corrected,line,,python.d.plugin,smartd_log +smartd_log.verify_total_unc_errors,,a dimension per device,errors,Verify Error Uncorrected,line,,python.d.plugin,smartd_log +smartd_log.sata_interface_downshift,,a dimension per device,events,SATA Interface Downshift,line,,python.d.plugin,smartd_log +smartd_log.udma_crc_error_count,,a dimension per device,errors,UDMA CRC Error Count,line,,python.d.plugin,smartd_log +smartd_log.throughput_performance,,a dimension per device,value,Throughput Performance,line,,python.d.plugin,smartd_log +smartd_log.seek_time_performance,,a dimension per device,value,Seek Time Performance,line,,python.d.plugin,smartd_log +smartd_log.start_stop_count,,a dimension per device,events,Start/Stop Count,line,,python.d.plugin,smartd_log +smartd_log.power_on_hours_count,,a dimension per device,hours,Power-On Hours Count,line,,python.d.plugin,smartd_log +smartd_log.power_cycle_count,,a dimension per device,events,Power Cycle Count,line,,python.d.plugin,smartd_log +smartd_log.unexpected_power_loss,,a dimension per device,events,Unexpected Power Loss,line,,python.d.plugin,smartd_log +smartd_log.spin_up_time,,a dimension per device,ms,Spin-Up Time,line,,python.d.plugin,smartd_log +smartd_log.spin_up_retries,,a dimension per device,retries,Spin-up Retries,line,,python.d.plugin,smartd_log +smartd_log.calibration_retries,,a dimension per device,retries,Calibration Retries,line,,python.d.plugin,smartd_log +smartd_log.airflow_temperature_celsius,,a dimension per device,celsius,Airflow Temperature Celsius,line,,python.d.plugin,smartd_log +smartd_log.temperature_celsius,,"a dimension per device",celsius,Temperature,line,,python.d.plugin,smartd_log +smartd_log.reallocated_sectors_count,,a dimension per device,sectors,Reallocated Sectors Count,line,,python.d.plugin,smartd_log +smartd_log.reserved_block_count,,a dimension per device,percentage,Reserved Block Count,line,,python.d.plugin,smartd_log +smartd_log.program_fail_count,,a dimension per device,errors,Program Fail Count,line,,python.d.plugin,smartd_log +smartd_log.erase_fail_count,,a dimension per device,failures,Erase Fail Count,line,,python.d.plugin,smartd_log +smartd_log.wear_leveller_worst_case_erase_count,,a dimension per device,erases,Wear Leveller Worst Case Erase Count,line,,python.d.plugin,smartd_log +smartd_log.unused_reserved_nand_blocks,,a dimension per device,blocks,Unused Reserved NAND Blocks,line,,python.d.plugin,smartd_log +smartd_log.reallocation_event_count,,a dimension per device,events,Reallocation Event Count,line,,python.d.plugin,smartd_log +smartd_log.current_pending_sector_count,,a dimension per device,sectors,Current Pending Sector Count,line,,python.d.plugin,smartd_log +smartd_log.offline_uncorrectable_sector_count,,a dimension per device,sectors,Offline Uncorrectable Sector Count,line,,python.d.plugin,smartd_log +smartd_log.percent_lifetime_used,,a dimension per device,percentage,Percent Lifetime Used,line,,python.d.plugin,smartd_log +smartd_log.media_wearout_indicator,,a dimension per device,percentage,Media Wearout Indicator,line,,python.d.plugin,smartd_log +smartd_log.nand_writes_1gib,,a dimension per device,GiB,NAND Writes,line,,python.d.plugin,smartd_log diff --git a/collectors/python.d.plugin/spigotmc/README.md b/collectors/python.d.plugin/spigotmc/README.md index 6d8e4b62..f39d9bab 100644 --- a/collectors/python.d.plugin/spigotmc/README.md +++ b/collectors/python.d.plugin/spigotmc/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "SpigotMC" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Webapps" +learn_rel_path: "Integrations/Monitor/Webapps" --> -# SpigotMC monitoring with Netdata +# SpigotMC collector Performs basic monitoring for Spigot Minecraft servers. @@ -36,6 +36,26 @@ password: pass By default, a connection to port 25575 on the local system is attempted with an empty password. ---- + +### Troubleshooting + +To troubleshoot issues with the `spigotmc` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `spigotmc` module in debug mode: + +```bash +./python.d.plugin spigotmc debug trace +``` + diff --git a/collectors/python.d.plugin/spigotmc/metrics.csv b/collectors/python.d.plugin/spigotmc/metrics.csv new file mode 100644 index 00000000..8d040b95 --- /dev/null +++ b/collectors/python.d.plugin/spigotmc/metrics.csv @@ -0,0 +1,4 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +spigotmc.tps,,"1 Minute Average, 5 Minute Average, 15 Minute Average",ticks,Spigot Ticks Per Second,line,,python.d.plugin,spigotmc +spigotmc.users,,Users,users,Minecraft Users,area,,python.d.plugin,spigotmc +spigotmc.mem,,"used, allocated, max",MiB,Minecraft Memory Usage,line,,python.d.plugin,spigotmc diff --git a/collectors/python.d.plugin/squid/README.md b/collectors/python.d.plugin/squid/README.md index ac6c8371..da534918 100644 --- a/collectors/python.d.plugin/squid/README.md +++ b/collectors/python.d.plugin/squid/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Squid" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Webapps" +learn_rel_path: "Integrations/Monitor/Webapps" --> -# Squid monitoring with Netdata +# Squid collector Monitors one or more squid instances depending on configuration. @@ -56,6 +56,26 @@ local: Without any configuration module will try to autodetect where squid presents its `counters` data ---- + +### Troubleshooting + +To troubleshoot issues with the `squid` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `squid` module in debug mode: + +```bash +./python.d.plugin squid debug trace +``` + diff --git a/collectors/python.d.plugin/squid/metrics.csv b/collectors/python.d.plugin/squid/metrics.csv new file mode 100644 index 00000000..c2899f2e --- /dev/null +++ b/collectors/python.d.plugin/squid/metrics.csv @@ -0,0 +1,5 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +squid.clients_net,squid instance,"in, out, hits",kilobits/s,Squid Client Bandwidth,area,,python.d.plugin,squid +squid.clients_requests,squid instance,"requests, hits, errors",requests/s,Squid Client Requests,line,,python.d.plugin,squid +squid.servers_net,squid instance,"in, out",kilobits/s,Squid Server Bandwidth,area,,python.d.plugin,squid +squid.servers_requests,squid instance,"requests, errors",requests/s,Squid Server Requests,line,,python.d.plugin,squid diff --git a/collectors/python.d.plugin/tomcat/README.md b/collectors/python.d.plugin/tomcat/README.md index 66ed6d97..923d6238 100644 --- a/collectors/python.d.plugin/tomcat/README.md +++ b/collectors/python.d.plugin/tomcat/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Tomcat" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Webapps" +learn_rel_path: "Integrations/Monitor/Webapps" --> -# Apache Tomcat monitoring with Netdata +# Apache Tomcat collector Presents memory utilization of tomcat containers. @@ -51,6 +51,26 @@ localhost: Without configuration, module attempts to connect to `http://localhost:8080/manager/status?XML=true`, without any credentials. So it will probably fail. ---- + +### Troubleshooting + +To troubleshoot issues with the `tomcat` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `tomcat` module in debug mode: + +```bash +./python.d.plugin tomcat debug trace +``` + diff --git a/collectors/python.d.plugin/tomcat/metrics.csv b/collectors/python.d.plugin/tomcat/metrics.csv new file mode 100644 index 00000000..6769fa3f --- /dev/null +++ b/collectors/python.d.plugin/tomcat/metrics.csv @@ -0,0 +1,9 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +tomcat.accesses,,"accesses, errors",requests/s,Requests,area,,python.d.plugin,tomcat +tomcat.bandwidth,,"sent, received",KiB/s,Bandwidth,area,,python.d.plugin,tomcat +tomcat.processing_time,,processing time,seconds,processing time,area,,python.d.plugin,tomcat +tomcat.threads,,"current, busy",current threads,Threads,area,,python.d.plugin,tomcat +tomcat.jvm,,"free, eden, survivor, tenured, code cache, compressed, metaspace",MiB,JVM Memory Pool Usage,stacked,,python.d.plugin,tomcat +tomcat.jvm_eden,,"used, committed, max",MiB,Eden Memory Usage,area,,python.d.plugin,tomcat +tomcat.jvm_survivor,,"used, committed, max",MiB,Survivor Memory Usage,area,,python.d.plugin,tomcat +tomcat.jvm_tenured,,"used, committed, max",MiB,Tenured Memory Usage,area,,python.d.plugin,tomcat diff --git a/collectors/python.d.plugin/tor/README.md b/collectors/python.d.plugin/tor/README.md index c6680376..15f7e228 100644 --- a/collectors/python.d.plugin/tor/README.md +++ b/collectors/python.d.plugin/tor/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Tor" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Apps" +learn_rel_path: "Integrations/Monitor/Apps" --> -# Tor monitoring with Netdata +# Tor collector Connects to the Tor control port to collect traffic statistics. @@ -64,6 +64,26 @@ For more options please read the manual. Without configuration, module attempts to connect to `127.0.0.1:9051`. ---- + +### Troubleshooting + +To troubleshoot issues with the `tor` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `tor` module in debug mode: + +```bash +./python.d.plugin tor debug trace +``` + diff --git a/collectors/python.d.plugin/tor/metrics.csv b/collectors/python.d.plugin/tor/metrics.csv new file mode 100644 index 00000000..62402d8d --- /dev/null +++ b/collectors/python.d.plugin/tor/metrics.csv @@ -0,0 +1,2 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +tor.traffic,,"read, write",KiB/s,Tor Traffic,area,,python.d.plugin,tor diff --git a/collectors/python.d.plugin/traefik/README.md b/collectors/python.d.plugin/traefik/README.md index cf30a82a..40ed24f0 100644 --- a/collectors/python.d.plugin/traefik/README.md +++ b/collectors/python.d.plugin/traefik/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "traefik-python.d.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Webapps" +learn_rel_path: "Integrations/Monitor/Webapps" --> -# Traefik monitoring with Netdata +# Traefik collector Uses the `health` API to provide statistics. @@ -73,6 +73,26 @@ local: Without configuration, module attempts to connect to `http://localhost:8080/health`. ---- + +### Troubleshooting + +To troubleshoot issues with the `traefik` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `traefik` module in debug mode: + +```bash +./python.d.plugin traefik debug trace +``` + diff --git a/collectors/python.d.plugin/traefik/metrics.csv b/collectors/python.d.plugin/traefik/metrics.csv new file mode 100644 index 00000000..77e1c294 --- /dev/null +++ b/collectors/python.d.plugin/traefik/metrics.csv @@ -0,0 +1,9 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +traefik.response_statuses,,"success, error, redirect, bad, other",requests/s,Response statuses,stacked,,python.d.plugin,traefik +traefik.response_codes,,"2xx, 5xx, 3xx, 4xx, 1xx, other",requests/s,Responses by codes,stacked,,python.d.plugin,traefik +traefik.detailed_response_codes,,a dimension for each response code family,requests/s,Detailed response codes,stacked,,python.d.plugin,traefik +traefik.requests,,requests,requests/s,Requests,line,,python.d.plugin,traefik +traefik.total_response_time,,response,seconds,Total response time,line,,python.d.plugin,traefik +traefik.average_response_time,,response,milliseconds,Average response time,line,,python.d.plugin,traefik +traefik.average_response_time_per_iteration,,response,milliseconds,Average response time per iteration,line,,python.d.plugin,traefik +traefik.uptime,,uptime,seconds,Uptime,line,,python.d.plugin,traefik diff --git a/collectors/python.d.plugin/uwsgi/README.md b/collectors/python.d.plugin/uwsgi/README.md index dcc2dc38..393be9fc 100644 --- a/collectors/python.d.plugin/uwsgi/README.md +++ b/collectors/python.d.plugin/uwsgi/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "uWSGI" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Webapps" +learn_rel_path: "Integrations/Monitor/Webapps" --> -# uWSGI monitoring with Netdata +# uWSGI collector Monitors performance metrics exposed by [`Stats Server`](https://uwsgi-docs.readthedocs.io/en/latest/StatsServer.html). @@ -53,3 +53,23 @@ localhost: When no configuration file is found, module tries to connect to TCP/IP socket: `localhost:1717`. +### Troubleshooting + +To troubleshoot issues with the `uwsgi` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `uwsgi` module in debug mode: + +```bash +./python.d.plugin uwsgi debug trace +``` + diff --git a/collectors/python.d.plugin/uwsgi/metrics.csv b/collectors/python.d.plugin/uwsgi/metrics.csv new file mode 100644 index 00000000..c974653f --- /dev/null +++ b/collectors/python.d.plugin/uwsgi/metrics.csv @@ -0,0 +1,9 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +uwsgi.requests,,a dimension per worker,requests/s,Requests,stacked,,python.d.plugin,uwsgi +uwsgi.tx,,a dimension per worker,KiB/s,Transmitted data,stacked,,python.d.plugin,uwsgi +uwsgi.avg_rt,,a dimension per worker,milliseconds,Average request time,line,,python.d.plugin,uwsgi +uwsgi.memory_rss,,a dimension per worker,MiB,RSS (Resident Set Size),stacked,,python.d.plugin,uwsgi +uwsgi.memory_vsz,,a dimension per worker,MiB,VSZ (Virtual Memory Size),stacked,,python.d.plugin,uwsgi +uwsgi.exceptions,,exceptions,exceptions,Exceptions,line,,python.d.plugin,uwsgi +uwsgi.harakiris,,harakiris,harakiris,Harakiris,line,,python.d.plugin,uwsgi +uwsgi.respawns,,respawns,respawns,Respawns,line,,python.d.plugin,uwsgi diff --git a/collectors/python.d.plugin/varnish/README.md b/collectors/python.d.plugin/varnish/README.md index ebcc00c5..d30a9fb1 100644 --- a/collectors/python.d.plugin/varnish/README.md +++ b/collectors/python.d.plugin/varnish/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "Varnish Cache" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Webapps" +learn_rel_path: "Integrations/Monitor/Webapps" --> -# Varnish Cache monitoring with Netdata +# Varnish Cache collector Provides HTTP accelerator global, Backends (VBE) and Storages (SMF, SMA, MSE) statistics using `varnishstat` tool. @@ -63,6 +63,26 @@ instance_name: 'name' The name of the `varnishd` instance to get logs from. If not specified, the host name is used. ---- + +### Troubleshooting + +To troubleshoot issues with the `varnish` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `varnish` module in debug mode: + +```bash +./python.d.plugin varnish debug trace +``` + diff --git a/collectors/python.d.plugin/varnish/metrics.csv b/collectors/python.d.plugin/varnish/metrics.csv new file mode 100644 index 00000000..bafb9fd1 --- /dev/null +++ b/collectors/python.d.plugin/varnish/metrics.csv @@ -0,0 +1,18 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +varnish.session_connection,,"accepted, dropped",connections/s,Connections Statistics,line,,python.d.plugin,varnish +varnish.client_requests,,received,requests/s,Client Requests,line,,python.d.plugin,varnish +varnish.all_time_hit_rate,,"hit, miss, hitpass",percentage,All History Hit Rate Ratio,stacked,,python.d.plugin,varnish +varnish.current_poll_hit_rate,,"hit, miss, hitpass",percentage,Current Poll Hit Rate Ratio,stacked,,python.d.plugin,varnish +varnish.cached_objects_expired,,objects,expired/s,Expired Objects,line,,python.d.plugin,varnish +varnish.cached_objects_nuked,,objects,nuked/s,Least Recently Used Nuked Objects,line,,python.d.plugin,varnish +varnish.threads_total,,None,number,Number Of Threads In All Pools,line,,python.d.plugin,varnish +varnish.threads_statistics,,"created, failed, limited",threads/s,Threads Statistics,line,,python.d.plugin,varnish +varnish.threads_queue_len,,in queue,requests,Current Queue Length,line,,python.d.plugin,varnish +varnish.backend_connections,,"successful, unhealthy, reused, closed, recycled, failed",connections/s,Backend Connections Statistics,line,,python.d.plugin,varnish +varnish.backend_requests,,sent,requests/s,Requests To The Backend,line,,python.d.plugin,varnish +varnish.esi_statistics,,"errors, warnings",problems/s,ESI Statistics,line,,python.d.plugin,varnish +varnish.memory_usage,,"free, allocated",MiB,Memory Usage,stacked,,python.d.plugin,varnish +varnish.uptime,,uptime,seconds,Uptime,line,,python.d.plugin,varnish +varnish.backend,Backend,"header, body",kilobits/s,Backend {backend_name},area,,python.d.plugin,varnish +varnish.storage_usage,Storage,"free, allocated",KiB,Storage {storage_name} Usage,stacked,,python.d.plugin,varnish +varnish.storage_alloc_objs,Storage,allocated,objects,Storage {storage_name} Allocated Objects,line,,python.d.plugin,varnish diff --git a/collectors/python.d.plugin/w1sensor/README.md b/collectors/python.d.plugin/w1sensor/README.md index 12a14a19..ca08b040 100644 --- a/collectors/python.d.plugin/w1sensor/README.md +++ b/collectors/python.d.plugin/w1sensor/README.md @@ -4,10 +4,10 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/pyth sidebar_label: "1-Wire sensors" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Remotes/Devices" +learn_rel_path: "Integrations/Monitor/Remotes/Devices" --> -# 1-Wire Sensors monitoring with Netdata +# 1-Wire Sensors collector Monitors sensor temperature. @@ -26,6 +26,25 @@ cd /etc/netdata # Replace this path with your Netdata config directory, if dif sudo ./edit-config python.d/w1sensor.conf ``` ---- +An example of a working configuration can be found in the default [configuration file](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/w1sensor/w1sensor.conf) of this collector. +### Troubleshooting + +To troubleshoot issues with the `w1sensor` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `w1sensor` module in debug mode: + +```bash +./python.d.plugin w1sensor debug trace +``` diff --git a/collectors/python.d.plugin/w1sensor/metrics.csv b/collectors/python.d.plugin/w1sensor/metrics.csv new file mode 100644 index 00000000..54564934 --- /dev/null +++ b/collectors/python.d.plugin/w1sensor/metrics.csv @@ -0,0 +1,2 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +w1sensor.temp,,a dimension per sensor,Celsius,1-Wire Temperature Sensor,line,,python.d.plugin,w1sensor diff --git a/collectors/python.d.plugin/zscores/README.md b/collectors/python.d.plugin/zscores/README.md index d89aa6a0..dcb685c9 100644 --- a/collectors/python.d.plugin/zscores/README.md +++ b/collectors/python.d.plugin/zscores/README.md @@ -1,16 +1,6 @@ - +# Basic anomaly detection using Z-scores -# 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. +By using smoothed, rolling [Z-Scores](https://en.wikipedia.org/wiki/Standard_score) for selected metrics or charts you can narrow down your focus and shorten root cause analysis. 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 @@ -87,7 +77,7 @@ the `zscores.conf` files alone to begin with. Then you can return to it later if more once the collector is running for a while. Edit the `python.d/zscores.conf` configuration file using `edit-config` from the your -agent's [config directory](https://learn.netdata.cloud/guides/step-by-step/step-04#find-your-netdataconf-file), which is +agent's [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory), which is usually at `/etc/netdata`. ```bash @@ -146,3 +136,23 @@ per_chart_agg: 'mean' # 'absmax' will take the max absolute value across all dim - If you activate this collector on a fresh node, it might take a little while to build up enough data to calculate a proper zscore. So until you actually have `train_secs` of available data the mean and stddev calculated will be subject to more noise. +### Troubleshooting + +To troubleshoot issues with the `zscores` module, run the `python.d.plugin` with the debug option enabled. The +output will give you the output of the data collection job or error messages on why the collector isn't working. + +First, navigate to your plugins directory, usually they are located under `/usr/libexec/netdata/plugins.d/`. If that's +not the case on your system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the +plugin's directory, switch to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash netdata +``` + +Now you can manually run the `zscores` module in debug mode: + +```bash +./python.d.plugin zscores debug trace +``` + diff --git a/collectors/python.d.plugin/zscores/metrics.csv b/collectors/python.d.plugin/zscores/metrics.csv new file mode 100644 index 00000000..5066c7c3 --- /dev/null +++ b/collectors/python.d.plugin/zscores/metrics.csv @@ -0,0 +1,3 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +zscores.z,,a dimension per chart or dimension,z,Z Score,line,,python.d.plugin,zscores +zscores.3stddev,,a dimension per chart or dimension,count,Z Score >3,stacked,,python.d.plugin,zscores diff --git a/collectors/slabinfo.plugin/README.md b/collectors/slabinfo.plugin/README.md index 320b1fc9..e0abaff8 100644 --- a/collectors/slabinfo.plugin/README.md +++ b/collectors/slabinfo.plugin/README.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/slab sidebar_label: "slabinfo.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/System metrics" +learn_rel_path: "Integrations/Monitor/System metrics" --> # slabinfo.plugin diff --git a/collectors/slabinfo.plugin/metrics.csv b/collectors/slabinfo.plugin/metrics.csv new file mode 100644 index 00000000..4391cb6f --- /dev/null +++ b/collectors/slabinfo.plugin/metrics.csv @@ -0,0 +1,4 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +mem.slabmemory,,a dimension per cache,B,"Memory Usage",line,,slabinfo.plugin, +mem.slabfilling,,a dimension per cache,%,"Object Filling",line,,slabinfo.plugin, +mem.slabwaste,,a dimension per cache,B,"Memory waste",line,,slabinfo.plugin, \ No newline at end of file diff --git a/collectors/slabinfo.plugin/slabinfo.c b/collectors/slabinfo.plugin/slabinfo.c index 52b53cd2..25b96e38 100644 --- a/collectors/slabinfo.plugin/slabinfo.c +++ b/collectors/slabinfo.plugin/slabinfo.c @@ -171,19 +171,19 @@ struct slabinfo *read_file_slabinfo() { char *name = procfile_lineword(ff, l, 0); struct slabinfo *s = get_slabstruct(name); - s->active_objs = str2uint64_t(procfile_lineword(ff, l, 1)); - s->num_objs = str2uint64_t(procfile_lineword(ff, l, 2)); - s->obj_size = str2uint64_t(procfile_lineword(ff, l, 3)); - s->obj_per_slab = str2uint64_t(procfile_lineword(ff, l, 4)); - s->pages_per_slab = str2uint64_t(procfile_lineword(ff, l, 5)); - - s->tune_limit = str2uint64_t(procfile_lineword(ff, l, 7)); - s->tune_batchcnt = str2uint64_t(procfile_lineword(ff, l, 8)); - s->tune_shared_factor = str2uint64_t(procfile_lineword(ff, l, 9)); - - s->data_active_slabs = str2uint64_t(procfile_lineword(ff, l, 11)); - s->data_num_slabs = str2uint64_t(procfile_lineword(ff, l, 12)); - s->data_shared_avail = str2uint64_t(procfile_lineword(ff, l, 13)); + s->active_objs = str2uint64_t(procfile_lineword(ff, l, 1), NULL); + s->num_objs = str2uint64_t(procfile_lineword(ff, l, 2), NULL); + s->obj_size = str2uint64_t(procfile_lineword(ff, l, 3), NULL); + s->obj_per_slab = str2uint64_t(procfile_lineword(ff, l, 4), NULL); + s->pages_per_slab = str2uint64_t(procfile_lineword(ff, l, 5), NULL); + + s->tune_limit = str2uint64_t(procfile_lineword(ff, l, 7), NULL); + s->tune_batchcnt = str2uint64_t(procfile_lineword(ff, l, 8), NULL); + s->tune_shared_factor = str2uint64_t(procfile_lineword(ff, l, 9), NULL); + + s->data_active_slabs = str2uint64_t(procfile_lineword(ff, l, 11), NULL); + s->data_num_slabs = str2uint64_t(procfile_lineword(ff, l, 12), NULL); + s->data_shared_avail = str2uint64_t(procfile_lineword(ff, l, 13), NULL); uint32_t memperslab = s->pages_per_slab * slab_pagesize; // Internal fragmentation: loss per slab, due to objects not being a multiple of pagesize diff --git a/collectors/statsd.plugin/README.md b/collectors/statsd.plugin/README.md index d65476ff..dd74923e 100644 --- a/collectors/statsd.plugin/README.md +++ b/collectors/statsd.plugin/README.md @@ -1,29 +1,40 @@ -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. +# StatsD -If you want to learn more about the StatsD protocol, we have written a [blog post](https://www.netdata.cloud/blog/introduction-to-statsd/) about it! +[StatsD](https://github.com/statsd/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. +If you want to learn more about the StatsD protocol, we have written a +[blog post](https://blog.netdata.cloud/introduction-to-statsd/) about it! -Netdata is a fully featured statsd server. It can collect statsd formatted metrics, visualize them on its dashboards and store them in it's database for long-term retention. -Netdata statsd is inside Netdata (an internal plugin, running inside the Netdata daemon), it is configured via `netdata.conf` and by-default listens on standard statsd port 8125. Netdata supports both TCP and UDP packets at the same time. +Netdata is a fully featured statsd server. It can collect statsd formatted metrics, visualize +them on its dashboards and store them in it's database for long-term retention. + +Netdata statsd is inside Netdata (an internal plugin, running inside the Netdata daemon), it is +configured via `netdata.conf` and by-default listens on standard statsd port 8125. Netdata supports +both TCP and UDP packets at the same time. Since statsd is embedded in Netdata, it means you now have a statsd server embedded on all your servers. -Netdata statsd is fast. It can collect several millions of metrics per second on modern hardware, using just 1 CPU core. The implementation uses two threads: one thread collects metrics, another thread updates the charts from the collected data. +Netdata statsd is fast. It can collect several millions of metrics per second on modern hardware, using +just 1 CPU core. The implementation uses two threads: one thread collects metrics, another thread updates +the charts from the collected data. ## Available StatsD synthetic application charts -Netdata ships with a few synthetic chart definitions to automatically present application metrics into a more uniform way. These synthetic charts are configuration files (you can create your own) that re-arrange statsd metrics into a more meaningful way. +Netdata ships with a few synthetic chart definitions to automatically present application metrics into a +more uniform way. These synthetic charts are configuration files (you can create your own) that re-arrange +statsd metrics into a more meaningful way. On synthetic charts, we can have alarms as with any metric and chart. @@ -38,13 +49,16 @@ On synthetic charts, we can have alarms as with any metric and chart. ## Metrics supported by Netdata -Netdata fully supports the StatsD protocol and also extends it to support more advanced Netdata specific use cases. All StatsD client libraries can be used with Netdata too. +Netdata fully supports the StatsD protocol and also extends it to support more advanced Netdata specific use cases. +All StatsD client libraries can be used with Netdata too. - **Gauges** - The application sends `name:value|g`, where `value` is any **decimal/fractional** number, StatsD reports the latest value collected and the number of times it was updated (events). + The application sends `name:value|g`, where `value` is any **decimal/fractional** number, StatsD reports the + latest value collected and the number of times it was updated (events). - The application may increment or decrement a previous value, by setting the first character of the value to `+` or `-` (so, the only way to set a gauge to an absolute negative value, is to first set it to zero). + The application may increment or decrement a previous value, by setting the first character of the value to + `+` or `-` (so, the only way to set a gauge to an absolute negative value, is to first set it to zero). [Sampling rate](#sampling-rates) is supported. [Tags](#tags) are supported for changing chart units, family and dimension name. @@ -305,7 +319,6 @@ For example, if you want to monitor the application `myapp` using StatsD and Net private charts = no gaps when not collected = no history = 60 -# memory mode = ram [dictionary] m1 = metric1 @@ -701,3 +714,341 @@ or even at a terminal prompt, like this: The function is smart enough to call `nc` just once and pass all the metrics to it. It will also automatically switch to TCP if the metrics to send are above 1000 bytes. If you have gotten thus far, make sure to check out our [community forums](https://community.netdata.cloud) to share your experience using Netdata with StatsD. + +## StatsD Step By Step Guide + +In this guide, we'll go through a scenario of visualizing our data in Netdata in a matter of seconds using +[k6](https://k6.io), an open-source tool for automating load testing that outputs metrics to the StatsD format. + +Although we'll use k6 as the use-case, the same principles can be applied to every application that supports +the StatsD protocol. Simply enable the StatsD output and point it to the node that runs Netdata, which is `localhost` in this case. + +In general, the process for creating a StatsD collector can be summarized in 2 steps: + +- 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](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. + +Now, let's see the above process in detail. + +### Prerequisites + +- A node with the [Netdata](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md) installed. +- An application to instrument. For this guide, that will be [k6](https://k6.io/docs/getting-started/installation). + +### Understanding the metrics + +The real in instrumenting an application with StatsD for you is to decide what metrics you +want to visualize and how you want them grouped. In other words, you need decide which metrics +will be grouped in the same charts and how the charts will be grouped on Netdata's dashboard. + +Start with documentation for the particular application that you want to monitor (or the +technological stack that you are using). In our case, the +[k6 documentation](https://k6.io/docs/using-k6/metrics/) has a whole page dedicated to the +metrics output by k6, along with descriptions. + +If you are using StatsD to monitor an existing application, you don't have much control over +these metrics. For example, k6 has a type called `trend`, which is identical to timers and histograms. +Thus, _k6 is clearly dictating_ which metrics can be used as histograms and simple gauges. + +On the other hand, if you are instrumenting your own code, you will need to not only decide what are +the "things" that you want to measure, but also decide which StatsD metric type is the appropriate for each. + +### Use private charts to see all available metrics + +In Netdata, every metric will receive its own chart, called a `private chart`. Although in the +final implementation this is something that we will disable, since it can create considerable noise +(imagine having 100s of metrics), it’s very handy while building the configuration file. + +You can get a quick visual representation of the metrics and their type (e.g it’s a gauge, a timer, etc.). + +An important thing to notice is that StatsD has different types of metrics, as illustrated in the +[supported metrics](#metrics-supported-by-netdata). Histograms and timers support mathematical operations +to be performed on top of the baseline metric, like reporting the `average` of the value. + +Here are some examples of default private charts. You can see that the histogram private charts will +visualize all the available operations. + +**Gauge private chart** + +![Gauge metric example](https://i.imgur.com/Sr5nJEV.png) + +**Histogram private chart** + +![Timer metric example](https://i.imgur.com/P4p0hvq.png) + +### Create a new StatsD configuration file + +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 +``` + +Copy the following configuration into your file as a starting point. + +```conf +[app] + name = k6 + metrics = k6* + private charts = yes + gaps when not collected = no + memory mode = dbengine +``` + +Next, you need is to understand how to organize metrics in Netdata’s StatsD. + +#### Synthetic charts + +Netdata lets you group the metrics exposed by your instrumented application with _synthetic charts_. + +First, create a `[dictionary]` section to transform the names of the metrics into human-readable equivalents. +`http_req_blocked`, `http_req_connecting`, `http_req_receiving`, and `http_reqs` are all metrics exposed by k6. + +``` +[dictionary] + http_req_blocked = Blocked HTTP Requests + http_req_connecting = Connecting HTTP Requests + http_req_receiving = Receiving HTTP Requests + http_reqs = Total HTTP requests +``` + +Continue this dictionary process with any other metrics you want to collect with Netdata. + +#### Families and context + +Families and context are additional ways to group metrics. Families control the submenu at right-hand menu and +it's a subcategory of the section. Given the metrics given by K6, we are organizing them in 2 major groups, +or `families`: `k6 native metrics` and `http metrics`. + +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](https://github.com/netdata/netdata/blob/master/web/README.md#families). + +#### Dimensions + +Now, having decided on how we are going to group the charts, we need to define how we are going to group +metrics into different charts. This is particularly important, since we decide: + +- What metrics **not** to show, since they are not useful for our use-case. +- What metrics to consolidate into the same charts, so as to reduce noise and increase visual correlation. + +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](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md). + We don't use 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 [dimension patterns](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**: + + - For all charts: + - `events`: The number of events (data points) received by the StatsD server + - `last`: The last value that the server received + + - For histograms and timers: + - `min`, `max`, `sum`, `average`, `percentile`, `median`, `stddev`: This is helpful if you want to see + different representations of the same value. You can find an example at the `[iteration_duration]` + above. Note that the baseline `metric` is the same, but the `name` of the dimension is different, + since we use the baseline, but we perform a computation on it, creating a different final metric for + visualization(dimension). + +- **MULTIPLIER DIVIDER**: Handy if you want to convert Kilobytes to Megabytes or you want to give negative value. + The second is handy for better visualization of send/receive. You can find an example at the **packets** submenu of the **IPv4 Networking Section**. + +If you define a chart, run Netdata to visualize metrics, and then add or remove a dimension from that chart, +this will result in a new chart with the same name, confusing Netdata. If you change the dimensions of the chart, +make sure to also change the `name` of that chart, since it serves as the `id` of that chart in Netdata's storage. +(e.g http_req --> http_req_1). + +#### Finalize your StatsD configuration file + +It's time to assemble all the pieces together and create the synthetic charts that will consist our application +dashboard in Netdata. We can do it in a few simple steps: + +- Decide which metrics we want to use (we have viewed all of them as private charts). For example, we want to use + `k6.http_requests`, `k6.vus`, etc. + +- Decide how we want organize them in different synthetic charts. For example, we want `k6.http_requests`, `k6.vus` + on their own, but `k6.http_req_blocked` and `k6.http_req_connecting` on the same chart. + +- For each synthetic chart, we define a **unique** name and a human readable title. + +- We decide at which `family` (submenu section) we want each synthetic chart to belong to. For example, here we + have defined 2 families: `http requests`, `k6_metrics`. + +- If we have multiple instances of the same metric, we can define different contexts, (Optional). + +- We define a dimension according to the syntax we highlighted above. + +- We define a type for each synthetic chart (line, area, stacked) + +- We define the units for each synthetic chart. + +Following the above steps, we append to the `k6.conf` that we defined above, the following configuration: + +``` +[http_req_total] + name = http_req_total + title = Total HTTP Requests + family = http requests + context = k6.http_requests + dimension = k6.http_reqs http_reqs last 1 1 sum + type = line + units = requests/s + +[vus] + name = vus + title = Virtual Active Users + family = k6_metrics + dimension = k6.vus vus last 1 1 + dimension = k6.vus_max vus_max last 1 1 + type = line + unit = vus + +[iteration_duration] + name = iteration_duration_2 + title = Iteration duration + family = k6_metrics + dimension = k6.iteration_duration iteration_duration last 1 1 + dimension = k6.iteration_duration iteration_duration_max max 1 1 + dimension = k6.iteration_duration iteration_duration_min min 1 1 + dimension = k6.iteration_duration iteration_duration_avg avg 1 1 + type = line + unit = s + +[dropped_iterations] + name = dropped_iterations + title = Dropped Iterations + family = k6_metrics + dimension = k6.dropped_iterations dropped_iterations last 1 1 + units = iterations + type = line + +[data] + name = data + title = K6 Data + family = k6_metrics + dimension = k6.data_received data_received last 1 1 + dimension = k6.data_sent data_sent last -1 1 + units = kb/s + type = area + +[http_req_status] + name = http_req_status + title = HTTP Requests Status + family = http requests + dimension = k6.http_req_blocked http_req_blocked last 1 1 + dimension = k6.http_req_connecting http_req_connecting last 1 1 + units = ms + type = line + +[http_req_duration] + name = http_req_duration + title = HTTP requests duration + family = http requests + dimension = k6.http_req_sending http_req_sending last 1 1 + dimension = k6.http_req_waiting http_req_waiting last 1 1 + dimension = k6.http_req_receiving http_req_receiving last 1 1 + units = ms + type = stacked +``` + +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 Netdata visualizes that in `requests/second`. + +To enable this StatsD configuration, [restart Netdata](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md). + +### Final touches + +At this point, you have used StatsD to gather metrics for k6, creating a whole new section in your +Netdata dashboard in the process. Moreover, you can further customize the icon of the particular section, +as well as the description for each chart. + +To edit the section, please follow the Netdata [documentation](https://github.com/netdata/netdata/blob/master/web/gui/README.md#customizing-the-local-dashboard). + +While the following configuration will be placed in a new file, as the documentation suggests, it is +instructing to use `dashboard_info.js` as a template. Open the file and see how the rest of sections and collectors have been defined. + +```javascript= +netdataDashboard.menu = { + 'k6': { + title: 'K6 Load Testing', + icon: '', + info: 'k6 is an open-source load testing tool and cloud service providing the best developer experience for API performance testing.' + }, + . + . + . +``` + +We can then add a description for each chart. Simply find the following section in `dashboard_info.js` to understand how a chart definitions are used: + +```javascript= +netdataDashboard.context = { + 'system.cpu': { + info: function (os) { + void (os); + return 'Total CPU utilization (all cores). 100% here means there is no CPU idle time at all. You can get per core usage at the CPUs section and per application usage at the Applications Monitoring section.' + + netdataDashboard.sparkline('
Keep an eye on iowait ', 'system.cpu', 'iowait', '%', '. If it is constantly high, your disks are a bottleneck and they slow your system down.') + + netdataDashboard.sparkline('
An important metric worth monitoring, is softirq ', 'system.cpu', 'softirq', '%', '. A constantly high percentage of softirq may indicate network driver issues.'); + }, + valueRange: "[0, 100]" + }, +``` + +Afterwards, you can open your `custom_dashboard_info.js`, as suggested in the documentation linked above, +and add something like the following example: + +```javascript= +netdataDashboard.context = { + 'k6.http_req_duration': { + info: "Total time for the request. It's equal to http_req_sending + http_req_waiting + http_req_receiving (i.e. how long did the remote server take to process the request and respond, without the initial DNS lookup/connection times)" + }, + +``` +The chart is identified as ``.``. + +These descriptions can greatly help the Netdata user who is monitoring your application in the midst of an incident. + +The `info` field supports `html`, embedding useful links and instructions in the description. + +### Vendoring a new collector + +While we learned how to visualize any data source in Netdata using the StatsD protocol, we have also created a new collector. + +As long as you use the same underlying collector, every new `myapp.conf` file will create a new data +source and dashboard section for Netdata. Netdata loads all the configuration files by default, but it will +**not** create dashboard sections or charts, unless it starts receiving data for that particular data source. +This means that we can now share our collector with the rest of the Netdata community. + +- Make sure you follow the [contributing guide](https://github.com/netdata/.github/edit/main/CONTRIBUTING.md) +- Fork the netdata/netdata repository +- Place the configuration file inside `netdata/collectors/statsd.plugin` +- Add a reference in `netdata/collectors/statsd.plugin/Makefile.am`. For example, if we contribute the `k6.conf` file: +```Makefile +dist_statsdconfig_DATA = \ + example.conf \ + k6.conf \ + $(NULL) +``` + + diff --git a/collectors/statsd.plugin/asterisk.md b/collectors/statsd.plugin/asterisk.md index 9d794811..e7a7b63c 100644 --- a/collectors/statsd.plugin/asterisk.md +++ b/collectors/statsd.plugin/asterisk.md @@ -3,11 +3,10 @@ title: "Asterisk monitoring with Netdata" 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" +learn_rel_path: "Integrations/Monitor/VoIP" --> -# Asterisk monitoring with Netdata +# Asterisk collector Monitors [Asterisk](https://www.asterisk.org/) dialplan application's statistics. diff --git a/collectors/statsd.plugin/k6.md b/collectors/statsd.plugin/k6.md index 7a1e3677..13608a8a 100644 --- a/collectors/statsd.plugin/k6.md +++ b/collectors/statsd.plugin/k6.md @@ -3,11 +3,10 @@ title: "K6 load test monitoring with Netdata" 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" +learn_rel_path: "Integrations/Monitor/apps" --> -# K6 Load Testing monitoring with Netdata +# K6 load test collector Monitors the impact of load testing experiments performed with [K6](https://k6.io/). diff --git a/collectors/statsd.plugin/statsd.c b/collectors/statsd.plugin/statsd.c index d15129b9..1425d0a9 100644 --- a/collectors/statsd.plugin/statsd.c +++ b/collectors/statsd.plugin/statsd.c @@ -1418,7 +1418,7 @@ static int statsd_readfile(const char *filename, STATSD_APP *app, STATSD_APP_CHA } else if (!strcmp(name, "metrics")) { simple_pattern_free(app->metrics); - app->metrics = simple_pattern_create(value, NULL, SIMPLE_PATTERN_EXACT); + app->metrics = simple_pattern_create(value, NULL, SIMPLE_PATTERN_EXACT, true); } else if (!strcmp(name, "private charts")) { if (!strcmp(value, "yes") || !strcmp(value, "on")) @@ -1480,7 +1480,7 @@ static int statsd_readfile(const char *filename, STATSD_APP *app, STATSD_APP_CHA else if (!strcmp(name, "dimension")) { // metric [name [type [multiplier [divisor]]]] char *words[10] = { NULL }; - size_t num_words = pluginsd_split_words(value, words, 10, NULL, NULL, 0); + size_t num_words = pluginsd_split_words(value, words, 10); int pattern = 0; size_t i = 0; @@ -1533,7 +1533,7 @@ static int statsd_readfile(const char *filename, STATSD_APP *app, STATSD_APP_CHA ); if(pattern) - dim->metric_pattern = simple_pattern_create(dim->metric, NULL, SIMPLE_PATTERN_EXACT); + dim->metric_pattern = simple_pattern_create(dim->metric, NULL, SIMPLE_PATTERN_EXACT, true); } else { error("STATSD: ignoring line %zu ('%s') of file '%s'. Unknown keyword for the [%s] section.", line, name, filename, chart->id); @@ -2129,7 +2129,7 @@ static inline void check_if_metric_is_for_app(STATSD_INDEX *index, STATSD_METRIC strcpy(wildcarded, dim->name); char *ws = &wildcarded[dim_name_len]; - if(simple_pattern_matches_extract(dim->metric_pattern, m->name, ws, wildcarded_len - dim_name_len)) { + if(simple_pattern_matches_extract(dim->metric_pattern, m->name, ws, wildcarded_len - dim_name_len) == SP_MATCHED_POSITIVE) { char *final_name = NULL; @@ -2462,7 +2462,9 @@ void *statsd_main(void *ptr) { statsd.recvmmsg_size = (size_t)config_get_number(CONFIG_SECTION_STATSD, "udp messages to process at once", (long long)statsd.recvmmsg_size); #endif - statsd.charts_for = simple_pattern_create(config_get(CONFIG_SECTION_STATSD, "create private charts for metrics matching", "*"), NULL, SIMPLE_PATTERN_EXACT); + statsd.charts_for = simple_pattern_create( + config_get(CONFIG_SECTION_STATSD, "create private charts for metrics matching", "*"), NULL, + SIMPLE_PATTERN_EXACT, true); statsd.max_private_charts_hard = (size_t)config_get_number(CONFIG_SECTION_STATSD, "max private charts hard limit", (long long)statsd.max_private_charts_hard); statsd.private_charts_rrd_history_entries = (int)config_get_number(CONFIG_SECTION_STATSD, "private charts history", default_rrd_history_entries); statsd.decimal_detail = (collected_number)config_get_number(CONFIG_SECTION_STATSD, "decimal detail", (long long int)statsd.decimal_detail); diff --git a/collectors/tc.plugin/README.md b/collectors/tc.plugin/README.md index bf8655a4..de5fd474 100644 --- a/collectors/tc.plugin/README.md +++ b/collectors/tc.plugin/README.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/tc.p sidebar_label: "tc.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Networking" +learn_rel_path: "Integrations/Monitor/Networking" --> # tc.plugin diff --git a/collectors/tc.plugin/metrics.csv b/collectors/tc.plugin/metrics.csv new file mode 100644 index 00000000..b8e15649 --- /dev/null +++ b/collectors/tc.plugin/metrics.csv @@ -0,0 +1,6 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +tc.qos,"network device, direction",a dimension per class,kilobits/s,"Class Usage",stacked,"device, name, family",tc.plugin, +tc.qos_packets,"network device, direction",a dimension per class,packets/s,"Class Packets",stacked,"device, name, family",tc.plugin, +tc.qos_dropped,"network device, direction",a dimension per class,packets/s,"Class Dropped Packets",stacked,"device, name, family",tc.plugin, +tc.qos_tokens,"network device, direction",a dimension per class,tokens,"Class Tokens",line,"device, name, family",tc.plugin, +tc.qos_ctokens,"network device, direction",a dimension per class,ctokens,"Class cTokens",line,"device, name, family",tc.plugin, \ No newline at end of file diff --git a/collectors/tc.plugin/plugin_tc.c b/collectors/tc.plugin/plugin_tc.c index a8ceca44..b7e493b6 100644 --- a/collectors/tc.plugin/plugin_tc.c +++ b/collectors/tc.plugin/plugin_tc.c @@ -478,9 +478,9 @@ static inline void tc_device_commit(struct tc_device *d) { localhost->rrd_update_every, d->enabled_all_classes_qdiscs ? RRDSET_TYPE_LINE : RRDSET_TYPE_STACKED); - rrdlabels_add(d->st_bytes->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO); - rrdlabels_add(d->st_bytes->rrdlabels, "name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO); - rrdlabels_add(d->st_bytes->rrdlabels, "family", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_packets->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_packets->rrdlabels, "name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_packets->rrdlabels, "family", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO); } else { if(unlikely(d->name_updated)) { @@ -490,10 +490,10 @@ static inline void tc_device_commit(struct tc_device *d) { } if(d->name && d->name_updated) - rrdlabels_add(d->st_bytes->rrdlabels, "name", string2str(d->name), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_packets->rrdlabels, "name", string2str(d->name), RRDLABEL_SRC_AUTO); if(d->family && d->family_updated) - rrdlabels_add(d->st_bytes->rrdlabels, "family", string2str(d->family), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_packets->rrdlabels, "family", string2str(d->family), RRDLABEL_SRC_AUTO); // TODO // update the family @@ -542,9 +542,9 @@ static inline void tc_device_commit(struct tc_device *d) { localhost->rrd_update_every, d->enabled_all_classes_qdiscs ? RRDSET_TYPE_LINE : RRDSET_TYPE_STACKED); - rrdlabels_add(d->st_bytes->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO); - rrdlabels_add(d->st_bytes->rrdlabels, "name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO); - rrdlabels_add(d->st_bytes->rrdlabels, "family", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_dropped->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_dropped->rrdlabels, "name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_dropped->rrdlabels, "family", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO); } else { if(unlikely(d->name_updated)) { @@ -554,10 +554,10 @@ static inline void tc_device_commit(struct tc_device *d) { } if(d->name && d->name_updated) - rrdlabels_add(d->st_bytes->rrdlabels, "name", string2str(d->name), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_dropped->rrdlabels, "name", string2str(d->name), RRDLABEL_SRC_AUTO); if(d->family && d->family_updated) - rrdlabels_add(d->st_bytes->rrdlabels, "family", string2str(d->family), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_dropped->rrdlabels, "family", string2str(d->family), RRDLABEL_SRC_AUTO); // TODO // update the family @@ -606,9 +606,9 @@ static inline void tc_device_commit(struct tc_device *d) { localhost->rrd_update_every, RRDSET_TYPE_LINE); - rrdlabels_add(d->st_bytes->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO); - rrdlabels_add(d->st_bytes->rrdlabels, "name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO); - rrdlabels_add(d->st_bytes->rrdlabels, "family", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_tokens->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_tokens->rrdlabels, "name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_tokens->rrdlabels, "family", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO); } else { if(unlikely(d->name_updated)) { @@ -618,10 +618,10 @@ static inline void tc_device_commit(struct tc_device *d) { } if(d->name && d->name_updated) - rrdlabels_add(d->st_bytes->rrdlabels, "name", string2str(d->name), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_tokens->rrdlabels, "name", string2str(d->name), RRDLABEL_SRC_AUTO); if(d->family && d->family_updated) - rrdlabels_add(d->st_bytes->rrdlabels, "family", string2str(d->family), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_tokens->rrdlabels, "family", string2str(d->family), RRDLABEL_SRC_AUTO); // TODO // update the family @@ -671,9 +671,9 @@ static inline void tc_device_commit(struct tc_device *d) { localhost->rrd_update_every, RRDSET_TYPE_LINE); - rrdlabels_add(d->st_bytes->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO); - rrdlabels_add(d->st_bytes->rrdlabels, "name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO); - rrdlabels_add(d->st_bytes->rrdlabels, "family", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_ctokens->rrdlabels, "device", string2str(d->id), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_ctokens->rrdlabels, "name", string2str(d->name?d->name:d->id), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_ctokens->rrdlabels, "family", string2str(d->family?d->family:d->id), RRDLABEL_SRC_AUTO); } else { debug(D_TC_LOOP, "TC: Updating _ctokens chart for device '%s'", string2str(d->name?d->name:d->id)); @@ -685,10 +685,10 @@ static inline void tc_device_commit(struct tc_device *d) { } if(d->name && d->name_updated) - rrdlabels_add(d->st_bytes->rrdlabels, "name", string2str(d->name), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_ctokens->rrdlabels, "name", string2str(d->name), RRDLABEL_SRC_AUTO); if(d->family && d->family_updated) - rrdlabels_add(d->st_bytes->rrdlabels, "family", string2str(d->family), RRDLABEL_SRC_AUTO); + rrdlabels_add(d->st_ctokens->rrdlabels, "family", string2str(d->family), RRDLABEL_SRC_AUTO); // TODO // update the family @@ -1065,7 +1065,7 @@ void *tc_main(void *ptr) { // debug(D_TC_LOOP, "SENT line '%s'", words[1]); if(likely(words[1] && *words[1])) { - class->bytes = str2ull(words[1]); + class->bytes = str2ull(words[1], NULL); class->updated = true; } else { @@ -1073,10 +1073,10 @@ void *tc_main(void *ptr) { } if(likely(words[3] && *words[3])) - class->packets = str2ull(words[3]); + class->packets = str2ull(words[3], NULL); if(likely(words[6] && *words[6])) - class->dropped = str2ull(words[6]); + class->dropped = str2ull(words[6], NULL); //if(likely(words[8] && *words[8])) // class->overlimits = str2ull(words[8]); @@ -1102,10 +1102,10 @@ void *tc_main(void *ptr) { // debug(D_TC_LOOP, "TOKENS line '%s'", words[1]); if(likely(words[1] && *words[1])) - class->tokens = str2ull(words[1]); + class->tokens = str2ull(words[1], NULL); if(likely(words[3] && *words[3])) - class->ctokens = str2ull(words[3]); + class->ctokens = str2ull(words[3], NULL); } else if(unlikely(device && first_hash == SETDEVICENAME_HASH && strcmp(words[0], "SETDEVICENAME") == 0)) { worker_is_busy(WORKER_TC_SETDEVICENAME); diff --git a/collectors/timex.plugin/README.md b/collectors/timex.plugin/README.md index ba202075..6173503b 100644 --- a/collectors/timex.plugin/README.md +++ b/collectors/timex.plugin/README.md @@ -5,7 +5,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/time sidebar_label: "timex.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/System metrics" +learn_rel_path: "Integrations/Monitor/System metrics" --> # timex.plugin diff --git a/collectors/timex.plugin/metrics.csv b/collectors/timex.plugin/metrics.csv new file mode 100644 index 00000000..c7e59cca --- /dev/null +++ b/collectors/timex.plugin/metrics.csv @@ -0,0 +1,4 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +system.clock_sync_state,,state,state,"System Clock Synchronization State",line,,timex.plugin, +system.clock_status,,"unsync, clockerr",status,"System Clock Status",line,,timex.plugin, +system.clock_sync_offset,,offset,milliseconds,"Computed Time Offset Between Local System and Reference Clock",line,,timex.plugin, \ No newline at end of file diff --git a/collectors/xenstat.plugin/README.md b/collectors/xenstat.plugin/README.md index 11c2bfdb..8d17a33c 100644 --- a/collectors/xenstat.plugin/README.md +++ b/collectors/xenstat.plugin/README.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/xens sidebar_label: "xenstat.plugin" learn_status: "Published" learn_topic_type: "References" -learn_rel_path: "References/Collectors references/Virtualized environments/Virtualize hosts" +learn_rel_path: "Integrations/Monitor/Virtualized environments/Virtualize hosts" --> # xenstat.plugin diff --git a/collectors/xenstat.plugin/metrics.csv b/collectors/xenstat.plugin/metrics.csv new file mode 100644 index 00000000..2256ddf1 --- /dev/null +++ b/collectors/xenstat.plugin/metrics.csv @@ -0,0 +1,16 @@ +metric,scope,dimensions,unit,description,chart_type,labels,plugin,module +xenstat.mem,,"free, used",MiB,"Memory Usage",stacked,,xenstat.plugin, +xenstat.domains,,domains,domains,"Number of Domains",line,,xenstat.plugin, +xenstat.cpus,,cpus,cpus,"Number of CPUs",line,,xenstat.plugin, +xenstat.cpu_freq,,frequency,MHz,"CPU Frequency",line,,xenstat.plugin, +xendomain.states,xendomain,"running, blocked, paused, shutdown, crashed, dying",boolean,"Domain States",line,,xenstat.plugin, +xendomain.cpu,xendomain,used,percentage,"CPU Usage (100% = 1 core)",line,,xenstat.plugin, +xendomain.mem,xendomain,"maximum, current",MiB,"Memory Reservation",line,,xenstat.plugin, +xendomain.vcpu,xendomain,a dimension per vcpu,percentage,"CPU Usage per VCPU",line,,xenstat.plugin, +xendomain.oo_req_vbd,"xendomain, vbd",requests,requests/s,"VBD{%u} Out Of Requests",line,,xenstat.plugin, +xendomain.requests_vbd,"xendomain, vbd","read, write",requests/s,"VBD{%u} Requests",line,,xenstat.plugin, +xendomain.sectors_vbd,"xendomain, vbd","read, write",sectors/s,"VBD{%u} Read/Written Sectors",line,,xenstat.plugin, +xendomain.bytes_network,"xendomain, network","received, sent",kilobits/s,"Network{%u} Received/Sent Bytes",line,,xenstat.plugin, +xendomain.packets_network,"xendomain, network","received, sent",packets/s,"Network{%u} Received/Sent Packets",line,,xenstat.plugin, +xendomain.errors_network,"xendomain, network","received, sent",errors/s,"Network{%u} Receive/Transmit Errors",line,,xenstat.plugin, +xendomain.drops_network,"xendomain, network","received, sent",drops/s,"Network{%u} Receive/Transmit Drops",line,,xenstat.plugin, \ No newline at end of file diff --git a/configure.ac b/configure.ac index 8c001322..3c4a75ae 100644 --- a/configure.ac +++ b/configure.ac @@ -233,6 +233,55 @@ if test "${enable_cloud}" = "no"; then AC_DEFINE([DISABLE_CLOUD], [1], [disable netdata cloud functionality]) fi +# ----------------------------------------------------------------------------- +# C++ version check + +# Check for C++17 support (optional) +# AX_CXX_COMPILE_STDCXX(17, noext, optional) + +if test "x$HAVE_CXX17" != "x1"; then + # Check for C++11 support (optional) + AX_CXX_COMPILE_STDCXX(11, noext, optional) +fi + +AC_MSG_CHECKING([c++ standard to use]) +if test "x$HAVE_CXX17" = "x1"; then + have_cxx17="yes" + have_cxx11="yes" + CPP_STD_FLAG="-std=c++17" + cpp_std_to_use="c++17" + AM_CONDITIONAL([HAVE_CXX17], [true]) + AM_CONDITIONAL([HAVE_CXX11], [true]) +elif test "x$HAVE_CXX11" = "x1"; then + have_cxx17="no" + have_cxx11="yes" + CPP_STD_FLAG="-std=c++11" + cpp_std_to_use="c++11" + AM_CONDITIONAL([HAVE_CXX17], [false]) + AM_CONDITIONAL([HAVE_CXX11], [true]) +else + have_cxx17="no" + have_cxx11="no" + CPP_STD_FLAG="" + cpp_std_to_use="no c++" + AM_CONDITIONAL([HAVE_CXX17], [false]) + AM_CONDITIONAL([HAVE_CXX11], [false]) +fi + +# PPC64LE needs -std=gnu++11 in order to build dlib. However, the rest of +# the agent's components use and have been tested only with -std=c++11. +# Skip ML compilation on that CPU until we reorganize and test the C++ flags. +if test "${host_cpu}" = "powerpc64le"; then + have_cxx17="no" + have_cxx11="no" + CPP_STD_FLAG="" + cpp_std_to_use="no c++ on powerpc64le" + AM_CONDITIONAL([HAVE_CXX17], [false]) + AM_CONDITIONAL([HAVE_CXX11], [false]) +fi + +AC_MSG_RESULT([${cpp_std_to_use}]) + # ----------------------------------------------------------------------------- # netdata required checks @@ -347,6 +396,17 @@ AC_CHECK_LIB( [AC_DEFINE([HAVE_PTHREAD_GETNAME_NP], [1], [Is set if pthread_getname_np is available])] ) +# ----------------------------------------------------------------------------- +# libdatachannel + +AC_CHECK_LIB([datachannel], [rtcCreatePeerConnection], + [LIBDATACHANNEL_FOUND=yes], + [LIBDATACHANNEL_FOUND=no]) + +if test "x$LIBDATACHANNEL_FOUND" = "xyes"; then + AC_DEFINE([HAVE_LIBDATACHANNEL], [1], [libdatachannel usability]) + OPTIONAL_DATACHANNEL_LIBS="-ldatachannel" +fi # ----------------------------------------------------------------------------- # libm @@ -481,6 +541,17 @@ PKG_CHECK_MODULES([JSON],[json-c],AC_CHECK_LIB( OPTIONAL_JSONC_LIBS="${JSONC_LIBS}" +# ----------------------------------------------------------------------------- +# YAML library + +AC_CHECK_LIB( + [yaml], + [yaml_parser_initialize], + [YAML_LIBS="-lyaml"] +) + +OPTIONAL_YAML_LIBS="${YAML_LIBS}" + # ----------------------------------------------------------------------------- # DB engine and HTTPS test "${enable_dbengine}" = "yes" -a -z "${LZ4_LIBS}" && \ @@ -610,6 +681,38 @@ fi AC_MSG_RESULT([${enable_jsonc}]) AM_CONDITIONAL([ENABLE_JSONC], [test "${enable_jsonc}" = "yes"]) +# ----------------------------------------------------------------------------- +# YAML + +if test -z "${YAML_LIBS}"; then + # Try and detect manual static build presence (from netdata-installer.sh) + AC_MSG_CHECKING([if statically built libyaml is present]) + HAVE_libyaml_a="no" + if test -f "externaldeps/libyaml/libyaml.a"; then + LIBS_BKP="${LIBS}" + LIBS="externaldeps/libyaml/libyaml.a" + AC_LINK_IFELSE([AC_LANG_SOURCE([[#include "externaldeps/libyaml/yaml.h" + int main (int argc, char **argv) { + yaml_parser_t parser; + yaml_parser_initialize(&parser); + }]])], + [HAVE_libyaml_a="yes"], + [HAVE_libyaml_a="no"]) + LIBS="${LIBS_BKP}" + fi + + if test "${HAVE_libyaml_a}" = "yes"; then + AC_DEFINE([LINK_STATIC_YAML], [1], [static yaml should be used]) + YAML_LIBS="static" + OPTIONAL_YAML_STATIC_CFLAGS="-I \$(abs_top_srcdir)/externaldeps/libyaml" + fi + AC_MSG_RESULT([${HAVE_libyaml_a}]) +fi +AM_CONDITIONAL([LINK_STATIC_YAML], [test "${YAML_LIBS}" = "static"]) + +test -z "${YAML_LIBS}" && \ + AC_MSG_ERROR([LIBYAML required but not found. Try installing 'libyaml-dev'.]) + # ----------------------------------------------------------------------------- # compiler options @@ -815,7 +918,6 @@ if test "$enable_cloud" != "no"; then 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)/aclk/aclk-schemas" OPTIONAL_PROTOBUF_CFLAGS="${PROTOBUF_CFLAGS}" - CXX11FLAG="-std=c++11" OPTIONAL_PROTOBUF_LIBS="${PROTOBUF_LIBS}" fi fi @@ -952,17 +1054,8 @@ AC_CHECK_HEADER( [have_nfnetlink_conntrack=no] ) -PKG_CHECK_MODULES( - [NFACCT], - [libnetfilter_acct], - [AC_CHECK_LIB( - [netfilter_acct], - [nfacct_alloc], - [have_libnetfilter_acct=yes], - [have_libnetfilter_acct=no] - )], - [have_libnetfilter_acct=no] -) +LIBS_BAK="${LIBS}" +LIBS="${LIBS} -lmnl" PKG_CHECK_MODULES( [LIBMNL], @@ -976,6 +1069,24 @@ PKG_CHECK_MODULES( [have_libmnl=no] ) + +LIBS="${LIBS}" + +PKG_CHECK_MODULES( + [NFACCT], + [libnetfilter_acct], + AC_SEARCH_LIBS( + [nfacct_alloc], + [netfilter_acct libnetfilter_acct], + [have_libnetfilter_acct=yes], + [have_libnetfilter_acct=no], + [${LIBS} -L/libnetfilter-acct-static/lib/libnetfilter_acct.a] + )], + [have_libnetfilter_acct=no] +) + +LIBS="${LIBS_BAK}" + test "${enable_plugin_nfacct}" = "yes" -a "${have_nfnetlink_conntrack}" != "yes" && \ AC_MSG_ERROR([nfnetlink_conntrack.h required but not found or too old]) @@ -1131,7 +1242,7 @@ fi # Check if submodules have not been fetched. Fail if ML was explicitly requested. AC_MSG_CHECKING([if git submodules are present for machine learning functionality]) -if test -f "ml/dlib/dlib/all/source.cpp" -a -f "ml/json/single_include/nlohmann/json.hpp"; then +if test -f "ml/dlib/dlib/all/source.cpp"; then AC_MSG_RESULT([yes]) have_ml_submodules="yes" else @@ -1143,22 +1254,8 @@ if test "${enable_ml}" = "yes" -a "${have_ml_submodules}" = "no"; then AC_MSG_ERROR([You have explicitly requested --enable-ml functionality but it cannot be built because the required git submodules are missing.]) fi -# Check if C++ toolchain does not support C++11. Fail if ML was explicitly requested. -AC_LANG_PUSH([C++]) -AX_CHECK_COMPILE_FLAG([-std=c++11], [have_cxx11=yes], [have_cxx11=no]) -AC_LANG_POP([C++]) - -# PPC64LE needs -std=gnu++11 in order to build dlib. However, the rest of -# the agent's components use and have been tested only with -std=c++11. -# Skip ML compilation on that CPU until we reorganize and test the C++ flags. -if test "${host_cpu}" = "powerpc64le"; then - have_cxx11="no" -fi - if test "${enable_ml}" = "yes" -a "${have_cxx11}" = "no"; then AC_MSG_ERROR([You have explicitly requested --enable-ml functionality but it cannot be built without a C++11 toolchain.]) -else - CXX11FLAG="$CXX11FLAG -std=c++11" fi # Decide if we should build ML @@ -1308,7 +1405,7 @@ if test "${enable_exporting_kinesis}" != "no" -a "${have_libaws_cpp_sdk_kinesis} enable_exporting_kinesis="yes" AC_DEFINE([HAVE_KINESIS], [1], [libaws-cpp-sdk-kinesis usability]) OPTIONAL_KINESIS_CFLAGS="${LIBCRYPTO_CFLAGS} ${LIBSSL_CFLAGS} ${LIBCURL_CFLAGS}" - CXX11FLAG="${AWS_CPP_SDK_KINESIS_CFLAGS} ${AWS_CPP_SDK_CORE_CFLAGS}" + OPTIONAL_KINESIS_CXXFLAGS="${AWS_CPP_SDK_KINESIS_CFLAGS} ${AWS_CPP_SDK_CORE_CFLAGS}" OPTIONAL_KINESIS_LIBS="${AWS_CPP_SDK_KINESIS_LIBS} ${AWS_CPP_SDK_CORE_LIBS} \ ${LIBCRYPTO_LIBS} ${LIBSSL_LIBS} ${LIBCURL_LIBS}" else @@ -1359,7 +1456,6 @@ if test "${enable_exporting_pubsub}" != "no" -a "${have_pubsub_protos}" = "yes" enable_exporting_pubsub="yes" AC_DEFINE([ENABLE_EXPORTING_PUBSUB], [1], [Pub/Sub API usability]) OPTIONAL_PUBSUB_CFLAGS="${GRPC_CFLAGS} ${PUBSUB_CFLAGS}" - CXX11FLAG="-std=c++11" OPTIONAL_PUBSUB_LIBS="${GRPC_LIBS} ${PUBSUB_LIBS}" else enable_pubsub="no" @@ -1379,7 +1475,7 @@ AC_MSG_CHECKING([for snappy::RawCompress in -lsnappy]) save_LIBS="${LIBS}" LIBS="-lsnappy" save_CXXFLAGS="${CXXFLAGS}" - CXXFLAGS="${CXXFLAGS} -std=c++11" + CXXFLAGS="${CXXFLAGS} ${CPP_STD_FLAG}" AC_TRY_LINK( [ @@ -1425,7 +1521,6 @@ if test "${enable_exporting_prometheus_remote_write}" != "no" -a "${have_libprot enable_exporting_prometheus_remote_write="yes" AC_DEFINE([ENABLE_PROMETHEUS_REMOTE_WRITE], [1], [Prometheus remote write API usability]) OPTIONAL_PROMETHEUS_REMOTE_WRITE_CFLAGS="${SNAPPY_CFLAGS} -I \$(abs_top_srcdir)/exporting/prometheus/remote_write" - CXX11FLAG="-std=c++11" OPTIONAL_PROMETHEUS_REMOTE_WRITE_LIBS="${SNAPPY_LIBS}" OPTIONAL_PROTOBUF_CFLAGS="${PROTOBUF_CFLAGS}" OPTIONAL_PROTOBUF_LIBS="${PROTOBUF_LIBS}" @@ -1593,10 +1688,10 @@ CFLAGS="${originalCFLAGS} ${OPTIONAL_LTO_CFLAGS} ${OPTIONAL_PROTOBUF_CFLAGS} ${O ${OPTIONAL_ZLIB_CFLAGS} ${OPTIONAL_UUID_CFLAGS} \ ${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_MONGOC_CFLAGS} ${LWS_CFLAGS} ${OPTIONAL_JSONC_STATIC_CFLAGS} ${OPTIONAL_YAML_STATIC_CFLAGS} ${OPTIONAL_BPF_CFLAGS} ${JUDY_CFLAGS} \ ${OPTIONAL_ACLK_CFLAGS} ${OPTIONAL_ML_CFLAGS} ${OPTIONAL_OS_DEP_CFLAGS}" -CXXFLAGS="${CFLAGS} ${CXX11FLAG}" +CXXFLAGS="${CFLAGS} ${OPTIONAL_KINESIS_CXXFLAGS} ${CPP_STD_FLAG}" CPPFLAGS="\ -DVARLIB_DIR=\"\\\"${varlibdir}\\\"\" \ @@ -1611,10 +1706,12 @@ CPPFLAGS="\ AC_SUBST([OPTIONAL_MATH_CFLAGS]) AC_SUBST([OPTIONAL_MATH_LIBS]) +AC_SUBST([OPTIONAL_DATACHANNEL_LIBS]) AC_SUBST([OPTIONAL_UV_LIBS]) AC_SUBST([OPTIONAL_LZ4_LIBS]) AC_SUBST([OPTIONAL_SSL_LIBS]) AC_SUBST([OPTIONAL_JSONC_LIBS]) +AC_SUBST([OPTIONAL_YAML_LIBS]) AC_SUBST([OPTIONAL_NFACCT_CFLAGS]) AC_SUBST([OPTIONAL_NFACCT_LIBS]) AC_SUBST([OPTIONAL_ZLIB_CFLAGS]) @@ -1711,6 +1808,7 @@ AC_CONFIG_FILES([ collectors/perf.plugin/Makefile daemon/Makefile database/Makefile + database/contexts/Makefile database/engine/Makefile database/ram/Makefile database/sqlite/Makefile @@ -1743,6 +1841,7 @@ AC_CONFIG_FILES([ libnetdata/locks/Makefile libnetdata/log/Makefile libnetdata/onewayalloc/Makefile + libnetdata/parser/Makefile libnetdata/popen/Makefile libnetdata/procfile/Makefile libnetdata/simple_pattern/Makefile @@ -1787,11 +1886,11 @@ AC_CONFIG_FILES([ web/api/health/Makefile web/gui/Makefile web/gui/dashboard/Makefile + web/rtc/Makefile web/server/Makefile web/server/static/Makefile claim/Makefile spawn/Makefile - parser/Makefile ]) AC_OUTPUT diff --git a/contrib/README.md b/contrib/README.md index 36abd3b1..2a5e6405 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -1,6 +1,10 @@ # Netdata contrib diff --git a/contrib/debian/conffiles b/contrib/debian/conffiles new file mode 100644 index 00000000..5ea613f3 --- /dev/null +++ b/contrib/debian/conffiles @@ -0,0 +1,4 @@ +/etc/default/netdata +/etc/init.d/netdata +/etc/logrotate.d/netdata +/etc/netdata/netdata.conf diff --git a/contrib/debian/control b/contrib/debian/control index c5e5791f..eeeb8d25 100644 --- a/contrib/debian/control +++ b/contrib/debian/control @@ -10,6 +10,7 @@ Build-Depends: debhelper (>= 9.20160709), libssl-dev, libmnl-dev, libjson-c-dev, + libyaml-dev, libcups2-dev, libipmimonitoring-dev, libnetfilter-acct-dev, diff --git a/contrib/debian/rules b/contrib/debian/rules index a4820cd4..149b19ec 100755 --- a/contrib/debian/rules +++ b/contrib/debian/rules @@ -10,9 +10,9 @@ BASE_CONFIG = system/netdata.conf SYSTEMD_VERSION = $(shell /bin/sh -c "systemd --version 2>&1 | head -n 1 | cut -f 2 -d ' '") ifeq ($(shell test $(SYSTEMD_VERSION) -ge 235 && echo "1"), 1) -SYSTEMD_UNIT = system/netdata.service.v235 +SYSTEMD_UNIT = system/systemd/netdata.service.v235 else -SYSTEMD_UNIT = system/netdata.service +SYSTEMD_UNIT = system/systemd/netdata.service endif ifeq ($(shell test `uname -m` != "x86_64" && echo "1"), 1) @@ -124,9 +124,14 @@ override_dh_fixperms: chmod 4750 $(TOP)-plugin-freeipmi/usr/libexec/netdata/plugins.d/freeipmi.plugin override_dh_installlogrotate: - cp system/netdata.logrotate debian/netdata.logrotate + cp system/logrotate/netdata debian/netdata.logrotate dh_installlogrotate +override_dh_installdeb: + dh_installdeb + @echo "Recreating conffiles without auto-adding /etc files" + @cp $(CURDIR)/debian/conffiles $(CURDIR)/debian/netdata/DEBIAN/conffiles + override_dh_clean: dh_clean diff --git a/contribution-guidelines.md b/contribution-guidelines.md deleted file mode 100644 index f6592980..00000000 --- a/contribution-guidelines.md +++ /dev/null @@ -1,817 +0,0 @@ -# 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: - -
-Reference Docs - -The template that is used for Reference files is: - -``` - -``` - -## Configuration files - -### Data collection - -``` -go.d/apache.conf -``` - -To make changes, see `the ./edit-config task ` - -### 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 - -## 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 - -
- -
-Task Docs - -The template that is used for Task files is: - -``` - -``` - -## 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 - -
- -
-Concept Docs - -The template of the Concept files is: - -``` - -``` - -## 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. - -
- -## 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
Service Discovery and Auto-Detection:
Install netdata with docker | -| **Recommended** | Getting started guide
Service discovery and auto-detection
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.
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.
Run `systemctl restart netdata` to restart the Netdata Agent. | -| **Recommended** | Run `sudo edit-config netdata.conf` to configure the Netdata Agent.
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 `

heading

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 - - -# 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*.

**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.

**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.

**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.

*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.

*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/coverity-scan.sh b/coverity-scan.sh index 9737545d..f144444f 100755 --- a/coverity-scan.sh +++ b/coverity-scan.sh @@ -40,7 +40,7 @@ set -e INSTALL_DIR="/opt" # the version of coverity to use -COVERITY_BUILD_VERSION="${COVERITY_BUILD_VERSION:-cov-analysis-linux64-2022.06}" +COVERITY_BUILD_VERSION="${COVERITY_BUILD_VERSION:-cov-analysis-linux64-2022.12.2}" # TODO: For some reasons this does not fully load on Debian 10 (Haven't checked if it happens on other distros yet), it breaks source packaging/installer/functions.sh || echo "Failed to fully load the functions library" @@ -150,6 +150,7 @@ installit() { COVERITY_PATH=$(find "${INSTALL_DIR}" -maxdepth 1 -name 'cov*linux*') export PATH=${PATH}:${COVERITY_PATH}/bin/ elif find . -name "*.tar.gz" > /dev/null 2>&1; then + ls ./*.tar.gz fatal "Downloaded coverity tool tarball does not appear to be the version we were expecting, exiting." else fatal "Failed to download coverity tool tarball!" diff --git a/daemon/README.md b/daemon/README.md index 7a17506b..65ac105c 100644 --- a/daemon/README.md +++ b/daemon/README.md @@ -1,102 +1,10 @@ - - # Netdata daemon -## Starting netdata - -- 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](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: - -```sh -http://127.0.0.1:19999/ -``` - -You can get the running config file at any time, by accessing `http://127.0.0.1:19999/netdata.conf`. - -### Starting Netdata at boot - -In the `system` directory you can find scripts and configurations for the -various distros. - -#### systemd - -The installer already installs `netdata.service` if it detects a systemd system. - -To install `netdata.service` by hand, run: - -```sh -# stop Netdata -killall netdata - -# copy netdata.service to systemd -cp system/netdata.service /etc/systemd/system/ - -# let systemd know there is a new service -systemctl daemon-reload - -# enable Netdata at boot -systemctl enable netdata - -# start Netdata -systemctl start netdata -``` - -#### init.d - -In the system directory you can find `netdata-lsb`. Copy it to the proper place according to your distribution -documentation. For Ubuntu, this can be done via running the following commands as root. - -```sh -# copy the Netdata startup file to /etc/init.d -cp system/netdata-lsb /etc/init.d/netdata - -# make sure it is executable -chmod +x /etc/init.d/netdata - -# enable it -update-rc.d netdata defaults -``` - -#### openrc (gentoo) - -In the `system` directory you can find `netdata-openrc`. Copy it to the proper -place according to your distribution documentation. - -#### CentOS / Red Hat Enterprise Linux +The Netdata daemon is practically a synonym for the Netdata Agent, as it controls its +entire operation. We support various methods to +[start, stop, or restart the daemon](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md). -For older versions of RHEL/CentOS that don't have systemd, an init script is included in the system directory. This can -be installed by running the following commands as root. - -```sh -# copy the Netdata startup file to /etc/init.d -cp system/netdata-init-d /etc/init.d/netdata - -# make sure it is executable -chmod +x /etc/init.d/netdata - -# enable it -chkconfig --add netdata -``` - -_There have been some recent work on the init script, see PR -_ - -#### other systems - -You can start Netdata by running it from `/etc/rc.local` or equivalent. +This document provides some basic information on the command line options, log files, and how to debug and troubleshoot ## Command line options @@ -303,7 +211,7 @@ You can use the following: For more information see `man sched`. -### scheduling priority for `rr` and `fifo` +### Scheduling priority for `rr` and `fifo` Once the policy is set to one of `rr` or `fifo`, the following will appear: @@ -324,7 +232,7 @@ When the policy is set to `other`, `nice`, or `batch`, the following will appear process nice level = 19 ``` -## scheduling settings and systemd +## Scheduling settings and systemd Netdata will not be able to set its scheduling policy and priority to more important values when it is started as the `netdata` user (systemd case). @@ -472,7 +380,7 @@ will contain the messages. > Do not forget to disable tracing (`debug flags = 0`) when you are done tracing. The file `debug.log` can grow too > fast. -### compiling Netdata with debugging +### Compiling Netdata with debugging To compile Netdata with debugging, use this: @@ -487,7 +395,7 @@ CFLAGS="-O1 -ggdb -DNETDATA_INTERNAL_CHECKS=1" ./netdata-installer.sh The above will compile and install Netdata with debugging info embedded. You can now use `debug flags` to set the section(s) you need to trace. -### debugging crashes +### Debugging crashes We have made the most to make Netdata crash free. If however, Netdata crashes on your system, it would be very helpful to provide stack traces of the crash. Without them, is will be almost impossible to find the issue (the code base is @@ -515,7 +423,7 @@ Run the following command and post the output on a github issue. gdb $(which netdata) /path/to/core/dump ``` -### you can reproduce a Netdata crash on your system +### You can reproduce a Netdata crash on your system > you need to have Netdata compiled with debugging info for this to work (check above) @@ -527,5 +435,3 @@ valgrind $(which netdata) -D Netdata will start and it will be a lot slower. Now reproduce the crash and `valgrind` will dump on your console the stack trace. Open a new github issue and post the output. - - diff --git a/daemon/analytics.c b/daemon/analytics.c index a2f52bc8..b3c802b8 100644 --- a/daemon/analytics.c +++ b/daemon/analytics.c @@ -140,14 +140,6 @@ void analytics_set_data_str(char **name, char *value) analytics_data.data_length += strlen(*name); } -/* - * Get data, used by web api v1 - */ -void analytics_get_data(char *name, BUFFER *wb) -{ - buffer_strcat(wb, name); -} - /* * Log hits on the allmetrics page, with prometheus parameter */ @@ -155,8 +147,8 @@ void analytics_log_prometheus(void) { if (netdata_anonymous_statistics_enabled == 1 && likely(analytics_data.prometheus_hits < ANALYTICS_MAX_PROMETHEUS_HITS)) { analytics_data.prometheus_hits++; - char b[7]; - snprintfz(b, 6, "%d", analytics_data.prometheus_hits); + char b[21]; + snprintfz(b, 20, "%zu", analytics_data.prometheus_hits); analytics_set_data(&analytics_data.netdata_allmetrics_prometheus_used, b); } } @@ -168,8 +160,8 @@ void analytics_log_shell(void) { if (netdata_anonymous_statistics_enabled == 1 && likely(analytics_data.shell_hits < ANALYTICS_MAX_SHELL_HITS)) { analytics_data.shell_hits++; - char b[7]; - snprintfz(b, 6, "%d", analytics_data.shell_hits); + char b[21]; + snprintfz(b, 20, "%zu", analytics_data.shell_hits); analytics_set_data(&analytics_data.netdata_allmetrics_shell_used, b); } } @@ -181,8 +173,8 @@ void analytics_log_json(void) { if (netdata_anonymous_statistics_enabled == 1 && likely(analytics_data.json_hits < ANALYTICS_MAX_JSON_HITS)) { analytics_data.json_hits++; - char b[7]; - snprintfz(b, 6, "%d", analytics_data.json_hits); + char b[21]; + snprintfz(b, 20, "%zu", analytics_data.json_hits); analytics_set_data(&analytics_data.netdata_allmetrics_json_used, b); } } @@ -194,8 +186,8 @@ void analytics_log_dashboard(void) { if (netdata_anonymous_statistics_enabled == 1 && likely(analytics_data.dashboard_hits < ANALYTICS_MAX_DASHBOARD_HITS)) { analytics_data.dashboard_hits++; - char b[7]; - snprintfz(b, 6, "%d", analytics_data.dashboard_hits); + char b[21]; + snprintfz(b, 20, "%zu", analytics_data.dashboard_hits); analytics_set_data(&analytics_data.netdata_dashboard_used, b); } } @@ -204,18 +196,18 @@ void analytics_log_dashboard(void) * Called when setting the oom score */ void analytics_report_oom_score(long long int score){ - char b[7]; - snprintfz(b, 6, "%d", (int)score); + char b[21]; + snprintfz(b, 20, "%lld", score); analytics_set_data(&analytics_data.netdata_config_oom_score, b); } void analytics_mirrored_hosts(void) { RRDHOST *host; - int count = 0; - int reachable = 0; - int unreachable = 0; - char b[11]; + size_t count = 0; + size_t reachable = 0; + size_t unreachable = 0; + char b[21]; rrd_rdlock(); rrdhost_foreach_read(host) @@ -229,11 +221,11 @@ void analytics_mirrored_hosts(void) } rrd_unlock(); - snprintfz(b, 10, "%d", count); + snprintfz(b, 20, "%zu", count); analytics_set_data(&analytics_data.netdata_mirrored_host_count, b); - snprintfz(b, 10, "%d", reachable); + snprintfz(b, 20, "%zu", reachable); analytics_set_data(&analytics_data.netdata_mirrored_hosts_reachable, b); - snprintfz(b, 10, "%d", unreachable); + snprintfz(b, 20, "%zu", unreachable); analytics_set_data(&analytics_data.netdata_mirrored_hosts_unreachable, b); } @@ -303,8 +295,8 @@ void analytics_collectors(void) analytics_set_data(&analytics_data.netdata_collectors, (char *)buffer_tostring(ap.both)); { - char b[7]; - snprintfz(b, 6, "%d", ap.c); + char b[21]; + snprintfz(b, 20, "%d", ap.c); analytics_set_data(&analytics_data.netdata_collectors_count, b); } @@ -362,16 +354,16 @@ void analytics_alarms_notifications(void) buffer_free(b); } -void analytics_get_install_type(void) +static void analytics_get_install_type(struct rrdhost_system_info *system_info) { - if (localhost->system_info->install_type == NULL) { + if (system_info->install_type == NULL) { analytics_set_data_str(&analytics_data.netdata_install_type, "unknown"); } else { - analytics_set_data_str(&analytics_data.netdata_install_type, localhost->system_info->install_type); + analytics_set_data_str(&analytics_data.netdata_install_type, system_info->install_type); } - if (localhost->system_info->prebuilt_dist != NULL) { - analytics_set_data_str(&analytics_data.netdata_prebuilt_distro, localhost->system_info->prebuilt_dist); + if (system_info->prebuilt_dist != NULL) { + analytics_set_data_str(&analytics_data.netdata_prebuilt_distro, system_info->prebuilt_dist); } } @@ -396,15 +388,16 @@ void analytics_https(void) void analytics_charts(void) { RRDSET *st; - int c = 0; + size_t c = 0; rrdset_foreach_read(st, localhost) if(rrdset_is_available_for_viewers(st)) c++; rrdset_foreach_done(st); + analytics_data.charts_count = c; { - char b[7]; - snprintfz(b, 6, "%d", c); + char b[21]; + snprintfz(b, 20, "%zu", c); analytics_set_data(&analytics_data.netdata_charts_count, b); } } @@ -412,7 +405,7 @@ void analytics_charts(void) void analytics_metrics(void) { RRDSET *st; - long int dimensions = 0; + size_t dimensions = 0; rrdset_foreach_read(st, localhost) { if (rrdset_is_available_for_viewers(st)) { RRDDIM *rd; @@ -426,17 +419,18 @@ void analytics_metrics(void) } rrdset_foreach_done(st); + analytics_data.metrics_count = dimensions; { - char b[7]; - snprintfz(b, 6, "%ld", dimensions); + char b[21]; + snprintfz(b, 20, "%zu", dimensions); analytics_set_data(&analytics_data.netdata_metrics_count, b); } } void analytics_alarms(void) { - int alarm_warn = 0, alarm_crit = 0, alarm_normal = 0; - char b[10]; + size_t alarm_warn = 0, alarm_crit = 0, alarm_normal = 0; + char b[21]; RRDCALC *rc; foreach_rrdcalc_in_rrdhost_read(localhost, rc) { if (unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec)) @@ -455,11 +449,11 @@ void analytics_alarms(void) } foreach_rrdcalc_in_rrdhost_done(rc); - snprintfz(b, 9, "%d", alarm_normal); + snprintfz(b, 20, "%zu", alarm_normal); analytics_set_data(&analytics_data.netdata_alarms_normal, b); - snprintfz(b, 9, "%d", alarm_warn); + snprintfz(b, 20, "%zu", alarm_warn); analytics_set_data(&analytics_data.netdata_alarms_warning, b); - snprintfz(b, 9, "%d", alarm_crit); + snprintfz(b, 20, "%zu", alarm_crit); analytics_set_data(&analytics_data.netdata_alarms_critical, b); } @@ -476,7 +470,8 @@ void analytics_misc(void) analytics_set_data_str(&analytics_data.netdata_host_aclk_implementation, ""); #endif - analytics_set_data(&analytics_data.netdata_config_exporting_enabled, appconfig_get_boolean(&exporting_config, CONFIG_SECTION_EXPORTING, "enabled", CONFIG_BOOLEAN_NO) ? "true" : "false"); + analytics_data.exporting_enabled = appconfig_get_boolean(&exporting_config, CONFIG_SECTION_EXPORTING, "enabled", CONFIG_BOOLEAN_NO); + analytics_set_data(&analytics_data.netdata_config_exporting_enabled, analytics_data.exporting_enabled ? "true" : "false"); analytics_set_data(&analytics_data.netdata_config_is_private_registry, "false"); analytics_set_data(&analytics_data.netdata_config_use_private_registry, "false"); @@ -539,20 +534,20 @@ void analytics_gather_mutable_meta_data(void) freez(claim_id); { - char b[7]; - snprintfz(b, 6, "%d", analytics_data.prometheus_hits); + char b[21]; + snprintfz(b, 20, "%zu", analytics_data.prometheus_hits); analytics_set_data(&analytics_data.netdata_allmetrics_prometheus_used, b); - snprintfz(b, 6, "%d", analytics_data.shell_hits); + snprintfz(b, 20, "%zu", analytics_data.shell_hits); analytics_set_data(&analytics_data.netdata_allmetrics_shell_used, b); - snprintfz(b, 6, "%d", analytics_data.json_hits); + snprintfz(b, 20, "%zu", analytics_data.json_hits); analytics_set_data(&analytics_data.netdata_allmetrics_json_used, b); - snprintfz(b, 6, "%d", analytics_data.dashboard_hits); + snprintfz(b, 20, "%zu", analytics_data.dashboard_hits); analytics_set_data(&analytics_data.netdata_dashboard_used, b); - snprintfz(b, 6, "%zu", rrdhost_hosts_available()); + snprintfz(b, 20, "%zu", rrdhost_hosts_available()); analytics_set_data(&analytics_data.netdata_config_hosts_available, b); } } @@ -637,7 +632,7 @@ static const char *verify_required_directory(const char *dir) * This is called after the rrdinit * These values will be sent on the START event */ -void set_late_global_environment() +void set_late_global_environment(struct rrdhost_system_info *system_info) { analytics_set_data(&analytics_data.netdata_config_stream_enabled, default_rrdpush_enabled ? "true" : "false"); analytics_set_data_str(&analytics_data.netdata_config_memory_mode, (char *)rrd_memory_mode_name(default_rrd_memory_mode)); @@ -681,7 +676,7 @@ void set_late_global_environment() buffer_free(bi); } - analytics_get_install_type(); + analytics_get_install_type(system_info); } void get_system_timezone(void) @@ -894,6 +889,9 @@ void set_global_environment() analytics_data.shell_hits = 0; analytics_data.json_hits = 0; analytics_data.dashboard_hits = 0; + analytics_data.charts_count = 0; + analytics_data.metrics_count = 0; + analytics_data.exporting_enabled = false; char *default_port = appconfig_get(&netdata_config, CONFIG_SECTION_WEB, "default port", NULL); int clean = 0; diff --git a/daemon/analytics.h b/daemon/analytics.h index d1ffcec1..34418316 100644 --- a/daemon/analytics.h +++ b/daemon/analytics.h @@ -63,14 +63,18 @@ struct analytics_data { size_t data_length; - uint8_t prometheus_hits; - uint8_t shell_hits; - uint8_t json_hits; - uint8_t dashboard_hits; + size_t prometheus_hits; + size_t shell_hits; + size_t json_hits; + size_t dashboard_hits; + + size_t charts_count; + size_t metrics_count; + + bool exporting_enabled; }; -void analytics_get_data(char *name, BUFFER *wb); -void set_late_global_environment(void); +void set_late_global_environment(struct rrdhost_system_info *system_info); void analytics_free_data(void); void set_global_environment(void); void send_statistics(const char *action, const char *action_result, const char *action_data); diff --git a/daemon/anonymous-statistics.sh.in b/daemon/anonymous-statistics.sh.in index 9f8df188..8676ffbe 100755 --- a/daemon/anonymous-statistics.sh.in +++ b/daemon/anonymous-statistics.sh.in @@ -69,6 +69,7 @@ NETDATA_USE_PRIVATE_REGISTRY="${40}" NETDATA_CONFIG_OOM_SCORE="${41}" NETDATA_PREBUILT_DISTRO="${42}" +[ -z "$NETDATA_REGISTRY_UNIQUE_ID" ] && NETDATA_REGISTRY_UNIQUE_ID="00000000-0000-0000-0000-000000000000" # define body of request to be sent REQ_BODY="$(cat << EOF @@ -165,7 +166,7 @@ EOF # send the anonymous statistics to the Netdata PostHog if [ -n "$(command -v curl 2> /dev/null)" ]; then - curl --silent -o /dev/null --write-out '%{http_code}' -X POST --max-time 2 --header "Content-Type: application/json" -d "${REQ_BODY}" https://posthog.netdata.cloud/capture/ + curl --silent -o /dev/null --write-out '%{http_code}' -X POST --max-time 2 --header "Content-Type: application/json" -d "${REQ_BODY}" https://app.posthog.com/capture/ else wget -q -O - --no-check-certificate \ --server-response \ @@ -173,5 +174,5 @@ else --timeout=1 \ --header 'Content-Type: application/json' \ --body-data "${REQ_BODY}" \ - 'https://posthog.netdata.cloud/capture/' 2>&1 | awk '/^ HTTP/{print $2}' + 'https://app.posthog.com/capture/' 2>&1 | awk '/^ HTTP/{print $2}' fi diff --git a/daemon/commands.c b/daemon/commands.c index 377a4002..fcb75b71 100644 --- a/daemon/commands.c +++ b/daemon/commands.c @@ -47,6 +47,7 @@ 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 cmd_status_t cmd_dumpconfig(char *args, char **message); static command_info_t command_info_array[] = { {"help", cmd_help_execute, CMD_TYPE_HIGH_PRIORITY}, // show help menu @@ -61,7 +62,8 @@ static command_info_t command_info_array[] = { {"write-config", cmd_write_config_execute, CMD_TYPE_ORTHOGONAL}, {"ping", cmd_ping_execute, CMD_TYPE_ORTHOGONAL}, {"aclk-state", cmd_aclk_state, CMD_TYPE_ORTHOGONAL}, - {"version", cmd_version, CMD_TYPE_ORTHOGONAL} + {"version", cmd_version, CMD_TYPE_ORTHOGONAL}, + {"dumpconfig", cmd_dumpconfig, CMD_TYPE_ORTHOGONAL} }; /* Mutexes for commands of type CMD_TYPE_ORTHOGONAL */ @@ -127,6 +129,8 @@ static cmd_status_t cmd_help_execute(char *args, char **message) " Return with 'pong' if agent is alive.\n" "aclk-state [json]\n" " Returns current state of ACLK and Cloud connection. (optionally in json).\n" + "dumpconfig\n" + " Returns the current netdata.conf on stdout.\n" "version\n" " Returns the netdata version.\n", MAX_COMMAND_LENGTH - 1); @@ -330,6 +334,17 @@ static cmd_status_t cmd_version(char *args, char **message) return CMD_STATUS_SUCCESS; } +static cmd_status_t cmd_dumpconfig(char *args, char **message) +{ + (void)args; + + BUFFER *wb = buffer_create(1024, NULL); + config_generate(wb, 0); + *message = strdupz(buffer_tostring(wb)); + buffer_free(wb); + return CMD_STATUS_SUCCESS; +} + static void cmd_lock_exclusive(unsigned index) { (void)index; @@ -393,32 +408,30 @@ static void pipe_write_cb(uv_write_t* req, int status) uv_close((uv_handle_t *)client, pipe_close_cb); --clients; - freez(client->data); + buffer_free(client->data); info("Command Clients = %u\n", clients); } -static inline void add_char_to_command_reply(char *reply_string, unsigned *reply_string_size, char character) +static inline void add_char_to_command_reply(BUFFER *reply_string, unsigned *reply_string_size, char character) { - reply_string[(*reply_string_size)++] = character; + buffer_fast_charcat(reply_string, character); + *reply_string_size +=1; } -static inline void add_string_to_command_reply(char *reply_string, unsigned *reply_string_size, char *str) +static inline void add_string_to_command_reply(BUFFER *reply_string, unsigned *reply_string_size, char *str) { unsigned len; len = strlen(str); - - if (MAX_COMMAND_LENGTH - 1 < len + *reply_string_size) - len = MAX_COMMAND_LENGTH - *reply_string_size - 1; - - strncpyz(reply_string + *reply_string_size, str, len); + buffer_fast_strcat(reply_string, str, len); *reply_string_size += len; } static void send_command_reply(struct command_context *cmd_ctx, cmd_status_t status, char *message) { int ret; - char *reply_string = mallocz(MAX_COMMAND_LENGTH); + BUFFER *reply_string = buffer_create(128, NULL); + char exit_status_string[MAX_EXIT_STATUS_LENGTH + 1] = {'\0', }; unsigned reply_string_size = 0; uv_buf_t write_buf; @@ -436,13 +449,12 @@ static void send_command_reply(struct command_context *cmd_ctx, cmd_status_t sta cmd_ctx->write_req.data = client; client->data = reply_string; - write_buf.base = reply_string; + write_buf.base = reply_string->buffer; write_buf.len = reply_string_size; ret = uv_write(&cmd_ctx->write_req, (uv_stream_t *)client, &write_buf, 1, pipe_write_cb); if (ret) { error("uv_write(): %s", uv_strerror(ret)); } - info("COMMAND: Sending reply: \"%s\"", reply_string); } cmd_status_t execute_command(cmd_t idx, char *args, char **message) diff --git a/daemon/commands.h b/daemon/commands.h index 78bdcc77..43a0ef96 100644 --- a/daemon/commands.h +++ b/daemon/commands.h @@ -26,6 +26,7 @@ typedef enum cmd { CMD_PING, CMD_ACLK_STATE, CMD_VERSION, + CMD_DUMPCONFIG, CMD_TOTAL_COMMANDS } cmd_t; diff --git a/daemon/common.h b/daemon/common.h index ca4d5c95..66ffd4a7 100644 --- a/daemon/common.h +++ b/daemon/common.h @@ -58,7 +58,8 @@ #include "exporting/exporting_engine.h" // the netdata API -#include "web/api/web_api_v1.h" +#include "web/server/web_client.h" +#include "web/rtc/webrtc.h" // all data collection plugins #include "collectors/all.h" diff --git a/daemon/config/README.md b/daemon/config/README.md index 4a6d0bb8..418b12cf 100644 --- a/daemon/config/README.md +++ b/daemon/config/README.md @@ -4,8 +4,7 @@ description: "The Netdata Agent's daemon is installed preconfigured to collect t 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_rel_path: "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" --> @@ -108,12 +107,9 @@ Please note that your data history will be lost if you have modified `history` p | 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. | -:::info - -The multiplication of all the **enabled** tiers `dbengine tier N update every iterations` values must be less than `65535`. - -::: - +> ### Info +> +>The multiplication of all the **enabled** tiers `dbengine tier N update every iterations` values must be less than `65535`. ### [directories] section options diff --git a/daemon/event_loop.c b/daemon/event_loop.c index 6f09cd65..5fd02377 100644 --- a/daemon/event_loop.c +++ b/daemon/event_loop.c @@ -49,6 +49,7 @@ void register_libuv_worker_jobs() { worker_register_job_name(UV_EVENT_DBENGINE_SHUTDOWN, "dbengine shutdown"); // metadata + worker_register_job_name(UV_EVENT_HOST_CONTEXT_LOAD, "metadata load host context"); worker_register_job_name(UV_EVENT_METADATA_STORE, "metadata store host"); worker_register_job_name(UV_EVENT_METADATA_CLEANUP, "metadata cleanup"); diff --git a/daemon/event_loop.h b/daemon/event_loop.h index 0d3cc0d0..1ff1c2c1 100644 --- a/daemon/event_loop.h +++ b/daemon/event_loop.h @@ -41,6 +41,7 @@ enum event_loop_job { UV_EVENT_DBENGINE_SHUTDOWN, // metadata + UV_EVENT_HOST_CONTEXT_LOAD, UV_EVENT_METADATA_STORE, UV_EVENT_METADATA_CLEANUP, diff --git a/daemon/global_statistics.c b/daemon/global_statistics.c index 0dc3ee64..ee68bebd 100644 --- a/daemon/global_statistics.c +++ b/daemon/global_statistics.c @@ -827,33 +827,7 @@ 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); - } + ml_update_global_statistics_charts(gs.ml_models_consulted); } // ---------------------------------------------------------------------------- diff --git a/daemon/main.c b/daemon/main.c index 7b2076f3..606de128 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -13,6 +13,7 @@ int netdata_zero_metrics_enabled; int netdata_anonymous_statistics_enabled; int libuv_worker_threads = MIN_LIBUV_WORKER_THREADS; +bool ieee754_doubles = false; struct netdata_static_thread *static_threads; @@ -147,10 +148,6 @@ static void service_to_buffer(BUFFER *wb, SERVICE_TYPE service) { 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) @@ -312,6 +309,8 @@ static bool service_wait_exit(SERVICE_TYPE service, usec_t timeout_ut) { timeout = false; \ } +void web_client_cache_destroy(void); + void netdata_cleanup_and_exit(int ret) { usec_t started_ut = now_monotonic_usec(); usec_t last_ut = started_ut; @@ -339,6 +338,15 @@ void netdata_cleanup_and_exit(int ret) { } #endif + delta_shutdown_time("close webrtc connections"); + + webrtc_close_all_connections(); + + delta_shutdown_time("disable ML detection and training threads"); + + ml_stop_threads(); + ml_fini(); + delta_shutdown_time("disable maintenance, new queries, new web requests, new streaming connections and aclk"); service_signal_exit( @@ -347,14 +355,14 @@ void netdata_cleanup_and_exit(int ret) { | ABILITY_WEB_REQUESTS | ABILITY_STREAMING_CONNECTIONS | SERVICE_ACLK + | SERVICE_ACLKSYNC ); - delta_shutdown_time("stop replication, exporters, ML training, health and web servers threads"); + delta_shutdown_time("stop replication, exporters, 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); @@ -366,11 +374,10 @@ void netdata_cleanup_and_exit(int ret) { | SERVICE_STREAMING , 3 * USEC_PER_SEC); - delta_shutdown_time("stop ML prediction and context threads"); + delta_shutdown_time("stop context thread"); timeout = !service_wait_exit( - SERVICE_ML_PREDICTION - | SERVICE_CONTEXT + SERVICE_CONTEXT , 3 * USEC_PER_SEC); delta_shutdown_time("stop maintenance thread"); @@ -379,6 +386,10 @@ void netdata_cleanup_and_exit(int ret) { SERVICE_MAINTENANCE , 3 * USEC_PER_SEC); + delta_shutdown_time("clear web client cache"); + + web_client_cache_destroy(); + delta_shutdown_time("clean rrdhost database"); rrdhost_cleanup_all(); @@ -387,11 +398,6 @@ void netdata_cleanup_and_exit(int ret) { metadata_sync_shutdown_prepare(); -#ifdef ENABLE_ACLK - delta_shutdown_time("signal aclk sync to stop"); - aclk_sync_exit_all(); -#endif - delta_shutdown_time("stop aclk threads"); timeout = !service_wait_exit( @@ -529,38 +535,41 @@ void web_server_config_options(void) web_x_frame_options = NULL; web_allow_connections_from = - simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow connections from", "localhost *"), - NULL, SIMPLE_PATTERN_EXACT); + simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow connections from", "localhost *"), + NULL, SIMPLE_PATTERN_EXACT, true); web_allow_connections_dns = make_dns_decision(CONFIG_SECTION_WEB, "allow connections by dns", "heuristic", web_allow_connections_from); web_allow_dashboard_from = - simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow dashboard from", "localhost *"), - NULL, SIMPLE_PATTERN_EXACT); + simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow dashboard from", "localhost *"), + NULL, SIMPLE_PATTERN_EXACT, true); web_allow_dashboard_dns = make_dns_decision(CONFIG_SECTION_WEB, "allow dashboard by dns", "heuristic", web_allow_dashboard_from); web_allow_badges_from = - simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow badges from", "*"), NULL, SIMPLE_PATTERN_EXACT); + simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow badges from", "*"), NULL, SIMPLE_PATTERN_EXACT, + true); web_allow_badges_dns = make_dns_decision(CONFIG_SECTION_WEB, "allow badges by dns", "heuristic", web_allow_badges_from); web_allow_registry_from = - simple_pattern_create(config_get(CONFIG_SECTION_REGISTRY, "allow from", "*"), NULL, SIMPLE_PATTERN_EXACT); + simple_pattern_create(config_get(CONFIG_SECTION_REGISTRY, "allow from", "*"), NULL, SIMPLE_PATTERN_EXACT, + true); web_allow_registry_dns = make_dns_decision(CONFIG_SECTION_REGISTRY, "allow by dns", "heuristic", web_allow_registry_from); web_allow_streaming_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow streaming from", "*"), - NULL, SIMPLE_PATTERN_EXACT); + NULL, SIMPLE_PATTERN_EXACT, true); web_allow_streaming_dns = make_dns_decision(CONFIG_SECTION_WEB, "allow streaming by dns", "heuristic", web_allow_streaming_from); // Note the default is not heuristic, the wildcards could match DNS but the intent is ip-addresses. web_allow_netdataconf_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow netdata.conf from", - "localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.*" - " 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.*" - " 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.*" - " 172.31.* UNKNOWN"), NULL, SIMPLE_PATTERN_EXACT); + "localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.*" + " 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.*" + " 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.*" + " 172.31.* UNKNOWN"), NULL, SIMPLE_PATTERN_EXACT, + true); web_allow_netdataconf_dns = make_dns_decision(CONFIG_SECTION_WEB, "allow netdata.conf by dns", "no", web_allow_netdataconf_from); web_allow_mgmt_from = - simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow management from", "localhost"), - NULL, SIMPLE_PATTERN_EXACT); + simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow management from", "localhost"), + NULL, SIMPLE_PATTERN_EXACT, true); web_allow_mgmt_dns = make_dns_decision(CONFIG_SECTION_WEB, "allow management by dns","heuristic",web_allow_mgmt_from); @@ -655,9 +664,14 @@ void cancel_main_threads() { int i, found = 0; usec_t max = 5 * USEC_PER_SEC, step = 100000; for (i = 0; static_threads[i].name != NULL ; i++) { - if(static_threads[i].enabled == NETDATA_MAIN_THREAD_RUNNING) { - info("EXIT: Stopping main thread: %s", static_threads[i].name); - netdata_thread_cancel(*static_threads[i].thread); + if (static_threads[i].enabled == NETDATA_MAIN_THREAD_RUNNING) { + if (static_threads[i].thread) { + info("EXIT: Stopping main thread: %s", static_threads[i].name); + netdata_thread_cancel(*static_threads[i].thread); + } else { + info("EXIT: No thread running (marking as EXITED): %s", static_threads[i].name); + static_threads[i].enabled = NETDATA_MAIN_THREAD_EXITED; + } found++; } } @@ -1107,8 +1121,12 @@ static void get_netdata_configured_variables() { // get default Database Engine page cache size in MiB default_rrdeng_page_cache_mb = (int) config_get_number(CONFIG_SECTION_DB, "dbengine page cache size MB", default_rrdeng_page_cache_mb); + default_rrdeng_extent_cache_mb = (int) config_get_number(CONFIG_SECTION_DB, "dbengine extent cache size MB", default_rrdeng_extent_cache_mb); db_engine_journal_check = config_get_boolean(CONFIG_SECTION_DB, "dbengine enable journal integrity check", CONFIG_BOOLEAN_NO); + if(default_rrdeng_extent_cache_mb < 0) + default_rrdeng_extent_cache_mb = 0; + 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; @@ -1314,9 +1332,12 @@ void post_conf_load(char **user) prev_msg = msg; \ } +int buffer_unittest(void); int pgc_unittest(void); int mrg_unittest(void); int julytest(void); +int pluginsd_parser_unittest(void); +void replication_initialize(void); int main(int argc, char **argv) { // initialize the system clocks @@ -1437,12 +1458,17 @@ int main(int argc, char **argv) { if(strcmp(optarg, "unittest") == 0) { unittest_running = true; + if (pluginsd_parser_unittest()) + return 1; + if (unit_test_static_threads()) return 1; if (unit_test_buffer()) return 1; if (unit_test_str2ld()) return 1; + if (buffer_unittest()) + return 1; if (unit_test_bitmap256()) return 1; // No call to load the config file on this code-path @@ -1479,15 +1505,6 @@ int main(int argc, char **argv) { else if(strcmp(optarg, "escapetest") == 0) { return command_argument_sanitization_tests(); } -#ifdef ENABLE_DBENGINE - else if(strcmp(optarg, "mctest") == 0) { - unittest_running = true; - return mc_unittest(); - } - else if(strcmp(optarg, "ctxtest") == 0) { - unittest_running = true; - return ctx_unittest(); - } else if(strcmp(optarg, "dicttest") == 0) { unittest_running = true; return dictionary_unittest(10000); @@ -1504,6 +1521,19 @@ int main(int argc, char **argv) { unittest_running = true; return rrdlabels_unittest(); } + else if(strcmp(optarg, "buffertest") == 0) { + unittest_running = true; + return buffer_unittest(); + } +#ifdef ENABLE_DBENGINE + else if(strcmp(optarg, "mctest") == 0) { + unittest_running = true; + return mc_unittest(); + } + else if(strcmp(optarg, "ctxtest") == 0) { + unittest_running = true; + return ctx_unittest(); + } else if(strcmp(optarg, "metatest") == 0) { unittest_running = true; return metadata_unittest(); @@ -1523,6 +1553,14 @@ int main(int argc, char **argv) { else if(strncmp(optarg, createdataset_string, strlen(createdataset_string)) == 0) { optarg += strlen(createdataset_string); unsigned history_seconds = strtoul(optarg, NULL, 0); + post_conf_load(&user); + get_netdata_configured_variables(); + default_rrd_update_every = 1; + registry_init(); + if(rrd_init("dbengine-dataset", NULL, true)) { + fprintf(stderr, "rrd_init failed for unittest\n"); + return 1; + } generate_dbengine_dataset(history_seconds); return 0; } @@ -1587,12 +1625,16 @@ int main(int argc, char **argv) { size_t len = strlen(needle) + 1; char wildcarded[len]; - SIMPLE_PATTERN *p = simple_pattern_create(haystack, NULL, SIMPLE_PATTERN_EXACT); - int ret = simple_pattern_matches_extract(p, needle, wildcarded, len); + SIMPLE_PATTERN *p = simple_pattern_create(haystack, NULL, SIMPLE_PATTERN_EXACT, true); + SIMPLE_PATTERN_RESULT ret = simple_pattern_matches_extract(p, needle, wildcarded, len); simple_pattern_free(p); - if(ret) { - fprintf(stdout, "RESULT: MATCHED - pattern '%s' matches '%s', wildcarded '%s'\n", haystack, needle, wildcarded); + if(ret == SP_MATCHED_POSITIVE) { + fprintf(stdout, "RESULT: POSITIVE MATCHED - pattern '%s' matches '%s', wildcarded '%s'\n", haystack, needle, wildcarded); + return 0; + } + else if(ret == SP_MATCHED_NEGATIVE) { + fprintf(stdout, "RESULT: NEGATIVE MATCHED - pattern '%s' matches '%s', wildcarded '%s'\n", haystack, needle, wildcarded); return 0; } else { @@ -1801,7 +1843,7 @@ int main(int argc, char **argv) { #endif // set libuv worker threads - libuv_worker_threads = (int)get_netdata_cpus() * 2; + libuv_worker_threads = (int)get_netdata_cpus() * 6; if(libuv_worker_threads < MIN_LIBUV_WORKER_THREADS) libuv_worker_threads = MIN_LIBUV_WORKER_THREADS; @@ -1866,10 +1908,14 @@ int main(int argc, char **argv) { // initialize the log files open_all_log_files(); + ieee754_doubles = is_system_ieee754_double(); + aral_judy_init(); get_system_timezone(); + replication_initialize(); + // -------------------------------------------------------------------- // get the certificate and start security @@ -1988,13 +2034,16 @@ int main(int argc, char **argv) { 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); + (void) registry_get_this_machine_guid(); system_info->hops = 0; get_install_type(&system_info->install_type, &system_info->prebuilt_arch, &system_info->prebuilt_dist); delta_startup_time("initialize RRD structures"); - if(rrd_init(netdata_configured_hostname, system_info, false)) + if(rrd_init(netdata_configured_hostname, system_info, false)) { + set_late_global_environment(system_info); fatal("Cannot initialize localhost instance with name '%s'.", netdata_configured_hostname); + } delta_startup_time("check for incomplete shutdown"); @@ -2036,8 +2085,7 @@ int main(int argc, char **argv) { netdata_zero_metrics_enabled = config_get_boolean_ondemand(CONFIG_SECTION_DB, "enable zero metrics", CONFIG_BOOLEAN_NO); - set_late_global_environment(); - + set_late_global_environment(system_info); for (i = 0; static_threads[i].name != NULL ; i++) { struct netdata_static_thread *st = &static_threads[i]; @@ -2048,6 +2096,7 @@ int main(int argc, char **argv) { } else debug(D_SYSTEM, "Not starting thread %s.", st->name); } + ml_start_threads(); // ------------------------------------------------------------------------ // Initialize netdata agent command serving from cli and signals @@ -2097,6 +2146,11 @@ int main(int argc, char **argv) { } #endif + // ------------------------------------------------------------------------ + // initialize WebRTC + + webrtc_initialize(); + // ------------------------------------------------------------------------ // unblock signals diff --git a/daemon/main.h b/daemon/main.h index 8704d609..3e32c5ad 100644 --- a/daemon/main.h +++ b/daemon/main.h @@ -33,16 +33,15 @@ typedef enum { 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_REPLICATION = (1 << 5), + SERVICE_WEB_SERVER = (1 << 6), + SERVICE_ACLK = (1 << 7), + SERVICE_HEALTH = (1 << 8), + SERVICE_STREAMING = (1 << 9), + SERVICE_CONTEXT = (1 << 10), + SERVICE_ANALYTICS = (1 << 11), + SERVICE_EXPORTERS = (1 << 12), + SERVICE_ACLKSYNC = (1 << 13) } SERVICE_TYPE; typedef enum { diff --git a/daemon/service.c b/daemon/service.c index 9761abd0..57c7c7f3 100644 --- a/daemon/service.c +++ b/daemon/service.c @@ -55,7 +55,7 @@ static void svc_rrddim_obsolete_to_archive(RRDDIM *rd) { if(rd->tiers[tier].db_collection_handle) { tiers_available++; - if(rd->tiers[tier].collect_ops->finalize(rd->tiers[tier].db_collection_handle)) + if(storage_engine_store_finalize(rd->tiers[tier].db_collection_handle)) tiers_said_no_retention++; rd->tiers[tier].db_collection_handle = NULL; diff --git a/daemon/system-info.sh b/daemon/system-info.sh index 1e334a3d..43f761c2 100755 --- a/daemon/system-info.sh +++ b/daemon/system-info.sh @@ -96,6 +96,11 @@ if [ "${CONTAINER}" = "unknown" ]; then CONT_DETECTION="dockerenv" fi + if [ -n "${KUBERNETES_SERVICE_HOST}" ]; then + CONTAINER="container" + CONT_DETECTION="kubernetes" + fi + fi # ------------------------------------------------------------------------------------------------- @@ -391,7 +396,7 @@ else # These translate to the prefixs of files in `/dev` indicating the device type. # They are sorted by lowest used device major number, with dynamically assigned ones at the end. # We use this to look up device major numbers in `/proc/devices` - device_names='hd sd mfm ad ftl pd nftl dasd intfl mmcblk ub xvd rfd vbd nvme virtblk blkext' + device_names='hd sd mfm ad ftl pd nftl dasd intfl mmcblk mmc ub xvd rfd vbd nvme virtblk blkext' for name in ${device_names}; do if grep -qE " ${name}\$" /proc/devices; then @@ -457,7 +462,7 @@ if [ "${VIRTUALIZATION}" != "none" ] && command -v curl > /dev/null 2>&1; then # Try GCE computeMetadata v1 if [ "${CLOUD_TYPE}" = "unknown" ]; then - if [ -n "$(curl --fail -s --connect-timeout 1 -m 3 --noproxy "*" -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1")" ]; then + if curl --fail -s --connect-timeout 1 -m 3 --noproxy "*" -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1" | grep -sq computeMetadata; then CLOUD_TYPE="GCP" CLOUD_INSTANCE_TYPE="$(curl --fail -s --connect-timeout 1 -m 3 --noproxy "*" -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/machine-type")" [ -n "$CLOUD_INSTANCE_TYPE" ] && CLOUD_INSTANCE_TYPE=$(basename "$CLOUD_INSTANCE_TYPE") @@ -466,16 +471,15 @@ if [ "${VIRTUALIZATION}" != "none" ] && command -v curl > /dev/null 2>&1; then fi fi - # TODO: needs to be tested in Microsoft Azure # Try Azure IMDS - # if [ "${CLOUD_TYPE}" = "unknown" ]; then - # AZURE_IMDS_DATA="$(curl --fail -s -m 5 -H "Metadata: true" --noproxy "*" "http://169.254.169.254/metadata/instance?version=2021-10-01")" - # if [ -n "${AZURE_IMDS_DATA}" ]; then - # CLOUD_TYPE="Azure" - # CLOUD_INSTANCE_TYPE="$(curl --fail -s -m 5 -H "Metadata: true" --noproxy "*" "http://169.254.169.254/metadata/instance/compute/vmSize?version=2021-10-01&format=text")" - # CLOUD_INSTANCE_REGION="$(curl --fail -s -m 5 -H "Metadata: true" --noproxy "*" "http://169.254.169.254/metadata/instance/compute/location?version=2021-10-01&format=text")" - # fi - # fi + if [ "${CLOUD_TYPE}" = "unknown" ]; then + AZURE_IMDS_DATA="$(curl --fail -s --connect-timeout 1 -m 3 -H "Metadata: true" --noproxy "*" "http://169.254.169.254/metadata/instance?api-version=2021-10-01")" + if [ -n "${AZURE_IMDS_DATA}" ] && echo "${AZURE_IMDS_DATA}" | grep -sq azEnvironment; then + CLOUD_TYPE="Azure" + CLOUD_INSTANCE_TYPE="$(curl --fail -s --connect-timeout 1 -m 3 -H "Metadata: true" --noproxy "*" "http://169.254.169.254/metadata/instance/compute/vmSize?api-version=2021-10-01&format=text")" + CLOUD_INSTANCE_REGION="$(curl --fail -s --connect-timeout 1 -m 3 -H "Metadata: true" --noproxy "*" "http://169.254.169.254/metadata/instance/compute/location?api-version=2021-10-01&format=text")" + fi + fi fi fi diff --git a/daemon/unit_test.c b/daemon/unit_test.c index 52b55c4e..fa3fa847 100644 --- a/daemon/unit_test.c +++ b/daemon/unit_test.c @@ -68,17 +68,36 @@ static int check_number_printing(void) { { .n = 0.000000001, .correct = "0" }, { .n = 99.99999999999999999, .correct = "100" }, { .n = -99.99999999999999999, .correct = "-100" }, + { .n = 123.4567899123456789, .correct = "123.4567899" }, { .n = 123.4567890123456789, .correct = "123.456789" }, + { .n = 123.4567800123456789, .correct = "123.45678" }, + { .n = 123.4567000123456789, .correct = "123.4567" }, + { .n = 123.4560000123456789, .correct = "123.456" }, + { .n = 123.4500000123456789, .correct = "123.45" }, + { .n = 123.4000000123456789, .correct = "123.4" }, + { .n = 123.0000000123456789, .correct = "123" }, + { .n = 123.0000000923456789, .correct = "123.0000001" }, + { .n = 4294967295.123456789, .correct = "4294967295.123457" }, + { .n = 8294967295.123456789, .correct = "8294967295.123457" }, + { .n = 1.000000000000002e+19, .correct = "1.000000000000001998e+19" }, + { .n = 9.2233720368547676e+18, .correct = "9.223372036854767584e+18" }, + { .n = 18446744073709541376.0, .correct = "1.84467440737095424e+19" }, + { .n = 18446744073709551616.0, .correct = "1.844674407370955136e+19" }, + { .n = 12318446744073710600192.0, .correct = "1.231844674407371008e+22" }, + { .n = 1677721499999999885312.0, .correct = "1.677721499999999872e+21" }, + { .n = -1677721499999999885312.0, .correct = "-1.677721499999999872e+21" }, + { .n = -1.677721499999999885312e40, .correct = "-1.677721499999999872e+40" }, + { .n = -16777214999999997337621690403742592008192.0, .correct = "-1.677721499999999616e+40" }, { .n = 9999.9999999, .correct = "9999.9999999" }, { .n = -9999.9999999, .correct = "-9999.9999999" }, { .n = 0, .correct = NULL }, }; - char netdata[50], system[50]; + char netdata[512 + 2], system[512 + 2]; int i, failed = 0; for(i = 0; values[i].correct ; i++) { print_netdata_double(netdata, values[i].n); - snprintfz(system, 49, "%0.12" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE)values[i].n); + snprintfz(system, 512, "%0.12" NETDATA_DOUBLE_MODIFIER, (NETDATA_DOUBLE)values[i].n); int ok = 1; if(strcmp(netdata, values[i].correct) != 0) { @@ -86,7 +105,18 @@ static int check_number_printing(void) { failed++; } - fprintf(stderr, "'%s' (system) printed as '%s' (netdata): %s\n", system, netdata, ok?"OK":"FAILED"); + NETDATA_DOUBLE parsed_netdata = str2ndd(netdata, NULL); + NETDATA_DOUBLE parsed_system = strtondd(netdata, NULL); + + if(parsed_system != parsed_netdata) + failed++; + + fprintf(stderr, "[%d]. '%s' (system) printed as '%s' (netdata): PRINT %s, " + "PARSED %0.12" NETDATA_DOUBLE_MODIFIER " (system), %0.12" NETDATA_DOUBLE_MODIFIER " (netdata): %s\n", + i, + system, netdata, ok?"OK":"FAILED", + parsed_system, parsed_netdata, + parsed_netdata == parsed_system ? "OK" : "FAILED"); } if(failed) return 1; @@ -395,9 +425,35 @@ int unit_test_storage() { } int unit_test_str2ld() { + is_system_ieee754_double(); + char *values[] = { - "1.2345678", "-35.6", "0.00123", "23842384234234.2", ".1", "1.2e-10", - "hello", "1wrong", "nan", "inf", NULL + "1.2345678", + "-35.6", + "0.00123", + "23842384234234.2", + ".1", + "1.2e-10", + "18446744073709551616.0", + "18446744073709551616123456789123456789123456789123456789123456789123456789123456789.0", + "1.8446744073709551616123456789123456789123456789123456789123456789123456789123456789e+300", + "9.", + "9.e2", + "1.2e", + "1.2e+", + "1.2e-", + "1.2e0", + "1.2e-0", + "1.2e+0", + "-1.2e+1", + "-1.2e-1", + "1.2e1", + "1.2e400", + "hello", + "1wrong", + "nan", + "inf", + NULL }; int i; @@ -427,7 +483,8 @@ int unit_test_str2ld() { } if(e_mine != e_sys) { - fprintf(stderr, "Value '%s' is parsed correctly, but endptr is not right\n", values[i]); + fprintf(stderr, "Value '%s' is parsed correctly, but endptr is not right (netdata returned %d, but system returned %d)\n", + values[i], (int)(e_mine - values[i]), (int)(e_sys - values[i])); return -1; } @@ -1880,7 +1937,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); + storage_engine_store_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; @@ -1931,13 +1988,13 @@ 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, STORAGE_PRIORITY_NORMAL); + storage_engine_query_init(rd[i][j]->tiers[0].backend, 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 = storage_engine_query_next_metric(&handle); value = sp.sum; time_retrieved = sp.start_time_s; end_time = sp.end_time_s; @@ -1959,7 +2016,7 @@ static int test_dbengine_check_metrics(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DI errors++; } } - rd[i][j]->tiers[0].query_ops->finalize(&handle); + storage_engine_query_finalize(&handle); } } } @@ -2327,7 +2384,7 @@ void generate_dbengine_dataset(unsigned history_seconds) } freez(thread_info); rrd_wrlock(); - rrdhost_free___while_having_rrd_wrlock(host, true); + rrdhost_free___while_having_rrd_wrlock(localhost, true); rrd_unlock(); } @@ -2387,13 +2444,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, STORAGE_PRIORITY_NORMAL); + storage_engine_query_init(rd->tiers[0].backend, 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(storage_engine_query_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", @@ -2403,7 +2460,7 @@ static void query_dbengine_chart(void *arg) break; } - STORAGE_POINT sp = rd->tiers[0].query_ops->next_metric(&handle); + STORAGE_POINT sp = storage_engine_query_next_metric(&handle); value = sp.sum; time_retrieved = sp.start_time_s; end_time = sp.end_time_s; @@ -2441,7 +2498,7 @@ static void query_dbengine_chart(void *arg) } } } - rd->tiers[0].query_ops->finalize(&handle); + storage_engine_query_finalize(&handle); } while(!thread_info->done); if(value_errors) diff --git a/database/Makefile.am b/database/Makefile.am index dc87e61b..21b4896d 100644 --- a/database/Makefile.am +++ b/database/Makefile.am @@ -7,6 +7,7 @@ SUBDIRS = \ engine \ ram \ sqlite \ + contexts \ $(NULL) dist_noinst_DATA = \ diff --git a/database/README.md b/database/README.md index becd4165..eb708162 100644 --- a/database/README.md +++ b/database/README.md @@ -1,13 +1,3 @@ - - # Database Netdata is fully capable of long-term metrics storage, at per-second granularity, via its default database engine @@ -42,13 +32,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](https://github.com/netdata/netdata/blob/master/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/enable-streaming.md) 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](https://github.com/netdata/netdata/blob/master/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/enable-streaming.md) 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. @@ -68,7 +58,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**](https://github.com/netdata/netdata/blob/master/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/enable-streaming.md) 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). @@ -144,8 +134,30 @@ Put the above lines in your boot sequence (`/etc/rc.local` or equivalent) to hav ### Monitoring Kernel Memory de-duplication performance -Netdata will create charts for kernel memory de-duplication performance, like this: +Netdata will create charts for kernel memory de-duplication performance, the **deduper (ksm)** charts can be seen under the **Memory** section in the Netdata UI. + +#### KSM summary + +The summary gives you a quick idea of how much savings (in terms of bytes and in terms of percentage) KSM is able to achieve. + +![image](https://user-images.githubusercontent.com/24860547/199454880-123ae7c4-071a-4811-95b8-18cf4e4f60a2.png) + +#### KSM pages merge performance + +This chart indicates the performance of page merging. **Shared** indicates used shared pages, **Unshared** indicates memory no longer shared (pages are unique but repeatedly checked for merging), **Sharing** indicates memory currently shared(how many more sites are sharing the pages, i.e. how much saved) and **Volatile** indicates volatile pages (changing too fast to be placed in a tree). + +A high ratio of Sharing to Shared indicates good sharing, but a high ratio of Unshared to Sharing indicates wasted effort. + +![image](https://user-images.githubusercontent.com/24860547/199455374-d63fd2c2-e12b-4ddf-947b-35371215eb05.png) + +#### KSM savings + +This chart shows the amount of memory saved by KSM. **Savings** indicates saved memory. **Offered** indicates memory marked as mergeable. + +![image](https://user-images.githubusercontent.com/24860547/199455604-43cd9248-1f6e-4c31-be56-e0b9e432f48a.png) -![image](https://cloud.githubusercontent.com/assets/2662304/11998786/eb23ae54-aab6-11e5-94d4-e848e8a5c56a.png) +#### KSM effectiveness +This chart tells you how well KSM is doing at what it is supposed to. It does this by charting the percentage of the mergeable pages that are currently merged. +![image](https://user-images.githubusercontent.com/24860547/199455770-4d7991ff-6b7e-4d96-9d23-33ffc572b370.png) diff --git a/database/contexts/Makefile.am b/database/contexts/Makefile.am new file mode 100644 index 00000000..59250a99 --- /dev/null +++ b/database/contexts/Makefile.am @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +SUBDIRS = \ + $(NULL) + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/database/contexts/README.md b/database/contexts/README.md new file mode 100644 index 00000000..e69de29b diff --git a/database/contexts/api_v1.c b/database/contexts/api_v1.c new file mode 100644 index 00000000..daf945ee --- /dev/null +++ b/database/contexts/api_v1.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "internal.h" + +static void rrd_flags_to_buffer_json_array_items(RRD_FLAGS flags, BUFFER *wb) { + if(flags & RRD_FLAG_QUEUED_FOR_HUB) + buffer_json_add_array_item_string(wb, "QUEUED"); + + if(flags & RRD_FLAG_DELETED) + buffer_json_add_array_item_string(wb, "DELETED"); + + if(flags & RRD_FLAG_COLLECTED) + buffer_json_add_array_item_string(wb, "COLLECTED"); + + if(flags & RRD_FLAG_UPDATED) + buffer_json_add_array_item_string(wb, "UPDATED"); + + if(flags & RRD_FLAG_ARCHIVED) + buffer_json_add_array_item_string(wb, "ARCHIVED"); + + if(flags & RRD_FLAG_OWN_LABELS) + buffer_json_add_array_item_string(wb, "OWN_LABELS"); + + if(flags & RRD_FLAG_LIVE_RETENTION) + buffer_json_add_array_item_string(wb, "LIVE_RETENTION"); + + if(flags & RRD_FLAG_HIDDEN) + buffer_json_add_array_item_string(wb, "HIDDEN"); + + if(flags & RRD_FLAG_QUEUED_FOR_PP) + buffer_json_add_array_item_string(wb, "PENDING_UPDATES"); +} + +// ---------------------------------------------------------------------------- +// /api/v1/context(s) API + +struct rrdcontext_to_json { + BUFFER *wb; + RRDCONTEXT_TO_JSON_OPTIONS options; + time_t after; + time_t before; + SIMPLE_PATTERN *chart_label_key; + SIMPLE_PATTERN *chart_labels_filter; + SIMPLE_PATTERN *chart_dimensions; + size_t written; + time_t now; + time_t combined_first_time_s; + time_t combined_last_time_s; + RRD_FLAGS combined_flags; +}; + +static inline int rrdmetric_to_json_callback(const DICTIONARY_ITEM *item, void *value, void *data) { + const char *id = dictionary_acquired_item_name(item); + struct rrdcontext_to_json * t = data; + RRDMETRIC *rm = value; + BUFFER *wb = t->wb; + RRDCONTEXT_TO_JSON_OPTIONS options = t->options; + time_t after = t->after; + time_t before = t->before; + + if(unlikely(rrd_flag_is_deleted(rm) && !(options & RRDCONTEXT_OPTION_SHOW_DELETED))) + return 0; + + if(after && (!rm->last_time_s || after > rm->last_time_s)) + return 0; + + if(before && (!rm->first_time_s || before < rm->first_time_s)) + return 0; + + if(t->chart_dimensions + && !simple_pattern_matches_string(t->chart_dimensions, rm->id) + && rm->name != rm->id + && !simple_pattern_matches_string(t->chart_dimensions, rm->name)) + return 0; + + if(t->written) { + 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 { + 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); + } + + buffer_json_member_add_object(wb, id); + + if(options & RRDCONTEXT_OPTION_SHOW_UUIDS) { + char uuid[UUID_STR_LEN]; + uuid_unparse(rm->uuid, uuid); + buffer_json_member_add_string(wb, "uuid", uuid); + } + + buffer_json_member_add_string(wb, "name", string2str(rm->name)); + buffer_json_member_add_time_t(wb, "first_time_t", rm->first_time_s); + buffer_json_member_add_time_t(wb, "last_time_t", rrd_flag_is_collected(rm) ? (long long)t->now : (long long)rm->last_time_s); + buffer_json_member_add_boolean(wb, "collected", rrd_flag_is_collected(rm)); + + if(options & RRDCONTEXT_OPTION_SHOW_DELETED) + buffer_json_member_add_boolean(wb, "deleted", rrd_flag_is_deleted(rm)); + + if(options & RRDCONTEXT_OPTION_SHOW_FLAGS) { + buffer_json_member_add_array(wb, "flags"); + rrd_flags_to_buffer_json_array_items(rrd_flags_get(rm), wb); + buffer_json_array_close(wb); + } + + buffer_json_object_close(wb); + t->written++; + return 1; +} + +static inline int rrdinstance_to_json_callback(const DICTIONARY_ITEM *item, void *value, void *data) { + const char *id = dictionary_acquired_item_name(item); + + struct rrdcontext_to_json *t_parent = data; + RRDINSTANCE *ri = value; + BUFFER *wb = t_parent->wb; + RRDCONTEXT_TO_JSON_OPTIONS options = t_parent->options; + time_t after = t_parent->after; + time_t before = t_parent->before; + bool has_filter = t_parent->chart_label_key || t_parent->chart_labels_filter || t_parent->chart_dimensions; + + if(unlikely(rrd_flag_is_deleted(ri) && !(options & RRDCONTEXT_OPTION_SHOW_DELETED))) + return 0; + + if(after && (!ri->last_time_s || after > ri->last_time_s)) + return 0; + + 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', NULL)) + return 0; + + if(t_parent->chart_labels_filter && !rrdlabels_match_simple_pattern_parsed(ri->rrdlabels, + t_parent->chart_labels_filter, ':', + NULL)) + return 0; + + 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, &netdata_buffers_statistics.buffers_api); + buffer_json_initialize(wb_metrics, "\"", "\"", wb->json.depth + 2, false, false); + + struct rrdcontext_to_json t_metrics = { + .wb = wb_metrics, + .options = options, + .chart_label_key = t_parent->chart_label_key, + .chart_labels_filter = t_parent->chart_labels_filter, + .chart_dimensions = t_parent->chart_dimensions, + .after = after, + .before = before, + .written = 0, + .now = t_parent->now, + }; + dictionary_walkthrough_read(ri->rrdmetrics, rrdmetric_to_json_callback, &t_metrics); + + if(has_filter && !t_metrics.written) { + buffer_free(wb_metrics); + return 0; + } + + 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) { + 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 { + t_parent->combined_first_time_s = first_time_s; + t_parent->combined_last_time_s = last_time_s; + t_parent->combined_flags = flags; + } + + buffer_json_member_add_object(wb, id); + + if(options & RRDCONTEXT_OPTION_SHOW_UUIDS) { + char uuid[UUID_STR_LEN]; + uuid_unparse(ri->uuid, uuid); + buffer_json_member_add_string(wb, "uuid", uuid); + } + + buffer_json_member_add_string(wb, "name", string2str(ri->name)); + buffer_json_member_add_string(wb, "context", string2str(ri->rc->id)); + buffer_json_member_add_string(wb, "title", string2str(ri->title)); + buffer_json_member_add_string(wb, "units", string2str(ri->units)); + buffer_json_member_add_string(wb, "family", string2str(ri->family)); + buffer_json_member_add_string(wb, "chart_type", rrdset_type_name(ri->chart_type)); + buffer_json_member_add_uint64(wb, "priority", ri->priority); + buffer_json_member_add_time_t(wb, "update_every", ri->update_every_s); + buffer_json_member_add_time_t(wb, "first_time_t", first_time_s); + buffer_json_member_add_time_t(wb, "last_time_t", (flags & RRD_FLAG_COLLECTED) ? (long long)t_parent->now : (long long)last_time_s); + buffer_json_member_add_boolean(wb, "collected", flags & RRD_FLAG_COLLECTED); + + if(options & RRDCONTEXT_OPTION_SHOW_DELETED) + buffer_json_member_add_boolean(wb, "deleted", rrd_flag_is_deleted(ri)); + + if(options & RRDCONTEXT_OPTION_SHOW_FLAGS) { + buffer_json_member_add_array(wb, "flags"); + rrd_flags_to_buffer_json_array_items(rrd_flags_get(ri), wb); + buffer_json_array_close(wb); + } + + if(options & RRDCONTEXT_OPTION_SHOW_LABELS && ri->rrdlabels && dictionary_entries(ri->rrdlabels)) { + buffer_json_member_add_object(wb, "labels"); + rrdlabels_to_buffer_json_members(ri->rrdlabels, wb); + buffer_json_object_close(wb); + } + + if(wb_metrics) { + buffer_json_member_add_object(wb, "dimensions"); + buffer_fast_strcat(wb, buffer_tostring(wb_metrics), buffer_strlen(wb_metrics)); + buffer_json_object_close(wb); + + buffer_free(wb_metrics); + } + + buffer_json_object_close(wb); + t_parent->written++; + return 1; +} + +static inline int rrdcontext_to_json_callback(const DICTIONARY_ITEM *item, void *value, void *data) { + const char *id = dictionary_acquired_item_name(item); + struct rrdcontext_to_json *t_parent = data; + RRDCONTEXT *rc = value; + BUFFER *wb = t_parent->wb; + RRDCONTEXT_TO_JSON_OPTIONS options = t_parent->options; + time_t after = t_parent->after; + time_t before = t_parent->before; + bool has_filter = t_parent->chart_label_key || t_parent->chart_labels_filter || t_parent->chart_dimensions; + + if(unlikely(rrd_flag_check(rc, RRD_FLAG_HIDDEN) && !(options & RRDCONTEXT_OPTION_SHOW_HIDDEN))) + return 0; + + if(unlikely(rrd_flag_is_deleted(rc) && !(options & RRDCONTEXT_OPTION_SHOW_DELETED))) + return 0; + + if(options & RRDCONTEXT_OPTION_DEEPSCAN) + rrdcontext_recalculate_context_retention(rc, RRD_FLAG_NONE, false); + + if(after && (!rc->last_time_s || after > rc->last_time_s)) + return 0; + + if(before && (!rc->first_time_s || before < rc->first_time_s)) + return 0; + + 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; + if((options & (RRDCONTEXT_OPTION_SHOW_LABELS|RRDCONTEXT_OPTION_SHOW_INSTANCES|RRDCONTEXT_OPTION_SHOW_METRICS)) + || t_parent->chart_label_key + || t_parent->chart_labels_filter + || t_parent->chart_dimensions) { + + wb_instances = buffer_create(4096, &netdata_buffers_statistics.buffers_api); + buffer_json_initialize(wb_instances, "\"", "\"", wb->json.depth + 2, false, false); + + struct rrdcontext_to_json t_instances = { + .wb = wb_instances, + .options = options, + .chart_label_key = t_parent->chart_label_key, + .chart_labels_filter = t_parent->chart_labels_filter, + .chart_dimensions = t_parent->chart_dimensions, + .after = after, + .before = before, + .written = 0, + .now = t_parent->now, + }; + dictionary_walkthrough_read(rc->rrdinstances, rrdinstance_to_json_callback, &t_instances); + + if(has_filter && !t_instances.written) { + buffer_free(wb_instances); + return 0; + } + + first_time_s = t_instances.combined_first_time_s; + last_time_s = t_instances.combined_last_time_s; + flags = t_instances.combined_flags; + } + + if(!(options & RRDCONTEXT_OPTION_SKIP_ID)) + buffer_json_member_add_object(wb, id); + + rrdcontext_lock(rc); + + buffer_json_member_add_string(wb, "title", string2str(rc->title)); + buffer_json_member_add_string(wb, "units", string2str(rc->units)); + buffer_json_member_add_string(wb, "family", string2str(rc->family)); + buffer_json_member_add_string(wb, "chart_type", rrdset_type_name(rc->chart_type)); + buffer_json_member_add_uint64(wb, "priority", rc->priority); + buffer_json_member_add_time_t(wb, "first_time_t", first_time_s); + buffer_json_member_add_time_t(wb, "last_time_t", (flags & RRD_FLAG_COLLECTED) ? (long long)t_parent->now : (long long)last_time_s); + buffer_json_member_add_boolean(wb, "collected", (flags & RRD_FLAG_COLLECTED)); + + if(options & RRDCONTEXT_OPTION_SHOW_DELETED) + buffer_json_member_add_boolean(wb, "deleted", rrd_flag_is_deleted(rc)); + + if(options & RRDCONTEXT_OPTION_SHOW_FLAGS) { + buffer_json_member_add_array(wb, "flags"); + rrd_flags_to_buffer_json_array_items(rrd_flags_get(rc), wb); + buffer_json_array_close(wb); + } + + if(options & RRDCONTEXT_OPTION_SHOW_QUEUED) { + buffer_json_member_add_array(wb, "queued_reasons"); + rrd_reasons_to_buffer_json_array_items(rc->queue.queued_flags, wb); + buffer_json_array_close(wb); + + buffer_json_member_add_time_t(wb, "last_queued", (time_t)(rc->queue.queued_ut / USEC_PER_SEC)); + buffer_json_member_add_time_t(wb, "scheduled_dispatch", (time_t)(rc->queue.scheduled_dispatch_ut / USEC_PER_SEC)); + buffer_json_member_add_time_t(wb, "last_dequeued", (time_t)(rc->queue.dequeued_ut / USEC_PER_SEC)); + buffer_json_member_add_uint64(wb, "dispatches", rc->queue.dispatches); + buffer_json_member_add_uint64(wb, "hub_version", rc->hub.version); + buffer_json_member_add_uint64(wb, "version", rc->version); + + buffer_json_member_add_array(wb, "pp_reasons"); + rrd_reasons_to_buffer_json_array_items(rc->pp.queued_flags, wb); + buffer_json_array_close(wb); + + buffer_json_member_add_time_t(wb, "pp_last_queued", (time_t)(rc->pp.queued_ut / USEC_PER_SEC)); + buffer_json_member_add_time_t(wb, "pp_last_dequeued", (time_t)(rc->pp.dequeued_ut / USEC_PER_SEC)); + buffer_json_member_add_uint64(wb, "pp_executed", rc->pp.executions); + } + + rrdcontext_unlock(rc); + + if(wb_instances) { + buffer_json_member_add_object(wb, "charts"); + buffer_fast_strcat(wb, buffer_tostring(wb_instances), buffer_strlen(wb_instances)); + buffer_json_object_close(wb); + + buffer_free(wb_instances); + } + + if(!(options & RRDCONTEXT_OPTION_SKIP_ID)) + buffer_json_object_close(wb); + + t_parent->written++; + return 1; +} + +int rrdcontext_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before, RRDCONTEXT_TO_JSON_OPTIONS options, const char *context, SIMPLE_PATTERN *chart_label_key, SIMPLE_PATTERN *chart_labels_filter, SIMPLE_PATTERN *chart_dimensions) { + if(!host->rrdctx.contexts) { + error("%s(): request for host '%s' that does not have rrdcontexts initialized.", __FUNCTION__, rrdhost_hostname(host)); + return HTTP_RESP_NOT_FOUND; + } + + RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item(host->rrdctx.contexts, context); + if(!rca) return HTTP_RESP_NOT_FOUND; + + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + + if(after != 0 && before != 0) + rrdr_relative_window_to_absolute(&after, &before, NULL); + + buffer_json_initialize(wb, "\"", "\"", 0, true, false); + struct rrdcontext_to_json t_contexts = { + .wb = wb, + .options = options|RRDCONTEXT_OPTION_SKIP_ID, + .chart_label_key = chart_label_key, + .chart_labels_filter = chart_labels_filter, + .chart_dimensions = chart_dimensions, + .after = after, + .before = before, + .written = 0, + .now = now_realtime_sec(), + }; + rrdcontext_to_json_callback((DICTIONARY_ITEM *)rca, rc, &t_contexts); + buffer_json_finalize(wb); + + rrdcontext_release(rca); + + if(!t_contexts.written) + return HTTP_RESP_NOT_FOUND; + + return HTTP_RESP_OK; +} + +int rrdcontexts_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before, RRDCONTEXT_TO_JSON_OPTIONS options, SIMPLE_PATTERN *chart_label_key, SIMPLE_PATTERN *chart_labels_filter, SIMPLE_PATTERN *chart_dimensions) { + if(!host->rrdctx.contexts) { + error("%s(): request for host '%s' that does not have rrdcontexts initialized.", __FUNCTION__, rrdhost_hostname(host)); + return HTTP_RESP_NOT_FOUND; + } + + char node_uuid[UUID_STR_LEN] = ""; + + if(host->node_id) + uuid_unparse(*host->node_id, node_uuid); + + if(after != 0 && before != 0) + rrdr_relative_window_to_absolute(&after, &before, NULL); + + buffer_json_initialize(wb, "\"", "\"", 0, true, false); + buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(host)); + buffer_json_member_add_string(wb, "machine_guid", host->machine_guid); + buffer_json_member_add_string(wb, "node_id", node_uuid); + buffer_json_member_add_string(wb, "claim_id", host->aclk_state.claimed_id ? host->aclk_state.claimed_id : ""); + + if(options & RRDCONTEXT_OPTION_SHOW_LABELS) { + buffer_json_member_add_object(wb, "host_labels"); + rrdlabels_to_buffer_json_members(host->rrdlabels, wb); + buffer_json_object_close(wb); + } + + buffer_json_member_add_object(wb, "contexts"); + struct rrdcontext_to_json t_contexts = { + .wb = wb, + .options = options, + .chart_label_key = chart_label_key, + .chart_labels_filter = chart_labels_filter, + .chart_dimensions = chart_dimensions, + .after = after, + .before = before, + .written = 0, + .now = now_realtime_sec(), + }; + dictionary_walkthrough_read(host->rrdctx.contexts, rrdcontext_to_json_callback, &t_contexts); + buffer_json_object_close(wb); + + buffer_json_finalize(wb); + + return HTTP_RESP_OK; +} + diff --git a/database/contexts/api_v2.c b/database/contexts/api_v2.c new file mode 100644 index 00000000..a08d1509 --- /dev/null +++ b/database/contexts/api_v2.c @@ -0,0 +1,566 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "internal.h" + +#include "aclk/aclk_capas.h" + +// ---------------------------------------------------------------------------- +// /api/v2/contexts API + +typedef enum __attribute__ ((__packed__)) { + FTS_MATCHED_NONE = 0, + FTS_MATCHED_HOST, + FTS_MATCHED_CONTEXT, + FTS_MATCHED_INSTANCE, + FTS_MATCHED_DIMENSION, + FTS_MATCHED_LABEL, + FTS_MATCHED_ALERT, + FTS_MATCHED_ALERT_INFO, + FTS_MATCHED_FAMILY, + FTS_MATCHED_TITLE, + FTS_MATCHED_UNITS, +} FTS_MATCH; + +static const char *fts_match_to_string(FTS_MATCH match) { + switch(match) { + case FTS_MATCHED_HOST: + return "HOST"; + + case FTS_MATCHED_CONTEXT: + return "CONTEXT"; + + case FTS_MATCHED_INSTANCE: + return "INSTANCE"; + + case FTS_MATCHED_DIMENSION: + return "DIMENSION"; + + case FTS_MATCHED_ALERT: + return "ALERT"; + + case FTS_MATCHED_ALERT_INFO: + return "ALERT_INFO"; + + case FTS_MATCHED_LABEL: + return "LABEL"; + + case FTS_MATCHED_FAMILY: + return "FAMILY"; + + case FTS_MATCHED_TITLE: + return "TITLE"; + + case FTS_MATCHED_UNITS: + return "UNITS"; + + default: + return "NONE"; + } +} + +struct rrdcontext_to_json_v2_entry { + size_t count; + STRING *id; + STRING *family; + uint32_t priority; + time_t first_time_s; + time_t last_time_s; + RRD_FLAGS flags; + FTS_MATCH match; +}; + +typedef struct full_text_search_index { + size_t searches; + size_t string_searches; + size_t char_searches; +} FTS_INDEX; + +static inline bool full_text_search_string(FTS_INDEX *fts, SIMPLE_PATTERN *q, STRING *ptr) { + fts->searches++; + fts->string_searches++; + return simple_pattern_matches_string(q, ptr); +} + +static inline bool full_text_search_char(FTS_INDEX *fts, SIMPLE_PATTERN *q, char *ptr) { + fts->searches++; + fts->char_searches++; + return simple_pattern_matches(q, ptr); +} + +struct rrdcontext_to_json_v2_data { + BUFFER *wb; + struct api_v2_contexts_request *request; + DICTIONARY *ctx; + + CONTEXTS_V2_OPTIONS options; + struct query_versions versions; + + struct { + SIMPLE_PATTERN *scope_pattern; + SIMPLE_PATTERN *pattern; + size_t ni; + } nodes; + + struct { + SIMPLE_PATTERN *scope_pattern; + SIMPLE_PATTERN *pattern; + } contexts; + + struct { + FTS_MATCH host_match; + char host_node_id_str[UUID_STR_LEN]; + SIMPLE_PATTERN *pattern; + FTS_INDEX fts; + } q; + + struct query_timings timings; +}; + +static FTS_MATCH rrdcontext_to_json_v2_full_text_search(struct rrdcontext_to_json_v2_data *ctl, RRDCONTEXT *rc, SIMPLE_PATTERN *q) { + if(unlikely(full_text_search_string(&ctl->q.fts, q, rc->id) || + full_text_search_string(&ctl->q.fts, q, rc->family))) + return FTS_MATCHED_CONTEXT; + + if(unlikely(full_text_search_string(&ctl->q.fts, q, rc->title))) + return FTS_MATCHED_TITLE; + + if(unlikely(full_text_search_string(&ctl->q.fts, q, rc->units))) + return FTS_MATCHED_UNITS; + + FTS_MATCH matched = FTS_MATCHED_NONE; + RRDINSTANCE *ri; + dfe_start_read(rc->rrdinstances, ri) { + if(matched) break; + + if(unlikely(full_text_search_string(&ctl->q.fts, q, ri->id)) || + (ri->name != ri->id && full_text_search_string(&ctl->q.fts, q, ri->name))) { + matched = FTS_MATCHED_INSTANCE; + break; + } + + RRDMETRIC *rm; + dfe_start_read(ri->rrdmetrics, rm) { + if(unlikely(full_text_search_string(&ctl->q.fts, q, rm->id)) || + (rm->name != rm->id && full_text_search_string(&ctl->q.fts, q, rm->name))) { + matched = FTS_MATCHED_DIMENSION; + break; + } + } + dfe_done(rm); + + size_t label_searches = 0; + if(unlikely(ri->rrdlabels && dictionary_entries(ri->rrdlabels) && + rrdlabels_match_simple_pattern_parsed(ri->rrdlabels, q, ':', &label_searches))) { + ctl->q.fts.searches += label_searches; + ctl->q.fts.char_searches += label_searches; + matched = FTS_MATCHED_LABEL; + break; + } + ctl->q.fts.searches += label_searches; + ctl->q.fts.char_searches += label_searches; + + if(ri->rrdset) { + RRDSET *st = ri->rrdset; + netdata_rwlock_rdlock(&st->alerts.rwlock); + for (RRDCALC *rcl = st->alerts.base; rcl; rcl = rcl->next) { + if(unlikely(full_text_search_string(&ctl->q.fts, q, rcl->name))) { + matched = FTS_MATCHED_ALERT; + break; + } + + if(unlikely(full_text_search_string(&ctl->q.fts, q, rcl->info))) { + matched = FTS_MATCHED_ALERT_INFO; + break; + } + } + netdata_rwlock_unlock(&st->alerts.rwlock); + } + } + dfe_done(ri); + return matched; +} + +static ssize_t rrdcontext_to_json_v2_add_context(void *data, RRDCONTEXT_ACQUIRED *rca, bool queryable_context __maybe_unused) { + struct rrdcontext_to_json_v2_data *ctl = data; + + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + + FTS_MATCH match = ctl->q.host_match; + if((ctl->options & CONTEXTS_V2_SEARCH) && ctl->q.pattern) { + match = rrdcontext_to_json_v2_full_text_search(ctl, rc, ctl->q.pattern); + + if(match == FTS_MATCHED_NONE) + return 0; + } + + struct rrdcontext_to_json_v2_entry t = { + .count = 0, + .id = rc->id, + .family = string_dup(rc->family), + .priority = rc->priority, + .first_time_s = rc->first_time_s, + .last_time_s = rc->last_time_s, + .flags = rc->flags, + .match = match, + }, *z = dictionary_set(ctl->ctx, string2str(rc->id), &t, sizeof(t)); + + if(!z->count) { + // we just added this + z->count = 1; + } + else { + // it is already in there + z->count++; + z->flags |= rc->flags; + + if(z->priority > rc->priority) + z->priority = rc->priority; + + if(z->first_time_s > rc->first_time_s) + z->first_time_s = rc->first_time_s; + + if(z->last_time_s < rc->last_time_s) + z->last_time_s = rc->last_time_s; + + if(z->family != rc->family) { + z->family = string_2way_merge(z->family, rc->family); + } + } + + return 1; +} + +void buffer_json_node_add_v2(BUFFER *wb, RRDHOST *host, size_t ni, usec_t duration_ut) { + buffer_json_member_add_string(wb, "mg", host->machine_guid); + if(host->node_id) + buffer_json_member_add_uuid(wb, "nd", host->node_id); + buffer_json_member_add_string(wb, "nm", rrdhost_hostname(host)); + buffer_json_member_add_uint64(wb, "ni", ni); + buffer_json_member_add_object(wb, "st"); + buffer_json_member_add_uint64(wb, "ai", 0); + buffer_json_member_add_uint64(wb, "code", 200); + buffer_json_member_add_string(wb, "msg", ""); + if(duration_ut) + buffer_json_member_add_double(wb, "ms", (NETDATA_DOUBLE)duration_ut / 1000.0); + buffer_json_object_close(wb); +} + +static ssize_t rrdcontext_to_json_v2_add_host(void *data, RRDHOST *host, bool queryable_host) { + if(!queryable_host || !host->rrdctx.contexts) + // the host matches the 'scope_host' but does not match the 'host' patterns + // or the host does not have any contexts + return 0; + + struct rrdcontext_to_json_v2_data *ctl = data; + BUFFER *wb = ctl->wb; + + if(ctl->request->timeout_ms && now_monotonic_usec() > ctl->timings.received_ut + ctl->request->timeout_ms * USEC_PER_MS) + // timed out + return -2; + + if(ctl->request->interrupt_callback && ctl->request->interrupt_callback(ctl->request->interrupt_callback_data)) + // interrupted + return -1; + + bool host_matched = (ctl->options & CONTEXTS_V2_NODES); + bool do_contexts = (ctl->options & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH)); + + ctl->q.host_match = FTS_MATCHED_NONE; + if((ctl->options & CONTEXTS_V2_SEARCH)) { + // check if we match the host itself + if(ctl->q.pattern && ( + full_text_search_string(&ctl->q.fts, ctl->q.pattern, host->hostname) || + full_text_search_char(&ctl->q.fts, ctl->q.pattern, host->machine_guid) || + (ctl->q.pattern && full_text_search_char(&ctl->q.fts, ctl->q.pattern, ctl->q.host_node_id_str)))) { + ctl->q.host_match = FTS_MATCHED_HOST; + do_contexts = true; + } + } + + if(do_contexts) { + // save it + SIMPLE_PATTERN *old_q = ctl->q.pattern; + + if(ctl->q.host_match == FTS_MATCHED_HOST) + // do not do pattern matching on contexts - we matched the host itself + ctl->q.pattern = NULL; + + ssize_t added = query_scope_foreach_context( + host, ctl->request->scope_contexts, + ctl->contexts.scope_pattern, ctl->contexts.pattern, + rrdcontext_to_json_v2_add_context, queryable_host, ctl); + + // restore it + ctl->q.pattern = old_q; + + if(added == -1) + return -1; + + if(added) + host_matched = true; + } + + if(host_matched && (ctl->options & (CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_DETAILED | CONTEXTS_V2_DEBUG))) { + buffer_json_add_array_item_object(wb); + buffer_json_node_add_v2(wb, host, ctl->nodes.ni++, 0); + + if(ctl->options & CONTEXTS_V2_NODES_DETAILED) { + buffer_json_member_add_string(wb, "version", rrdhost_program_version(host)); + buffer_json_member_add_uint64(wb, "hops", host->system_info ? host->system_info->hops : (host == localhost) ? 0 : 1); + buffer_json_member_add_string(wb, "state", (host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN)) ? "reachable" : "stale"); + buffer_json_member_add_boolean(wb, "isDeleted", false); + + buffer_json_member_add_array(wb, "services"); + buffer_json_array_close(wb); + + buffer_json_member_add_array(wb, "nodeInstanceCapabilities"); + + struct capability *capas = aclk_get_node_instance_capas(host); + struct capability *capa = capas; + while(capa->name != NULL) { + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "name", capa->name); + buffer_json_member_add_uint64(wb, "version", capa->version); + buffer_json_member_add_boolean(wb, "enabled", capa->enabled); + buffer_json_object_close(wb); + capa++; + } + buffer_json_array_close(wb); + freez(capas); + + web_client_api_request_v1_info_summary_alarm_statuses(host, wb, "alarmCounters"); + + host_labels2json(host, wb, "hostLabels"); + + buffer_json_member_add_object(wb, "mlInfo"); + buffer_json_member_add_boolean(wb, "mlCapable", ml_capable(host)); + buffer_json_member_add_boolean(wb, "mlEnabled", ml_enabled(host)); + buffer_json_object_close(wb); + + if(host->system_info) { + buffer_json_member_add_string_or_empty(wb, "architecture", host->system_info->architecture); + buffer_json_member_add_string_or_empty(wb, "kernelName", host->system_info->kernel_name); + buffer_json_member_add_string_or_empty(wb, "kernelVersion", host->system_info->kernel_version); + buffer_json_member_add_string_or_empty(wb, "cpuFrequency", host->system_info->host_cpu_freq); + buffer_json_member_add_string_or_empty(wb, "cpus", host->system_info->host_cores); + buffer_json_member_add_string_or_empty(wb, "memory", host->system_info->host_ram_total); + buffer_json_member_add_string_or_empty(wb, "diskSpace", host->system_info->host_disk_space); + buffer_json_member_add_string_or_empty(wb, "container", host->system_info->container); + buffer_json_member_add_string_or_empty(wb, "virtualization", host->system_info->virtualization); + buffer_json_member_add_string_or_empty(wb, "os", host->system_info->host_os_id); + buffer_json_member_add_string_or_empty(wb, "osName", host->system_info->host_os_name); + buffer_json_member_add_string_or_empty(wb, "osVersion", host->system_info->host_os_version); + } + + buffer_json_member_add_object(wb, "status"); + + size_t receiver_hops = host->system_info ? host->system_info->hops : (host == localhost) ? 0 : 1; + buffer_json_member_add_object(wb, "collection"); + buffer_json_member_add_uint64(wb, "hops", receiver_hops); + buffer_json_member_add_boolean(wb, "online", host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN | RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED)); + buffer_json_member_add_boolean(wb, "replicating", rrdhost_receiver_replicating_charts(host)); + buffer_json_object_close(wb); // collection + + buffer_json_member_add_object(wb, "streaming"); + buffer_json_member_add_uint64(wb, "hops", host->sender ? host->sender->hops : receiver_hops + 1); + buffer_json_member_add_boolean(wb, "online", rrdhost_flag_check(host, RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED)); + buffer_json_member_add_boolean(wb, "replicating", rrdhost_sender_replicating_charts(host)); + buffer_json_object_close(wb); // streaming + + buffer_json_object_close(wb); // status + } + + buffer_json_object_close(wb); + } + + return host_matched ? 1 : 0; +} + +static void buffer_json_contexts_v2_options_to_array(BUFFER *wb, CONTEXTS_V2_OPTIONS options) { + if(options & CONTEXTS_V2_DEBUG) + buffer_json_add_array_item_string(wb, "debug"); + + if(options & (CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_DETAILED)) + buffer_json_add_array_item_string(wb, "nodes"); + + if(options & CONTEXTS_V2_CONTEXTS) + buffer_json_add_array_item_string(wb, "contexts"); + + if(options & CONTEXTS_V2_SEARCH) + buffer_json_add_array_item_string(wb, "search"); +} + +void buffer_json_query_timings(BUFFER *wb, const char *key, struct query_timings *timings) { + timings->finished_ut = now_monotonic_usec(); + if(!timings->executed_ut) + timings->executed_ut = timings->finished_ut; + if(!timings->preprocessed_ut) + timings->preprocessed_ut = timings->received_ut; + buffer_json_member_add_object(wb, key); + buffer_json_member_add_double(wb, "prep_ms", (NETDATA_DOUBLE)(timings->preprocessed_ut - timings->received_ut) / USEC_PER_MS); + buffer_json_member_add_double(wb, "query_ms", (NETDATA_DOUBLE)(timings->executed_ut - timings->preprocessed_ut) / USEC_PER_MS); + buffer_json_member_add_double(wb, "output_ms", (NETDATA_DOUBLE)(timings->finished_ut - timings->executed_ut) / USEC_PER_MS); + buffer_json_member_add_double(wb, "total_ms", (NETDATA_DOUBLE)(timings->finished_ut - timings->received_ut) / USEC_PER_MS); + buffer_json_member_add_double(wb, "cloud_ms", (NETDATA_DOUBLE)(timings->finished_ut - timings->received_ut) / USEC_PER_MS); + buffer_json_object_close(wb); +} + +void buffer_json_agents_array_v2(BUFFER *wb, struct query_timings *timings, time_t now_s) { + if(!now_s) + now_s = now_realtime_sec(); + + buffer_json_member_add_array(wb, "agents"); + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "mg", localhost->machine_guid); + buffer_json_member_add_uuid(wb, "nd", localhost->node_id); + buffer_json_member_add_string(wb, "nm", rrdhost_hostname(localhost)); + buffer_json_member_add_time_t(wb, "now", now_s); + buffer_json_member_add_uint64(wb, "ai", 0); + + if(timings) + buffer_json_query_timings(wb, "timings", timings); + + buffer_json_object_close(wb); + buffer_json_array_close(wb); +} + +void buffer_json_cloud_timings(BUFFER *wb, const char *key, struct query_timings *timings) { + buffer_json_member_add_object(wb, key); + buffer_json_member_add_double(wb, "routing_ms", 0.0); + buffer_json_member_add_double(wb, "node_max_ms", 0.0); + buffer_json_member_add_double(wb, "total_ms", (NETDATA_DOUBLE)(timings->finished_ut - timings->received_ut) / USEC_PER_MS); + buffer_json_object_close(wb); +} + +void contexts_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) { + struct rrdcontext_to_json_v2_entry *z = value; + string_freez(z->family); +} + +int rrdcontext_to_json_v2(BUFFER *wb, struct api_v2_contexts_request *req, CONTEXTS_V2_OPTIONS options) { + int resp = HTTP_RESP_OK; + + if(options & CONTEXTS_V2_SEARCH) + options |= CONTEXTS_V2_CONTEXTS; + + struct rrdcontext_to_json_v2_data ctl = { + .wb = wb, + .request = req, + .ctx = NULL, + .options = options, + .versions = { 0 }, + .nodes.scope_pattern = string_to_simple_pattern(req->scope_nodes), + .nodes.pattern = string_to_simple_pattern(req->nodes), + .contexts.pattern = string_to_simple_pattern(req->contexts), + .contexts.scope_pattern = string_to_simple_pattern(req->scope_contexts), + .q.pattern = string_to_simple_pattern_nocase(req->q), + .timings = { + .received_ut = now_monotonic_usec(), + } + }; + + if(options & CONTEXTS_V2_CONTEXTS) { + ctl.ctx = dictionary_create_advanced( + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, + sizeof(struct rrdcontext_to_json_v2_entry)); + + dictionary_register_delete_callback(ctl.ctx, contexts_delete_callback, NULL); + } + + time_t now_s = now_realtime_sec(); + buffer_json_initialize(wb, "\"", "\"", 0, true, false); + buffer_json_member_add_uint64(wb, "api", 2); + + if(options & CONTEXTS_V2_DEBUG) { + buffer_json_member_add_object(wb, "request"); + + buffer_json_member_add_object(wb, "scope"); + buffer_json_member_add_string(wb, "scope_nodes", req->scope_nodes); + buffer_json_member_add_string(wb, "scope_contexts", req->scope_contexts); + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "selectors"); + buffer_json_member_add_string(wb, "nodes", req->nodes); + buffer_json_member_add_string(wb, "contexts", req->contexts); + buffer_json_object_close(wb); + + buffer_json_member_add_string(wb, "q", req->q); + buffer_json_member_add_array(wb, "options"); + buffer_json_contexts_v2_options_to_array(wb, options); + buffer_json_array_close(wb); + + buffer_json_object_close(wb); + } + + if(options & (CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_DETAILED | CONTEXTS_V2_DEBUG)) + buffer_json_member_add_array(wb, "nodes"); + + ssize_t ret = query_scope_foreach_host(ctl.nodes.scope_pattern, ctl.nodes.pattern, + rrdcontext_to_json_v2_add_host, &ctl, + &ctl.versions, ctl.q.host_node_id_str); + + if(unlikely(ret < 0)) { + buffer_flush(wb); + + if(ret == -2) { + buffer_strcat(wb, "query timeout"); + resp = HTTP_RESP_GATEWAY_TIMEOUT; + } + else { + buffer_strcat(wb, "query interrupted"); + resp = HTTP_RESP_BACKEND_FETCH_FAILED; + } + goto cleanup; + } + + if(options & (CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_DETAILED | CONTEXTS_V2_DEBUG)) + buffer_json_array_close(wb); + + ctl.timings.executed_ut = now_monotonic_usec(); + version_hashes_api_v2(wb, &ctl.versions); + + if(options & CONTEXTS_V2_CONTEXTS) { + buffer_json_member_add_object(wb, "contexts"); + struct rrdcontext_to_json_v2_entry *z; + dfe_start_read(ctl.ctx, z){ + bool collected = z->flags & RRD_FLAG_COLLECTED; + + buffer_json_member_add_object(wb, string2str(z->id)); + { + buffer_json_member_add_string(wb, "family", string2str(z->family)); + buffer_json_member_add_uint64(wb, "priority", z->priority); + buffer_json_member_add_time_t(wb, "first_entry", z->first_time_s); + buffer_json_member_add_time_t(wb, "last_entry", collected ? now_s : z->last_time_s); + buffer_json_member_add_boolean(wb, "live", collected); + if (options & CONTEXTS_V2_SEARCH) + buffer_json_member_add_string(wb, "match", fts_match_to_string(z->match)); + } + buffer_json_object_close(wb); + } + dfe_done(z); + buffer_json_object_close(wb); // contexts + } + + if(options & CONTEXTS_V2_SEARCH) { + buffer_json_member_add_object(wb, "searches"); + buffer_json_member_add_uint64(wb, "strings", ctl.q.fts.string_searches); + buffer_json_member_add_uint64(wb, "char", ctl.q.fts.char_searches); + buffer_json_member_add_uint64(wb, "total", ctl.q.fts.searches); + buffer_json_object_close(wb); + } + + buffer_json_agents_array_v2(wb, &ctl.timings, now_s); + buffer_json_cloud_timings(wb, "timings", &ctl.timings); + buffer_json_finalize(wb); + +cleanup: + dictionary_destroy(ctl.ctx); + simple_pattern_free(ctl.nodes.scope_pattern); + simple_pattern_free(ctl.nodes.pattern); + simple_pattern_free(ctl.contexts.pattern); + simple_pattern_free(ctl.contexts.scope_pattern); + simple_pattern_free(ctl.q.pattern); + + return resp; +} + diff --git a/database/contexts/context.c b/database/contexts/context.c new file mode 100644 index 00000000..f941050d --- /dev/null +++ b/database/contexts/context.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "internal.h" + +inline const char *rrdcontext_acquired_id(RRDCONTEXT_ACQUIRED *rca) { + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + return string2str(rc->id); +} + +inline bool rrdcontext_acquired_belongs_to_host(RRDCONTEXT_ACQUIRED *rca, RRDHOST *host) { + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + return rc->rrdhost == host; +} + +// ---------------------------------------------------------------------------- +// RRDCONTEXT + +static void rrdcontext_freez(RRDCONTEXT *rc) { + string_freez(rc->id); + string_freez(rc->title); + string_freez(rc->units); + string_freez(rc->family); +} + +static void rrdcontext_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdhost) { + RRDHOST *host = (RRDHOST *)rrdhost; + RRDCONTEXT *rc = (RRDCONTEXT *)value; + + rc->rrdhost = host; + rc->flags = rc->flags & RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS; // no need for atomics at constructor + + if(rc->hub.version) { + // we are loading data from the SQL database + + if(rc->version) + error("RRDCONTEXT: context '%s' is already initialized with version %"PRIu64", but it is loaded again from SQL with version %"PRIu64"", string2str(rc->id), rc->version, rc->hub.version); + + // IMPORTANT + // replace all string pointers in rc->hub with our own versions + // the originals are coming from a tmp allocation of sqlite + + string_freez(rc->id); + rc->id = string_strdupz(rc->hub.id); + rc->hub.id = string2str(rc->id); + + string_freez(rc->title); + rc->title = string_strdupz(rc->hub.title); + rc->hub.title = string2str(rc->title); + + string_freez(rc->units); + rc->units = string_strdupz(rc->hub.units); + rc->hub.units = string2str(rc->units); + + string_freez(rc->family); + rc->family = string_strdupz(rc->hub.family); + rc->hub.family = string2str(rc->family); + + rc->chart_type = rrdset_type_id(rc->hub.chart_type); + rc->hub.chart_type = rrdset_type_name(rc->chart_type); + + rc->version = rc->hub.version; + rc->priority = rc->hub.priority; + 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_s) + rrd_flag_set_deleted(rc, RRD_FLAG_NONE); + else { + if (rc->last_time_s == 0) + rrd_flag_set_collected(rc); + else + rrd_flag_set_archived(rc); + } + + rc->flags |= RRD_FLAG_UPDATE_REASON_LOAD_SQL; // no need for atomics at constructor + } + else { + // we are adding this context now for the first time + rc->version = now_realtime_sec(); + } + + rrdinstances_create_in_rrdcontext(rc); + netdata_mutex_init(&rc->mutex); + + // signal the react callback to do the job + rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_NEW_OBJECT); +} + +static void rrdcontext_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdhost __maybe_unused) { + + RRDCONTEXT *rc = (RRDCONTEXT *)value; + + rrdinstances_destroy_from_rrdcontext(rc); + netdata_mutex_destroy(&rc->mutex); + rrdcontext_freez(rc); +} + +static bool rrdcontext_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *rrdhost __maybe_unused) { + RRDCONTEXT *rc = (RRDCONTEXT *)old_value; + RRDCONTEXT *rc_new = (RRDCONTEXT *)new_value; + + //current rc is not archived, new_rc is archived, don't merge + if (!rrd_flag_is_archived(rc) && rrd_flag_is_archived(rc_new)) { + rrdcontext_freez(rc_new); + return false; + } + + rrdcontext_lock(rc); + + if(rc->title != rc_new->title) { + STRING *old_title = rc->title; + if (rrd_flag_is_archived(rc) && !rrd_flag_is_archived(rc_new)) + rc->title = string_dup(rc_new->title); + else + rc->title = string_2way_merge(rc->title, rc_new->title); + string_freez(old_title); + rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + if(rc->units != rc_new->units) { + STRING *old_units = rc->units; + rc->units = string_dup(rc_new->units); + string_freez(old_units); + rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + if(rc->family != rc_new->family) { + STRING *old_family = rc->family; + if (rrd_flag_is_archived(rc) && !rrd_flag_is_archived(rc_new)) + rc->family = string_dup(rc_new->family); + else + rc->family = string_2way_merge(rc->family, rc_new->family); + string_freez(old_family); + rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + if(rc->chart_type != rc_new->chart_type) { + rc->chart_type = rc_new->chart_type; + rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + if(rc->priority != rc_new->priority) { + rc->priority = rc_new->priority; + rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + rrd_flag_set(rc, rc_new->flags & RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS); // no need for atomics on rc_new + + if(rrd_flag_is_collected(rc) && rrd_flag_is_archived(rc)) + rrd_flag_set_collected(rc); + + if(rrd_flag_is_updated(rc)) + rrd_flag_set(rc, RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT); + + rrdcontext_unlock(rc); + + // free the resources of the new one + rrdcontext_freez(rc_new); + + // the react callback will continue from here + return rrd_flag_is_updated(rc); +} + +static void rrdcontext_react_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdhost __maybe_unused) { + RRDCONTEXT *rc = (RRDCONTEXT *)value; + rrdcontext_trigger_updates(rc, __FUNCTION__ ); +} + +void rrdcontext_trigger_updates(RRDCONTEXT *rc, const char *function) { + if(rrd_flag_is_updated(rc) || !rrd_flag_check(rc, RRD_FLAG_LIVE_RETENTION)) + rrdcontext_queue_for_post_processing(rc, function, rc->flags); +} + +static void rrdcontext_hub_queue_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *context, void *nothing __maybe_unused) { + RRDCONTEXT *rc = context; + rrd_flag_set(rc, RRD_FLAG_QUEUED_FOR_HUB); + rc->queue.queued_ut = now_realtime_usec(); + rc->queue.queued_flags = rrd_flags_get(rc); +} + +static void rrdcontext_hub_queue_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *context, void *nothing __maybe_unused) { + RRDCONTEXT *rc = context; + rrd_flag_clear(rc, RRD_FLAG_QUEUED_FOR_HUB); +} + +static bool rrdcontext_hub_queue_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *context, void *new_context __maybe_unused, void *nothing __maybe_unused) { + // context and new_context are the same + // we just need to update the timings + RRDCONTEXT *rc = context; + rrd_flag_set(rc, RRD_FLAG_QUEUED_FOR_HUB); + rc->queue.queued_ut = now_realtime_usec(); + rc->queue.queued_flags |= rrd_flags_get(rc); + + return true; +} + +static void rrdcontext_post_processing_queue_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *context, void *nothing __maybe_unused) { + RRDCONTEXT *rc = context; + rrd_flag_set(rc, RRD_FLAG_QUEUED_FOR_PP); + rc->pp.queued_flags = rc->flags; + rc->pp.queued_ut = now_realtime_usec(); +} + +static void rrdcontext_post_processing_queue_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *context, void *nothing __maybe_unused) { + RRDCONTEXT *rc = context; + rrd_flag_clear(rc, RRD_FLAG_QUEUED_FOR_PP); + rc->pp.dequeued_ut = now_realtime_usec(); +} + +static bool rrdcontext_post_processing_queue_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *context, void *new_context __maybe_unused, void *nothing __maybe_unused) { + RRDCONTEXT *rc = context; + bool changed = false; + + if(!(rc->flags & RRD_FLAG_QUEUED_FOR_PP)) { + rrd_flag_set(rc, RRD_FLAG_QUEUED_FOR_PP); + changed = true; + } + + if(rc->pp.queued_flags != rc->flags) { + rc->pp.queued_flags |= rc->flags; + changed = true; + } + + return changed; +} + + +void rrdhost_create_rrdcontexts(RRDHOST *host) { + if(unlikely(!host)) return; + if(likely(host->rrdctx.contexts)) return; + + host->rrdctx.contexts = dictionary_create_advanced( + DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, + &dictionary_stats_category_rrdcontext, sizeof(RRDCONTEXT)); + + dictionary_register_insert_callback(host->rrdctx.contexts, rrdcontext_insert_callback, host); + dictionary_register_delete_callback(host->rrdctx.contexts, rrdcontext_delete_callback, host); + dictionary_register_conflict_callback(host->rrdctx.contexts, rrdcontext_conflict_callback, host); + dictionary_register_react_callback(host->rrdctx.contexts, rrdcontext_react_callback, host); + + host->rrdctx.hub_queue = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_VALUE_LINK_DONT_CLONE, &dictionary_stats_category_rrdcontext, 0); + dictionary_register_insert_callback(host->rrdctx.hub_queue, rrdcontext_hub_queue_insert_callback, NULL); + dictionary_register_delete_callback(host->rrdctx.hub_queue, rrdcontext_hub_queue_delete_callback, NULL); + dictionary_register_conflict_callback(host->rrdctx.hub_queue, rrdcontext_hub_queue_conflict_callback, NULL); + + host->rrdctx.pp_queue = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_VALUE_LINK_DONT_CLONE, &dictionary_stats_category_rrdcontext, 0); + dictionary_register_insert_callback(host->rrdctx.pp_queue, rrdcontext_post_processing_queue_insert_callback, NULL); + dictionary_register_delete_callback(host->rrdctx.pp_queue, rrdcontext_post_processing_queue_delete_callback, NULL); + dictionary_register_conflict_callback(host->rrdctx.pp_queue, rrdcontext_post_processing_queue_conflict_callback, NULL); +} + +void rrdhost_destroy_rrdcontexts(RRDHOST *host) { + if(unlikely(!host)) return; + if(unlikely(!host->rrdctx.contexts)) return; + + DICTIONARY *old; + + if(host->rrdctx.hub_queue) { + old = host->rrdctx.hub_queue; + host->rrdctx.hub_queue = NULL; + + RRDCONTEXT *rc; + dfe_start_write(old, rc) { + dictionary_del(old, string2str(rc->id)); + } + dfe_done(rc); + dictionary_destroy(old); + } + + if(host->rrdctx.pp_queue) { + old = host->rrdctx.pp_queue; + host->rrdctx.pp_queue = NULL; + + RRDCONTEXT *rc; + dfe_start_write(old, rc) { + dictionary_del(old, string2str(rc->id)); + } + dfe_done(rc); + dictionary_destroy(old); + } + + old = host->rrdctx.contexts; + host->rrdctx.contexts = NULL; + dictionary_destroy(old); +} + diff --git a/database/contexts/instance.c b/database/contexts/instance.c new file mode 100644 index 00000000..665022af --- /dev/null +++ b/database/contexts/instance.c @@ -0,0 +1,524 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "internal.h" + +// ---------------------------------------------------------------------------- +// helper one-liners for RRDINSTANCE + +bool rrdinstance_acquired_id_and_name_are_same(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return ri->id == ri->name; +} + +inline const char *rrdinstance_acquired_id(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return string2str(ri->id); +} + +inline const char *rrdinstance_acquired_name(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return string2str(ri->name); +} + +inline bool rrdinstance_acquired_has_name(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return (ri->name && ri->name != ri->id); +} + +inline const char *rrdinstance_acquired_units(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return string2str(ri->units); +} + +inline STRING *rrdinstance_acquired_units_dup(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return string_dup(ri->units); +} + +inline DICTIONARY *rrdinstance_acquired_labels(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return ri->rrdlabels; +} + +inline DICTIONARY *rrdinstance_acquired_functions(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + if(!ri->rrdset) return NULL; + return ri->rrdset->functions_view; +} + +inline RRDHOST *rrdinstance_acquired_rrdhost(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return ri->rc->rrdhost; +} + +inline bool rrdinstance_acquired_belongs_to_context(RRDINSTANCE_ACQUIRED *ria, RRDCONTEXT_ACQUIRED *rca) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + return ri->rc == rc; +} + +inline time_t rrdinstance_acquired_update_every(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return ri->update_every_s; +} + +// ---------------------------------------------------------------------------- +// RRDINSTANCE + +static void rrdinstance_free(RRDINSTANCE *ri) { + + if(rrd_flag_check(ri, RRD_FLAG_OWN_LABELS)) + dictionary_destroy(ri->rrdlabels); + + rrdmetrics_destroy_from_rrdinstance(ri); + string_freez(ri->id); + string_freez(ri->name); + string_freez(ri->title); + string_freez(ri->units); + string_freez(ri->family); + + ri->id = NULL; + ri->name = NULL; + ri->title = NULL; + ri->units = NULL; + ri->family = NULL; + ri->rc = NULL; + ri->rrdlabels = NULL; + ri->rrdmetrics = NULL; + ri->rrdset = NULL; +} + +static void rrdinstance_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdcontext) { + RRDINSTANCE *ri = value; + + // link it to its parent + ri->rc = rrdcontext; + + ri->flags = ri->flags & RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS; // no need for atomics + + if(!ri->name) + ri->name = string_dup(ri->id); + + if(ri->rrdset) { + ri->rrdlabels = ri->rrdset->rrdlabels; + ri->flags &= ~RRD_FLAG_OWN_LABELS; // no need of atomics at the constructor + } + else { + ri->rrdlabels = rrdlabels_create(); + ri->flags |= RRD_FLAG_OWN_LABELS; // no need of atomics at the constructor + } + + if(ri->rrdset) { + if(unlikely(rrdset_flag_check(ri->rrdset, RRDSET_FLAG_HIDDEN))) + ri->flags |= RRD_FLAG_HIDDEN; // no need of atomics at the constructor + else + 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 + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_NEW_OBJECT); +} + +static void rrdinstance_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdcontext __maybe_unused) { + RRDINSTANCE *ri = (RRDINSTANCE *)value; + + internal_error(ri->rrdset, "RRDINSTANCE: '%s' is freed but there is a RRDSET linked to it.", string2str(ri->id)); + + rrdinstance_free(ri); +} + +static bool rrdinstance_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *rrdcontext __maybe_unused) { + RRDINSTANCE *ri = (RRDINSTANCE *)old_value; + RRDINSTANCE *ri_new = (RRDINSTANCE *)new_value; + + internal_error(ri->id != ri_new->id, + "RRDINSTANCE: '%s' cannot change id to '%s'", + string2str(ri->id), string2str(ri_new->id)); + + if(uuid_memcmp(&ri->uuid, &ri_new->uuid) != 0) { +#ifdef NETDATA_INTERNAL_CHECKS + char uuid1[UUID_STR_LEN], uuid2[UUID_STR_LEN]; + uuid_unparse(ri->uuid, uuid1); + uuid_unparse(ri_new->uuid, uuid2); + internal_error(true, "RRDINSTANCE: '%s' of host '%s' changed UUID from '%s' to '%s'", + string2str(ri->id), rrdhost_hostname(ri->rc->rrdhost), uuid1, uuid2); +#endif + + uuid_copy(ri->uuid, ri_new->uuid); + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + if(ri->rrdset && ri_new->rrdset && ri->rrdset != ri_new->rrdset) { + ri->rrdset = ri_new->rrdset; + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LINKING); + } + +#ifdef NETDATA_INTERNAL_CHECKS + if(ri->rrdset && uuid_memcmp(&ri->uuid, &ri->rrdset->chart_uuid) != 0) { + char uuid1[UUID_STR_LEN], uuid2[UUID_STR_LEN]; + uuid_unparse(ri->uuid, uuid1); + uuid_unparse(ri->rrdset->chart_uuid, uuid2); + internal_error(true, "RRDINSTANCE: '%s' is linked to RRDSET '%s' but they have different UUIDs. RRDINSTANCE has '%s', RRDSET has '%s'", string2str(ri->id), rrdset_id(ri->rrdset), uuid1, uuid2); + } +#endif + + if(ri->name != ri_new->name) { + STRING *old = ri->name; + ri->name = string_dup(ri_new->name); + string_freez(old); + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + if(ri->title != ri_new->title) { + STRING *old = ri->title; + ri->title = string_dup(ri_new->title); + string_freez(old); + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + if(ri->units != ri_new->units) { + STRING *old = ri->units; + ri->units = string_dup(ri_new->units); + string_freez(old); + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + if(ri->family != ri_new->family) { + STRING *old = ri->family; + ri->family = string_dup(ri_new->family); + string_freez(old); + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + if(ri->chart_type != ri_new->chart_type) { + ri->chart_type = ri_new->chart_type; + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + if(ri->priority != ri_new->priority) { + ri->priority = ri_new->priority; + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + 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); + } + + if(ri->rrdset != ri_new->rrdset) { + ri->rrdset = ri_new->rrdset; + + if(ri->rrdset && rrd_flag_check(ri, RRD_FLAG_OWN_LABELS)) { + DICTIONARY *old = ri->rrdlabels; + ri->rrdlabels = ri->rrdset->rrdlabels; + rrd_flag_clear(ri, RRD_FLAG_OWN_LABELS); + rrdlabels_destroy(old); + } + else if(!ri->rrdset && !rrd_flag_check(ri, RRD_FLAG_OWN_LABELS)) { + ri->rrdlabels = rrdlabels_create(); + rrd_flag_set(ri, RRD_FLAG_OWN_LABELS); + } + } + + if(ri->rrdset) { + if(unlikely(rrdset_flag_check(ri->rrdset, RRDSET_FLAG_HIDDEN))) + rrd_flag_set(ri, RRD_FLAG_HIDDEN); + else + rrd_flag_clear(ri, RRD_FLAG_HIDDEN); + } + + rrd_flag_set(ri, ri_new->flags & RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS); // no need for atomics on ri_new + + if(rrd_flag_is_collected(ri) && rrd_flag_is_archived(ri)) + rrd_flag_set_collected(ri); + + if(rrd_flag_is_updated(ri)) + rrd_flag_set(ri, RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT); + + // free the new one + rrdinstance_free(ri_new); + + // the react callback will continue from here + return rrd_flag_is_updated(ri); +} + +static void rrdinstance_react_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdcontext __maybe_unused) { + RRDINSTANCE *ri = value; + + rrdinstance_trigger_updates(ri, __FUNCTION__ ); +} + +void rrdinstances_create_in_rrdcontext(RRDCONTEXT *rc) { + if(unlikely(!rc || rc->rrdinstances)) return; + + 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); + dictionary_register_react_callback(rc->rrdinstances, rrdinstance_react_callback, rc); +} + +void rrdinstances_destroy_from_rrdcontext(RRDCONTEXT *rc) { + if(unlikely(!rc || !rc->rrdinstances)) return; + + dictionary_destroy(rc->rrdinstances); + rc->rrdinstances = NULL; +} + +void rrdinstance_trigger_updates(RRDINSTANCE *ri, const char *function) { + RRDSET *st = ri->rrdset; + + if(likely(st)) { + if(unlikely((unsigned int) st->priority != ri->priority)) { + ri->priority = st->priority; + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + 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); + } + } + else if(unlikely(rrd_flag_is_collected(ri))) { + // there is no rrdset, but we have it as collected! + + rrd_flag_set_archived(ri); + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LINKING); + } + + if(rrd_flag_is_updated(ri) || !rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION)) { + rrd_flag_set_updated(ri->rc, RRD_FLAG_UPDATE_REASON_TRIGGERED); + rrdcontext_queue_for_post_processing(ri->rc, function, ri->flags); + } +} + +// ---------------------------------------------------------------------------- +// RRDINSTANCE HOOKS ON RRDSET + +inline void rrdinstance_from_rrdset(RRDSET *st) { + RRDCONTEXT trc = { + .id = string_dup(st->context), + .title = string_dup(st->title), + .units = string_dup(st->units), + .family = string_dup(st->family), + .priority = st->priority, + .chart_type = st->chart_type, + .flags = RRD_FLAG_NONE, // no need for atomics + .rrdhost = st->rrdhost, + }; + + RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_set_and_acquire_item(st->rrdhost->rrdctx.contexts, string2str(trc.id), &trc, sizeof(trc)); + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + + RRDINSTANCE tri = { + .id = string_dup(st->id), + .name = string_dup(st->name), + .units = string_dup(st->units), + .family = string_dup(st->family), + .title = string_dup(st->title), + .chart_type = st->chart_type, + .priority = st->priority, + .update_every_s = st->update_every, + .flags = RRD_FLAG_NONE, // no need for atomics + .rrdset = st, + }; + uuid_copy(tri.uuid, st->chart_uuid); + + RRDINSTANCE_ACQUIRED *ria = (RRDINSTANCE_ACQUIRED *)dictionary_set_and_acquire_item(rc->rrdinstances, string2str(tri.id), &tri, sizeof(tri)); + + RRDCONTEXT_ACQUIRED *rca_old = st->rrdcontext; + RRDINSTANCE_ACQUIRED *ria_old = st->rrdinstance; + + st->rrdcontext = rca; + st->rrdinstance = ria; + + if(rca == rca_old) { + rrdcontext_release(rca_old); + rca_old = NULL; + } + + if(ria == ria_old) { + rrdinstance_release(ria_old); + ria_old = NULL; + } + + if(rca_old && ria_old) { + // Oops! The chart changed context! + + // RRDCONTEXT *rc_old = rrdcontext_acquired_value(rca_old); + RRDINSTANCE *ri_old = rrdinstance_acquired_value(ria_old); + + // migrate all dimensions to the new metrics + RRDDIM *rd; + rrddim_foreach_read(rd, st) { + if (!rd->rrdmetric) continue; + + 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_s = 0; + rm_old->last_time_s = 0; + + rrdmetric_release(rd->rrdmetric); + rd->rrdmetric = NULL; + + rrdmetric_from_rrddim(rd); + } + rrddim_foreach_done(rd); + + // mark the old instance, ready to be deleted + if(!rrd_flag_check(ri_old, RRD_FLAG_OWN_LABELS)) + ri_old->rrdlabels = rrdlabels_create(); + + 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_s = 0; + ri_old->last_time_s = 0; + + rrdinstance_trigger_updates(ri_old, __FUNCTION__ ); + rrdinstance_release(ria_old); + + /* + // trigger updates on the old context + 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_s = 0; + rc_old->last_time_s = 0; + rrdcontext_unlock(rc_old); + rrdcontext_trigger_updates(rc_old, __FUNCTION__ ); + } + else + rrdcontext_trigger_updates(rc_old, __FUNCTION__ ); + */ + + rrdcontext_release(rca_old); + rca_old = NULL; + ria_old = NULL; + } + + if(rca_old || ria_old) + fatal("RRDCONTEXT: cannot switch rrdcontext without switching rrdinstance too"); +} + +#define rrdset_get_rrdinstance(st) rrdset_get_rrdinstance_with_trace(st, __FUNCTION__); +static inline RRDINSTANCE *rrdset_get_rrdinstance_with_trace(RRDSET *st, const char *function) { + if(unlikely(!st->rrdinstance)) { + error("RRDINSTANCE: RRDSET '%s' is not linked to an RRDINSTANCE at %s()", rrdset_id(st), function); + return NULL; + } + + RRDINSTANCE *ri = rrdinstance_acquired_value(st->rrdinstance); + if(unlikely(!ri)) { + error("RRDINSTANCE: RRDSET '%s' lost its link to an RRDINSTANCE at %s()", rrdset_id(st), function); + return NULL; + } + + if(unlikely(ri->rrdset != st)) + fatal("RRDINSTANCE: '%s' is not linked to RRDSET '%s' at %s()", string2str(ri->id), rrdset_id(st), function); + + return ri; +} + +inline void rrdinstance_rrdset_is_freed(RRDSET *st) { + RRDINSTANCE *ri = rrdset_get_rrdinstance(st); + if(unlikely(!ri)) return; + + rrd_flag_set_archived(ri); + + if(!rrd_flag_check(ri, RRD_FLAG_OWN_LABELS)) { + ri->rrdlabels = rrdlabels_create(); + rrdlabels_copy(ri->rrdlabels, st->rrdlabels); + rrd_flag_set(ri, RRD_FLAG_OWN_LABELS); + } + + ri->rrdset = NULL; + + rrdinstance_trigger_updates(ri, __FUNCTION__ ); + + rrdinstance_release(st->rrdinstance); + st->rrdinstance = NULL; + + rrdcontext_release(st->rrdcontext); + st->rrdcontext = NULL; +} + +inline void rrdinstance_rrdset_has_updated_retention(RRDSET *st) { + RRDINSTANCE *ri = rrdset_get_rrdinstance(st); + if(unlikely(!ri)) return; + + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION); + rrdinstance_trigger_updates(ri, __FUNCTION__ ); +} + +inline void rrdinstance_updated_rrdset_name(RRDSET *st) { + // the chart may not be initialized when this is called + if(unlikely(!st->rrdinstance)) return; + + RRDINSTANCE *ri = rrdset_get_rrdinstance(st); + if(unlikely(!ri)) return; + + if(st->name != ri->name) { + STRING *old = ri->name; + ri->name = string_dup(st->name); + string_freez(old); + + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + rrdinstance_trigger_updates(ri, __FUNCTION__ ); + } +} + +inline void rrdinstance_updated_rrdset_flags_no_action(RRDINSTANCE *ri, RRDSET *st) { + if(unlikely(ri->rrdset != st)) + fatal("RRDCONTEXT: instance '%s' is not linked to chart '%s' on host '%s'", + string2str(ri->id), rrdset_id(st), rrdhost_hostname(st->rrdhost)); + + bool st_is_hidden = rrdset_flag_check(st, RRDSET_FLAG_HIDDEN); + bool ri_is_hidden = rrd_flag_check(ri, RRD_FLAG_HIDDEN); + + if(unlikely(st_is_hidden != ri_is_hidden)) { + if (unlikely(st_is_hidden && !ri_is_hidden)) + rrd_flag_set_updated(ri, RRD_FLAG_HIDDEN | RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + + else if (unlikely(!st_is_hidden && ri_is_hidden)) { + rrd_flag_clear(ri, RRD_FLAG_HIDDEN); + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + } +} + +inline void rrdinstance_updated_rrdset_flags(RRDSET *st) { + RRDINSTANCE *ri = rrdset_get_rrdinstance(st); + if(unlikely(!ri)) return; + + if(unlikely(rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED|RRDSET_FLAG_OBSOLETE))) + rrd_flag_set_archived(ri); + + rrdinstance_updated_rrdset_flags_no_action(ri, st); + + rrdinstance_trigger_updates(ri, __FUNCTION__ ); +} + +inline void rrdinstance_collected_rrdset(RRDSET *st) { + RRDINSTANCE *ri = rrdset_get_rrdinstance(st); + if(unlikely(!ri)) { + rrdcontext_updated_rrdset(st); + ri = rrdset_get_rrdinstance(st); + if(unlikely(!ri)) + return; + } + + rrdinstance_updated_rrdset_flags_no_action(ri, st); + + if(unlikely(ri->internal.collected_metrics_count && !rrd_flag_is_collected(ri))) + rrd_flag_set_collected(ri); + + // we use this variable to detect BEGIN/END without SET + ri->internal.collected_metrics_count = 0; + + rrdinstance_trigger_updates(ri, __FUNCTION__ ); +} + diff --git a/database/contexts/internal.h b/database/contexts/internal.h new file mode 100644 index 00000000..9917d58e --- /dev/null +++ b/database/contexts/internal.h @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_RRDCONTEXT_INTERNAL_H +#define NETDATA_RRDCONTEXT_INTERNAL_H 1 + +#include "rrdcontext.h" +#include "../sqlite/sqlite_context.h" +#include "../../aclk/schema-wrappers/context.h" +#include "../../aclk/aclk_contexts_api.h" +#include "../../aclk/aclk.h" +#include "../storage_engine.h" + +#define MESSAGES_PER_BUNDLE_TO_SEND_TO_HUB_PER_HOST 5000 +#define FULL_RETENTION_SCAN_DELAY_AFTER_DB_ROTATION_SECS 120 +#define RRDCONTEXT_WORKER_THREAD_HEARTBEAT_USEC (1000 * USEC_PER_MS) +#define RRDCONTEXT_MINIMUM_ALLOWED_PRIORITY 10 + +#define LOG_TRANSITIONS false + +#define WORKER_JOB_HOSTS 1 +#define WORKER_JOB_CHECK 2 +#define WORKER_JOB_SEND 3 +#define WORKER_JOB_DEQUEUE 4 +#define WORKER_JOB_RETENTION 5 +#define WORKER_JOB_QUEUED 6 +#define WORKER_JOB_CLEANUP 7 +#define WORKER_JOB_CLEANUP_DELETE 8 +#define WORKER_JOB_PP_METRIC 9 // post-processing metrics +#define WORKER_JOB_PP_INSTANCE 10 // post-processing instances +#define WORKER_JOB_PP_CONTEXT 11 // post-processing contexts +#define WORKER_JOB_HUB_QUEUE_SIZE 12 +#define WORKER_JOB_PP_QUEUE_SIZE 13 + + +typedef enum __attribute__ ((__packed__)) { + RRD_FLAG_NONE = 0, + RRD_FLAG_DELETED = (1 << 0), // this is a deleted object (metrics, instances, contexts) + RRD_FLAG_COLLECTED = (1 << 1), // this object is currently being collected + RRD_FLAG_UPDATED = (1 << 2), // this object has updates to propagate + RRD_FLAG_ARCHIVED = (1 << 3), // this object is not currently being collected + RRD_FLAG_OWN_LABELS = (1 << 4), // this instance has its own labels - not linked to an RRDSET + RRD_FLAG_LIVE_RETENTION = (1 << 5), // we have got live retention from the database + RRD_FLAG_QUEUED_FOR_HUB = (1 << 6), // this context is currently queued to be dispatched to hub + RRD_FLAG_QUEUED_FOR_PP = (1 << 7), // this context is currently queued to be post-processed + RRD_FLAG_HIDDEN = (1 << 8), // don't expose this to the hub or the API + + RRD_FLAG_UPDATE_REASON_TRIGGERED = (1 << 9), // the update was triggered by the child object + RRD_FLAG_UPDATE_REASON_LOAD_SQL = (1 << 10), // this object has just been loaded from SQL + RRD_FLAG_UPDATE_REASON_NEW_OBJECT = (1 << 11), // this object has just been created + RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT = (1 << 12), // we received an update on this object + RRD_FLAG_UPDATE_REASON_CHANGED_LINKING = (1 << 13), // an instance or a metric switched RRDSET or RRDDIM + RRD_FLAG_UPDATE_REASON_CHANGED_METADATA = (1 << 14), // this context or instance changed uuid, name, units, title, family, chart type, priority, update every, rrd changed flags + RRD_FLAG_UPDATE_REASON_ZERO_RETENTION = (1 << 15), // this object has no retention + RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T = (1 << 16), // this object changed its oldest time in the db + RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T = (1 << 17), // this object change its latest time in the db + RRD_FLAG_UPDATE_REASON_STOPPED_BEING_COLLECTED = (1 << 18), // this object has stopped being collected + RRD_FLAG_UPDATE_REASON_STARTED_BEING_COLLECTED = (1 << 19), // this object has started being collected + RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD = (1 << 20), // this context belongs to a host that just disconnected + RRD_FLAG_UPDATE_REASON_UNUSED = (1 << 21), // this context is not used anymore + RRD_FLAG_UPDATE_REASON_DB_ROTATION = (1 << 22), // this context changed because of a db rotation + + // action to perform on an object + RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION = (1 << 30), // this object has to update its retention from the db +} RRD_FLAGS; + +struct rrdcontext_reason { + RRD_FLAGS flag; + const char *name; + usec_t delay_ut; +}; + +extern struct rrdcontext_reason rrdcontext_reasons[]; + +#define RRD_FLAG_ALL_UPDATE_REASONS ( \ + RRD_FLAG_UPDATE_REASON_TRIGGERED \ + |RRD_FLAG_UPDATE_REASON_LOAD_SQL \ + |RRD_FLAG_UPDATE_REASON_NEW_OBJECT \ + |RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT \ + |RRD_FLAG_UPDATE_REASON_CHANGED_LINKING \ + |RRD_FLAG_UPDATE_REASON_CHANGED_METADATA \ + |RRD_FLAG_UPDATE_REASON_ZERO_RETENTION \ + |RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T \ + |RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T \ + |RRD_FLAG_UPDATE_REASON_STOPPED_BEING_COLLECTED \ + |RRD_FLAG_UPDATE_REASON_STARTED_BEING_COLLECTED \ + |RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD \ + |RRD_FLAG_UPDATE_REASON_DB_ROTATION \ + |RRD_FLAG_UPDATE_REASON_UNUSED \ + ) + +#define RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS ( \ + RRD_FLAG_ARCHIVED \ + |RRD_FLAG_HIDDEN \ + |RRD_FLAG_ALL_UPDATE_REASONS \ + ) + +#define RRD_FLAGS_REQUIRED_FOR_DELETIONS ( \ + RRD_FLAG_DELETED \ + |RRD_FLAG_LIVE_RETENTION \ +) + +#define RRD_FLAGS_PREVENTING_DELETIONS ( \ + RRD_FLAG_QUEUED_FOR_HUB \ + |RRD_FLAG_COLLECTED \ + |RRD_FLAG_QUEUED_FOR_PP \ +) + +// get all the flags of an object +#define rrd_flags_get(obj) __atomic_load_n(&((obj)->flags), __ATOMIC_SEQ_CST) + +// check if ANY of the given flags (bits) is set +#define rrd_flag_check(obj, flag) (rrd_flags_get(obj) & (flag)) + +// check if ALL the given flags (bits) are set +#define rrd_flag_check_all(obj, flag) (rrd_flag_check(obj, flag) == (flag)) + +// set one or more flags (bits) +#define rrd_flag_set(obj, flag) __atomic_or_fetch(&((obj)->flags), flag, __ATOMIC_SEQ_CST) + +// clear one or more flags (bits) +#define rrd_flag_clear(obj, flag) __atomic_and_fetch(&((obj)->flags), ~(flag), __ATOMIC_SEQ_CST) + +// replace the flags of an object, with the supplied ones +#define rrd_flags_replace(obj, all_flags) __atomic_store_n(&((obj)->flags), all_flags, __ATOMIC_SEQ_CST) + +static inline void +rrd_flag_add_remove_atomic(RRD_FLAGS *flags, RRD_FLAGS check, RRD_FLAGS conditionally_add, RRD_FLAGS always_remove) { + RRD_FLAGS expected, desired; + + do { + expected = *flags; + + desired = expected; + desired &= ~(always_remove); + + if(!(expected & check)) + desired |= (check | conditionally_add); + + } while(!__atomic_compare_exchange_n(flags, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)); +} + +#define rrd_flag_set_collected(obj) \ + rrd_flag_add_remove_atomic(&((obj)->flags) \ + /* check this flag */ \ + , RRD_FLAG_COLLECTED \ + \ + /* add these flags together with the above, if the above is not already set */ \ + , RRD_FLAG_UPDATE_REASON_STARTED_BEING_COLLECTED | RRD_FLAG_UPDATED \ + \ + /* always remove these flags */ \ + , RRD_FLAG_ARCHIVED \ + | RRD_FLAG_DELETED \ + | RRD_FLAG_UPDATE_REASON_STOPPED_BEING_COLLECTED \ + | RRD_FLAG_UPDATE_REASON_ZERO_RETENTION \ + | RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD \ + ) + +#define rrd_flag_set_archived(obj) \ + rrd_flag_add_remove_atomic(&((obj)->flags) \ + /* check this flag */ \ + , RRD_FLAG_ARCHIVED \ + \ + /* add these flags together with the above, if the above is not already set */ \ + , RRD_FLAG_UPDATE_REASON_STOPPED_BEING_COLLECTED | RRD_FLAG_UPDATED \ + \ + /* always remove these flags */ \ + , RRD_FLAG_COLLECTED \ + | RRD_FLAG_DELETED \ + | RRD_FLAG_UPDATE_REASON_STARTED_BEING_COLLECTED \ + | RRD_FLAG_UPDATE_REASON_ZERO_RETENTION \ + ) + +#define rrd_flag_set_deleted(obj, reason) \ + rrd_flag_add_remove_atomic(&((obj)->flags) \ + /* check this flag */ \ + , RRD_FLAG_DELETED \ + \ + /* add these flags together with the above, if the above is not already set */ \ + , RRD_FLAG_UPDATE_REASON_ZERO_RETENTION | RRD_FLAG_UPDATED | (reason) \ + \ + /* always remove these flags */ \ + , RRD_FLAG_ARCHIVED \ + | RRD_FLAG_COLLECTED \ + ) + +#define rrd_flag_is_collected(obj) rrd_flag_check(obj, RRD_FLAG_COLLECTED) +#define rrd_flag_is_archived(obj) rrd_flag_check(obj, RRD_FLAG_ARCHIVED) +#define rrd_flag_is_deleted(obj) rrd_flag_check(obj, RRD_FLAG_DELETED) +#define rrd_flag_is_updated(obj) rrd_flag_check(obj, RRD_FLAG_UPDATED) + +// mark an object as updated, providing reasons (additional bits) +#define rrd_flag_set_updated(obj, reason) rrd_flag_set(obj, RRD_FLAG_UPDATED | (reason)) + +// clear an object as being updated, clearing also all the reasons +#define rrd_flag_unset_updated(obj) rrd_flag_clear(obj, RRD_FLAG_UPDATED | RRD_FLAG_ALL_UPDATE_REASONS) + + +typedef struct rrdmetric { + uuid_t uuid; + + STRING *id; + STRING *name; + + RRDDIM *rrddim; + + time_t first_time_s; + time_t last_time_s; + RRD_FLAGS flags; + + struct rrdinstance *ri; +} RRDMETRIC; + +typedef struct rrdinstance { + uuid_t uuid; + + STRING *id; + STRING *name; + STRING *title; + STRING *units; + STRING *family; + uint32_t priority:24; + RRDSET_TYPE chart_type; + + RRD_FLAGS flags; // flags related to this instance + time_t first_time_s; + time_t last_time_s; + + 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 + + struct rrdcontext *rc; + DICTIONARY *rrdmetrics; + + struct { + uint32_t collected_metrics_count; // a temporary variable to detect BEGIN/END without SET + // don't use it for other purposes + // it goes up and then resets to zero, on every iteration + } internal; +} RRDINSTANCE; + +typedef struct rrdcontext { + uint64_t version; + + STRING *id; + STRING *title; + STRING *units; + STRING *family; + uint32_t priority; + RRDSET_TYPE chart_type; + + RRD_FLAGS flags; + time_t first_time_s; + time_t last_time_s; + + VERSIONED_CONTEXT_DATA hub; + + DICTIONARY *rrdinstances; + RRDHOST *rrdhost; + + struct { + RRD_FLAGS queued_flags; // the last flags that triggered the post-processing + usec_t queued_ut; // the last time this was queued + usec_t dequeued_ut; // the last time we sent (or deduplicated) this context + size_t executions; // how many times this context has been processed + } pp; + + struct { + RRD_FLAGS queued_flags; // the last flags that triggered the queueing + usec_t queued_ut; // the last time this was queued + usec_t delay_calc_ut; // the last time we calculated the scheduled_dispatched_ut + usec_t scheduled_dispatch_ut; // the time it was/is scheduled to be sent + usec_t dequeued_ut; // the last time we sent (or deduplicated) this context + size_t dispatches; // the number of times this has been dispatched to hub + } queue; + + netdata_mutex_t mutex; +} RRDCONTEXT; + + +// ---------------------------------------------------------------------------- +// helper one-liners for RRDMETRIC + +bool rrdmetric_update_retention(RRDMETRIC *rm); + +static inline RRDMETRIC *rrdmetric_acquired_value(RRDMETRIC_ACQUIRED *rma) { + return dictionary_acquired_item_value((DICTIONARY_ITEM *)rma); +} + +static inline RRDMETRIC_ACQUIRED *rrdmetric_acquired_dup(RRDMETRIC_ACQUIRED *rma) { + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + return (RRDMETRIC_ACQUIRED *)dictionary_acquired_item_dup(rm->ri->rrdmetrics, (DICTIONARY_ITEM *)rma); +} + +static inline void rrdmetric_release(RRDMETRIC_ACQUIRED *rma) { + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + dictionary_acquired_item_release(rm->ri->rrdmetrics, (DICTIONARY_ITEM *)rma); +} + +void rrdmetric_rrddim_is_freed(RRDDIM *rd); +void rrdmetric_updated_rrddim_flags(RRDDIM *rd); +void rrdmetric_collected_rrddim(RRDDIM *rd); + +// ---------------------------------------------------------------------------- +// helper one-liners for RRDINSTANCE + +static inline RRDINSTANCE *rrdinstance_acquired_value(RRDINSTANCE_ACQUIRED *ria) { + return dictionary_acquired_item_value((DICTIONARY_ITEM *)ria); +} + +static inline RRDINSTANCE_ACQUIRED *rrdinstance_acquired_dup(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return (RRDINSTANCE_ACQUIRED *)dictionary_acquired_item_dup(ri->rc->rrdinstances, (DICTIONARY_ITEM *)ria); +} + +static inline void rrdinstance_release(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + dictionary_acquired_item_release(ri->rc->rrdinstances, (DICTIONARY_ITEM *)ria); +} + +void rrdinstance_from_rrdset(RRDSET *st); +void rrdinstance_rrdset_is_freed(RRDSET *st); +void rrdinstance_rrdset_has_updated_retention(RRDSET *st); +void rrdinstance_updated_rrdset_name(RRDSET *st); +void rrdinstance_updated_rrdset_flags_no_action(RRDINSTANCE *ri, RRDSET *st); +void rrdinstance_updated_rrdset_flags(RRDSET *st); +void rrdinstance_collected_rrdset(RRDSET *st); + +void rrdcontext_queue_for_post_processing(RRDCONTEXT *rc, const char *function, RRD_FLAGS flags); + +// ---------------------------------------------------------------------------- +// helper one-liners for RRDCONTEXT + +static inline RRDCONTEXT *rrdcontext_acquired_value(RRDCONTEXT_ACQUIRED *rca) { + return dictionary_acquired_item_value((DICTIONARY_ITEM *)rca); +} + +static inline RRDCONTEXT_ACQUIRED *rrdcontext_acquired_dup(RRDCONTEXT_ACQUIRED *rca) { + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + return (RRDCONTEXT_ACQUIRED *)dictionary_acquired_item_dup(rc->rrdhost->rrdctx.contexts, (DICTIONARY_ITEM *)rca); +} + +static inline void rrdcontext_release(RRDCONTEXT_ACQUIRED *rca) { + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + dictionary_acquired_item_release(rc->rrdhost->rrdctx.contexts, (DICTIONARY_ITEM *)rca); +} + +// ---------------------------------------------------------------------------- +// Forward definitions + +void rrdcontext_recalculate_context_retention(RRDCONTEXT *rc, RRD_FLAGS reason, bool worker_jobs); +void rrdcontext_recalculate_host_retention(RRDHOST *host, RRD_FLAGS reason, bool worker_jobs); + +#define rrdcontext_lock(rc) netdata_mutex_lock(&((rc)->mutex)) +#define rrdcontext_unlock(rc) netdata_mutex_unlock(&((rc)->mutex)) + +void rrdinstance_trigger_updates(RRDINSTANCE *ri, const char *function); +void rrdcontext_trigger_updates(RRDCONTEXT *rc, const char *function); + +void rrdinstances_create_in_rrdcontext(RRDCONTEXT *rc); +void rrdinstances_destroy_from_rrdcontext(RRDCONTEXT *rc); + +void rrdmetrics_destroy_from_rrdinstance(RRDINSTANCE *ri); +void rrdmetrics_create_in_rrdinstance(RRDINSTANCE *ri); + +void rrdmetric_from_rrddim(RRDDIM *rd); + +void rrd_reasons_to_buffer_json_array_items(RRD_FLAGS flags, BUFFER *wb); + +#define rrdcontext_version_hash(host) rrdcontext_version_hash_with_callback(host, NULL, false, NULL) +uint64_t rrdcontext_version_hash_with_callback( + RRDHOST *host, + void (*callback)(RRDCONTEXT *, bool, void *), + bool snapshot, + void *bundle); + +void rrdcontext_message_send_unsafe(RRDCONTEXT *rc, bool snapshot __maybe_unused, void *bundle __maybe_unused); + +#endif //NETDATA_RRDCONTEXT_INTERNAL_H diff --git a/database/contexts/metric.c b/database/contexts/metric.c new file mode 100644 index 00000000..80756b54 --- /dev/null +++ b/database/contexts/metric.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "internal.h" + +static void rrdmetric_trigger_updates(RRDMETRIC *rm, const char *function); + +inline const char *rrdmetric_acquired_id(RRDMETRIC_ACQUIRED *rma) { + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + return string2str(rm->id); +} + +inline const char *rrdmetric_acquired_name(RRDMETRIC_ACQUIRED *rma) { + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + return string2str(rm->name); +} + +inline bool rrdmetric_acquired_has_name(RRDMETRIC_ACQUIRED *rma) { + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + return (rm->name && rm->name != rm->id); +} + +inline STRING *rrdmetric_acquired_id_dup(RRDMETRIC_ACQUIRED *rma) { + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + return string_dup(rm->id); +} + +inline STRING *rrdmetric_acquired_name_dup(RRDMETRIC_ACQUIRED *rma) { + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + return string_dup(rm->name); +} + +inline NETDATA_DOUBLE rrdmetric_acquired_last_stored_value(RRDMETRIC_ACQUIRED *rma) { + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + + if(rm->rrddim) + return rm->rrddim->last_stored_value; + + return NAN; +} + +inline bool rrdmetric_acquired_belongs_to_instance(RRDMETRIC_ACQUIRED *rma, RRDINSTANCE_ACQUIRED *ria) { + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return rm->ri == ri; +} + +inline time_t rrdmetric_acquired_first_entry(RRDMETRIC_ACQUIRED *rma) { + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + return rm->first_time_s; +} + +inline time_t rrdmetric_acquired_last_entry(RRDMETRIC_ACQUIRED *rma) { + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + + if(rrd_flag_check(rm, RRD_FLAG_COLLECTED)) + return 0; + + return rm->last_time_s; +} + +// ---------------------------------------------------------------------------- +// RRDMETRIC + +// free the contents of RRDMETRIC. +// RRDMETRIC itself is managed by DICTIONARY - no need to free it here. +static void rrdmetric_free(RRDMETRIC *rm) { + string_freez(rm->id); + string_freez(rm->name); + + rm->id = NULL; + rm->name = NULL; + rm->ri = NULL; +} + +// called when this rrdmetric is inserted to the rrdmetrics dictionary of a rrdinstance +// the constructor of the rrdmetric object +static void rrdmetric_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdinstance) { + RRDMETRIC *rm = value; + + // link it to its parent + rm->ri = rrdinstance; + + // remove flags that we need to figure out at runtime + rm->flags = rm->flags & RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS; // no need for atomics + + // signal the react callback to do the job + rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_NEW_OBJECT); +} + +// called when this rrdmetric is deleted from the rrdmetrics dictionary of a rrdinstance +// the destructor of the rrdmetric object +static void rrdmetric_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdinstance __maybe_unused) { + RRDMETRIC *rm = value; + + internal_error(rm->rrddim, "RRDMETRIC: '%s' is freed but there is a RRDDIM linked to it.", string2str(rm->id)); + + // free the resources + rrdmetric_free(rm); +} + +// called when the same rrdmetric is inserted again to the rrdmetrics dictionary of a rrdinstance +// while this is called, the dictionary is write locked, but there may be other users of the object +static bool rrdmetric_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *rrdinstance __maybe_unused) { + RRDMETRIC *rm = old_value; + RRDMETRIC *rm_new = new_value; + + internal_error(rm->id != rm_new->id, + "RRDMETRIC: '%s' cannot change id to '%s'", + string2str(rm->id), string2str(rm_new->id)); + + if(uuid_memcmp(&rm->uuid, &rm_new->uuid) != 0) { +#ifdef NETDATA_INTERNAL_CHECKS + char uuid1[UUID_STR_LEN], uuid2[UUID_STR_LEN]; + uuid_unparse(rm->uuid, uuid1); + uuid_unparse(rm_new->uuid, uuid2); + + time_t old_first_time_s = 0; + time_t old_last_time_s = 0; + if(rrdmetric_update_retention(rm)) { + 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_s = 0; + time_t new_last_time_s = 0; + if(rrdmetric_update_retention(rm)) { + new_first_time_s = rm->first_time_s; + new_last_time_s = rm->last_time_s; + } + + internal_error(true, + "RRDMETRIC: '%s' of instance '%s' of host '%s' changed UUID from '%s' (retention %ld to %ld, %ld secs) to '%s' (retention %ld to %ld, %ld secs)" + , string2str(rm->id) + , string2str(rm->ri->id) + , rrdhost_hostname(rm->ri->rc->rrdhost) + , 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); +#endif + rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + if(rm->rrddim && rm_new->rrddim && rm->rrddim != rm_new->rrddim) { + rm->rrddim = rm_new->rrddim; + rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_LINKING); + } + +#ifdef NETDATA_INTERNAL_CHECKS + if(rm->rrddim && uuid_memcmp(&rm->uuid, &rm->rrddim->metric_uuid) != 0) { + char uuid1[UUID_STR_LEN], uuid2[UUID_STR_LEN]; + uuid_unparse(rm->uuid, uuid1); + uuid_unparse(rm_new->uuid, uuid2); + internal_error(true, "RRDMETRIC: '%s' is linked to RRDDIM '%s' but they have different UUIDs. RRDMETRIC has '%s', RRDDIM has '%s'", string2str(rm->id), rrddim_id(rm->rrddim), uuid1, uuid2); + } +#endif + + if(rm->rrddim != rm_new->rrddim) + rm->rrddim = rm_new->rrddim; + + if(rm->name != rm_new->name) { + STRING *old = rm->name; + rm->name = string_dup(rm_new->name); + string_freez(old); + rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + + 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_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); + } + + rrd_flag_set(rm, rm_new->flags & RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS); // no needs for atomics on rm_new + + if(rrd_flag_is_collected(rm) && rrd_flag_is_archived(rm)) + rrd_flag_set_collected(rm); + + if(rrd_flag_check(rm, RRD_FLAG_UPDATED)) + rrd_flag_set(rm, RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT); + + rrdmetric_free(rm_new); + + // the react callback will continue from here + return rrd_flag_is_updated(rm); +} + +// this is called after the insert or the conflict callbacks, +// but the dictionary is now unlocked +static void rrdmetric_react_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdinstance __maybe_unused) { + RRDMETRIC *rm = value; + rrdmetric_trigger_updates(rm, __FUNCTION__ ); +} + +void rrdmetrics_create_in_rrdinstance(RRDINSTANCE *ri) { + if(unlikely(!ri)) return; + if(likely(ri->rrdmetrics)) return; + + 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); + dictionary_register_react_callback(ri->rrdmetrics, rrdmetric_react_callback, ri); +} + +void rrdmetrics_destroy_from_rrdinstance(RRDINSTANCE *ri) { + if(unlikely(!ri || !ri->rrdmetrics)) return; + dictionary_destroy(ri->rrdmetrics); + ri->rrdmetrics = NULL; +} + +// trigger post-processing of the rrdmetric, escalating changes to the rrdinstance it belongs +static void rrdmetric_trigger_updates(RRDMETRIC *rm, const char *function) { + if(unlikely(rrd_flag_is_collected(rm)) && (!rm->rrddim || rrd_flag_check(rm, RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD))) + rrd_flag_set_archived(rm); + + if(rrd_flag_is_updated(rm) || !rrd_flag_check(rm, RRD_FLAG_LIVE_RETENTION)) { + rrd_flag_set_updated(rm->ri, RRD_FLAG_UPDATE_REASON_TRIGGERED); + rrdcontext_queue_for_post_processing(rm->ri->rc, function, rm->flags); + } +} + +// ---------------------------------------------------------------------------- +// RRDMETRIC HOOKS ON RRDDIM + +void rrdmetric_from_rrddim(RRDDIM *rd) { + if(unlikely(!rd->rrdset)) + fatal("RRDMETRIC: rrddim '%s' does not have a rrdset.", rrddim_id(rd)); + + if(unlikely(!rd->rrdset->rrdhost)) + fatal("RRDMETRIC: rrdset '%s' does not have a rrdhost", rrdset_id(rd->rrdset)); + + if(unlikely(!rd->rrdset->rrdinstance)) + fatal("RRDMETRIC: rrdset '%s' does not have a rrdinstance", rrdset_id(rd->rrdset)); + + RRDINSTANCE *ri = rrdinstance_acquired_value(rd->rrdset->rrdinstance); + + RRDMETRIC trm = { + .id = string_dup(rd->id), + .name = string_dup(rd->name), + .flags = RRD_FLAG_NONE, // no need for atomics + .rrddim = rd, + }; + uuid_copy(trm.uuid, rd->metric_uuid); + + RRDMETRIC_ACQUIRED *rma = (RRDMETRIC_ACQUIRED *)dictionary_set_and_acquire_item(ri->rrdmetrics, string2str(trm.id), &trm, sizeof(trm)); + + if(rd->rrdmetric) + rrdmetric_release(rd->rrdmetric); + + rd->rrdmetric = rma; +} + +#define rrddim_get_rrdmetric(rd) rrddim_get_rrdmetric_with_trace(rd, __FUNCTION__) +static inline RRDMETRIC *rrddim_get_rrdmetric_with_trace(RRDDIM *rd, const char *function) { + if(unlikely(!rd->rrdmetric)) { + error("RRDMETRIC: RRDDIM '%s' is not linked to an RRDMETRIC at %s()", rrddim_id(rd), function); + return NULL; + } + + RRDMETRIC *rm = rrdmetric_acquired_value(rd->rrdmetric); + if(unlikely(!rm)) { + error("RRDMETRIC: RRDDIM '%s' lost the link to its RRDMETRIC at %s()", rrddim_id(rd), function); + return NULL; + } + + if(unlikely(rm->rrddim != rd)) + fatal("RRDMETRIC: '%s' is not linked to RRDDIM '%s' at %s()", string2str(rm->id), rrddim_id(rd), function); + + return rm; +} + +inline void rrdmetric_rrddim_is_freed(RRDDIM *rd) { + RRDMETRIC *rm = rrddim_get_rrdmetric(rd); + if(unlikely(!rm)) return; + + if(unlikely(rrd_flag_is_collected(rm))) + rrd_flag_set_archived(rm); + + rm->rrddim = NULL; + rrdmetric_trigger_updates(rm, __FUNCTION__ ); + rrdmetric_release(rd->rrdmetric); + rd->rrdmetric = NULL; +} + +inline void rrdmetric_updated_rrddim_flags(RRDDIM *rd) { + RRDMETRIC *rm = rrddim_get_rrdmetric(rd); + if(unlikely(!rm)) return; + + if(unlikely(rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED|RRDDIM_FLAG_OBSOLETE))) { + if(unlikely(rrd_flag_is_collected(rm))) + rrd_flag_set_archived(rm); + } + + rrdmetric_trigger_updates(rm, __FUNCTION__ ); +} + +inline void rrdmetric_collected_rrddim(RRDDIM *rd) { + RRDMETRIC *rm = rrddim_get_rrdmetric(rd); + if(unlikely(!rm)) return; + + if(unlikely(!rrd_flag_is_collected(rm))) + rrd_flag_set_collected(rm); + + // we use this variable to detect BEGIN/END without SET + rm->ri->internal.collected_metrics_count++; + + rrdmetric_trigger_updates(rm, __FUNCTION__ ); +} + diff --git a/database/contexts/query_scope.c b/database/contexts/query_scope.c new file mode 100644 index 00000000..f3bcd0b3 --- /dev/null +++ b/database/contexts/query_scope.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "internal.h" + +ssize_t query_scope_foreach_host(SIMPLE_PATTERN *scope_hosts_sp, SIMPLE_PATTERN *hosts_sp, + foreach_host_cb_t cb, void *data, + struct query_versions *versions, + char *host_node_id_str) { + char uuid[UUID_STR_LEN]; + if(!host_node_id_str) host_node_id_str = uuid; + host_node_id_str[0] = '\0'; + + RRDHOST *host; + ssize_t added = 0; + uint64_t v_hash = 0; + uint64_t h_hash = 0; + uint64_t a_hash = 0; + uint64_t t_hash = 0; + + dfe_start_read(rrdhost_root_index, host) { + if(host->node_id) + uuid_unparse_lower(*host->node_id, host_node_id_str); + else + host_node_id_str[0] = '\0'; + + SIMPLE_PATTERN_RESULT match = SP_MATCHED_POSITIVE; + if(scope_hosts_sp) { + match = simple_pattern_matches_string_extract(scope_hosts_sp, host->hostname, NULL, 0); + if(match == SP_NOT_MATCHED) { + match = simple_pattern_matches_extract(scope_hosts_sp, host->machine_guid, NULL, 0); + if(match == SP_NOT_MATCHED && *host_node_id_str) + match = simple_pattern_matches_extract(scope_hosts_sp, host_node_id_str, NULL, 0); + } + } + + if(match != SP_MATCHED_POSITIVE) + continue; + + dfe_unlock(host); + + if(hosts_sp) { + match = simple_pattern_matches_string_extract(hosts_sp, host->hostname, NULL, 0); + if(match == SP_NOT_MATCHED) { + match = simple_pattern_matches_extract(hosts_sp, host->machine_guid, NULL, 0); + if(match == SP_NOT_MATCHED && *host_node_id_str) + match = simple_pattern_matches_extract(hosts_sp, host_node_id_str, NULL, 0); + } + } + + bool queryable_host = (match == SP_MATCHED_POSITIVE); + + v_hash += dictionary_version(host->rrdctx.contexts); + h_hash += dictionary_version(host->rrdctx.hub_queue); + a_hash += dictionary_version(host->rrdcalc_root_index); + t_hash += __atomic_load_n(&host->health_transitions, __ATOMIC_RELAXED); + ssize_t ret = cb(data, host, queryable_host); + if(ret < 0) { + added = ret; + break; + } + added += ret; + } + dfe_done(host); + + if(versions) { + versions->contexts_hard_hash = v_hash; + versions->contexts_soft_hash = h_hash; + versions->alerts_hard_hash = a_hash; + versions->alerts_soft_hash = t_hash; + } + + return added; +} + +ssize_t query_scope_foreach_context(RRDHOST *host, const char *scope_contexts, SIMPLE_PATTERN *scope_contexts_sp, + SIMPLE_PATTERN *contexts_sp, foreach_context_cb_t cb, bool queryable_host, void *data) { + if(unlikely(!host->rrdctx.contexts)) + return 0; + + ssize_t added = 0; + + RRDCONTEXT_ACQUIRED *rca = NULL; + + if(scope_contexts) + rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item(host->rrdctx.contexts, scope_contexts); + + if(likely(rca)) { + // we found it! + + bool queryable_context = queryable_host; + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + if(queryable_context && contexts_sp && !simple_pattern_matches_string(contexts_sp, rc->id)) + queryable_context = false; + + added = cb(data, rca, queryable_context); + + rrdcontext_release(rca); + } + else { + // Probably it is a pattern, we need to search for it... + RRDCONTEXT *rc; + dfe_start_read(host->rrdctx.contexts, rc) { + if(scope_contexts_sp && !simple_pattern_matches_string(scope_contexts_sp, rc->id)) + continue; + + dfe_unlock(rc); + + bool queryable_context = queryable_host; + if(queryable_context && contexts_sp && !simple_pattern_matches_string(contexts_sp, rc->id)) + queryable_context = false; + + ssize_t ret = cb(data, (RRDCONTEXT_ACQUIRED *)rc_dfe.item, queryable_context); + + if(ret < 0) { + added = ret; + break; + } + + added += ret; + } + dfe_done(rc); + } + + return added; +} + diff --git a/database/contexts/query_target.c b/database/contexts/query_target.c new file mode 100644 index 00000000..69386a3f --- /dev/null +++ b/database/contexts/query_target.c @@ -0,0 +1,1219 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "internal.h" + +#define QUERY_TARGET_MAX_REALLOC_INCREASE 500 +#define query_target_realloc_size(size, start) \ + (size) ? ((size) < QUERY_TARGET_MAX_REALLOC_INCREASE ? (size) * 2 : (size) + QUERY_TARGET_MAX_REALLOC_INCREASE) : (start); + +static void query_metric_release(QUERY_TARGET *qt, QUERY_METRIC *qm); +static void query_dimension_release(QUERY_DIMENSION *qd); +static void query_instance_release(QUERY_INSTANCE *qi); +static void query_context_release(QUERY_CONTEXT *qc); +static void query_node_release(QUERY_NODE *qn); + +static __thread QUERY_TARGET *thread_qt = NULL; +static struct { + struct { + SPINLOCK spinlock; + size_t count; + QUERY_TARGET *base; + } available; + + struct { + SPINLOCK spinlock; + size_t count; + QUERY_TARGET *base; + } used; +} query_target_base = { + .available = { + .spinlock = NETDATA_SPINLOCK_INITIALIZER, + .base = NULL, + .count = 0, + }, + .used = { + .spinlock = NETDATA_SPINLOCK_INITIALIZER, + .base = NULL, + .count = 0, + }, +}; + +static void query_target_destroy(QUERY_TARGET *qt) { + __atomic_sub_fetch(&netdata_buffers_statistics.query_targets_size, qt->query.size * sizeof(*qt->query.array), __ATOMIC_RELAXED); + freez(qt->query.array); + + __atomic_sub_fetch(&netdata_buffers_statistics.query_targets_size, qt->dimensions.size * sizeof(*qt->dimensions.array), __ATOMIC_RELAXED); + freez(qt->dimensions.array); + + __atomic_sub_fetch(&netdata_buffers_statistics.query_targets_size, qt->instances.size * sizeof(*qt->instances.array), __ATOMIC_RELAXED); + freez(qt->instances.array); + + __atomic_sub_fetch(&netdata_buffers_statistics.query_targets_size, qt->contexts.size * sizeof(*qt->contexts.array), __ATOMIC_RELAXED); + freez(qt->contexts.array); + + __atomic_sub_fetch(&netdata_buffers_statistics.query_targets_size, qt->nodes.size * sizeof(*qt->nodes.array), __ATOMIC_RELAXED); + freez(qt->nodes.array); + + freez(qt); +} + +void query_target_release(QUERY_TARGET *qt) { + if(unlikely(!qt)) return; + + internal_fatal(!qt->internal.used, "QUERY TARGET: qt to be released is not used"); + + simple_pattern_free(qt->nodes.scope_pattern); + qt->nodes.scope_pattern = NULL; + + simple_pattern_free(qt->nodes.pattern); + qt->nodes.pattern = NULL; + + simple_pattern_free(qt->contexts.scope_pattern); + qt->contexts.scope_pattern = NULL; + + simple_pattern_free(qt->contexts.pattern); + qt->contexts.pattern = NULL; + + simple_pattern_free(qt->instances.pattern); + qt->instances.pattern = NULL; + + simple_pattern_free(qt->instances.chart_label_key_pattern); + qt->instances.chart_label_key_pattern = NULL; + + simple_pattern_free(qt->instances.labels_pattern); + qt->instances.labels_pattern = NULL; + + simple_pattern_free(qt->query.pattern); + qt->query.pattern = NULL; + + // release the query + for(size_t i = 0, used = qt->query.used; i < used ;i++) { + QUERY_METRIC *qm = query_metric(qt, i); + query_metric_release(qt, qm); + } + qt->query.used = 0; + + // release the dimensions + for(size_t i = 0, used = qt->dimensions.used; i < used ; i++) { + QUERY_DIMENSION *qd = query_dimension(qt, i); + query_dimension_release(qd); + } + qt->dimensions.used = 0; + + // release the instances + for(size_t i = 0, used = qt->instances.used; i < used ;i++) { + QUERY_INSTANCE *qi = query_instance(qt, i); + query_instance_release(qi); + } + qt->instances.used = 0; + + // release the contexts + for(size_t i = 0, used = qt->contexts.used; i < used ;i++) { + QUERY_CONTEXT *qc = query_context(qt, i); + rrdcontext_release(qc->rca); + qc->rca = NULL; + } + qt->contexts.used = 0; + + // release the nodes + for(size_t i = 0, used = qt->nodes.used; i < used ; i++) { + QUERY_NODE *qn = query_node(qt, i); + query_node_release(qn); + } + qt->nodes.used = 0; + + qt->db.minimum_latest_update_every_s = 0; + qt->db.first_time_s = 0; + qt->db.last_time_s = 0; + + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) + qt->group_by[g].used = 0; + + qt->id[0] = '\0'; + + netdata_spinlock_lock(&query_target_base.used.spinlock); + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(query_target_base.used.base, qt, internal.prev, internal.next); + query_target_base.used.count--; + netdata_spinlock_unlock(&query_target_base.used.spinlock); + + qt->internal.used = false; + thread_qt = NULL; + + if (qt->internal.queries > 1000) { + query_target_destroy(qt); + } + else { + netdata_spinlock_lock(&query_target_base.available.spinlock); + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(query_target_base.available.base, qt, internal.prev, internal.next); + query_target_base.available.count++; + netdata_spinlock_unlock(&query_target_base.available.spinlock); + } +} + +static QUERY_TARGET *query_target_get(void) { + netdata_spinlock_lock(&query_target_base.available.spinlock); + QUERY_TARGET *qt = query_target_base.available.base; + if (qt) { + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(query_target_base.available.base, qt, internal.prev, internal.next); + query_target_base.available.count--; + } + netdata_spinlock_unlock(&query_target_base.available.spinlock); + + if(unlikely(!qt)) + qt = callocz(1, sizeof(*qt)); + + netdata_spinlock_lock(&query_target_base.used.spinlock); + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(query_target_base.used.base, qt, internal.prev, internal.next); + query_target_base.used.count++; + netdata_spinlock_unlock(&query_target_base.used.spinlock); + + qt->internal.used = true; + qt->internal.queries++; + thread_qt = qt; + + return qt; +} + +// this is used to release a query target from a cancelled thread +void query_target_free(void) { + query_target_release(thread_qt); +} + +// ---------------------------------------------------------------------------- +// query API + +typedef struct query_target_locals { + time_t start_s; + + QUERY_TARGET *qt; + + RRDSET *st; + + const char *scope_nodes; + const char *scope_contexts; + + const char *nodes; + const char *contexts; + const char *instances; + const char *dimensions; + const char *chart_label_key; + const char *labels; + const char *alerts; + + long long after; + long long before; + bool match_ids; + bool match_names; + + size_t metrics_skipped_due_to_not_matching_timeframe; + + char host_node_id_str[UUID_STR_LEN]; + QUERY_NODE *qn; // temp to pass on callbacks, ignore otherwise - no need to free +} QUERY_TARGET_LOCALS; + +struct storage_engine *query_metric_storage_engine(QUERY_TARGET *qt, QUERY_METRIC *qm, size_t tier) { + QUERY_NODE *qn = query_node(qt, qm->link.query_node_id); + return qn->rrdhost->db[tier].eng; +} + +static inline void query_metric_release(QUERY_TARGET *qt, QUERY_METRIC *qm) { + qm->plan.used = 0; + + // reset the tiers + for(size_t tier = 0; tier < storage_tiers ;tier++) { + if(qm->tiers[tier].db_metric_handle) { + STORAGE_ENGINE *eng = query_metric_storage_engine(qt, qm, tier); + eng->api.metric_release(qm->tiers[tier].db_metric_handle); + qm->tiers[tier].db_metric_handle = NULL; + } + } +} + +static bool query_metric_add(QUERY_TARGET_LOCALS *qtl, QUERY_NODE *qn, QUERY_CONTEXT *qc, + QUERY_INSTANCE *qi, size_t qd_slot, RRDMETRIC *rm, RRDR_DIMENSION_FLAGS options) { + QUERY_TARGET *qt = qtl->qt; + RRDINSTANCE *ri = rm->ri; + + 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_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 = qn->rrdhost->db[tier].eng; + tier_retention[tier].eng = eng; + tier_retention[tier].db_update_every_s = (time_t) (qn->rrdhost->db[tier].tier_grouping * ri->update_every_s); + + 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(qn->rrdhost->db[tier].instance, &rm->uuid); + + if(tier_retention[tier].db_metric_handle) { + tier_retention[tier].db_first_time_s = storage_engine_oldest_time_s(tier_retention[tier].eng->backend, tier_retention[tier].db_metric_handle); + tier_retention[tier].db_last_time_s = storage_engine_latest_time_s(tier_retention[tier].eng->backend, tier_retention[tier].db_metric_handle); + + 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_s) + common_last_time_s = tier_retention[tier].db_last_time_s; + else + common_last_time_s = MAX(common_last_time_s, tier_retention[tier].db_last_time_s); + + 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_s = 0; + tier_retention[tier].db_last_time_s = 0; + tier_retention[tier].db_update_every_s = 0; + } + } + + for (size_t tier = 0; tier < storage_tiers; tier++) { + if(!qt->db.tiers[tier].update_every || (tier_retention[tier].db_update_every_s && tier_retention[tier].db_update_every_s < qt->db.tiers[tier].update_every)) + qt->db.tiers[tier].update_every = tier_retention[tier].db_update_every_s; + + if(!qt->db.tiers[tier].retention.first_time_s || (tier_retention[tier].db_first_time_s && tier_retention[tier].db_first_time_s < qt->db.tiers[tier].retention.first_time_s)) + qt->db.tiers[tier].retention.first_time_s = tier_retention[tier].db_first_time_s; + + if(!qt->db.tiers[tier].retention.last_time_s || (tier_retention[tier].db_last_time_s && tier_retention[tier].db_last_time_s > qt->db.tiers[tier].retention.last_time_s)) + qt->db.tiers[tier].retention.last_time_s = tier_retention[tier].db_last_time_s; + } + + bool timeframe_matches = + (tiers_added && + query_matches_retention(qt->window.after, qt->window.before, common_first_time_s, common_last_time_s, common_update_every_s)) + ? true : false; + + if(timeframe_matches) { + if(ri->rrdset) + ri->rrdset->last_accessed_time_s = qtl->start_s; + + if (qt->query.used == qt->query.size) { + size_t old_mem = qt->query.size * sizeof(*qt->query.array); + qt->query.size = query_target_realloc_size(qt->query.size, 4); + size_t new_mem = qt->query.size * sizeof(*qt->query.array); + 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++]; + memset(qm, 0, sizeof(*qm)); + + qm->status = options; + + qm->link.query_node_id = qn->slot; + qm->link.query_context_id = qc->slot; + qm->link.query_instance_id = qi->slot; + qm->link.query_dimension_id = qd_slot; + + 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_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++) { + internal_fatal(tier_retention[tier].eng != query_metric_storage_engine(qt, qm, tier), "QUERY TARGET: storage engine mismatch"); + qm->tiers[tier].db_metric_handle = tier_retention[tier].db_metric_handle; + 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; + } + + return true; + } + + // cleanup anything we allocated to the retention we will not use + for(size_t tier = 0; tier < storage_tiers ;tier++) { + if (tier_retention[tier].db_metric_handle) + tier_retention[tier].eng->api.metric_release(tier_retention[tier].db_metric_handle); + } + + return false; +} + +static inline bool rrdmetric_retention_matches_query(QUERY_TARGET *qt, RRDMETRIC *rm, time_t now_s) { + time_t first_time_s = rm->first_time_s; + time_t last_time_s = rrd_flag_is_collected(rm) ? now_s : rm->last_time_s; + time_t update_every_s = rm->ri->update_every_s; + return query_matches_retention(qt->window.after, qt->window.before, first_time_s, last_time_s, update_every_s); +} + +static inline void query_dimension_release(QUERY_DIMENSION *qd) { + rrdmetric_release(qd->rma); + qd->rma = NULL; +} + +static QUERY_DIMENSION *query_dimension_allocate(QUERY_TARGET *qt, RRDMETRIC_ACQUIRED *rma, QUERY_STATUS status, size_t priority) { + if(qt->dimensions.used == qt->dimensions.size) { + size_t old_mem = qt->dimensions.size * sizeof(*qt->dimensions.array); + qt->dimensions.size = query_target_realloc_size(qt->dimensions.size, 4); + size_t new_mem = qt->dimensions.size * sizeof(*qt->dimensions.array); + qt->dimensions.array = reallocz(qt->dimensions.array, new_mem); + + __atomic_add_fetch(&netdata_buffers_statistics.query_targets_size, new_mem - old_mem, __ATOMIC_RELAXED); + } + QUERY_DIMENSION *qd = &qt->dimensions.array[qt->dimensions.used]; + memset(qd, 0, sizeof(*qd)); + + qd->slot = qt->dimensions.used++; + qd->rma = rrdmetric_acquired_dup(rma); + qd->status = status; + qd->priority = priority; + + return qd; +} + +static bool query_dimension_add(QUERY_TARGET_LOCALS *qtl, QUERY_NODE *qn, QUERY_CONTEXT *qc, QUERY_INSTANCE *qi, + RRDMETRIC_ACQUIRED *rma, bool queryable_instance, size_t *metrics_added, size_t priority) { + QUERY_TARGET *qt = qtl->qt; + + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + if(rrd_flag_is_deleted(rm)) + return false; + + QUERY_STATUS status = QUERY_STATUS_NONE; + + bool undo = false; + if(!queryable_instance) { + if(rrdmetric_retention_matches_query(qt, rm, qtl->start_s)) { + qi->metrics.excluded++; + qc->metrics.excluded++; + qn->metrics.excluded++; + status |= QUERY_STATUS_EXCLUDED; + } + else + undo = true; + } + else { + RRDR_DIMENSION_FLAGS options = RRDR_DIMENSION_DEFAULT; + bool needed = false; + + if (qt->query.pattern) { + // the user asked for specific dimensions + + SIMPLE_PATTERN_RESULT ret = SP_NOT_MATCHED; + + if(qtl->match_ids) + ret = simple_pattern_matches_string_extract(qt->query.pattern, rm->id, NULL, 0); + + if(ret == SP_NOT_MATCHED && qtl->match_names && (rm->name != rm->id || !qtl->match_ids)) + ret = simple_pattern_matches_string_extract(qt->query.pattern, rm->name, NULL, 0); + + if(ret == SP_MATCHED_POSITIVE) { + needed = true; + options |= RRDR_DIMENSION_SELECTED | RRDR_DIMENSION_NONZERO; + } + else { + // the user selection does not match this dimension + // but, we may still need to query it + + if (query_target_needs_all_dimensions(qt)) { + // this is percentage calculation + // so, we need this dimension to calculate the percentage + needed = true; + options |= RRDR_DIMENSION_HIDDEN; + } + else { + // the user did not select this dimension + // and the calculation is not percentage + // so, no need to query it + ; + } + } + } + else { + // we don't have a dimensions pattern + // so this is a selected dimension + // if it is not hidden + + if(rrd_flag_check(rm, RRD_FLAG_HIDDEN) || (rm->rrddim && rrddim_option_check(rm->rrddim, RRDDIM_OPTION_HIDDEN))) { + // this is a hidden dimension + // we don't need to query it + status |= QUERY_STATUS_DIMENSION_HIDDEN; + options |= RRDR_DIMENSION_HIDDEN; + + if (query_target_needs_all_dimensions(qt)) { + // this is percentage calculation + // so, we need this dimension to calculate the percentage + needed = true; + } + } + else { + // this is a not hidden dimension + // and the user did not provide any selection for dimensions + // so, we need to query it + needed = true; + options |= RRDR_DIMENSION_SELECTED; + } + } + + if (needed) { + if(query_metric_add(qtl, qn, qc, qi, qt->dimensions.used, rm, options)) { + (*metrics_added)++; + + qi->metrics.selected++; + qc->metrics.selected++; + qn->metrics.selected++; + } + else { + undo = true; + qtl->metrics_skipped_due_to_not_matching_timeframe++; + } + } + else if(rrdmetric_retention_matches_query(qt, rm, qtl->start_s)) { + qi->metrics.excluded++; + qc->metrics.excluded++; + qn->metrics.excluded++; + status |= QUERY_STATUS_EXCLUDED; + } + else + undo = true; + } + + if(undo) + return false; + + query_dimension_allocate(qt, rma, status, priority); + return true; +} + +static inline STRING *rrdinstance_create_id_fqdn_v1(RRDINSTANCE_ACQUIRED *ria) { + if(unlikely(!ria)) + return NULL; + + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return string_dup(ri->id); +} + +static inline STRING *rrdinstance_create_name_fqdn_v1(RRDINSTANCE_ACQUIRED *ria) { + if(unlikely(!ria)) + return NULL; + + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return string_dup(ri->name); +} + +static inline STRING *rrdinstance_create_id_fqdn_v2(RRDINSTANCE_ACQUIRED *ria) { + if(unlikely(!ria)) + return NULL; + + char buffer[RRD_ID_LENGTH_MAX + 1]; + + RRDHOST *host = rrdinstance_acquired_rrdhost(ria); + snprintfz(buffer, RRD_ID_LENGTH_MAX, "%s@%s", rrdinstance_acquired_id(ria), host->machine_guid); + return string_strdupz(buffer); +} + +static inline STRING *rrdinstance_create_name_fqdn_v2(RRDINSTANCE_ACQUIRED *ria) { + if(unlikely(!ria)) + return NULL; + + char buffer[RRD_ID_LENGTH_MAX + 1]; + + RRDHOST *host = rrdinstance_acquired_rrdhost(ria); + snprintfz(buffer, RRD_ID_LENGTH_MAX, "%s@%s", rrdinstance_acquired_name(ria), rrdhost_hostname(host)); + return string_strdupz(buffer); +} + +inline STRING *query_instance_id_fqdn(QUERY_INSTANCE *qi, size_t version) { + if(!qi->id_fqdn) { + if (version <= 1) + qi->id_fqdn = rrdinstance_create_id_fqdn_v1(qi->ria); + else + qi->id_fqdn = rrdinstance_create_id_fqdn_v2(qi->ria); + } + + return qi->id_fqdn; +} + +inline STRING *query_instance_name_fqdn(QUERY_INSTANCE *qi, size_t version) { + if(!qi->name_fqdn) { + if (version <= 1) + qi->name_fqdn = rrdinstance_create_name_fqdn_v1(qi->ria); + else + qi->name_fqdn = rrdinstance_create_name_fqdn_v2(qi->ria); + } + + return qi->name_fqdn; +} + +RRDSET *rrdinstance_acquired_rrdset(RRDINSTANCE_ACQUIRED *ria) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + return ri->rrdset; +} + +const char *rrdcontext_acquired_units(RRDCONTEXT_ACQUIRED *rca) { + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + return string2str(rc->units); +} + +RRDSET_TYPE rrdcontext_acquired_chart_type(RRDCONTEXT_ACQUIRED *rca) { + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + return rc->chart_type; +} + +const char *rrdcontext_acquired_title(RRDCONTEXT_ACQUIRED *rca) { + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + return string2str(rc->title); +} + +static void query_target_eval_instance_rrdcalc(QUERY_TARGET_LOCALS *qtl __maybe_unused, + QUERY_NODE *qn, QUERY_CONTEXT *qc, QUERY_INSTANCE *qi) { + RRDSET *st = rrdinstance_acquired_rrdset(qi->ria); + if (st) { + netdata_rwlock_rdlock(&st->alerts.rwlock); + for (RRDCALC *rc = st->alerts.base; rc; rc = rc->next) { + switch(rc->status) { + case RRDCALC_STATUS_CLEAR: + qi->alerts.clear++; + qc->alerts.clear++; + qn->alerts.clear++; + break; + + case RRDCALC_STATUS_WARNING: + qi->alerts.warning++; + qc->alerts.warning++; + qn->alerts.warning++; + break; + + case RRDCALC_STATUS_CRITICAL: + qi->alerts.critical++; + qc->alerts.critical++; + qn->alerts.critical++; + break; + + default: + case RRDCALC_STATUS_UNINITIALIZED: + case RRDCALC_STATUS_UNDEFINED: + case RRDCALC_STATUS_REMOVED: + qi->alerts.other++; + qc->alerts.other++; + qn->alerts.other++; + break; + } + } + netdata_rwlock_unlock(&st->alerts.rwlock); + } +} + +static bool query_target_match_alert_pattern(RRDINSTANCE_ACQUIRED *ria, SIMPLE_PATTERN *pattern) { + if(!pattern) + return true; + + RRDSET *st = rrdinstance_acquired_rrdset(ria); + if (!st) + return false; + + BUFFER *wb = NULL; + bool matched = false; + netdata_rwlock_rdlock(&st->alerts.rwlock); + if (st->alerts.base) { + for (RRDCALC *rc = st->alerts.base; rc; rc = rc->next) { + SIMPLE_PATTERN_RESULT ret = simple_pattern_matches_string_extract(pattern, rc->name, NULL, 0); + + if(ret == SP_MATCHED_POSITIVE) { + matched = true; + break; + } + else if(ret == SP_MATCHED_NEGATIVE) + break; + + if (!wb) + wb = buffer_create(0, NULL); + else + buffer_flush(wb); + + buffer_fast_strcat(wb, string2str(rc->name), string_strlen(rc->name)); + buffer_fast_strcat(wb, ":", 1); + buffer_strcat(wb, rrdcalc_status2string(rc->status)); + + ret = simple_pattern_matches_buffer_extract(pattern, wb, NULL, 0); + + if(ret == SP_MATCHED_POSITIVE) { + matched = true; + break; + } + else if(ret == SP_MATCHED_NEGATIVE) + break; + } + } + netdata_rwlock_unlock(&st->alerts.rwlock); + + buffer_free(wb); + return matched; +} + +static inline void query_instance_release(QUERY_INSTANCE *qi) { + if(qi->ria) { + rrdinstance_release(qi->ria); + qi->ria = NULL; + } + + string_freez(qi->id_fqdn); + qi->id_fqdn = NULL; + + string_freez(qi->name_fqdn); + qi->name_fqdn = NULL; +} + +static inline QUERY_INSTANCE *query_instance_allocate(QUERY_TARGET *qt, RRDINSTANCE_ACQUIRED *ria, size_t qn_slot) { + if(qt->instances.used == qt->instances.size) { + size_t old_mem = qt->instances.size * sizeof(*qt->instances.array); + qt->instances.size = query_target_realloc_size(qt->instances.size, 2); + size_t new_mem = qt->instances.size * sizeof(*qt->instances.array); + qt->instances.array = reallocz(qt->instances.array, new_mem); + + __atomic_add_fetch(&netdata_buffers_statistics.query_targets_size, new_mem - old_mem, __ATOMIC_RELAXED); + } + QUERY_INSTANCE *qi = &qt->instances.array[qt->instances.used]; + memset(qi, 0, sizeof(*qi)); + + qi->slot = qt->instances.used; + qt->instances.used++; + qi->ria = rrdinstance_acquired_dup(ria); + qi->query_host_id = qn_slot; + + return qi; +} + +static inline SIMPLE_PATTERN_RESULT query_instance_matches(QUERY_INSTANCE *qi, + RRDINSTANCE *ri, + SIMPLE_PATTERN *instances_sp, + bool match_ids, + bool match_names, + size_t version, + char *host_node_id_str) { + SIMPLE_PATTERN_RESULT ret = SP_MATCHED_POSITIVE; + + if(instances_sp) { + ret = SP_NOT_MATCHED; + + if(match_ids) + ret = simple_pattern_matches_string_extract(instances_sp, ri->id, NULL, 0); + if (ret == SP_NOT_MATCHED && match_names && (ri->name != ri->id || !match_ids)) + ret = simple_pattern_matches_string_extract(instances_sp, ri->name, NULL, 0); + if (ret == SP_NOT_MATCHED && match_ids) + ret = simple_pattern_matches_string_extract(instances_sp, query_instance_id_fqdn(qi, version), NULL, 0); + if (ret == SP_NOT_MATCHED && match_names) + ret = simple_pattern_matches_string_extract(instances_sp, query_instance_name_fqdn(qi, version), NULL, 0); + + if (ret == SP_NOT_MATCHED && match_ids && host_node_id_str[0]) { + char buffer[RRD_ID_LENGTH_MAX + 1]; + snprintfz(buffer, RRD_ID_LENGTH_MAX, "%s@%s", rrdinstance_acquired_id(qi->ria), host_node_id_str); + ret = simple_pattern_matches_extract(instances_sp, buffer, NULL, 0); + } + } + + return ret; +} + +static inline bool query_instance_matches_labels(RRDINSTANCE *ri, SIMPLE_PATTERN *chart_label_key_sp, SIMPLE_PATTERN *labels_sp) { + if ((chart_label_key_sp && !rrdlabels_match_simple_pattern_parsed( + ri->rrdlabels, chart_label_key_sp, '\0', NULL)) || + (labels_sp && !rrdlabels_match_simple_pattern_parsed( + ri->rrdlabels, labels_sp, ':', NULL))) + return false; + + return true; +} + +static bool query_instance_add(QUERY_TARGET_LOCALS *qtl, QUERY_NODE *qn, QUERY_CONTEXT *qc, + RRDINSTANCE_ACQUIRED *ria, bool queryable_instance, bool filter_instances) { + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + if(rrd_flag_is_deleted(ri)) + return false; + + QUERY_TARGET *qt = qtl->qt; + QUERY_INSTANCE *qi = query_instance_allocate(qt, ria, qn->slot); + + 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 && filter_instances) + queryable_instance = (SP_MATCHED_POSITIVE == query_instance_matches( + qi, ri, qt->instances.pattern, qtl->match_ids, qtl->match_names, qt->request.version, qtl->host_node_id_str)); + + if(queryable_instance) + queryable_instance = query_instance_matches_labels(ri, qt->instances.chart_label_key_pattern, qt->instances.labels_pattern); + + if(queryable_instance) { + if(qt->instances.alerts_pattern && !query_target_match_alert_pattern(ria, qt->instances.alerts_pattern)) + queryable_instance = false; + } + + if(queryable_instance && qt->request.version >= 2) + query_target_eval_instance_rrdcalc(qtl, qn, qc, qi); + + size_t dimensions_added = 0, metrics_added = 0, priority = 0; + + if(unlikely(qt->request.rma)) { + if(query_dimension_add(qtl, qn, qc, qi, qt->request.rma, queryable_instance, &metrics_added, priority++)) + dimensions_added++; + } + else { + RRDMETRIC *rm; + dfe_start_read(ri->rrdmetrics, rm) { + if(query_dimension_add(qtl, qn, qc, qi, (RRDMETRIC_ACQUIRED *) rm_dfe.item, + queryable_instance, &metrics_added, priority++)) + dimensions_added++; + } + dfe_done(rm); + } + + if(!dimensions_added) { + qt->instances.used--; + query_instance_release(qi); + return false; + } + else { + if(metrics_added) { + qc->instances.selected++; + qn->instances.selected++; + } + else { + qc->instances.excluded++; + qn->instances.excluded++; + } + } + + return true; +} + +static inline void query_context_release(QUERY_CONTEXT *qc) { + rrdcontext_release(qc->rca); + qc->rca = NULL; +} + +static inline QUERY_CONTEXT *query_context_allocate(QUERY_TARGET *qt, RRDCONTEXT_ACQUIRED *rca) { + if(qt->contexts.used == qt->contexts.size) { + size_t old_mem = qt->contexts.size * sizeof(*qt->contexts.array); + qt->contexts.size = query_target_realloc_size(qt->contexts.size, 2); + size_t new_mem = qt->contexts.size * sizeof(*qt->contexts.array); + qt->contexts.array = reallocz(qt->contexts.array, new_mem); + + __atomic_add_fetch(&netdata_buffers_statistics.query_targets_size, new_mem - old_mem, __ATOMIC_RELAXED); + } + QUERY_CONTEXT *qc = &qt->contexts.array[qt->contexts.used]; + memset(qc, 0, sizeof(*qc)); + qc->slot = qt->contexts.used++; + qc->rca = rrdcontext_acquired_dup(rca); + + return qc; +} + +static ssize_t query_context_add(void *data, RRDCONTEXT_ACQUIRED *rca, bool queryable_context) { + QUERY_TARGET_LOCALS *qtl = data; + + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + if(rrd_flag_is_deleted(rc)) + return 0; + + QUERY_NODE *qn = qtl->qn; + QUERY_TARGET *qt = qtl->qt; + QUERY_CONTEXT *qc = query_context_allocate(qt, rca); + + ssize_t added = 0; + if(unlikely(qt->request.ria)) { + if(query_instance_add(qtl, qn, qc, qt->request.ria, queryable_context, false)) + added++; + } + else if(unlikely(qtl->st && qtl->st->rrdcontext == rca && qtl->st->rrdinstance)) { + if(query_instance_add(qtl, qn, qc, qtl->st->rrdinstance, queryable_context, false)) + added++; + } + else { + RRDINSTANCE *ri; + dfe_start_read(rc->rrdinstances, ri) { + if(query_instance_add(qtl, qn, qc, (RRDINSTANCE_ACQUIRED *) ri_dfe.item, queryable_context, true)) + added++; + } + dfe_done(ri); + } + + if(!added) { + query_context_release(qc); + qt->contexts.used--; + return 0; + } + + return added; +} + +static inline void query_node_release(QUERY_NODE *qn) { + qn->rrdhost = NULL; +} + +static inline QUERY_NODE *query_node_allocate(QUERY_TARGET *qt, RRDHOST *host) { + if(qt->nodes.used == qt->nodes.size) { + size_t old_mem = qt->nodes.size * sizeof(*qt->nodes.array); + qt->nodes.size = query_target_realloc_size(qt->nodes.size, 2); + size_t new_mem = qt->nodes.size * sizeof(*qt->nodes.array); + qt->nodes.array = reallocz(qt->nodes.array, new_mem); + + __atomic_add_fetch(&netdata_buffers_statistics.query_targets_size, new_mem - old_mem, __ATOMIC_RELAXED); + } + QUERY_NODE *qn = &qt->nodes.array[qt->nodes.used]; + memset(qn, 0, sizeof(*qn)); + + qn->slot = qt->nodes.used++; + qn->rrdhost = host; + + return qn; +} + +static ssize_t query_node_add(void *data, RRDHOST *host, bool queryable_host) { + QUERY_TARGET_LOCALS *qtl = data; + QUERY_TARGET *qt = qtl->qt; + QUERY_NODE *qn = query_node_allocate(qt, host); + + if(host->node_id) { + if(!qtl->host_node_id_str[0]) + uuid_unparse_lower(*host->node_id, qn->node_id); + else + memcpy(qn->node_id, qtl->host_node_id_str, sizeof(qn->node_id)); + } + else + qn->node_id[0] = '\0'; + + // is the chart given valid? + if(unlikely(qtl->st && (!qtl->st->rrdinstance || !qtl->st->rrdcontext))) { + error("QUERY TARGET: RRDSET '%s' given, but it is not linked to rrdcontext structures. Linking it now.", rrdset_name(qtl->st)); + rrdinstance_from_rrdset(qtl->st); + + if(unlikely(qtl->st && (!qtl->st->rrdinstance || !qtl->st->rrdcontext))) { + error("QUERY TARGET: RRDSET '%s' given, but failed to be linked to rrdcontext structures. Switching to context query.", + rrdset_name(qtl->st)); + + if (!is_valid_sp(qtl->instances)) + qtl->instances = rrdset_name(qtl->st); + + qtl->st = NULL; + } + } + + qtl->qn = qn; + + ssize_t added = 0; + if(unlikely(qt->request.rca)) { + if(query_context_add(qtl, qt->request.rca, true)) + added++; + } + else if(unlikely(qtl->st)) { + // single chart data queries + if(query_context_add(qtl, qtl->st->rrdcontext, true)) + added++; + } + else { + // context pattern queries + added = query_scope_foreach_context( + host, qtl->scope_contexts, + qt->contexts.scope_pattern, qt->contexts.pattern, + query_context_add, queryable_host, qtl); + + if(added < 0) + added = 0; + } + + qtl->qn = NULL; + + if(!added) { + query_node_release(qn); + qt->nodes.used--; + return false; + } + + return true; +} + +void query_target_generate_name(QUERY_TARGET *qt) { + char options_buffer[100 + 1]; + web_client_api_request_v1_data_options_to_string(options_buffer, 100, qt->request.options); + + char resampling_buffer[20 + 1] = ""; + if(qt->request.resampling_time > 1) + snprintfz(resampling_buffer, 20, "/resampling:%lld", (long long)qt->request.resampling_time); + + char tier_buffer[20 + 1] = ""; + if(qt->request.options & RRDR_OPTION_SELECTED_TIER) + snprintfz(tier_buffer, 20, "/tier:%zu", qt->request.tier); + + if(qt->request.st) + snprintfz(qt->id, MAX_QUERY_TARGET_ID_LENGTH, "chart://hosts:%s/instance:%s/dimensions:%s/after:%lld/before:%lld/points:%zu/group:%s%s/options:%s%s%s" + , rrdhost_hostname(qt->request.st->rrdhost) + , rrdset_name(qt->request.st) + , (qt->request.dimensions) ? qt->request.dimensions : "*" + , (long long)qt->request.after + , (long long)qt->request.before + , qt->request.points + , time_grouping_tostring(qt->request.time_group_method) + , qt->request.time_group_options ? qt->request.time_group_options : "" + , options_buffer + , resampling_buffer + , tier_buffer + ); + else if(qt->request.host && qt->request.rca && qt->request.ria && qt->request.rma) + snprintfz(qt->id, MAX_QUERY_TARGET_ID_LENGTH, "metric://hosts:%s/context:%s/instance:%s/dimension:%s/after:%lld/before:%lld/points:%zu/group:%s%s/options:%s%s%s" + , rrdhost_hostname(qt->request.host) + , rrdcontext_acquired_id(qt->request.rca) + , rrdinstance_acquired_id(qt->request.ria) + , rrdmetric_acquired_id(qt->request.rma) + , (long long)qt->request.after + , (long long)qt->request.before + , qt->request.points + , time_grouping_tostring(qt->request.time_group_method) + , qt->request.time_group_options ? qt->request.time_group_options : "" + , options_buffer + , resampling_buffer + , tier_buffer + ); + else if(qt->request.version >= 2) + snprintfz(qt->id, MAX_QUERY_TARGET_ID_LENGTH, "data_v2://scope_nodes:%s/scope_contexts:%s/nodes:%s/contexts:%s/instances:%s/labels:%s/dimensions:%s/after:%lld/before:%lld/points:%zu/time_group:%s%s/options:%s%s%s" + , qt->request.scope_nodes ? qt->request.scope_nodes : "*" + , qt->request.scope_contexts ? qt->request.scope_contexts : "*" + , qt->request.nodes ? qt->request.nodes : "*" + , (qt->request.contexts) ? qt->request.contexts : "*" + , (qt->request.instances) ? qt->request.instances : "*" + , (qt->request.labels) ? qt->request.labels : "*" + , (qt->request.dimensions) ? qt->request.dimensions : "*" + , (long long)qt->request.after + , (long long)qt->request.before + , qt->request.points + , time_grouping_tostring(qt->request.time_group_method) + , qt->request.time_group_options ? qt->request.time_group_options : "" + , options_buffer + , resampling_buffer + , tier_buffer + ); + else + snprintfz(qt->id, MAX_QUERY_TARGET_ID_LENGTH, "context://hosts:%s/contexts:%s/instances:%s/dimensions:%s/after:%lld/before:%lld/points:%zu/group:%s%s/options:%s%s%s" + , (qt->request.host) ? rrdhost_hostname(qt->request.host) : ((qt->request.nodes) ? qt->request.nodes : "*") + , (qt->request.contexts) ? qt->request.contexts : "*" + , (qt->request.instances) ? qt->request.instances : "*" + , (qt->request.dimensions) ? qt->request.dimensions : "*" + , (long long)qt->request.after + , (long long)qt->request.before + , qt->request.points + , time_grouping_tostring(qt->request.time_group_method) + , qt->request.time_group_options ? qt->request.time_group_options : "" + , options_buffer + , resampling_buffer + , tier_buffer + ); + + json_fix_string(qt->id); +} + +QUERY_TARGET *query_target_create(QUERY_TARGET_REQUEST *qtr) { + if(!service_running(ABILITY_DATA_QUERIES)) + return NULL; + + QUERY_TARGET *qt = query_target_get(); + + if(!qtr->received_ut) + qtr->received_ut = now_monotonic_usec(); + + qt->timings.received_ut = qtr->received_ut; + + if(qtr->nodes && !qtr->scope_nodes) + qtr->scope_nodes = qtr->nodes; + + if(qtr->contexts && !qtr->scope_contexts) + qtr->scope_contexts = qtr->contexts; + + memset(&qt->db, 0, sizeof(qt->db)); + qt->query_points = STORAGE_POINT_UNSET; + + // copy the request into query_thread_target + qt->request = *qtr; + + query_target_generate_name(qt); + qt->window.after = qt->request.after; + qt->window.before = qt->request.before; + + qt->window.options = qt->request.options; + if(query_target_has_percentage_of_instance(qt)) + qt->window.options &= ~RRDR_OPTION_PERCENTAGE; + + rrdr_relative_window_to_absolute(&qt->window.after, &qt->window.before, &qt->window.now); + + // prepare our local variables - we need these across all these functions + QUERY_TARGET_LOCALS qtl = { + .qt = qt, + .start_s = now_realtime_sec(), + .st = qt->request.st, + .scope_nodes = qt->request.scope_nodes, + .scope_contexts = qt->request.scope_contexts, + .nodes = qt->request.nodes, + .contexts = qt->request.contexts, + .instances = qt->request.instances, + .dimensions = qt->request.dimensions, + .chart_label_key = qt->request.chart_label_key, + .labels = qt->request.labels, + .alerts = qt->request.alerts, + }; + + RRDHOST *host = qt->request.host; + + // prepare all the patterns + qt->nodes.scope_pattern = string_to_simple_pattern(qtl.scope_nodes); + qt->nodes.pattern = string_to_simple_pattern(qtl.nodes); + + qt->contexts.pattern = string_to_simple_pattern(qtl.contexts); + qt->contexts.scope_pattern = string_to_simple_pattern(qtl.scope_contexts); + + qt->instances.pattern = string_to_simple_pattern(qtl.instances); + qt->query.pattern = string_to_simple_pattern(qtl.dimensions); + qt->instances.chart_label_key_pattern = string_to_simple_pattern(qtl.chart_label_key); + qt->instances.labels_pattern = string_to_simple_pattern(qtl.labels); + qt->instances.alerts_pattern = string_to_simple_pattern(qtl.alerts); + + qtl.match_ids = qt->request.options & RRDR_OPTION_MATCH_IDS; + qtl.match_names = qt->request.options & RRDR_OPTION_MATCH_NAMES; + if(likely(!qtl.match_ids && !qtl.match_names)) + qtl.match_ids = qtl.match_names = true; + + // verify that the chart belongs to the host we are interested + if(qtl.st) { + if (!host) { + // It is NULL, set it ourselves. + host = qtl.st->rrdhost; + } + else if (unlikely(host != qtl.st->rrdhost)) { + // Oops! A different host! + error("QUERY TARGET: RRDSET '%s' given does not belong to host '%s'. Switching query host to '%s'", + rrdset_name(qtl.st), rrdhost_hostname(host), rrdhost_hostname(qtl.st->rrdhost)); + host = qtl.st->rrdhost; + } + } + + if(host) { + if(host->node_id) + uuid_unparse_lower(*host->node_id, qtl.host_node_id_str); + else + qtl.host_node_id_str[0] = '\0'; + + // single host query + qt->versions.contexts_hard_hash = dictionary_version(host->rrdctx.contexts); + qt->versions.contexts_soft_hash = dictionary_version(host->rrdctx.hub_queue); + qt->versions.alerts_hard_hash = dictionary_version(host->rrdcalc_root_index); + qt->versions.alerts_soft_hash = __atomic_load_n(&host->health_transitions, __ATOMIC_RELAXED); + query_node_add(&qtl, host, true); + qtl.nodes = rrdhost_hostname(host); + } + else + query_scope_foreach_host(qt->nodes.scope_pattern, qt->nodes.pattern, + query_node_add, &qtl, + &qt->versions, + qtl.host_node_id_str); + + // we need the available db retention for this call + // so it has to be done last + query_target_calculate_window(qt); + + qt->timings.preprocessed_ut = now_monotonic_usec(); + + return qt; +} + +ssize_t weights_foreach_rrdmetric_in_context(RRDCONTEXT_ACQUIRED *rca, + SIMPLE_PATTERN *instances_sp, + SIMPLE_PATTERN *chart_label_key_sp, + SIMPLE_PATTERN *labels_sp, + SIMPLE_PATTERN *alerts_sp, + SIMPLE_PATTERN *dimensions_sp, + bool match_ids, bool match_names, + size_t version, + weights_add_metric_t cb, + void *data + ) { + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + if(!rc || rrd_flag_is_deleted(rc)) + return 0; + + char host_node_id_str[UUID_STR_LEN] = ""; + + bool proceed = true; + + ssize_t count = 0; + RRDINSTANCE *ri; + dfe_start_read(rc->rrdinstances, ri) { + if(rrd_flag_is_deleted(ri)) + continue; + + RRDINSTANCE_ACQUIRED *ria = (RRDINSTANCE_ACQUIRED *) ri_dfe.item; + + if(instances_sp) { + QUERY_INSTANCE qi = { .ria = ria, }; + SIMPLE_PATTERN_RESULT ret = query_instance_matches(&qi, ri, instances_sp, match_ids, match_names, version, host_node_id_str); + qi.ria = NULL; + query_instance_release(&qi); + + if (ret != SP_MATCHED_POSITIVE) + continue; + } + + if(!query_instance_matches_labels(ri, chart_label_key_sp, labels_sp)) + continue; + + if(alerts_sp && !query_target_match_alert_pattern(ria, alerts_sp)) + continue; + + dfe_unlock(ri); + + RRDMETRIC *rm; + dfe_start_read(ri->rrdmetrics, rm) { + if(rrd_flag_is_deleted(rm)) + continue; + + if(dimensions_sp) { + SIMPLE_PATTERN_RESULT ret = SP_NOT_MATCHED; + + if (match_ids) + ret = simple_pattern_matches_string_extract(dimensions_sp, rm->id, NULL, 0); + + if (ret == SP_NOT_MATCHED && match_names && (rm->name != rm->id || !match_ids)) + ret = simple_pattern_matches_string_extract(dimensions_sp, rm->name, NULL, 0); + + if(ret != SP_MATCHED_POSITIVE) + continue; + } + + dfe_unlock(rm); + + RRDMETRIC_ACQUIRED *rma = (RRDMETRIC_ACQUIRED *)rm_dfe.item; + ssize_t ret = cb(data, rc->rrdhost, rca, ria, rma); + + if(ret < 0) { + proceed = false; + break; + } + + count += ret; + } + dfe_done(rm); + + if(unlikely(!proceed)) + break; + } + dfe_done(ri); + + return count; +} diff --git a/database/contexts/rrdcontext.c b/database/contexts/rrdcontext.c new file mode 100644 index 00000000..40a7e420 --- /dev/null +++ b/database/contexts/rrdcontext.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "internal.h" + +// ---------------------------------------------------------------------------- +// visualizing flags + +struct rrdcontext_reason rrdcontext_reasons[] = { + // context related + {RRD_FLAG_UPDATE_REASON_TRIGGERED, "triggered transition", 65 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_NEW_OBJECT, "object created", 65 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT, "object updated", 65 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_LOAD_SQL, "loaded from sql", 65 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_CHANGED_METADATA, "changed metadata", 65 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_ZERO_RETENTION, "has no retention", 65 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T, "updated first_time_t", 65 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T, "updated last_time_t", 65 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_STOPPED_BEING_COLLECTED, "stopped collected", 65 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_STARTED_BEING_COLLECTED, "started collected", 5 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_UNUSED, "unused", 5 * USEC_PER_SEC }, + + // not context related + {RRD_FLAG_UPDATE_REASON_CHANGED_LINKING, "changed rrd link", 65 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD, "child disconnected", 65 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_DB_ROTATION, "db rotation", 65 * USEC_PER_SEC }, + {RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION, "updated retention", 65 * USEC_PER_SEC }, + + // terminator + {0, NULL, 0 }, +}; + +void rrd_reasons_to_buffer_json_array_items(RRD_FLAGS flags, BUFFER *wb) { + for(int i = 0, added = 0; rrdcontext_reasons[i].name ; i++) { + if (flags & rrdcontext_reasons[i].flag) { + buffer_json_add_array_item_string(wb, rrdcontext_reasons[i].name); + added++; + } + } +} +// ---------------------------------------------------------------------------- +// public API + +void rrdcontext_updated_rrddim(RRDDIM *rd) { + rrdmetric_from_rrddim(rd); +} + +void rrdcontext_removed_rrddim(RRDDIM *rd) { + rrdmetric_rrddim_is_freed(rd); +} + +void rrdcontext_updated_rrddim_algorithm(RRDDIM *rd) { + rrdmetric_updated_rrddim_flags(rd); +} + +void rrdcontext_updated_rrddim_multiplier(RRDDIM *rd) { + rrdmetric_updated_rrddim_flags(rd); +} + +void rrdcontext_updated_rrddim_divisor(RRDDIM *rd) { + rrdmetric_updated_rrddim_flags(rd); +} + +void rrdcontext_updated_rrddim_flags(RRDDIM *rd) { + rrdmetric_updated_rrddim_flags(rd); +} + +void rrdcontext_collected_rrddim(RRDDIM *rd) { + rrdmetric_collected_rrddim(rd); +} + +void rrdcontext_updated_rrdset(RRDSET *st) { + rrdinstance_from_rrdset(st); +} + +void rrdcontext_removed_rrdset(RRDSET *st) { + rrdinstance_rrdset_is_freed(st); +} + +void rrdcontext_updated_retention_rrdset(RRDSET *st) { + rrdinstance_rrdset_has_updated_retention(st); +} + +void rrdcontext_updated_rrdset_name(RRDSET *st) { + rrdinstance_updated_rrdset_name(st); +} + +void rrdcontext_updated_rrdset_flags(RRDSET *st) { + rrdinstance_updated_rrdset_flags(st); +} + +void rrdcontext_collected_rrdset(RRDSET *st) { + rrdinstance_collected_rrdset(st); +} + +void rrdcontext_host_child_connected(RRDHOST *host) { + (void)host; + + // no need to do anything here + ; +} + +usec_t rrdcontext_next_db_rotation_ut = 0; +void rrdcontext_db_rotation(void) { + // called when the db rotates its database + rrdcontext_next_db_rotation_ut = now_realtime_usec() + FULL_RETENTION_SCAN_DELAY_AFTER_DB_ROTATION_SECS * USEC_PER_SEC; +} + +int rrdcontext_find_dimension_uuid(RRDSET *st, const char *id, uuid_t *store_uuid) { + if(!st->rrdhost) return 1; + if(!st->context) return 2; + + RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item(st->rrdhost->rrdctx.contexts, string2str(st->context)); + if(!rca) return 3; + + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + + RRDINSTANCE_ACQUIRED *ria = (RRDINSTANCE_ACQUIRED *)dictionary_get_and_acquire_item(rc->rrdinstances, string2str(st->id)); + if(!ria) { + rrdcontext_release(rca); + return 4; + } + + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + + RRDMETRIC_ACQUIRED *rma = (RRDMETRIC_ACQUIRED *)dictionary_get_and_acquire_item(ri->rrdmetrics, id); + if(!rma) { + rrdinstance_release(ria); + rrdcontext_release(rca); + return 5; + } + + RRDMETRIC *rm = rrdmetric_acquired_value(rma); + + uuid_copy(*store_uuid, rm->uuid); + + rrdmetric_release(rma); + rrdinstance_release(ria); + rrdcontext_release(rca); + return 0; +} + +int rrdcontext_find_chart_uuid(RRDSET *st, uuid_t *store_uuid) { + if(!st->rrdhost) return 1; + if(!st->context) return 2; + + RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item(st->rrdhost->rrdctx.contexts, string2str(st->context)); + if(!rca) return 3; + + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + + RRDINSTANCE_ACQUIRED *ria = (RRDINSTANCE_ACQUIRED *)dictionary_get_and_acquire_item(rc->rrdinstances, string2str(st->id)); + if(!ria) { + rrdcontext_release(rca); + return 4; + } + + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + uuid_copy(*store_uuid, ri->uuid); + + rrdinstance_release(ria); + rrdcontext_release(rca); + return 0; +} + +void rrdcontext_host_child_disconnected(RRDHOST *host) { + rrdcontext_recalculate_host_retention(host, RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD, false); +} + +int rrdcontext_foreach_instance_with_rrdset_in_context(RRDHOST *host, const char *context, int (*callback)(RRDSET *st, void *data), void *data) { + if(unlikely(!host || !context || !*context || !callback)) + return -1; + + RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item(host->rrdctx.contexts, context); + if(unlikely(!rca)) return -1; + + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + if(unlikely(!rc)) return -1; + + int ret = 0; + RRDINSTANCE *ri; + dfe_start_read(rc->rrdinstances, ri) { + if(ri->rrdset) { + int r = callback(ri->rrdset, data); + if(r >= 0) ret += r; + else { + ret = r; + break; + } + } + } + dfe_done(ri); + + rrdcontext_release(rca); + + return ret; +} + +// ---------------------------------------------------------------------------- +// ACLK interface + +static bool rrdhost_check_our_claim_id(const char *claim_id) { + if(!localhost->aclk_state.claimed_id) return false; + return (strcasecmp(claim_id, localhost->aclk_state.claimed_id) == 0) ? true : false; +} + +static RRDHOST *rrdhost_find_by_node_id(const char *node_id) { + uuid_t uuid; + if (uuid_parse(node_id, uuid)) + return NULL; + + RRDHOST *host = NULL; + dfe_start_read(rrdhost_root_index, host) { + if(!host->node_id) continue; + + if(uuid_memcmp(&uuid, host->node_id) == 0) + break; + } + dfe_done(host); + + return host; +} + +void rrdcontext_hub_checkpoint_command(void *ptr) { + struct ctxs_checkpoint *cmd = ptr; + + if(!rrdhost_check_our_claim_id(cmd->claim_id)) { + error("RRDCONTEXT: received checkpoint command for claim_id '%s', node id '%s', but this is not our claim id. Ours '%s', received '%s'. Ignoring command.", + cmd->claim_id, cmd->node_id, + localhost->aclk_state.claimed_id?localhost->aclk_state.claimed_id:"NOT SET", + cmd->claim_id); + + return; + } + + RRDHOST *host = rrdhost_find_by_node_id(cmd->node_id); + if(!host) { + error("RRDCONTEXT: received checkpoint command for claim id '%s', node id '%s', but there is no node with such node id here. Ignoring command.", + cmd->claim_id, cmd->node_id); + + return; + } + + if(rrdhost_flag_check(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS)) { + info("RRDCONTEXT: received checkpoint command for claim id '%s', node id '%s', while node '%s' has an active context streaming.", + cmd->claim_id, cmd->node_id, rrdhost_hostname(host)); + + // disable it temporarily, so that our worker will not attempt to send messages in parallel + rrdhost_flag_clear(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS); + } + + uint64_t our_version_hash = rrdcontext_version_hash(host); + + if(cmd->version_hash != our_version_hash) { + error("RRDCONTEXT: received version hash %"PRIu64" for host '%s', does not match our version hash %"PRIu64". Sending snapshot of all contexts.", + cmd->version_hash, rrdhost_hostname(host), our_version_hash); + +#ifdef ENABLE_ACLK + // prepare the snapshot + char uuid[UUID_STR_LEN]; + uuid_unparse_lower(*host->node_id, uuid); + contexts_snapshot_t bundle = contexts_snapshot_new(cmd->claim_id, uuid, our_version_hash); + + // do a deep scan on every metric of the host to make sure all our data are updated + rrdcontext_recalculate_host_retention(host, RRD_FLAG_NONE, false); + + // calculate version hash and pack all the messages together in one go + our_version_hash = rrdcontext_version_hash_with_callback(host, rrdcontext_message_send_unsafe, true, bundle); + + // update the version + contexts_snapshot_set_version(bundle, our_version_hash); + + // send it + aclk_send_contexts_snapshot(bundle); +#endif + } + + internal_error(true, "RRDCONTEXT: host '%s' enabling streaming of contexts", rrdhost_hostname(host)); + rrdhost_flag_set(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS); + char node_str[UUID_STR_LEN]; + uuid_unparse_lower(*host->node_id, node_str); + log_access("ACLK REQ [%s (%s)]: STREAM CONTEXTS ENABLED", node_str, rrdhost_hostname(host)); +} + +void rrdcontext_hub_stop_streaming_command(void *ptr) { + struct stop_streaming_ctxs *cmd = ptr; + + if(!rrdhost_check_our_claim_id(cmd->claim_id)) { + error("RRDCONTEXT: received stop streaming command for claim_id '%s', node id '%s', but this is not our claim id. Ours '%s', received '%s'. Ignoring command.", + cmd->claim_id, cmd->node_id, + localhost->aclk_state.claimed_id?localhost->aclk_state.claimed_id:"NOT SET", + cmd->claim_id); + + return; + } + + RRDHOST *host = rrdhost_find_by_node_id(cmd->node_id); + if(!host) { + error("RRDCONTEXT: received stop streaming command for claim id '%s', node id '%s', but there is no node with such node id here. Ignoring command.", + cmd->claim_id, cmd->node_id); + + return; + } + + if(!rrdhost_flag_check(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS)) { + error("RRDCONTEXT: received stop streaming command for claim id '%s', node id '%s', but node '%s' does not have active context streaming. Ignoring command.", + cmd->claim_id, cmd->node_id, rrdhost_hostname(host)); + + return; + } + + internal_error(true, "RRDCONTEXT: host '%s' disabling streaming of contexts", rrdhost_hostname(host)); + rrdhost_flag_clear(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS); +} + +bool rrdcontext_retention_match(RRDCONTEXT_ACQUIRED *rca, time_t after, time_t before) { + if(unlikely(!rca)) return false; + + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + + if(rrd_flag_is_collected(rc)) + return query_matches_retention(after, before, rc->first_time_s, before > rc->last_time_s ? before : rc->last_time_s, 1); + else + return query_matches_retention(after, before, rc->first_time_s, rc->last_time_s, 1); +} \ No newline at end of file diff --git a/database/contexts/rrdcontext.h b/database/contexts/rrdcontext.h new file mode 100644 index 00000000..5328483d --- /dev/null +++ b/database/contexts/rrdcontext.h @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_RRDCONTEXT_H +#define NETDATA_RRDCONTEXT_H 1 + +// ---------------------------------------------------------------------------- +// RRDMETRIC + +typedef struct rrdmetric_acquired RRDMETRIC_ACQUIRED; + +// ---------------------------------------------------------------------------- +// RRDINSTANCE + +typedef struct rrdinstance_acquired RRDINSTANCE_ACQUIRED; + +// ---------------------------------------------------------------------------- +// RRDCONTEXT + +typedef struct rrdcontext_acquired RRDCONTEXT_ACQUIRED; + +// ---------------------------------------------------------------------------- + +#include "../rrd.h" + +bool rrdinstance_acquired_id_and_name_are_same(RRDINSTANCE_ACQUIRED *ria); +const char *rrdmetric_acquired_id(RRDMETRIC_ACQUIRED *rma); +const char *rrdmetric_acquired_name(RRDMETRIC_ACQUIRED *rma); +bool rrdmetric_acquired_has_name(RRDMETRIC_ACQUIRED *rma); + +STRING *rrdmetric_acquired_id_dup(RRDMETRIC_ACQUIRED *rma); +STRING *rrdmetric_acquired_name_dup(RRDMETRIC_ACQUIRED *rma); + +NETDATA_DOUBLE rrdmetric_acquired_last_stored_value(RRDMETRIC_ACQUIRED *rma); +time_t rrdmetric_acquired_first_entry(RRDMETRIC_ACQUIRED *rma); +time_t rrdmetric_acquired_last_entry(RRDMETRIC_ACQUIRED *rma); +bool rrdmetric_acquired_belongs_to_instance(RRDMETRIC_ACQUIRED *rma, RRDINSTANCE_ACQUIRED *ria); + +const char *rrdinstance_acquired_id(RRDINSTANCE_ACQUIRED *ria); +const char *rrdinstance_acquired_name(RRDINSTANCE_ACQUIRED *ria); +bool rrdinstance_acquired_has_name(RRDINSTANCE_ACQUIRED *ria); +const char *rrdinstance_acquired_units(RRDINSTANCE_ACQUIRED *ria); +STRING *rrdinstance_acquired_units_dup(RRDINSTANCE_ACQUIRED *ria); +DICTIONARY *rrdinstance_acquired_labels(RRDINSTANCE_ACQUIRED *ria); +DICTIONARY *rrdinstance_acquired_functions(RRDINSTANCE_ACQUIRED *ria); +RRDHOST *rrdinstance_acquired_rrdhost(RRDINSTANCE_ACQUIRED *ria); +RRDSET *rrdinstance_acquired_rrdset(RRDINSTANCE_ACQUIRED *ria); + +bool rrdinstance_acquired_belongs_to_context(RRDINSTANCE_ACQUIRED *ria, RRDCONTEXT_ACQUIRED *rca); +time_t rrdinstance_acquired_update_every(RRDINSTANCE_ACQUIRED *ria); + +const char *rrdcontext_acquired_units(RRDCONTEXT_ACQUIRED *rca); +const char *rrdcontext_acquired_title(RRDCONTEXT_ACQUIRED *rca); +RRDSET_TYPE rrdcontext_acquired_chart_type(RRDCONTEXT_ACQUIRED *rca); + +// ---------------------------------------------------------------------------- +// public API for rrdhost + +void rrdhost_load_rrdcontext_data(RRDHOST *host); +void rrdhost_create_rrdcontexts(RRDHOST *host); +void rrdhost_destroy_rrdcontexts(RRDHOST *host); + +void rrdcontext_host_child_connected(RRDHOST *host); +void rrdcontext_host_child_disconnected(RRDHOST *host); + +int rrdcontext_foreach_instance_with_rrdset_in_context(RRDHOST *host, const char *context, int (*callback)(RRDSET *st, void *data), void *data); + +typedef enum { + RRDCONTEXT_OPTION_NONE = 0, + RRDCONTEXT_OPTION_SHOW_METRICS = (1 << 0), + RRDCONTEXT_OPTION_SHOW_INSTANCES = (1 << 1), + RRDCONTEXT_OPTION_SHOW_LABELS = (1 << 2), + RRDCONTEXT_OPTION_SHOW_QUEUED = (1 << 3), + RRDCONTEXT_OPTION_SHOW_FLAGS = (1 << 4), + RRDCONTEXT_OPTION_SHOW_DELETED = (1 << 5), + RRDCONTEXT_OPTION_DEEPSCAN = (1 << 6), + RRDCONTEXT_OPTION_SHOW_UUIDS = (1 << 7), + RRDCONTEXT_OPTION_SHOW_HIDDEN = (1 << 8), + RRDCONTEXT_OPTION_SKIP_ID = (1 << 31), // internal use +} RRDCONTEXT_TO_JSON_OPTIONS; + +#define RRDCONTEXT_OPTIONS_ALL (RRDCONTEXT_OPTION_SHOW_METRICS|RRDCONTEXT_OPTION_SHOW_INSTANCES|RRDCONTEXT_OPTION_SHOW_LABELS|RRDCONTEXT_OPTION_SHOW_QUEUED|RRDCONTEXT_OPTION_SHOW_FLAGS|RRDCONTEXT_OPTION_SHOW_DELETED|RRDCONTEXT_OPTION_SHOW_UUIDS|RRDCONTEXT_OPTION_SHOW_HIDDEN) + +int rrdcontext_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before, RRDCONTEXT_TO_JSON_OPTIONS options, const char *context, SIMPLE_PATTERN *chart_label_key, SIMPLE_PATTERN *chart_labels_filter, SIMPLE_PATTERN *chart_dimensions); +int rrdcontexts_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before, RRDCONTEXT_TO_JSON_OPTIONS options, SIMPLE_PATTERN *chart_label_key, SIMPLE_PATTERN *chart_labels_filter, SIMPLE_PATTERN *chart_dimensions); + +// ---------------------------------------------------------------------------- +// public API for rrdcontexts + +const char *rrdcontext_acquired_id(RRDCONTEXT_ACQUIRED *rca); +bool rrdcontext_acquired_belongs_to_host(RRDCONTEXT_ACQUIRED *rca, RRDHOST *host); + +// ---------------------------------------------------------------------------- +// public API for rrddims + +void rrdcontext_updated_rrddim(RRDDIM *rd); +void rrdcontext_removed_rrddim(RRDDIM *rd); +void rrdcontext_updated_rrddim_algorithm(RRDDIM *rd); +void rrdcontext_updated_rrddim_multiplier(RRDDIM *rd); +void rrdcontext_updated_rrddim_divisor(RRDDIM *rd); +void rrdcontext_updated_rrddim_flags(RRDDIM *rd); +void rrdcontext_collected_rrddim(RRDDIM *rd); +int rrdcontext_find_dimension_uuid(RRDSET *st, const char *id, uuid_t *store_uuid); + +// ---------------------------------------------------------------------------- +// public API for rrdsets + +void rrdcontext_updated_rrdset(RRDSET *st); +void rrdcontext_removed_rrdset(RRDSET *st); +void rrdcontext_updated_rrdset_name(RRDSET *st); +void rrdcontext_updated_rrdset_flags(RRDSET *st); +void rrdcontext_updated_retention_rrdset(RRDSET *st); +void rrdcontext_collected_rrdset(RRDSET *st); +int rrdcontext_find_chart_uuid(RRDSET *st, uuid_t *store_uuid); + +// ---------------------------------------------------------------------------- +// public API for ACLK + +void rrdcontext_hub_checkpoint_command(void *cmd); +void rrdcontext_hub_stop_streaming_command(void *cmd); + + +// ---------------------------------------------------------------------------- +// public API for threads + +void rrdcontext_db_rotation(void); +void *rrdcontext_main(void *); + +// ---------------------------------------------------------------------------- +// public API for queries + +typedef enum __attribute__ ((__packed__)) { + QUERY_STATUS_NONE = 0, + QUERY_STATUS_QUERIED = (1 << 0), + QUERY_STATUS_DIMENSION_HIDDEN = (1 << 1), + QUERY_STATUS_EXCLUDED = (1 << 2), + QUERY_STATUS_FAILED = (1 << 3), +} QUERY_STATUS; + +typedef struct query_plan_entry { + size_t tier; + time_t after; + time_t before; +} QUERY_PLAN_ENTRY; + +#define QUERY_PLANS_MAX (RRD_STORAGE_TIERS) + +typedef struct query_metrics_counts { // counts the number of metrics related to an object + size_t selected; // selected to be queried + size_t excluded; // not selected to be queried + size_t queried; // successfully queried + size_t failed; // failed to be queried +} QUERY_METRICS_COUNTS; + +typedef struct query_instances_counts { // counts the number of instances related to an object + size_t selected; // selected to be queried + size_t excluded; // not selected to be queried + size_t queried; // successfully queried + size_t failed; // failed to be queried +} QUERY_INSTANCES_COUNTS; + +typedef struct query_alerts_counts { // counts the number of alerts related to an object + size_t clear; // number of alerts in clear state + size_t warning; // number of alerts in warning state + size_t critical; // number of alerts in critical state + size_t other; // number of alerts in any other state +} QUERY_ALERTS_COUNTS; + +typedef struct query_node { + uint32_t slot; + RRDHOST *rrdhost; + char node_id[UUID_STR_LEN]; + usec_t duration_ut; + + STORAGE_POINT query_points; + QUERY_INSTANCES_COUNTS instances; + QUERY_METRICS_COUNTS metrics; + QUERY_ALERTS_COUNTS alerts; +} QUERY_NODE; + +typedef struct query_context { + uint32_t slot; + RRDCONTEXT_ACQUIRED *rca; + + STORAGE_POINT query_points; + QUERY_INSTANCES_COUNTS instances; + QUERY_METRICS_COUNTS metrics; + QUERY_ALERTS_COUNTS alerts; +} QUERY_CONTEXT; + +typedef struct query_instance { + uint32_t slot; + uint32_t query_host_id; + RRDINSTANCE_ACQUIRED *ria; + STRING *id_fqdn; // never access this directly - it is created on demand via query_instance_id_fqdn() + STRING *name_fqdn; // never access this directly - it is created on demand via query_instance_name_fqdn() + + STORAGE_POINT query_points; + QUERY_METRICS_COUNTS metrics; + QUERY_ALERTS_COUNTS alerts; +} QUERY_INSTANCE; + +typedef struct query_dimension { + uint32_t slot; + uint32_t priority; + RRDMETRIC_ACQUIRED *rma; + QUERY_STATUS status; +} QUERY_DIMENSION; + +typedef struct query_metric { + RRDR_DIMENSION_FLAGS status; + + struct query_metric_tier { + STORAGE_METRIC_HANDLE *db_metric_handle; + 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 { + uint32_t query_node_id; + uint32_t query_context_id; + uint32_t query_instance_id; + uint32_t query_dimension_id; + } link; + + STORAGE_POINT query_points; + + struct { + uint32_t slot; + uint32_t first_slot; + STRING *id; + STRING *name; + STRING *units; + } grouped_as; + + usec_t duration_ut; +} QUERY_METRIC; + +#define MAX_QUERY_TARGET_ID_LENGTH 255 +#define MAX_QUERY_GROUP_BY_PASSES 2 + +typedef bool (*qt_interrupt_callback_t)(void *data); + +struct group_by_pass { + RRDR_GROUP_BY group_by; + char *group_by_label; + RRDR_GROUP_BY_FUNCTION aggregation; +}; + +typedef struct query_target_request { + size_t version; + + const char *scope_nodes; + const char *scope_contexts; + + // selecting / filtering metrics to be queried + RRDHOST *host; // the host to be queried (can be NULL, hosts will be used) + RRDCONTEXT_ACQUIRED *rca; // the context to be queried (can be NULL) + RRDINSTANCE_ACQUIRED *ria; // the instance to be queried (can be NULL) + RRDMETRIC_ACQUIRED *rma; // the metric to be queried (can be NULL) + RRDSET *st; // the chart to be queried (NULL, for context queries) + const char *nodes; // hosts simple pattern + const char *contexts; // contexts simple pattern (context queries) + const char *instances; // charts simple pattern (for context queries) + const char *dimensions; // dimensions simple pattern + const char *chart_label_key; // select only the chart having this label key + const char *labels; // select only the charts having this combo of label key:value + const char *alerts; // select only the charts having this combo of alert name:status + + time_t after; // the requested timeframe + time_t before; // the requested timeframe + size_t points; // the requested number of points to be returned + + uint32_t format; // DATASOURCE_FORMAT + RRDR_OPTIONS options; + time_t timeout_ms; // the timeout of the query in milliseconds + + size_t tier; + QUERY_SOURCE query_source; + STORAGE_PRIORITY priority; + + // resampling metric values across time + time_t resampling_time; + + // grouping metric values across time + RRDR_TIME_GROUPING time_group_method; + const char *time_group_options; + + // group by across multiple time-series + struct group_by_pass group_by[MAX_QUERY_GROUP_BY_PASSES]; + + usec_t received_ut; + + qt_interrupt_callback_t interrupt_callback; + void *interrupt_callback_data; +} QUERY_TARGET_REQUEST; + +#define GROUP_BY_MAX_LABEL_KEYS 10 + +struct query_tier_statistics { + size_t queries; + size_t points; + time_t update_every; + struct { + time_t first_time_s; + time_t last_time_s; + } retention; +}; + +struct query_versions { + uint64_t contexts_hard_hash; + uint64_t contexts_soft_hash; + uint64_t alerts_hard_hash; + uint64_t alerts_soft_hash; +}; + +struct query_timings { + usec_t received_ut; + usec_t preprocessed_ut; + usec_t executed_ut; + usec_t finished_ut; +}; + +#define query_view_update_every(qt) ((qt)->window.group * (qt)->window.query_granularity) + +typedef struct query_target { + char id[MAX_QUERY_TARGET_ID_LENGTH + 1]; // query identifier (for logging) + QUERY_TARGET_REQUEST request; + + struct { + time_t now; // the current timestamp, the absolute max for any query timestamp + bool relative; // true when the request made with relative timestamps, true if it was absolute + bool aligned; + time_t after; // the absolute timestamp this query is about + time_t before; // the absolute timestamp this query is about + time_t query_granularity; + size_t points; // the number of points the query will return (maybe different from the request) + size_t group; + RRDR_TIME_GROUPING time_group_method; + const char *time_group_options; + size_t resampling_group; + NETDATA_DOUBLE resampling_divisor; + RRDR_OPTIONS options; + size_t tier; + } window; + + struct { + size_t queries[RRD_STORAGE_TIERS]; + 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 + struct query_tier_statistics tiers[RRD_STORAGE_TIERS]; + } db; + + struct { + QUERY_METRIC *array; // the metrics to be queried (all of them should be queried, no exceptions) + uint32_t used; // how many items of the array are used + uint32_t size; // the size of the array + SIMPLE_PATTERN *pattern; + } query; + + struct { + QUERY_DIMENSION *array; + uint32_t used; // how many items of the array are used + uint32_t size; // the size of the array + } dimensions; + + struct { + QUERY_INSTANCE *array; + uint32_t used; // how many items of the array are used + uint32_t size; // the size of the array + SIMPLE_PATTERN *pattern; + SIMPLE_PATTERN *labels_pattern; + SIMPLE_PATTERN *alerts_pattern; + SIMPLE_PATTERN *chart_label_key_pattern; + } instances; + + struct { + QUERY_CONTEXT *array; + uint32_t used; // how many items of the array are used + uint32_t size; // the size of the array + SIMPLE_PATTERN *pattern; + SIMPLE_PATTERN *scope_pattern; + } contexts; + + struct { + QUERY_NODE *array; + uint32_t used; // how many items of the array are used + uint32_t size; // the size of the array + SIMPLE_PATTERN *pattern; + SIMPLE_PATTERN *scope_pattern; + } nodes; + + struct { + size_t used; + char *label_keys[GROUP_BY_MAX_LABEL_KEYS * MAX_QUERY_GROUP_BY_PASSES]; + } group_by[MAX_QUERY_GROUP_BY_PASSES]; + + STORAGE_POINT query_points; + struct query_versions versions; + struct query_timings timings; + + struct { + SPINLOCK spinlock; + bool used; // when true, this query is currently being used + size_t queries; // how many query we have done so far with this QUERY_TARGET - not related to database queries + struct query_target *prev; + struct query_target *next; + } internal; +} QUERY_TARGET; + +static inline NEVERNULL QUERY_NODE *query_node(QUERY_TARGET *qt, size_t id) { + internal_fatal(id >= qt->nodes.used, "QUERY: invalid query host id"); + return &qt->nodes.array[id]; +} + +static inline NEVERNULL QUERY_CONTEXT *query_context(QUERY_TARGET *qt, size_t query_context_id) { + internal_fatal(query_context_id >= qt->contexts.used, "QUERY: invalid query context id"); + return &qt->contexts.array[query_context_id]; +} + +static inline NEVERNULL QUERY_INSTANCE *query_instance(QUERY_TARGET *qt, size_t query_instance_id) { + internal_fatal(query_instance_id >= qt->instances.used, "QUERY: invalid query instance id"); + return &qt->instances.array[query_instance_id]; +} + +static inline NEVERNULL QUERY_DIMENSION *query_dimension(QUERY_TARGET *qt, size_t query_dimension_id) { + internal_fatal(query_dimension_id >= qt->dimensions.used, "QUERY: invalid query dimension id"); + return &qt->dimensions.array[query_dimension_id]; +} + +static inline NEVERNULL QUERY_METRIC *query_metric(QUERY_TARGET *qt, size_t id) { + internal_fatal(id >= qt->query.used, "QUERY: invalid query metric id"); + return &qt->query.array[id]; +} + +static inline const char *query_metric_id(QUERY_TARGET *qt, QUERY_METRIC *qm) { + QUERY_DIMENSION *qd = query_dimension(qt, qm->link.query_dimension_id); + return rrdmetric_acquired_id(qd->rma); +} + +static inline const char *query_metric_name(QUERY_TARGET *qt, QUERY_METRIC *qm) { + QUERY_DIMENSION *qd = query_dimension(qt, qm->link.query_dimension_id); + return rrdmetric_acquired_name(qd->rma); +} + +struct storage_engine *query_metric_storage_engine(QUERY_TARGET *qt, QUERY_METRIC *qm, size_t tier); + +STRING *query_instance_id_fqdn(QUERY_INSTANCE *qi, size_t version); +STRING *query_instance_name_fqdn(QUERY_INSTANCE *qi, size_t version); + +void query_target_free(void); +void query_target_release(QUERY_TARGET *qt); + +QUERY_TARGET *query_target_create(QUERY_TARGET_REQUEST *qtr); + +struct api_v2_contexts_request { + char *scope_nodes; + char *scope_contexts; + char *nodes; + char *contexts; + char *q; + + time_t timeout_ms; + + qt_interrupt_callback_t interrupt_callback; + void *interrupt_callback_data; +}; + +typedef enum __attribute__ ((__packed__)) { + CONTEXTS_V2_DEBUG = (1 << 0), + CONTEXTS_V2_SEARCH = (1 << 1), + CONTEXTS_V2_NODES = (1 << 2), + CONTEXTS_V2_NODES_DETAILED = (1 << 3), + CONTEXTS_V2_CONTEXTS = (1 << 4), +} CONTEXTS_V2_OPTIONS; + +int rrdcontext_to_json_v2(BUFFER *wb, struct api_v2_contexts_request *req, CONTEXTS_V2_OPTIONS options); + +RRDCONTEXT_TO_JSON_OPTIONS rrdcontext_to_json_parse_options(char *o); +void buffer_json_agents_array_v2(BUFFER *wb, struct query_timings *timings, time_t now_s); +void buffer_json_node_add_v2(BUFFER *wb, RRDHOST *host, size_t ni, usec_t duration_ut); +void buffer_json_query_timings(BUFFER *wb, const char *key, struct query_timings *timings); +void buffer_json_cloud_timings(BUFFER *wb, const char *key, struct query_timings *timings); + +// ---------------------------------------------------------------------------- +// scope + +typedef ssize_t (*foreach_host_cb_t)(void *data, RRDHOST *host, bool queryable); +ssize_t query_scope_foreach_host(SIMPLE_PATTERN *scope_hosts_sp, SIMPLE_PATTERN *hosts_sp, + foreach_host_cb_t cb, void *data, + struct query_versions *versions, + char *host_node_id_str); + +typedef ssize_t (*foreach_context_cb_t)(void *data, RRDCONTEXT_ACQUIRED *rca, bool queryable_context); +ssize_t query_scope_foreach_context(RRDHOST *host, const char *scope_contexts, SIMPLE_PATTERN *scope_contexts_sp, SIMPLE_PATTERN *contexts_sp, foreach_context_cb_t cb, bool queryable_host, void *data); + +// ---------------------------------------------------------------------------- +// public API for weights + +typedef ssize_t (*weights_add_metric_t)(void *data, RRDHOST *host, RRDCONTEXT_ACQUIRED *rca, RRDINSTANCE_ACQUIRED *ria, RRDMETRIC_ACQUIRED *rma); +ssize_t weights_foreach_rrdmetric_in_context(RRDCONTEXT_ACQUIRED *rca, + SIMPLE_PATTERN *instances_sp, + SIMPLE_PATTERN *chart_label_key_sp, + SIMPLE_PATTERN *labels_sp, + SIMPLE_PATTERN *alerts_sp, + SIMPLE_PATTERN *dimensions_sp, + bool match_ids, bool match_names, + size_t version, + weights_add_metric_t cb, + void *data); + +bool rrdcontext_retention_match(RRDCONTEXT_ACQUIRED *rca, time_t after, time_t before); + +#define query_matches_retention(after, before, first_entry_s, last_entry_s, update_every_s) \ + (((first_entry_s) - ((update_every_s) * 2) <= (before)) && \ + ((last_entry_s) + ((update_every_s) * 2) >= (after))) + +#define query_target_aggregatable(qt) ((qt)->window.options & RRDR_OPTION_RETURN_RAW) + +static inline bool query_target_has_percentage_of_instance(QUERY_TARGET *qt) { + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) + if(qt->request.group_by[g].group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE) + return true; + + return false; +} + +static inline bool query_target_needs_all_dimensions(QUERY_TARGET *qt) { + if(qt->request.options & RRDR_OPTION_PERCENTAGE) + return true; + + return query_target_has_percentage_of_instance(qt); +} + +static inline bool query_target_has_percentage_units(QUERY_TARGET *qt) { + if(qt->window.time_group_method == RRDR_GROUPING_CV) + return true; + + if((qt->request.options & RRDR_OPTION_PERCENTAGE) && !(qt->window.options & RRDR_OPTION_RETURN_RAW)) + return true; + + return query_target_has_percentage_of_instance(qt); +} + +#endif // NETDATA_RRDCONTEXT_H + diff --git a/database/contexts/worker.c b/database/contexts/worker.c new file mode 100644 index 00000000..22e28b2a --- /dev/null +++ b/database/contexts/worker.c @@ -0,0 +1,1094 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "internal.h" + +static uint64_t rrdcontext_get_next_version(RRDCONTEXT *rc); + +static bool check_if_cloud_version_changed_unsafe(RRDCONTEXT *rc, bool sending __maybe_unused); + +static void rrdcontext_delete_from_sql_unsafe(RRDCONTEXT *rc); + +static void rrdcontext_dequeue_from_post_processing(RRDCONTEXT *rc); +static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAGS reason, bool worker_jobs); + +static void rrdcontext_garbage_collect_single_host(RRDHOST *host, bool worker_jobs); +static void rrdcontext_garbage_collect_for_all_hosts(void); + +extern usec_t rrdcontext_next_db_rotation_ut; + +// ---------------------------------------------------------------------------- +// load from SQL + +static void rrdinstance_load_clabel(SQL_CLABEL_DATA *sld, void *data) { + RRDINSTANCE *ri = data; + rrdlabels_add(ri->rrdlabels, sld->label_key, sld->label_value, sld->label_source); +} + +static void rrdinstance_load_dimension(SQL_DIMENSION_DATA *sd, void *data) { + RRDINSTANCE *ri = data; + + RRDMETRIC trm = { + .id = string_strdupz(sd->id), + .name = string_strdupz(sd->name), + .flags = RRD_FLAG_ARCHIVED | RRD_FLAG_UPDATE_REASON_LOAD_SQL, // no need for atomic + }; + if(sd->hidden) trm.flags |= RRD_FLAG_HIDDEN; + + uuid_copy(trm.uuid, sd->dim_id); + + dictionary_set(ri->rrdmetrics, string2str(trm.id), &trm, sizeof(trm)); +} + +static void rrdinstance_load_chart_callback(SQL_CHART_DATA *sc, void *data) { + RRDHOST *host = data; + + RRDCONTEXT tc = { + .id = string_strdupz(sc->context), + .title = string_strdupz(sc->title), + .units = string_strdupz(sc->units), + .family = string_strdupz(sc->family), + .priority = sc->priority, + .chart_type = sc->chart_type, + .flags = RRD_FLAG_ARCHIVED | RRD_FLAG_UPDATE_REASON_LOAD_SQL, // no need for atomics + .rrdhost = host, + }; + + RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_set_and_acquire_item(host->rrdctx.contexts, string2str(tc.id), &tc, sizeof(tc)); + RRDCONTEXT *rc = rrdcontext_acquired_value(rca); + + RRDINSTANCE tri = { + .id = string_strdupz(sc->id), + .name = string_strdupz(sc->name), + .title = string_strdupz(sc->title), + .units = string_strdupz(sc->units), + .family = string_strdupz(sc->family), + .chart_type = sc->chart_type, + .priority = sc->priority, + .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); + + RRDINSTANCE_ACQUIRED *ria = (RRDINSTANCE_ACQUIRED *)dictionary_set_and_acquire_item(rc->rrdinstances, sc->id, &tri, sizeof(tri)); + RRDINSTANCE *ri = rrdinstance_acquired_value(ria); + + ctx_get_dimension_list(&ri->uuid, rrdinstance_load_dimension, ri); + ctx_get_label_list(&ri->uuid, rrdinstance_load_clabel, ri); + rrdinstance_trigger_updates(ri, __FUNCTION__ ); + rrdinstance_release(ria); + rrdcontext_release(rca); +} + +static void rrdcontext_load_context_callback(VERSIONED_CONTEXT_DATA *ctx_data, void *data) { + RRDHOST *host = data; + (void)host; + + RRDCONTEXT trc = { + .id = string_strdupz(ctx_data->id), + .flags = RRD_FLAG_ARCHIVED | RRD_FLAG_UPDATE_REASON_LOAD_SQL, // no need for atomics + + // no need to set more data here + // we only need the hub data + + .hub = *ctx_data, + }; + dictionary_set(host->rrdctx.contexts, string2str(trc.id), &trc, sizeof(trc)); +} + +void rrdhost_load_rrdcontext_data(RRDHOST *host) { + if(host->rrdctx.contexts) return; + + rrdhost_create_rrdcontexts(host); + ctx_get_context_list(&host->host_uuid, rrdcontext_load_context_callback, host); + ctx_get_chart_list(&host->host_uuid, rrdinstance_load_chart_callback, host); + + RRDCONTEXT *rc; + dfe_start_read(host->rrdctx.contexts, rc) { + rrdcontext_trigger_updates(rc, __FUNCTION__ ); + } + dfe_done(rc); + + rrdcontext_garbage_collect_single_host(host, false); +} + +// ---------------------------------------------------------------------------- +// version hash calculation + +uint64_t rrdcontext_version_hash_with_callback( + RRDHOST *host, + void (*callback)(RRDCONTEXT *, bool, void *), + bool snapshot, + void *bundle) { + + if(unlikely(!host || !host->rrdctx.contexts)) return 0; + + RRDCONTEXT *rc; + uint64_t hash = 0; + + // loop through all contexts of the host + dfe_start_read(host->rrdctx.contexts, rc) { + + rrdcontext_lock(rc); + + if(unlikely(rrd_flag_check(rc, RRD_FLAG_HIDDEN))) { + rrdcontext_unlock(rc); + continue; + } + + if(unlikely(callback)) + callback(rc, snapshot, bundle); + + // skip any deleted contexts + if(unlikely(rrd_flag_is_deleted(rc))) { + rrdcontext_unlock(rc); + continue; + } + + // we use rc->hub.* which has the latest + // metadata we have sent to the hub + + // if a context is currently queued, rc->hub.* does NOT + // reflect the queued changes. rc->hub.* is updated with + // their metadata, after messages are dispatched to hub. + + // when the context is being collected, + // rc->hub.last_time_t is already zero + + hash += rc->hub.version + rc->hub.last_time_s - rc->hub.first_time_s; + + rrdcontext_unlock(rc); + + } + dfe_done(rc); + + return hash; +} + +// ---------------------------------------------------------------------------- +// retention recalculation + +void rrdcontext_recalculate_context_retention(RRDCONTEXT *rc, RRD_FLAGS reason, bool worker_jobs) { + rrdcontext_post_process_updates(rc, true, reason, worker_jobs); +} + +void rrdcontext_recalculate_host_retention(RRDHOST *host, RRD_FLAGS reason, bool worker_jobs) { + if(unlikely(!host || !host->rrdctx.contexts)) return; + + RRDCONTEXT *rc; + dfe_start_read(host->rrdctx.contexts, rc) { + rrdcontext_recalculate_context_retention(rc, reason, worker_jobs); + } + dfe_done(rc); +} + +static void rrdcontext_recalculate_retention_all_hosts(void) { + rrdcontext_next_db_rotation_ut = 0; + RRDHOST *host; + dfe_start_reentrant(rrdhost_root_index, host) { + worker_is_busy(WORKER_JOB_RETENTION); + rrdcontext_recalculate_host_retention(host, RRD_FLAG_UPDATE_REASON_DB_ROTATION, true); + } + dfe_done(host); +} + +// ---------------------------------------------------------------------------- +// garbage collector + +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_s(rm->rrddim); + max_last_time_t = rrddim_last_entry_s(rm->rrddim); + } + else { + RRDHOST *rrdhost = rm->ri->rc->rrdhost; + for (size_t tier = 0; tier < storage_tiers; tier++) { + STORAGE_ENGINE *eng = rrdhost->db[tier].eng; + + time_t first_time_t, last_time_t; + 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; + + if (last_time_t > max_last_time_t) + max_last_time_t = last_time_t; + } + } + } + + if((min_first_time_t == LONG_MAX || min_first_time_t == 0) && max_last_time_t == 0) + return false; + + 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, 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; + } + + // check if retention changed + + 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_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_s && !rm->last_time_s)) + rrd_flag_set_deleted(rm, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); + + rrd_flag_set(rm, RRD_FLAG_LIVE_RETENTION); + + return true; +} + +static inline bool rrdmetric_should_be_deleted(RRDMETRIC *rm) { + if(likely(!rrd_flag_check(rm, RRD_FLAGS_REQUIRED_FOR_DELETIONS))) + return false; + + if(likely(rrd_flag_check(rm, RRD_FLAGS_PREVENTING_DELETIONS))) + return false; + + if(likely(rm->rrddim)) + return false; + + rrdmetric_update_retention(rm); + if(rm->first_time_s || rm->last_time_s) + return false; + + return true; +} + +static inline bool rrdinstance_should_be_deleted(RRDINSTANCE *ri) { + if(likely(!rrd_flag_check(ri, RRD_FLAGS_REQUIRED_FOR_DELETIONS))) + return false; + + if(likely(rrd_flag_check(ri, RRD_FLAGS_PREVENTING_DELETIONS))) + return false; + + if(likely(ri->rrdset)) + return false; + + if(unlikely(dictionary_referenced_items(ri->rrdmetrics) != 0)) + return false; + + if(unlikely(dictionary_entries(ri->rrdmetrics) != 0)) + return false; + + if(ri->first_time_s || ri->last_time_s) + return false; + + return true; +} + +static inline bool rrdcontext_should_be_deleted(RRDCONTEXT *rc) { + if(likely(!rrd_flag_check(rc, RRD_FLAGS_REQUIRED_FOR_DELETIONS))) + return false; + + if(likely(rrd_flag_check(rc, RRD_FLAGS_PREVENTING_DELETIONS))) + return false; + + if(unlikely(dictionary_referenced_items(rc->rrdinstances) != 0)) + return false; + + if(unlikely(dictionary_entries(rc->rrdinstances) != 0)) + return false; + + if(unlikely(rc->first_time_s || rc->last_time_s)) + return false; + + return true; +} + +void rrdcontext_delete_from_sql_unsafe(RRDCONTEXT *rc) { + // we need to refresh the string pointers in rc->hub + // in case the context changed values + rc->hub.id = string2str(rc->id); + rc->hub.title = string2str(rc->title); + rc->hub.units = string2str(rc->units); + rc->hub.family = string2str(rc->family); + + // delete it from SQL + if(ctx_delete_context(&rc->rrdhost->host_uuid, &rc->hub) != 0) + error("RRDCONTEXT: failed to delete context '%s' version %"PRIu64" from SQL.", rc->hub.id, rc->hub.version); +} + +static void rrdcontext_garbage_collect_single_host(RRDHOST *host, bool worker_jobs) { + + internal_error(true, "RRDCONTEXT: garbage collecting context structures of host '%s'", rrdhost_hostname(host)); + + RRDCONTEXT *rc; + dfe_start_reentrant(host->rrdctx.contexts, rc) { + if(unlikely(worker_jobs && !service_running(SERVICE_CONTEXT))) break; + + if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP); + + rrdcontext_lock(rc); + + RRDINSTANCE *ri; + dfe_start_reentrant(rc->rrdinstances, ri) { + if(unlikely(worker_jobs && !service_running(SERVICE_CONTEXT))) break; + + RRDMETRIC *rm; + dfe_start_write(ri->rrdmetrics, rm) { + if(rrdmetric_should_be_deleted(rm)) { + if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP_DELETE); + if(!dictionary_del(ri->rrdmetrics, string2str(rm->id))) + error("RRDCONTEXT: metric '%s' of instance '%s' of context '%s' of host '%s', failed to be deleted from rrdmetrics dictionary.", + string2str(rm->id), + string2str(ri->id), + string2str(rc->id), + rrdhost_hostname(host)); + else + internal_error( + true, + "RRDCONTEXT: metric '%s' of instance '%s' of context '%s' of host '%s', deleted from rrdmetrics dictionary.", + string2str(rm->id), + string2str(ri->id), + string2str(rc->id), + rrdhost_hostname(host)); + } + } + dfe_done(rm); + + if(rrdinstance_should_be_deleted(ri)) { + if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP_DELETE); + if(!dictionary_del(rc->rrdinstances, string2str(ri->id))) + error("RRDCONTEXT: instance '%s' of context '%s' of host '%s', failed to be deleted from rrdmetrics dictionary.", + string2str(ri->id), + string2str(rc->id), + rrdhost_hostname(host)); + else + internal_error( + true, + "RRDCONTEXT: instance '%s' of context '%s' of host '%s', deleted from rrdmetrics dictionary.", + string2str(ri->id), + string2str(rc->id), + rrdhost_hostname(host)); + } + } + dfe_done(ri); + + if(unlikely(rrdcontext_should_be_deleted(rc))) { + if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP_DELETE); + rrdcontext_dequeue_from_post_processing(rc); + rrdcontext_delete_from_sql_unsafe(rc); + + if(!dictionary_del(host->rrdctx.contexts, string2str(rc->id))) + error("RRDCONTEXT: context '%s' of host '%s', failed to be deleted from rrdmetrics dictionary.", + string2str(rc->id), + rrdhost_hostname(host)); + else + internal_error( + true, + "RRDCONTEXT: context '%s' of host '%s', deleted from rrdmetrics dictionary.", + string2str(rc->id), + rrdhost_hostname(host)); + } + + // the item is referenced in the dictionary + // so, it is still here to unlock, even if we have deleted it + rrdcontext_unlock(rc); + } + dfe_done(rc); +} + +static void rrdcontext_garbage_collect_for_all_hosts(void) { + RRDHOST *host; + dfe_start_reentrant(rrdhost_root_index, host) { + rrdcontext_garbage_collect_single_host(host, true); + } + dfe_done(host); +} + +// ---------------------------------------------------------------------------- +// post processing + +static void rrdmetric_process_updates(RRDMETRIC *rm, bool force, RRD_FLAGS reason, bool worker_jobs) { + if(reason != RRD_FLAG_NONE) + rrd_flag_set_updated(rm, reason); + + if(!force && !rrd_flag_is_updated(rm) && rrd_flag_check(rm, RRD_FLAG_LIVE_RETENTION) && !rrd_flag_check(rm, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION)) + return; + + if(worker_jobs) + worker_is_busy(WORKER_JOB_PP_METRIC); + + if(reason & RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD) { + rrd_flag_set_archived(rm); + rrd_flag_set(rm, RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD); + } + if(rrd_flag_is_deleted(rm) && (reason & RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION)) + rrd_flag_set_archived(rm); + + rrdmetric_update_retention(rm); + + rrd_flag_unset_updated(rm); +} + +static void rrdinstance_post_process_updates(RRDINSTANCE *ri, bool force, RRD_FLAGS reason, bool worker_jobs) { + if(reason != RRD_FLAG_NONE) + rrd_flag_set_updated(ri, reason); + + if(!force && !rrd_flag_is_updated(ri) && rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION)) + return; + + if(worker_jobs) + worker_is_busy(WORKER_JOB_PP_INSTANCE); + + time_t min_first_time_t = LONG_MAX, max_last_time_t = 0; + size_t metrics_active = 0, metrics_deleted = 0; + bool live_retention = true, currently_collected = false; + if(dictionary_entries(ri->rrdmetrics) > 0) { + RRDMETRIC *rm; + dfe_start_read((DICTIONARY *)ri->rrdmetrics, rm) { + if(unlikely(!service_running(SERVICE_CONTEXT))) break; + + RRD_FLAGS reason_to_pass = reason; + if(rrd_flag_check(ri, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION)) + reason_to_pass |= RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION; + + rrdmetric_process_updates(rm, force, reason_to_pass, worker_jobs); + + if(unlikely(!rrd_flag_check(rm, RRD_FLAG_LIVE_RETENTION))) + live_retention = false; + + if (unlikely((rrdmetric_should_be_deleted(rm)))) { + metrics_deleted++; + continue; + } + + if(!currently_collected && rrd_flag_check(rm, RRD_FLAG_COLLECTED) && rm->first_time_s) + currently_collected = true; + + metrics_active++; + + 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_s && rm->last_time_s > max_last_time_t) + max_last_time_t = rm->last_time_s; + } + dfe_done(rm); + } + + if(unlikely(live_retention && !rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION))) + rrd_flag_set(ri, RRD_FLAG_LIVE_RETENTION); + else if(unlikely(!live_retention && rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION))) + rrd_flag_clear(ri, RRD_FLAG_LIVE_RETENTION); + + if(unlikely(!metrics_active)) { + // no metrics available + + 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_s) { + ri->last_time_s = 0; + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T); + } + + rrd_flag_set_deleted(ri, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); + } + else { + // we have active metrics... + + if (unlikely(min_first_time_t == LONG_MAX)) + min_first_time_t = 0; + + if (unlikely(min_first_time_t == 0 || max_last_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_s) { + ri->last_time_s = 0; + rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T); + } + + if(likely(live_retention)) + rrd_flag_set_deleted(ri, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); + } + else { + rrd_flag_clear(ri, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); + + 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_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); + } + + if(likely(currently_collected)) + rrd_flag_set_collected(ri); + else + rrd_flag_set_archived(ri); + } + } + + rrd_flag_unset_updated(ri); +} + +static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAGS reason, bool worker_jobs) { + if(reason != RRD_FLAG_NONE) + rrd_flag_set_updated(rc, reason); + + 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; + bool live_retention = true, currently_collected = false, hidden = true; + if(dictionary_entries(rc->rrdinstances) > 0) { + RRDINSTANCE *ri; + dfe_start_reentrant(rc->rrdinstances, ri) { + if(unlikely(!service_running(SERVICE_CONTEXT))) break; + + RRD_FLAGS reason_to_pass = reason; + if(rrd_flag_check(rc, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION)) + reason_to_pass |= RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION; + + rrdinstance_post_process_updates(ri, force, reason_to_pass, worker_jobs); + + if(unlikely(hidden && !rrd_flag_check(ri, RRD_FLAG_HIDDEN))) + hidden = false; + + if(unlikely(live_retention && !rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION))) + live_retention = false; + + if (unlikely(rrdinstance_should_be_deleted(ri))) { + instances_deleted++; + continue; + } + + if(unlikely(!currently_collected && rrd_flag_is_collected(ri) && ri->first_time_s)) + currently_collected = true; + + internal_error(rc->units != ri->units, + "RRDCONTEXT: '%s' rrdinstance '%s' has different units, context '%s', instance '%s'", + string2str(rc->id), string2str(ri->id), + string2str(rc->units), string2str(ri->units)); + + instances_active++; + + 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_s && ri->first_time_s < min_first_time_t) + min_first_time_t = ri->first_time_s; + + 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; + } + + { + bool previous_hidden = rrd_flag_check(rc, RRD_FLAG_HIDDEN); + if (hidden != previous_hidden) { + if (hidden && !rrd_flag_check(rc, RRD_FLAG_HIDDEN)) + rrd_flag_set(rc, RRD_FLAG_HIDDEN); + else if (!hidden && rrd_flag_check(rc, RRD_FLAG_HIDDEN)) + rrd_flag_clear(rc, RRD_FLAG_HIDDEN); + } + + bool previous_live_retention = rrd_flag_check(rc, RRD_FLAG_LIVE_RETENTION); + if (live_retention != previous_live_retention) { + if (live_retention && !rrd_flag_check(rc, RRD_FLAG_LIVE_RETENTION)) + rrd_flag_set(rc, RRD_FLAG_LIVE_RETENTION); + else if (!live_retention && rrd_flag_check(rc, RRD_FLAG_LIVE_RETENTION)) + rrd_flag_clear(rc, RRD_FLAG_LIVE_RETENTION); + } + } + + rrdcontext_lock(rc); + rc->pp.executions++; + + if(unlikely(!instances_active)) { + // we had some instances, but they are gone now... + + 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_s) { + rc->last_time_s = 0; + rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T); + } + + rrd_flag_set_deleted(rc, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); + } + else { + // we have some active instances... + + if (unlikely(min_first_time_t == LONG_MAX)) + min_first_time_t = 0; + + if (unlikely(min_first_time_t == 0 && max_last_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_s) { + rc->last_time_s = 0; + rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T); + } + + rrd_flag_set_deleted(rc, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); + } + else { + rrd_flag_clear(rc, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); + + 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_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); + } + + if(likely(currently_collected)) + rrd_flag_set_collected(rc); + else + rrd_flag_set_archived(rc); + } + + if (min_priority != LONG_MAX && rc->priority != min_priority) { + rc->priority = min_priority; + rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); + } + } + + if(unlikely(rrd_flag_is_updated(rc) && rc->rrdhost->rrdctx.hub_queue)) { + if(check_if_cloud_version_changed_unsafe(rc, false)) { + rc->version = rrdcontext_get_next_version(rc); + dictionary_set((DICTIONARY *)rc->rrdhost->rrdctx.hub_queue, + string2str(rc->id), rc, sizeof(*rc)); + } + } + + rrd_flag_unset_updated(rc); + rrdcontext_unlock(rc); +} + +void rrdcontext_queue_for_post_processing(RRDCONTEXT *rc, const char *function __maybe_unused, RRD_FLAGS flags __maybe_unused) { + if(unlikely(!rc->rrdhost->rrdctx.pp_queue)) return; + + if(!rrd_flag_check(rc, RRD_FLAG_QUEUED_FOR_PP)) { + dictionary_set((DICTIONARY *)rc->rrdhost->rrdctx.pp_queue, + string2str(rc->id), + rc, + sizeof(*rc)); + +#if(defined(NETDATA_INTERNAL_CHECKS) && defined(LOG_POST_PROCESSING_QUEUE_INSERTIONS)) + { + BUFFER *wb_flags = buffer_create(1000); + rrd_flags_to_buffer(flags, wb_flags); + + BUFFER *wb_reasons = buffer_create(1000); + rrd_reasons_to_buffer(flags, wb_reasons); + + internal_error(true, "RRDCONTEXT: '%s' update triggered by function %s(), due to flags: %s, reasons: %s", + string2str(rc->id), function, + buffer_tostring(wb_flags), + buffer_tostring(wb_reasons)); + + buffer_free(wb_reasons); + buffer_free(wb_flags); + } +#endif + } +} + +static void rrdcontext_dequeue_from_post_processing(RRDCONTEXT *rc) { + if(unlikely(!rc->rrdhost->rrdctx.pp_queue)) return; + dictionary_del(rc->rrdhost->rrdctx.pp_queue, string2str(rc->id)); +} + +static void rrdcontext_post_process_queued_contexts(RRDHOST *host) { + if(unlikely(!host->rrdctx.pp_queue)) return; + + RRDCONTEXT *rc; + dfe_start_reentrant(host->rrdctx.pp_queue, rc) { + if(unlikely(!service_running(SERVICE_CONTEXT))) break; + + rrdcontext_dequeue_from_post_processing(rc); + rrdcontext_post_process_updates(rc, false, RRD_FLAG_NONE, true); + } + dfe_done(rc); +} + +// ---------------------------------------------------------------------------- +// dispatching contexts to cloud + +static uint64_t rrdcontext_get_next_version(RRDCONTEXT *rc) { + time_t now = now_realtime_sec(); + uint64_t version = MAX(rc->version, rc->hub.version); + version = MAX((uint64_t)now, version); + version++; + return version; +} + +void rrdcontext_message_send_unsafe(RRDCONTEXT *rc, bool snapshot __maybe_unused, void *bundle __maybe_unused) { + + // save it, so that we know the last version we sent to hub + rc->version = rc->hub.version = rrdcontext_get_next_version(rc); + rc->hub.id = string2str(rc->id); + rc->hub.title = string2str(rc->title); + rc->hub.units = string2str(rc->units); + 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_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 + struct context_updated message = { + .id = rc->hub.id, + .version = rc->hub.version, + .title = rc->hub.title, + .units = rc->hub.units, + .family = rc->hub.family, + .chart_type = rc->hub.chart_type, + .priority = rc->hub.priority, + .first_entry = rc->hub.first_time_s, + .last_entry = rc->hub.last_time_s, + .deleted = rc->hub.deleted, + }; + + if(likely(!rrd_flag_check(rc, RRD_FLAG_HIDDEN))) { + if (snapshot) { + if (!rc->hub.deleted) + contexts_snapshot_add_ctx_update(bundle, &message); + } + else + contexts_updated_add_ctx_update(bundle, &message); + } +#endif + + // store it to SQL + + if(rrd_flag_is_deleted(rc)) + rrdcontext_delete_from_sql_unsafe(rc); + + else if (ctx_store_context(&rc->rrdhost->host_uuid, &rc->hub) != 0) + error("RRDCONTEXT: failed to save context '%s' version %"PRIu64" to SQL.", rc->hub.id, rc->hub.version); +} + +static bool check_if_cloud_version_changed_unsafe(RRDCONTEXT *rc, bool sending __maybe_unused) { + bool id_changed = false, + title_changed = false, + units_changed = false, + family_changed = false, + chart_type_changed = false, + priority_changed = false, + first_time_changed = false, + last_time_changed = false, + deleted_changed = false; + + RRD_FLAGS flags = rrd_flags_get(rc); + + if(unlikely(string2str(rc->id) != rc->hub.id)) + id_changed = true; + + if(unlikely(string2str(rc->title) != rc->hub.title)) + title_changed = true; + + if(unlikely(string2str(rc->units) != rc->hub.units)) + units_changed = true; + + if(unlikely(string2str(rc->family) != rc->hub.family)) + family_changed = true; + + if(unlikely(rrdset_type_name(rc->chart_type) != rc->hub.chart_type)) + chart_type_changed = true; + + if(unlikely(rc->priority != rc->hub.priority)) + priority_changed = true; + + 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_s) != rc->hub.last_time_s)) + last_time_changed = true; + + if(unlikely(((flags & RRD_FLAG_DELETED) ? true : false) != rc->hub.deleted)) + deleted_changed = true; + + if(unlikely(id_changed || title_changed || units_changed || family_changed || chart_type_changed || priority_changed || first_time_changed || last_time_changed || deleted_changed)) { + + internal_error(LOG_TRANSITIONS, + "RRDCONTEXT: %s NEW VERSION '%s'%s of host '%s', version %"PRIu64", title '%s'%s, units '%s'%s, family '%s'%s, chart type '%s'%s, priority %u%s, first_time_t %ld%s, last_time_t %ld%s, deleted '%s'%s, (queued for %llu ms, expected %llu ms)", + sending?"SENDING":"QUEUE", + string2str(rc->id), id_changed ? " (CHANGED)" : "", + rrdhost_hostname(rc->rrdhost), + rc->version, + string2str(rc->title), title_changed ? " (CHANGED)" : "", + string2str(rc->units), units_changed ? " (CHANGED)" : "", + string2str(rc->family), family_changed ? " (CHANGED)" : "", + rrdset_type_name(rc->chart_type), chart_type_changed ? " (CHANGED)" : "", + rc->priority, priority_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 + ); + + return true; + } + + return false; +} + +static inline usec_t rrdcontext_calculate_queued_dispatch_time_ut(RRDCONTEXT *rc, usec_t now_ut) { + + if(likely(rc->queue.delay_calc_ut >= rc->queue.queued_ut)) + return rc->queue.scheduled_dispatch_ut; + + RRD_FLAGS flags = rc->queue.queued_flags; + + usec_t delay = LONG_MAX; + int i; + struct rrdcontext_reason *reason; + for(i = 0, reason = &rrdcontext_reasons[i]; reason->name ; reason = &rrdcontext_reasons[++i]) { + if(unlikely(flags & reason->flag)) { + if(reason->delay_ut < delay) + delay = reason->delay_ut; + } + } + + if(unlikely(delay == LONG_MAX)) { + internal_error(true, "RRDCONTEXT: '%s', cannot find minimum delay of flags %x", string2str(rc->id), (unsigned int)flags); + delay = 60 * USEC_PER_SEC; + } + + rc->queue.delay_calc_ut = now_ut; + usec_t dispatch_ut = rc->queue.scheduled_dispatch_ut = rc->queue.queued_ut + delay; + return dispatch_ut; +} + +static void rrdcontext_dequeue_from_hub_queue(RRDCONTEXT *rc) { + dictionary_del(rc->rrdhost->rrdctx.hub_queue, string2str(rc->id)); +} + +static void rrdcontext_dispatch_queued_contexts_to_hub(RRDHOST *host, usec_t now_ut) { + + // check if we have received a streaming command for this host + if(!rrdhost_flag_check(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS) || !aclk_connected || !host->rrdctx.hub_queue) + return; + + // check if there are queued items to send + if(!dictionary_entries(host->rrdctx.hub_queue)) + return; + + if(!host->node_id) + return; + + size_t messages_added = 0; + contexts_updated_t bundle = NULL; + + RRDCONTEXT *rc; + dfe_start_reentrant(host->rrdctx.hub_queue, rc) { + if(unlikely(!service_running(SERVICE_CONTEXT))) break; + + if(unlikely(messages_added >= MESSAGES_PER_BUNDLE_TO_SEND_TO_HUB_PER_HOST)) + break; + + worker_is_busy(WORKER_JOB_QUEUED); + usec_t dispatch_ut = rrdcontext_calculate_queued_dispatch_time_ut(rc, now_ut); + char *claim_id = get_agent_claimid(); + + if(unlikely(now_ut >= dispatch_ut) && claim_id) { + worker_is_busy(WORKER_JOB_CHECK); + + rrdcontext_lock(rc); + + if(check_if_cloud_version_changed_unsafe(rc, true)) { + worker_is_busy(WORKER_JOB_SEND); + +#ifdef ENABLE_ACLK + if(!bundle) { + // prepare the bundle to send the messages + char uuid[UUID_STR_LEN]; + uuid_unparse_lower(*host->node_id, uuid); + + bundle = contexts_updated_new(claim_id, uuid, 0, now_ut); + } +#endif + // update the hub data of the context, give a new version, pack the message + // and save an update to SQL + rrdcontext_message_send_unsafe(rc, false, bundle); + messages_added++; + + rc->queue.dispatches++; + rc->queue.dequeued_ut = now_ut; + } + else + rc->version = rc->hub.version; + + // remove it from the queue + worker_is_busy(WORKER_JOB_DEQUEUE); + rrdcontext_dequeue_from_hub_queue(rc); + + if(unlikely(rrdcontext_should_be_deleted(rc))) { + // this is a deleted context - delete it forever... + + worker_is_busy(WORKER_JOB_CLEANUP_DELETE); + + rrdcontext_dequeue_from_post_processing(rc); + rrdcontext_delete_from_sql_unsafe(rc); + + STRING *id = string_dup(rc->id); + rrdcontext_unlock(rc); + + // delete it from the master dictionary + if(!dictionary_del(host->rrdctx.contexts, string2str(rc->id))) + error("RRDCONTEXT: '%s' of host '%s' failed to be deleted from rrdcontext dictionary.", + string2str(id), rrdhost_hostname(host)); + + string_freez(id); + } + else + rrdcontext_unlock(rc); + } + freez(claim_id); + } + dfe_done(rc); + +#ifdef ENABLE_ACLK + if(service_running(SERVICE_CONTEXT) && bundle) { + // we have a bundle to send messages + + // update the version hash + contexts_updated_update_version_hash(bundle, rrdcontext_version_hash(host)); + + // send it + aclk_send_contexts_updated(bundle); + } + else if(bundle) + contexts_updated_delete(bundle); +#endif + +} + +// ---------------------------------------------------------------------------- +// worker thread + +static void rrdcontext_main_cleanup(void *ptr) { + struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr; + static_thread->enabled = NETDATA_MAIN_THREAD_EXITING; + + // custom code + worker_unregister(); + + static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; +} + +void *rrdcontext_main(void *ptr) { + netdata_thread_cleanup_push(rrdcontext_main_cleanup, ptr); + + worker_register("RRDCONTEXT"); + worker_register_job_name(WORKER_JOB_HOSTS, "hosts"); + worker_register_job_name(WORKER_JOB_CHECK, "dedup checks"); + worker_register_job_name(WORKER_JOB_SEND, "sent contexts"); + worker_register_job_name(WORKER_JOB_DEQUEUE, "deduplicated contexts"); + worker_register_job_name(WORKER_JOB_RETENTION, "metrics retention"); + worker_register_job_name(WORKER_JOB_QUEUED, "queued contexts"); + worker_register_job_name(WORKER_JOB_CLEANUP, "cleanups"); + worker_register_job_name(WORKER_JOB_CLEANUP_DELETE, "deletes"); + worker_register_job_name(WORKER_JOB_PP_METRIC, "check metrics"); + worker_register_job_name(WORKER_JOB_PP_INSTANCE, "check instances"); + worker_register_job_name(WORKER_JOB_PP_CONTEXT, "check contexts"); + + worker_register_job_custom_metric(WORKER_JOB_HUB_QUEUE_SIZE, "hub queue size", "contexts", WORKER_METRIC_ABSOLUTE); + worker_register_job_custom_metric(WORKER_JOB_PP_QUEUE_SIZE, "post processing queue size", "contexts", WORKER_METRIC_ABSOLUTE); + + heartbeat_t hb; + heartbeat_init(&hb); + usec_t step = RRDCONTEXT_WORKER_THREAD_HEARTBEAT_USEC; + + while (service_running(SERVICE_CONTEXT)) { + worker_is_idle(); + heartbeat_next(&hb, step); + + if(unlikely(!service_running(SERVICE_CONTEXT))) break; + + usec_t now_ut = now_realtime_usec(); + + if(rrdcontext_next_db_rotation_ut && now_ut > rrdcontext_next_db_rotation_ut) { + rrdcontext_recalculate_retention_all_hosts(); + rrdcontext_garbage_collect_for_all_hosts(); + rrdcontext_next_db_rotation_ut = 0; + } + + size_t hub_queued_contexts_for_all_hosts = 0; + size_t pp_queued_contexts_for_all_hosts = 0; + + RRDHOST *host; + dfe_start_reentrant(rrdhost_root_index, host) { + if(unlikely(!service_running(SERVICE_CONTEXT))) break; + + worker_is_busy(WORKER_JOB_HOSTS); + + if(host->rrdctx.pp_queue) { + pp_queued_contexts_for_all_hosts += dictionary_entries(host->rrdctx.pp_queue); + rrdcontext_post_process_queued_contexts(host); + dictionary_garbage_collect(host->rrdctx.pp_queue); + } + + if(host->rrdctx.hub_queue) { + hub_queued_contexts_for_all_hosts += dictionary_entries(host->rrdctx.hub_queue); + rrdcontext_dispatch_queued_contexts_to_hub(host, now_ut); + dictionary_garbage_collect(host->rrdctx.hub_queue); + } + + if (host->rrdctx.contexts) + dictionary_garbage_collect(host->rrdctx.contexts); + } + dfe_done(host); + + worker_set_metric(WORKER_JOB_HUB_QUEUE_SIZE, (NETDATA_DOUBLE)hub_queued_contexts_for_all_hosts); + worker_set_metric(WORKER_JOB_PP_QUEUE_SIZE, (NETDATA_DOUBLE)pp_queued_contexts_for_all_hosts); + } + + netdata_thread_cleanup_pop(1); + return NULL; +} diff --git a/database/engine/README.md b/database/engine/README.md index 664d4050..89001864 100644 --- a/database/engine/README.md +++ b/database/engine/README.md @@ -1,17 +1,9 @@ - - -# DBENGINE +# Database engine DBENGINE is the time-series database of Netdata. +![image](https://user-images.githubusercontent.com/2662304/233838474-d4f8f0b9-61dc-4409-a708-97d403cd153a.png) + ## Design ### Data Points @@ -118,53 +110,13 @@ Tiers are supported in Netdata Agents with version `netdata-1.35.0.138.nightly` 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] - mode = dbengine - - # per second data collection - update every = 1 - - # number of tiers used (1 to 5, 3 being default) - storage tiers = 3 - - # Tier 0, per second data - dbengine multihost disk space MB = 256 - - # 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: +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. -``` -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 -``` +Configuring how the number of tiers and the disk space allocated to each tier is how you can +[change how long netdata stores metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md). -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 +### 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. @@ -172,36 +124,9 @@ 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 requirements and retention -``` -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 -``` +See (change how long netdata stores metrics)[https://github.com/netdata/netdata/edit/master/docs/store/change-metrics-storage.md] #### Exceptions @@ -262,216 +187,6 @@ The time-ranges of the queries running control the amount of shared memory requi 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 --- - ---- - - -## Legacy configuration - -### v1.35.1 and prior - -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. - -To configure the database engine, look for the `page cache size MB` and `dbengine multihost disk space MB` settings in -the `[db]` section of your `netdata.conf`. - -```conf -[db] - dbengine page cache size MB = 32 - dbengine multihost disk space MB = 256 -``` - -### 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`). - -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 - -All existing metrics belonging to child nodes are automatically converted to legacy dbengine instances and the localhost -metrics are transferred to the multihost dbengine instance. - -All new child nodes are automatically transferred to the multihost dbengine instance and share its page cache and disk -space. If you want to migrate a child node from its legacy dbengine instance to the multihost dbengine instance, you -must delete the instance's directory, which is located in `/var/cache/netdata/MACHINE_GUID/dbengine`, after stopping the -Agent. - -##### Information - -For more information about setting `[db].mode` on your nodes, in addition to other streaming configurations, see -[streaming](https://github.com/netdata/netdata/blob/master/streaming/README.md). - -## Requirements & limitations - -### Memory - -Using database mode `dbengine` we can overcome most memory restrictions and store a dataset that is much larger than the -available memory. - -There are explicit memory requirements **per** DB engine **instance**: - -- The total page cache memory footprint will be an additional `#dimensions-being-collected x 4096 x 2` bytes over what - the user configured with `dbengine page cache size MB`. - - -- an additional `#pages-on-disk x 4096 x 0.03` bytes of RAM are allocated for metadata. - - - roughly speaking this is 3% of the uncompressed disk space taken by the DB files. - - - for very highly compressible data (compression ratio > 90%) this RAM overhead is comparable to the disk space - footprint. - -An important observation is that RAM usage depends on both the `page cache size` and the `dbengine multihost disk space` -options. - -You can use -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 - -There are explicit disk space requirements **per** DB engine **instance**: - -- The total disk space footprint will be the maximum between `#dimensions-being-collected x 4096 x 2` bytes or what the - user configured with `dbengine multihost disk space` or `dbengine disk space`. - -### File descriptor - -The Database Engine may keep a **significant** amount of files open per instance (e.g. per streaming child or parent -server). When configuring your system you should make sure there are at least 50 file descriptors available per -`dbengine` instance. - -Netdata allocates 25% of the available file descriptors to its Database Engine instances. This means that only 25% of -the file descriptors that are available to the Netdata service are accessible by dbengine instances. You should take -that into account when configuring your service or system-wide file descriptor limits. You can roughly estimate that the -Netdata service needs 2048 file descriptors for every 10 streaming child hosts when streaming is configured to use -`[db].mode = dbengine`. - -If for example one wants to allocate 65536 file descriptors to the Netdata service on a systemd system one needs to -override the Netdata service by running `sudo systemctl edit netdata` and creating a file with contents: - -```sh -[Service] -LimitNOFILE=65536 -``` - -For other types of services one can add the line: - -```sh -ulimit -n 65536 -``` - -at the beginning of the service file. Alternatively you can change the system-wide limits of the kernel by changing -`/etc/sysctl.conf`. For linux that would be: - -```conf -fs.file-max = 65536 -``` - -In FreeBSD and OS X you change the lines like this: - -```conf -kern.maxfilesperproc=65536 -kern.maxfiles=65536 -``` - -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 -and their corresponding journalfiles, e.g.: - -```sh -datafile-1-0000000001.ndf -journalfile-1-0000000001.njf -datafile-1-0000000002.ndf -journalfile-1-0000000002.njf -datafile-1-0000000003.ndf -journalfile-1-0000000003.njf -... -``` - -They are located under their host's cache directory in the directory `./dbengine` (e.g. for localhost the default -location is `/var/cache/netdata/dbengine/*`). The higher numbered filenames contain more recent metric data. The user -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](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 - -The DB engine stores chart metric values in 4096-byte pages in memory. Each chart dimension gets its own page to store -consecutive values generated from the data collectors. Those pages comprise the **Page Cache**. - -When those pages fill up, they are slowly compressed and flushed to disk. It can -take `4096 / 4 = 1024 seconds = 17 minutes`, for a chart dimension that is being collected every 1 second, to fill a -page. Pages can be cut short when we stop Netdata or the DB engine instance so as to not lose the data. When we query -the DB engine for data we trigger disk read I/O requests that fill the Page Cache with the requested pages and -potentially evict cold (not recently used) -pages. - -When the disk quota is exceeded the oldest values are removed from the DB engine at real time, by automatically deleting -the oldest datafile and journalfile pair. Any corresponding pages residing in the Page Cache will also be invalidated -and removed. The DB engine logic will try to maintain between 10 and 20 file pairs at any point in time. - -The Database Engine uses direct I/O to avoid polluting the OS filesystem caches and does not generate excessive I/O -traffic so as to create the minimum possible interference with other applications. - -## Evaluation - -We have evaluated the performance of the `dbengine` API that the netdata daemon uses internally. This is **not** the web -API of netdata. Our benchmarks ran on a **single** `dbengine` instance, multiple of which can be running in a Netdata -parent node. We used a server with an AMD Ryzen Threadripper 2950X 16-Core Processor and 2 disk drives, a Seagate -Constellation ES.3 2TB magnetic HDD and a SAMSUNG MZQLB960HAJR-00007 960GB NAND Flash SSD. - -For our workload, we defined 32 charts with 128 metrics each, giving us a total of 4096 metrics. We defined 1 worker -thread per chart (32 threads) that generates new data points with a data generation interval of 1 second. The time axis -of the time-series is emulated and accelerated so that the worker threads can generate as many data points as possible -without delays. - -We also defined 32 worker threads that perform queries on random metrics with semi-random time ranges. The starting time -of the query is randomly selected between the beginning of the time-series and the time of the latest data point. The -ending time is randomly selected between 1 second and 1 hour after the starting time. The pseudo-random numbers are -generated with a uniform distribution. - -The data are written to the database at the same time as they are read from it. This is a concurrent read/write mixed -workload with a duration of 60 seconds. The faster `dbengine` runs, the bigger the dataset size becomes since more data -points will be generated. We set a page cache size of 64MiB for the two disk-bound scenarios. This way, the dataset size -of the metric data is much bigger than the RAM that is being used for caching so as to trigger I/O requests most of the -time. In our final scenario, we set the page cache size to 16 GiB. That way, the dataset fits in the page cache so as to -avoid all disk bottlenecks. - -The reported numbers are the following: - -| device | page cache | dataset | reads/sec | writes/sec | -|:------:|:----------:|--------:|----------:|-----------:| -| HDD | 64 MiB | 4.1 GiB | 813K | 18.0M | -| SSD | 64 MiB | 9.8 GiB | 1.7M | 43.0M | -| N/A | 16 GiB | 6.8 GiB | 118.2M | 30.2M | - -where "reads/sec" is the number of metric data points being read from the database via its API per second and -"writes/sec" is the number of metric data points being written to the database per second. - -Notice that the HDD numbers are pretty high and not much slower than the SSD numbers. This is thanks to the database -engine design being optimized for rotating media. In the database engine disk I/O requests are: -- asynchronous to mask the high I/O latency of HDDs. -- mostly large to reduce the amount of HDD seeking time. -- mostly sequential to reduce the amount of HDD seeking time. -- compressed to reduce the amount of required throughput. -As a result, the HDD is not thousands of times slower than the SSD, which is typical for other workloads. -An interesting observation to make is that the CPU-bound run (16 GiB page cache) generates fewer data than the SSD run -(6.8 GiB vs 9.8 GiB). The reason is that the 32 reader threads in the SSD scenario are more frequently blocked by I/O, -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 index 4091684b..bc3ba6b6 100644 --- a/database/engine/cache.c +++ b/database/engine/cache.c @@ -1189,6 +1189,9 @@ premature_exit: } static PGC_PAGE *page_add(PGC *cache, PGC_ENTRY *entry, bool *added) { + internal_fatal(entry->start_time_s < 0 || entry->end_time_s < 0, + "DBENGINE CACHE: timestamps are negative"); + __atomic_add_fetch(&cache->stats.workers_add, 1, __ATOMIC_RELAXED); size_t partition = pgc_indexing_partition(cache, entry->metric_id); @@ -1199,6 +1202,12 @@ static PGC_PAGE *page_add(PGC *cache, PGC_ENTRY *entry, bool *added) { PGC_PAGE *page; size_t spins = 0; + if(unlikely(entry->start_time_s < 0)) + entry->start_time_s = 0; + + if(unlikely(entry->end_time_s < 0)) + entry->end_time_s = 0; + do { if(++spins > 1) __atomic_add_fetch(&cache->stats.insert_spins, 1, __ATOMIC_RELAXED); @@ -1755,7 +1764,7 @@ PGC *pgc_create(const char *name, 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_pages_per_inline_eviction = 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; @@ -1946,7 +1955,7 @@ 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) { if(page->update_every_s == 0) - page->update_every_s = update_every_s; + page->update_every_s = (uint32_t) update_every_s; return page->update_every_s; } @@ -2083,7 +2092,7 @@ void pgc_open_cache_to_journal_v2(PGC *cache, Word_t section, unsigned datafile_ 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"); + info("DBENGINE: migration to journal v2 for datafile %u is postponed, another jv2 indexer is already running for this section", datafile_fileno); pgc_ll_unlock(cache, &cache->hot); return; } diff --git a/database/engine/datafile.c b/database/engine/datafile.c index 286ae1e3..8c413d8d 100644 --- a/database/engine/datafile.c +++ b/database/engine/datafile.c @@ -34,17 +34,6 @@ static struct rrdengine_datafile *datafile_alloc_and_init(struct rrdengine_insta 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; @@ -390,8 +379,8 @@ static int scan_data_files_cmp(const void *a, const void *b) /* Returns number of datafiles that were loaded or < 0 on error */ static int scan_data_files(struct rrdengine_instance *ctx) { - int ret; - unsigned tier, no, matched_files, i,failed_to_load; + int ret, matched_files, failed_to_load, i; + unsigned tier, no; uv_fs_t req; uv_dirent_t dent; struct rrdengine_datafile **datafiles, *datafile; diff --git a/database/engine/datafile.h b/database/engine/datafile.h index 274add91..a08f3ae0 100644 --- a/database/engine/datafile.h +++ b/database/engine/datafile.h @@ -70,7 +70,6 @@ struct rrdengine_datafile { } extent_queries; }; -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); diff --git a/database/engine/journalfile.c b/database/engine/journalfile.c index de2b909c..9998ee54 100644 --- a/database/engine/journalfile.c +++ b/database/engine/journalfile.c @@ -40,7 +40,7 @@ static void update_metric_retention_and_granularity_by_uuid( .section = (Word_t) ctx, .first_time_s = first_time_s, .last_time_s = last_time_s, - .latest_update_every_s = update_every_s + .latest_update_every_s = (uint32_t) update_every_s }; uuid_copy(entry.uuid, *uuid); metric = mrg_metric_add_and_acquire(main_mrg, entry, &added); @@ -617,7 +617,7 @@ static void journalfile_restore_extent_metadata(struct rrdengine_instance *ctx, .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, + .latest_update_every_s = (uint32_t) vd.update_every_s, }; uuid_copy(entry.uuid, *temp_id); @@ -911,15 +911,10 @@ void journalfile_v2_populate_retention_to_mrg(struct rrdengine_instance *ctx, st 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); + ctx, &metric->uuid, start_time_s, end_time_s, (time_t) metric->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++; } @@ -1038,7 +1033,7 @@ 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)); + return memcmp(metric1->uuid, metric2->uuid, sizeof(uuid_t)); } @@ -1084,6 +1079,7 @@ void *journalfile_v2_write_metric_page(struct journal_v2_header *j2_header, void 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)); + metric->update_every_s = 0; return ++metric; } @@ -1128,7 +1124,7 @@ void *journalfile_v2_write_data_page(struct journal_v2_header *j2_header, void * 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->update_every_s = (uint32_t) page_info->update_every_s; data_page->page_length = (uint16_t) (ei ? ei->page_length : page_info->page_length); data_page->type = 0; @@ -1136,7 +1132,8 @@ void *journalfile_v2_write_data_page(struct journal_v2_header *j2_header, void * } // 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) +static void *journalfile_v2_write_descriptors(struct journal_v2_header *j2_header, void *data, struct jv2_metrics_info *metric_info, + struct journal_metric_list *current_metric) { Pvoid_t *PValue; @@ -1148,13 +1145,16 @@ void *journalfile_v2_write_descriptors(struct journal_v2_header *j2_header, void Word_t index_time = 0; bool first = true; struct jv2_page_info *page_info; + uint32_t update_every_s = 0; 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); + update_every_s = (uint32_t) page_info->update_every_s; if (NULL == data_page) break; } + current_metric->update_every_s = update_every_s; return data_page; } @@ -1291,6 +1291,7 @@ void journalfile_migrate_to_v2_callback(Word_t section, unsigned datafile_fileno // Calculate current UUID offset from start of file. We will store this in the data page header uint32_t uuid_offset = data - data_start; + struct journal_metric_list *current_metric = (void *) data; // Write the UUID we are processing data = (void *) journalfile_v2_write_metric_page(&j2_header, data, metric_info, pages_offset); if (unlikely(!data)) @@ -1308,7 +1309,7 @@ void journalfile_migrate_to_v2_callback(Word_t section, unsigned datafile_fileno uuid_offset); // Start writing descr @ time - void *page_trailer = journalfile_v2_write_descriptors(&j2_header, metric_page, metric_info); + void *page_trailer = journalfile_v2_write_descriptors(&j2_header, metric_page, metric_info, current_metric); if (unlikely(!page_trailer)) break; diff --git a/database/engine/journalfile.h b/database/engine/journalfile.h index 5fbcc90f..f6be6bcd 100644 --- a/database/engine/journalfile.h +++ b/database/engine/journalfile.h @@ -59,9 +59,9 @@ static inline uint64_t journalfile_current_size(struct rrdengine_journalfile *jo // Journal v2 structures -#define JOURVAL_V2_MAGIC (0x01221019) -#define JOURVAL_V2_REBUILD_MAGIC (0x00221019) -#define JOURVAL_V2_SKIP_MAGIC (0x02221019) +#define JOURVAL_V2_MAGIC (0x01230317) +#define JOURVAL_V2_REBUILD_MAGIC (0x00230317) +#define JOURVAL_V2_SKIP_MAGIC (0x02230317) struct journal_v2_block_trailer { union { @@ -93,13 +93,14 @@ struct journal_page_list { }; // UUID_LIST -// 32 bytes +// 36 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 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) + uint32_t update_every_s; // Last update every for this metric in this journal (last page collected) }; // 16 bytes diff --git a/database/engine/journalfile.ksy b/database/engine/journalfile.ksy deleted file mode 100644 index 858db83d..00000000 --- a/database/engine/journalfile.ksy +++ /dev/null @@ -1,144 +0,0 @@ -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/journalfile_v2.ksy.in b/database/engine/journalfile_v2.ksy.in new file mode 100644 index 00000000..6a656bc4 --- /dev/null +++ b/database/engine/journalfile_v2.ksy.in @@ -0,0 +1,150 @@ +meta: + id: journalfile_v2`'ifdef(`VIRT_MEMBERS',`_virtmemb') + endian: le + application: netdata + file-extension: njfv2 + license: GPL-3.0-or-later + +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: journal_v2_page_block + repeat: expr + repeat-expr: _root.journal_v2_header.metric_count + - id: padding + 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 +ifdef(`VIRT_MEMBERS', +` instances: + page_block: + type: journal_v2_page_block + io: _root._io + pos: page_offset +')dnl + 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 +ifdef(`VIRT_MEMBERS', +` instances: + extent: + io: _root._io + type: journal_v2_extent_list + pos: _root.journal_v2_header.extent_offset + (extent_idx * 16) +')dnl + 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 +ifdef(`VIRT_MEMBERS', +` instances: + trailer: + io: _root._io + type: journal_v2_block_trailer + pos: _root._io.size - 4 +')dnl + 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 diff --git a/database/engine/metric.c b/database/engine/metric.c index 9dc9d9eb..6b65df9b 100644 --- a/database/engine/metric.c +++ b/database/engine/metric.c @@ -105,7 +105,7 @@ static inline size_t uuid_partition(MRG *mrg __maybe_unused, uuid_t *uuid) { } 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); + bool has_retention = (metric->first_time_s > 0 || metric->latest_time_s_clean > 0 || metric->latest_time_s_hot > 0); if(has_retention && !(metric->flags & METRIC_FLAG_HAS_RETENTION)) { metric->flags |= METRIC_FLAG_HAS_RETENTION; @@ -210,8 +210,8 @@ static METRIC *metric_add_and_acquire(MRG *mrg, MRG_ENTRY *entry, bool *ret) { 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->first_time_s = MAX(0, entry->first_time_s); + metric->latest_time_s_clean = MAX(0, entry->last_time_s); metric->latest_time_s_hot = 0; metric->latest_update_every_s = entry->latest_update_every_s; metric->writer = 0; @@ -388,6 +388,11 @@ Word_t mrg_metric_section(MRG *mrg __maybe_unused, METRIC *metric) { } bool mrg_metric_set_first_time_s(MRG *mrg __maybe_unused, METRIC *metric, time_t first_time_s) { + internal_fatal(first_time_s < 0, "DBENGINE METRIC: timestamp is negative"); + + if(unlikely(first_time_s < 0)) + return false; + netdata_spinlock_lock(&metric->spinlock); metric->first_time_s = first_time_s; metric_has_retention_unsafe(mrg, metric); @@ -397,12 +402,25 @@ bool mrg_metric_set_first_time_s(MRG *mrg __maybe_unused, METRIC *metric, time_t } 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 < 0 || last_time_s < 0 || update_every_s < 0, + "DBENGINE METRIC: timestamp is negative"); 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"); + if(unlikely(first_time_s < 0)) + first_time_s = 0; + + if(unlikely(last_time_s < 0)) + last_time_s = 0; + + if(unlikely(update_every_s < 0)) + update_every_s = 0; + + if(unlikely(!first_time_s && !last_time_s && !update_every_s)) + return; + netdata_spinlock_lock(&metric->spinlock); if(unlikely(first_time_s && (!metric->first_time_s || first_time_s < metric->first_time_s))) @@ -412,16 +430,18 @@ void mrg_metric_expand_retention(MRG *mrg __maybe_unused, METRIC *metric, time_t metric->latest_time_s_clean = last_time_s; if(likely(update_every_s)) - metric->latest_update_every_s = update_every_s; + metric->latest_update_every_s = (uint32_t) update_every_s; } else if(unlikely(!metric->latest_update_every_s && update_every_s)) - metric->latest_update_every_s = update_every_s; + metric->latest_update_every_s = (uint32_t) 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) { + internal_fatal(first_time_s < 0, "DBENGINE METRIC: timestamp is negative"); + bool ret = false; netdata_spinlock_lock(&metric->spinlock); @@ -474,6 +494,11 @@ void mrg_metric_get_retention(MRG *mrg __maybe_unused, METRIC *metric, time_t *f } bool mrg_metric_set_clean_latest_time_s(MRG *mrg __maybe_unused, METRIC *metric, time_t latest_time_s) { + internal_fatal(latest_time_s < 0, "DBENGINE METRIC: timestamp is negative"); + + if(unlikely(latest_time_s < 0)) + return false; + netdata_spinlock_lock(&metric->spinlock); // internal_fatal(latest_time_s > max_acceptable_collected_time(), @@ -487,9 +512,6 @@ bool mrg_metric_set_clean_latest_time_s(MRG *mrg __maybe_unused, METRIC *metric, 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; @@ -517,7 +539,7 @@ bool mrg_metric_zero_disk_retention(MRG *mrg __maybe_unused, METRIC *metric) { 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) + if ((is_hot || is_dirty) && page_first_time_s > 0 && 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) @@ -548,18 +570,20 @@ bool mrg_metric_zero_disk_retention(MRG *mrg __maybe_unused, METRIC *metric) { } bool mrg_metric_set_hot_latest_time_s(MRG *mrg __maybe_unused, METRIC *metric, time_t latest_time_s) { + internal_fatal(latest_time_s < 0, "DBENGINE METRIC: timestamp is negative"); + // internal_fatal(latest_time_s > max_acceptable_collected_time(), // "DBENGINE METRIC: metric latest time is in the future"); + if(unlikely(latest_time_s < 0)) + return false; + 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; @@ -574,23 +598,27 @@ time_t mrg_metric_get_latest_time_s(MRG *mrg __maybe_unused, METRIC *metric) { } bool mrg_metric_set_update_every(MRG *mrg __maybe_unused, METRIC *metric, time_t update_every_s) { - if(!update_every_s) + internal_fatal(update_every_s < 0, "DBENGINE METRIC: timestamp is negative"); + + if(update_every_s <= 0) return false; netdata_spinlock_lock(&metric->spinlock); - metric->latest_update_every_s = update_every_s; + metric->latest_update_every_s = (uint32_t) 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) + internal_fatal(update_every_s < 0, "DBENGINE METRIC: timestamp is negative"); + + if(update_every_s <= 0) return false; netdata_spinlock_lock(&metric->spinlock); if(!metric->latest_update_every_s) - metric->latest_update_every_s = update_every_s; + metric->latest_update_every_s = (uint32_t) update_every_s; netdata_spinlock_unlock(&metric->spinlock); return true; diff --git a/database/engine/pagecache.c b/database/engine/pagecache.c index b4902d78..02d08a16 100644 --- a/database/engine/pagecache.c +++ b/database/engine/pagecache.c @@ -99,11 +99,6 @@ inline TIME_RANGE_COMPARE is_page_in_time_range(time_t page_first_time_s, time_t return PAGE_IS_IN_RANGE; } -static int journal_metric_uuid_compare(const void *key, const void *metric) -{ - return uuid_compare(*(uuid_t *) key, ((struct journal_metric_list *) metric)->uuid); -} - static inline struct page_details *pdc_find_page_for_time( Pcvoid_t PArray, time_t wanted_time_s, @@ -310,7 +305,7 @@ static size_t get_page_list_from_pgc(PGC *cache, METRIC *metric, struct rrdengin 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->update_every_s = (uint32_t) page_update_every_s; pd->page = (open_cache_mode) ? NULL : page; pd->status |= tags; @@ -581,7 +576,7 @@ static size_t get_page_list_from_journal_v2(struct rrdengine_instance *ctx, METR .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, + .update_every_s = (uint32_t) page_update_every_s, .data = datafile, .size = 0, .custom_data = (uint8_t *) &ei, @@ -635,7 +630,7 @@ void add_page_details_from_journal_v2(PGC_PAGE *page, void *JudyL_pptr) { 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->update_every_s = (uint32_t) 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; } @@ -774,7 +769,10 @@ inline void rrdeng_prep_wait(PDC *pdc) { } } -void rrdeng_prep_query(PDC *pdc) { +void rrdeng_prep_query(struct page_details_control *pdc, bool worker) { + if(worker) + worker_is_busy(UV_EVENT_DBENGINE_QUERY); + size_t pages_to_load = 0; pdc->page_list_JudyL = get_page_list(pdc->ctx, pdc->metric, pdc->start_time_s * USEC_PER_SEC, @@ -785,10 +783,10 @@ void rrdeng_prep_query(PDC *pdc) { 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); + if(likely(pdc->priority == STORAGE_PRIORITY_SYNCHRONOUS)) + pdc_route_synchronously(pdc->ctx, 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); } else @@ -797,6 +795,9 @@ void rrdeng_prep_query(PDC *pdc) { completion_mark_complete(&pdc->prep_completion); pdc_release_and_destroy_if_unreferenced(pdc, true, true); + + if(worker) + worker_is_idle(); } /** @@ -827,7 +828,11 @@ void pg_cache_preload(struct rrdeng_query_handle *handle) { 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); + + if(unlikely(handle->pdc->priority == STORAGE_PRIORITY_SYNCHRONOUS)) + rrdeng_prep_query(handle->pdc, false); + else + rrdeng_enq_cmd(handle->ctx, RRDENG_OPCODE_QUERY, handle->pdc, NULL, handle->priority, NULL, NULL); } else { completion_mark_complete(&handle->pdc->prep_completion); @@ -924,7 +929,8 @@ struct pgc_page *pg_cache_lookup_next( 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); + page_update_every_s = pgc_page_fix_update_every(page, last_update_every_s); + pd->update_every_s = (uint32_t) page_update_every_s; } size_t entries_by_size = page_entries_by_size(page_length, CTX_POINT_SIZE_BYTES(ctx)); @@ -1009,7 +1015,7 @@ void pgc_open_add_hot_page(Word_t section, Word_t metric_id, time_t start_time_s .metric_id = metric_id, .start_time_s = start_time_s, .end_time_s = end_time_s, - .update_every_s = update_every_s, + .update_every_s = (uint32_t) update_every_s, .size = 0, .data = datafile, .custom_data = (uint8_t *) &ext_io_data, @@ -1069,6 +1075,8 @@ void pgc_and_mrg_initialize(void) main_cache_size = target_cache_size - extent_cache_size; } + extent_cache_size += (size_t)(default_rrdeng_extent_cache_mb * 1024ULL * 1024ULL); + main_cache = pgc_create( "main_cache", main_cache_size, diff --git a/database/engine/pagecache.h b/database/engine/pagecache.h index 9ab7db07..5242db89 100644 --- a/database/engine/pagecache.h +++ b/database/engine/pagecache.h @@ -45,16 +45,14 @@ struct rrdeng_page_info { }; struct pg_alignment { - uint32_t page_position; uint32_t refcount; - uint16_t initial_slots; }; struct rrdeng_query_handle; struct page_details_control; void rrdeng_prep_wait(struct page_details_control *pdc); -void rrdeng_prep_query(struct page_details_control *pdc); +void rrdeng_prep_query(struct page_details_control *pdc, bool worker); 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); diff --git a/database/engine/pdc.c b/database/engine/pdc.c index 8b8e7195..42fb2f6d 100644 --- a/database/engine/pdc.c +++ b/database/engine/pdc.c @@ -692,8 +692,9 @@ VALIDATED_PAGE_DESCRIPTOR validate_page( 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 <= 0 || + vd.end_time_s <= 0 || + vd.update_every_s < 0 || (vd.start_time_s == vd.end_time_s && vd.entries > 1) || (vd.update_every_s == 0 && vd.entries > 1) ) @@ -835,7 +836,7 @@ static void epdl_extent_loading_error_log(struct rrdengine_instance *ctx, EPDL * uuid_unparse_lower(descr->uuid, uuid); used_descr = true; } - else if (epdl) { + else { struct page_details *pd = NULL; Word_t start = 0; @@ -855,7 +856,7 @@ static void epdl_extent_loading_error_log(struct rrdengine_instance *ctx, EPDL * } } - if(!used_epdl && !used_descr && epdl && epdl->pdc) { + if(!used_epdl && !used_descr && epdl->pdc) { start_time_s = epdl->pdc->start_time_s; end_time_s = epdl->pdc->end_time_s; } @@ -1059,7 +1060,7 @@ static bool epdl_populate_pages_from_extent_data( .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, + .update_every_s = (uint32_t) vd.update_every_s, .size = (size_t) ((page_data == DBENGINE_EMPTY_PAGE) ? 0 : vd.page_length), .data = page_data }; @@ -1150,6 +1151,9 @@ static inline void datafile_extent_read_free(void *buffer) { } void epdl_find_extent_and_populate_pages(struct rrdengine_instance *ctx, EPDL *epdl, bool worker) { + if(worker) + worker_is_busy(UV_EVENT_DBENGINE_EXTENT_CACHE_LOOKUP); + size_t *statistics_counter = NULL; PDC_PAGE_STATUS not_loaded_pages_tag = 0, loaded_pages_tag = 0; @@ -1172,9 +1176,6 @@ void epdl_find_extent_and_populate_pages(struct rrdengine_instance *ctx, EPDL *e goto cleanup; } - if(worker) - worker_is_busy(UV_EVENT_DBENGINE_EXTENT_CACHE_LOOKUP); - bool extent_found_in_cache = false; void *extent_compressed_data = NULL; diff --git a/database/engine/rrdengine.c b/database/engine/rrdengine.c index d64868f0..7811a5ea 100644 --- a/database/engine/rrdengine.c +++ b/database/engine/rrdengine.c @@ -16,6 +16,24 @@ unsigned rrdeng_pages_per_extent = MAX_PAGES_PER_EXTENT; #error Please increase WORKER_UTILIZATION_MAX_JOB_TYPES to at least (RRDENG_MAX_OPCODE + 2) #endif +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 inline struct rrdeng_cmd rrdeng_deq_cmd(bool from_worker); +static inline void worker_dispatch_extent_read(struct rrdeng_cmd cmd, bool from_worker); +static inline void worker_dispatch_query_prep(struct rrdeng_cmd cmd, bool from_worker); + struct rrdeng_main { uv_thread_t thread; uv_loop_t loop; @@ -45,7 +63,6 @@ struct rrdeng_main { struct { size_t dispatched; size_t executing; - size_t pending_cb; } atomics; } work_cmd; @@ -132,8 +149,22 @@ static void work_request_init(void) { ); } -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); +enum LIBUV_WORKERS_STATUS { + LIBUV_WORKERS_RELAXED, + LIBUV_WORKERS_STRESSED, + LIBUV_WORKERS_CRITICAL, +}; + +static inline enum LIBUV_WORKERS_STATUS work_request_full(void) { + size_t dispatched = __atomic_load_n(&rrdeng_main.work_cmd.atomics.dispatched, __ATOMIC_RELAXED); + + if(dispatched >= (size_t)(libuv_worker_threads)) + return LIBUV_WORKERS_CRITICAL; + + else if(dispatched >= (size_t)(libuv_worker_threads - RESERVED_LIBUV_WORKER_THREADS)) + return LIBUV_WORKERS_STRESSED; + + return LIBUV_WORKERS_RELAXED; } static inline void work_done(struct rrdeng_work *work_request) { @@ -147,12 +178,38 @@ static void work_standard_worker(uv_work_t *req) { 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(); + if(work_request->opcode == RRDENG_OPCODE_EXTENT_READ || work_request->opcode == RRDENG_OPCODE_QUERY) { + internal_fatal(work_request->after_work_cb != NULL, "DBENGINE: opcodes with a callback should not boosted"); + + while(1) { + struct rrdeng_cmd cmd = rrdeng_deq_cmd(true); + if (cmd.opcode == RRDENG_OPCODE_NOOP) + break; + + worker_is_busy(UV_EVENT_WORKER_INIT); + switch (cmd.opcode) { + case RRDENG_OPCODE_EXTENT_READ: + worker_dispatch_extent_read(cmd, true); + break; + + case RRDENG_OPCODE_QUERY: + worker_dispatch_query_prep(cmd, true); + break; + + default: + fatal("DBENGINE: Opcode should not be executed synchronously"); + break; + } + 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)); @@ -167,7 +224,6 @@ static void after_work_standard_callback(uv_work_t* req, int status) { 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(); } @@ -369,20 +425,6 @@ void wal_release(WAL *wal) { // ---------------------------------------------------------------------------- // 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), @@ -465,14 +507,33 @@ static inline bool rrdeng_cmd_has_waiting_opcodes_in_lower_priorities(STORAGE_PR return false; } -static inline struct rrdeng_cmd rrdeng_deq_cmd(void) { +#define opcode_empty (struct rrdeng_cmd) { \ + .ctx = NULL, \ + .opcode = RRDENG_OPCODE_NOOP, \ + .priority = STORAGE_PRIORITY_BEST_EFFORT, \ + .completion = NULL, \ + .data = NULL, \ +} + +static inline struct rrdeng_cmd rrdeng_deq_cmd(bool from_worker) { struct rrdeng_cmd *cmd = NULL; + enum LIBUV_WORKERS_STATUS status = work_request_full(); + + STORAGE_PRIORITY min_priority, max_priority; + min_priority = STORAGE_PRIORITY_INTERNAL_DBENGINE; + max_priority = (status != LIBUV_WORKERS_RELAXED) ? STORAGE_PRIORITY_INTERNAL_DBENGINE : STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE - 1; - STORAGE_PRIORITY max_priority = work_request_full() ? STORAGE_PRIORITY_INTERNAL_DBENGINE : STORAGE_PRIORITY_BEST_EFFORT; + if(from_worker) { + if(status == LIBUV_WORKERS_CRITICAL) + return opcode_empty; + + min_priority = STORAGE_PRIORITY_INTERNAL_QUERY_PREP; + max_priority = 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++) { + for(STORAGE_PRIORITY priority = min_priority; priority <= max_priority ; priority++) { cmd = rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[priority]; if(cmd) { @@ -508,13 +569,7 @@ static inline struct rrdeng_cmd rrdeng_deq_cmd(void) { aral_freez(rrdeng_main.cmd_queue.ar, cmd); } else - ret = (struct rrdeng_cmd) { - .ctx = NULL, - .opcode = RRDENG_OPCODE_NOOP, - .priority = STORAGE_PRIORITY_BEST_EFFORT, - .completion = NULL, - .data = NULL, - }; + ret = opcode_empty; return ret; } @@ -927,11 +982,6 @@ struct uuid_first_time_s { size_t df_index_oldest; }; -static int journal_metric_compare(const void *key, const void *metric) -{ - return uuid_compare(*(uuid_t *) key, ((struct journal_metric_list *) metric)->uuid); -} - struct rrdengine_datafile *datafile_release_and_acquire_next_for_retention(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile) { uv_rwlock_rdlock(&ctx->datafiles.rwlock); @@ -987,7 +1037,10 @@ void find_uuid_first_time( 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); + struct journal_metric_list *live_entry = + bsearch(uuid_original_entry->uuid,uuid_list,journal_metric_count, + sizeof(*uuid_list), journal_metric_uuid_compare); + if (!live_entry) { // Not found in this journal not_matching_bsearches++; @@ -1087,13 +1140,20 @@ void find_uuid_first_time( } 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); + + if (unlikely(!j2_header)) { + if (worker) + worker_is_idle(); + return; + } + + __atomic_add_fetch(&rrdeng_cache_efficiency_stats.metrics_retention_started, 1, __ATOMIC_RELAXED); + 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; @@ -1348,14 +1408,9 @@ static void *cache_evict_tp_worker(struct rrdengine_instance *ctx __maybe_unused return data; } -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) { - ; -} - 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); + rrdeng_prep_query(pdc, true); return data; } @@ -1435,21 +1490,28 @@ static void *journal_v2_indexing_tp_worker(struct rrdengine_instance *ctx __mayb 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)) { + if(journalfile_v2_data_available(datafile->journalfile)) { + // journal file v2 is already there for this datafile + datafile = datafile->next; + continue; + } 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) + if(!available) { + info("DBENGINE: journal file %u needs to be indexed, but it has writers working on it - skipping it for now", datafile->fileno); + datafile = datafile->next; 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++; } + 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))) @@ -1472,10 +1534,6 @@ static void after_do_cache_evict(struct rrdengine_instance *ctx __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); @@ -1604,6 +1662,26 @@ bool rrdeng_dbengine_spawn(struct rrdengine_instance *ctx __maybe_unused) { return true; } +static inline void worker_dispatch_extent_read(struct rrdeng_cmd cmd, bool from_worker) { + struct rrdengine_instance *ctx = cmd.ctx; + EPDL *epdl = cmd.data; + + if(from_worker) + epdl_find_extent_and_populate_pages(ctx, epdl, true); + else + work_dispatch(ctx, epdl, NULL, cmd.opcode, extent_read_tp_worker, NULL); +} + +static inline void worker_dispatch_query_prep(struct rrdeng_cmd cmd, bool from_worker) { + struct rrdengine_instance *ctx = cmd.ctx; + PDC *pdc = cmd.data; + + if(from_worker) + rrdeng_prep_query(pdc, true); + else + work_dispatch(ctx, pdc, NULL, cmd.opcode, query_prep_tp_worker, NULL); +} + void dbengine_event_loop(void* arg) { sanity_check(); uv_thread_set_name_np(pthread_self(), "DBENGINE"); @@ -1661,25 +1739,19 @@ void dbengine_event_loop(void* arg) { /* wait for commands */ do { worker_is_busy(RRDENG_OPCODE_MAX); - cmd = rrdeng_deq_cmd(); + cmd = rrdeng_deq_cmd(RRDENG_OPCODE_NOOP); opcode = cmd.opcode; worker_is_busy(opcode); switch (opcode) { - 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); + case RRDENG_OPCODE_EXTENT_READ: + worker_dispatch_extent_read(cmd, false); break; - } - 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); + case RRDENG_OPCODE_QUERY: + worker_dispatch_query_prep(cmd, false); break; - } case RRDENG_OPCODE_EXTENT_WRITE: { struct rrdengine_instance *ctx = cmd.ctx; diff --git a/database/engine/rrdengine.h b/database/engine/rrdengine.h index 49266681..69e41235 100644 --- a/database/engine/rrdengine.h +++ b/database/engine/rrdengine.h @@ -160,9 +160,7 @@ struct jv2_page_info { }; 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_1ST_METRIC_WRITER = (1 << 0), } RRDENG_COLLECT_HANDLE_OPTIONS; typedef enum __attribute__ ((__packed__)) { @@ -183,12 +181,17 @@ typedef enum __attribute__ ((__packed__)) { } RRDENG_COLLECT_PAGE_FLAGS; struct rrdeng_collect_handle { + struct storage_collect_handle common; // has to be first item + + RRDENG_COLLECT_PAGE_FLAGS page_flags; + RRDENG_COLLECT_HANDLE_OPTIONS options; + uint8_t type; + struct metric *metric; struct pgc_page *page; + void *data; + size_t data_size; 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; @@ -515,4 +518,8 @@ static inline time_t max_acceptable_collected_time(void) { void datafile_delete(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile, bool update_retention, bool worker); +static inline int journal_metric_uuid_compare(const void *key, const void *metric) { + return uuid_memcmp((uuid_t *)key, &(((struct journal_metric_list *) metric)->uuid)); +} + #endif /* NETDATA_RRDENGINE_H */ diff --git a/database/engine/rrdengineapi.c b/database/engine/rrdengineapi.c index 27497bbb..ddc306ed 100755 --- a/database/engine/rrdengineapi.c +++ b/database/engine/rrdengineapi.c @@ -35,16 +35,16 @@ __attribute__((constructor)) void initialize_multidb_ctx(void) { multidb_ctx[4] = &multidb_ctx_storage_tier4; } -int default_rrdeng_page_fetch_timeout = 3; -int default_rrdeng_page_fetch_retries = 3; int db_engine_journal_check = 0; int default_rrdeng_disk_quota_mb = 256; int default_multidb_disk_quota_mb = 256; #if defined(ENV32BIT) int default_rrdeng_page_cache_mb = 16; +int default_rrdeng_extent_cache_mb = 0; #else int default_rrdeng_page_cache_mb = 32; +int default_rrdeng_extent_cache_mb = 0; #endif // ---------------------------------------------------------------------------- @@ -163,7 +163,7 @@ STORAGE_METRIC_HANDLE *rrdeng_metric_get_or_create(RRDDIM *rd, STORAGE_INSTANCE } #ifdef NETDATA_INTERNAL_CHECKS - if(uuid_compare(rd->metric_uuid, *mrg_metric_uuid(main_mrg, metric)) != 0) { + if(uuid_memcmp(&rd->metric_uuid, mrg_metric_uuid(main_mrg, metric)) != 0) { char uuid1[UUID_STR_LEN + 1]; char uuid2[UUID_STR_LEN + 1]; @@ -255,8 +255,11 @@ STORAGE_COLLECT_HANDLE *rrdeng_store_metric_init(STORAGE_METRIC_HANDLE *db_metri struct rrdeng_collect_handle *handle; handle = callocz(1, sizeof(struct rrdeng_collect_handle)); + handle->common.backend = STORAGE_ENGINE_BACKEND_DBENGINE; handle->metric = metric; handle->page = NULL; + handle->data = NULL; + handle->data_size = 0; handle->page_position = 0; handle->page_entries_max = 0; handle->update_every_ut = (usec_t)update_every * USEC_PER_SEC; @@ -340,6 +343,8 @@ void rrdeng_store_metric_flush_current_page(STORAGE_COLLECT_HANDLE *collection_h handle->page_flags = 0; handle->page_position = 0; handle->page_entries_max = 0; + handle->data = NULL; + handle->data_size = 0; // important! // we should never zero page end time ut, because this will allow @@ -348,6 +353,8 @@ void rrdeng_store_metric_flush_current_page(STORAGE_COLLECT_HANDLE *collection_h // handle->page_start_time_ut; check_and_fix_mrg_update_every(handle); + + timing_step(TIMING_STEP_DBENGINE_FLUSH_PAGE); } static void rrdeng_store_metric_create_new_page(struct rrdeng_collect_handle *handle, @@ -365,7 +372,7 @@ static void rrdeng_store_metric_create_new_page(struct rrdeng_collect_handle *ha .end_time_s = point_in_time_s, .size = data_size, .data = data, - .update_every_s = update_every_s, + .update_every_s = (uint32_t) update_every_s, .hot = true }; @@ -414,62 +421,57 @@ static void rrdeng_store_metric_create_new_page(struct rrdeng_collect_handle *ha handle->page_flags |= RRDENG_PAGE_CREATED_IN_FUTURE; check_and_fix_mrg_update_every(handle); + + timing_step(TIMING_STEP_DBENGINE_CREATE_NEW_PAGE); } -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; +static size_t aligned_allocation_entries(size_t max_slots, size_t target_slot, time_t now_s) { + size_t slots = target_slot; + size_t pos = (now_s % max_slots); - if(handle->options & RRDENG_FIRST_PAGE_ALLOCATED) { - // any page except the first - size = tier_page_size[ctx->config.tier]; - } - else { - size_t final_slots = 0; + if(pos > slots) + slots += max_slots - pos; - // 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); + else if(pos < slots) + slots -= pos; - if(handle->alignment->initial_slots) { - final_slots = handle->alignment->initial_slots; - } - else { - max_slots -= 3; + else + slots = max_slots; - size_t smaller_slot = indexing_partition((Word_t)handle->alignment, max_slots); - final_slots = smaller_slot; + return slots; +} - time_t now_s = (time_t)(point_in_time_ut / USEC_PER_SEC); - size_t current_pos = (now_s % max_slots); +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); - if(current_pos > final_slots) - final_slots += max_slots - current_pos; + size_t max_size = tier_page_size[ctx->config.tier]; + size_t max_slots = max_size / CTX_POINT_SIZE_BYTES(ctx); - else if(current_pos < final_slots) - final_slots -= current_pos; + size_t slots = aligned_allocation_entries( + max_slots, + indexing_partition((Word_t) handle->alignment, max_slots), + (time_t) (point_in_time_ut / USEC_PER_SEC) + ); - if(final_slots < 3) { - final_slots += 3; - smaller_slot += 3; + if(slots < max_slots / 3) + slots = max_slots / 3; - if(smaller_slot >= max_slots) - smaller_slot -= max_slots; - } + if(slots < 3) + slots = 3; - max_slots += 3; - handle->alignment->initial_slots = smaller_slot + 3; + size_t size = slots * CTX_POINT_SIZE_BYTES(ctx); - 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"); - } + // internal_error(true, "PAGE ALLOC %zu bytes (%zu max)", size, max_size); - size = final_slots * CTX_POINT_SIZE_BYTES(ctx); - } + internal_fatal(slots < 3 || slots > max_slots, "ooops! wrong distribution of metrics across time"); + internal_fatal(size > tier_page_size[ctx->config.tier] || size < CTX_POINT_SIZE_BYTES(ctx) * 2, "ooops! wrong page size"); *data_size = size; - return dbengine_page_alloc(size); + void *d = dbengine_page_alloc(size); + + timing_step(TIMING_STEP_DBENGINE_PAGE_ALLOC); + + return d; } static void rrdeng_store_metric_append_point(STORAGE_COLLECT_HANDLE *collection_handle, @@ -484,75 +486,33 @@ static void rrdeng_store_metric_append_point(STORAGE_COLLECT_HANDLE *collection_ struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)collection_handle; struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric); - bool perfect_page_alignment = false; - void *data; - size_t data_size; + if(unlikely(!handle->data)) + handle->data = rrdeng_alloc_new_metric_data(handle, &handle->data_size, point_in_time_ut); - 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; + timing_step(TIMING_STEP_DBENGINE_CHECK_DATA); - 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); - } + if(likely(ctx->config.page_type == PAGE_METRICS)) { + storage_number *tier0_metric_data = handle->data; + tier0_metric_data[handle->page_position] = pack_storage_number(n, flags); + } + else if(likely(ctx->config.page_type == PAGE_TIER)) { + storage_number_tier1_t *tier12_metric_data = handle->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; + tier12_metric_data[handle->page_position] = number_tier1; } else - data = rrdeng_alloc_new_metric_data(handle, &data_size, point_in_time_ut); - - switch (ctx->config.page_type) { - case PAGE_METRICS: { - storage_number *tier0_metric_data = data; - tier0_metric_data[handle->page_position] = pack_storage_number(n, flags); - } - break; + fatal("DBENGINE: cannot store metric on unknown page type id %d", ctx->config.page_type); - 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; - 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", ctx->config.page_type); - logged = true; - } - } - break; - } + timing_step(TIMING_STEP_DBENGINE_PACK); if(unlikely(!handle->page)){ - rrdeng_store_metric_create_new_page(handle, ctx, point_in_time_ut, data, data_size); + rrdeng_store_metric_create_new_page(handle, ctx, point_in_time_ut, handle->data, handle->data_size); // handle->position is set to 1 already - - if (0 == handle->alignment->page_position) { - /* this is the leading dimension that defines chart alignment */ - perfect_page_alignment = true; - } } else { // update an existing page @@ -566,11 +526,12 @@ static void rrdeng_store_metric_append_point(STORAGE_COLLECT_HANDLE *collection_ } } - if (perfect_page_alignment) - handle->alignment->page_position = handle->page_position; + timing_step(TIMING_STEP_DBENGINE_PAGE_FIN); // update the metric information mrg_metric_set_hot_latest_time_s(main_mrg, handle->metric, (time_t) (point_in_time_ut / USEC_PER_SEC)); + + timing_step(TIMING_STEP_DBENGINE_MRG_UPDATE); } static void store_metric_next_error_log(struct rrdeng_collect_handle *handle, usec_t point_in_time_ut, const char *msg) { @@ -612,6 +573,8 @@ void rrdeng_store_metric_next(STORAGE_COLLECT_HANDLE *collection_handle, const uint16_t anomaly_count, const SN_FLAGS flags) { + timing_step(TIMING_STEP_RRDSET_STORE_METRIC); + struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)collection_handle; #ifdef NETDATA_INTERNAL_CHECKS @@ -619,59 +582,62 @@ void rrdeng_store_metric_next(STORAGE_COLLECT_HANDLE *collection_handle, handle->page_flags |= RRDENG_PAGE_FUTURE_POINT; #endif - if(likely(handle->page_end_time_ut + handle->update_every_ut == point_in_time_ut)) { + usec_t delta_ut = point_in_time_ut - handle->page_end_time_ut; + + if(likely(delta_ut == handle->update_every_ut)) { // happy path ; } + else if(unlikely(point_in_time_ut > handle->page_end_time_ut)) { + if(handle->page) { + 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 { + // loop to fill the gap + 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); + } + } + } + } + } 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; } - else if(unlikely(point_in_time_ut == handle->page_end_time_ut)) { + 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; } - 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 { - // loop to fill the gap - 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); - } - } - } - } + timing_step(TIMING_STEP_DBENGINE_FIRST_CHECK); rrdeng_store_metric_append_point(collection_handle, point_in_time_ut, @@ -776,10 +742,10 @@ void rrdeng_load_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, handle = rrdeng_query_handle_get(); register_query_handle(handle); - if(unlikely(priority < STORAGE_PRIORITY_HIGH)) + if (unlikely(priority < STORAGE_PRIORITY_HIGH)) priority = STORAGE_PRIORITY_HIGH; - else if(unlikely(priority > STORAGE_PRIORITY_BEST_EFFORT)) - priority = STORAGE_PRIORITY_BEST_EFFORT; + else if (unlikely(priority >= STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE)) + priority = STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE - 1; handle->ctx = ctx; handle->metric = metric; @@ -809,6 +775,7 @@ void rrdeng_load_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, rrddim_handle->start_time_s = handle->start_time_s; rrddim_handle->end_time_s = handle->end_time_s; rrddim_handle->priority = priority; + rrddim_handle->backend = STORAGE_ENGINE_BACKEND_DBENGINE; pg_cache_preload(handle); @@ -824,6 +791,7 @@ void rrdeng_load_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, rrddim_handle->start_time_s = handle->start_time_s; rrddim_handle->end_time_s = 0; rrddim_handle->priority = priority; + rrddim_handle->backend = STORAGE_ENGINE_BACKEND_DBENGINE; } } @@ -906,6 +874,7 @@ STORAGE_POINT rrdeng_load_metric_next(struct storage_engine_query_handle *rrddim // We need to get a new page if (!rrdeng_load_page_next(rrddim_handle, false)) { + 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; } diff --git a/database/engine/rrdengineapi.h b/database/engine/rrdengineapi.h index feb79b97..514954af 100644 --- a/database/engine/rrdengineapi.h +++ b/database/engine/rrdengineapi.h @@ -12,11 +12,8 @@ #define RRDENG_FD_BUDGET_PER_INSTANCE (50) -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 default_rrdeng_extent_cache_mb; extern int db_engine_journal_check; extern int default_rrdeng_disk_quota_mb; extern int default_multidb_disk_quota_mb; @@ -27,9 +24,6 @@ extern size_t tier_page_size[]; #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, - uuid_t *ret_uuid); - 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); diff --git a/database/engine/rrdenginelib.c b/database/engine/rrdenginelib.c index 7ec626c5..984a591e 100644 --- a/database/engine/rrdenginelib.c +++ b/database/engine/rrdenginelib.c @@ -1,72 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "rrdengine.h" -#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); -//} - int check_file_properties(uv_file file, uint64_t *file_size, size_t min_size) { int ret; diff --git a/database/ram/README.md b/database/ram/README.md index 73562f0f..56cb7275 100644 --- a/database/ram/README.md +++ b/database/ram/README.md @@ -2,6 +2,10 @@ title: "RAM database modes" description: "Netdata's RAM database modes." custom_edit_url: https://github.com/netdata/netdata/edit/master/database/ram/README.md +sidebar_label: "RAM database modes" +learn_status: "Published" +learn_topic_type: "References" +learn_rel_path: "Developers/Database" --> -# RAM modes \ No newline at end of file +# RAM database modes diff --git a/database/ram/rrddim_mem.c b/database/ram/rrddim_mem.c index 0f17d6cb..a417c5ae 100644 --- a/database/ram/rrddim_mem.c +++ b/database/ram/rrddim_mem.c @@ -143,6 +143,7 @@ STORAGE_COLLECT_HANDLE *rrddim_collect_init(STORAGE_METRIC_HANDLE *db_metric_han 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->common.backend = STORAGE_ENGINE_BACKEND_RRDDIM; ch->rd = rd; ch->db_metric_handle = db_metric_handle; @@ -204,7 +205,7 @@ static inline void rrddim_fill_the_gap(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 n, NETDATA_DOUBLE min_value __maybe_unused, NETDATA_DOUBLE max_value __maybe_unused, uint16_t count __maybe_unused, @@ -226,7 +227,7 @@ void rrddim_collect_store_metric(STORAGE_COLLECT_HANDLE *collection_handle, 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); + rd->db[mh->current_entry] = pack_storage_number(n, 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; @@ -340,6 +341,7 @@ void rrddim_query_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_e handle->start_time_s = start_time_s; handle->end_time_s = end_time_s; handle->priority = priority; + handle->backend = STORAGE_ENGINE_BACKEND_RRDDIM; struct mem_query_handle* h = mallocz(sizeof(struct mem_query_handle)); h->db_metric_handle = db_metric_handle; diff --git a/database/ram/rrddim_mem.h b/database/ram/rrddim_mem.h index 373a2bd7..a75814a0 100644 --- a/database/ram/rrddim_mem.h +++ b/database/ram/rrddim_mem.h @@ -6,6 +6,8 @@ #include "database/rrd.h" struct mem_collect_handle { + struct storage_collect_handle common; // has to be first item + STORAGE_METRIC_HANDLE *db_metric_handle; RRDDIM *rd; }; @@ -32,7 +34,7 @@ void rrddim_metrics_group_release(STORAGE_INSTANCE *db_instance, STORAGE_METRICS 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_ut, NETDATA_DOUBLE number, +void rrddim_collect_store_metric(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, diff --git a/database/rrd.h b/database/rrd.h index 42eeb165..0f67a3b7 100644 --- a/database/rrd.h +++ b/database/rrd.h @@ -30,9 +30,9 @@ typedef struct rrdhost_acquired RRDHOST_ACQUIRED; typedef struct rrdset_acquired RRDSET_ACQUIRED; typedef struct rrddim_acquired RRDDIM_ACQUIRED; -typedef struct ml_host ml_host_t; -typedef struct ml_chart ml_chart_t; -typedef struct ml_dimension ml_dimension_t; +typedef struct ml_host rrd_ml_host_t; +typedef struct ml_chart rrd_ml_chart_t; +typedef struct ml_dimension rrd_ml_dimension_t; typedef enum __attribute__ ((__packed__)) { QUERY_SOURCE_UNKNOWN = 0, @@ -54,6 +54,9 @@ typedef enum __attribute__ ((__packed__)) storage_priority { STORAGE_PRIORITY_LOW, STORAGE_PRIORITY_BEST_EFFORT, + // synchronous query, not to be dispatched to workers or queued + STORAGE_PRIORITY_SYNCHRONOUS, + STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE, } STORAGE_PRIORITY; @@ -106,30 +109,39 @@ RRD_MEMORY_MODE rrd_memory_mode_id(const char *name); typedef struct storage_query_handle STORAGE_QUERY_HANDLE; +typedef enum __attribute__ ((__packed__)) { + STORAGE_ENGINE_BACKEND_RRDDIM = 1, + STORAGE_ENGINE_BACKEND_DBENGINE = 2, +} STORAGE_ENGINE_BACKEND; + +#define is_valid_backend(backend) ((backend) >= STORAGE_ENGINE_BACKEND_RRDDIM && (backend) <= STORAGE_ENGINE_BACKEND_DBENGINE) + // 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; + STORAGE_ENGINE_BACKEND backend; + 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 +// ---------------------------------------------------------------------------- +// chart types - // 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 +typedef enum __attribute__ ((__packed__)) rrdset_type { + RRDSET_TYPE_LINE = 0, + RRDSET_TYPE_AREA = 1, + RRDSET_TYPE_STACKED = 2, +} RRDSET_TYPE; - size_t count; // the number of original points aggregated - size_t anomaly_count; // the number of original points found anomalous +#define RRDSET_TYPE_LINE_NAME "line" +#define RRDSET_TYPE_AREA_NAME "area" +#define RRDSET_TYPE_STACKED_NAME "stacked" - SN_FLAGS flags; // flags stored with the point -} STORAGE_POINT; +RRDSET_TYPE rrdset_type_id(const char *name); +const char *rrdset_type_name(RRDSET_TYPE chart_type); -#include "rrdcontext.h" +#include "contexts/rrdcontext.h" extern bool unittest_running; extern bool dbengine_enabled; @@ -158,38 +170,22 @@ extern time_t rrdset_free_obsolete_time_s; #if defined(ENV32BIT) #define MIN_LIBUV_WORKER_THREADS 8 -#define MAX_LIBUV_WORKER_THREADS 64 +#define MAX_LIBUV_WORKER_THREADS 128 #define RESERVED_LIBUV_WORKER_THREADS 3 #else #define MIN_LIBUV_WORKER_THREADS 16 -#define MAX_LIBUV_WORKER_THREADS 128 +#define MAX_LIBUV_WORKER_THREADS 1024 #define RESERVED_LIBUV_WORKER_THREADS 6 #endif extern int libuv_worker_threads; +extern bool ieee754_doubles; -#define RRD_ID_LENGTH_MAX 200 +#define RRD_ID_LENGTH_MAX 1000 typedef long long total_number; #define TOTAL_NUMBER_FORMAT "%lld" -// ---------------------------------------------------------------------------- -// chart types - -typedef enum __attribute__ ((__packed__)) rrdset_type { - RRDSET_TYPE_LINE = 0, - RRDSET_TYPE_AREA = 1, - RRDSET_TYPE_STACKED = 2, -} RRDSET_TYPE; - -#define RRDSET_TYPE_LINE_NAME "line" -#define RRDSET_TYPE_AREA_NAME "area" -#define RRDSET_TYPE_STACKED_NAME "stacked" - -RRDSET_TYPE rrdset_type_id(const char *name); -const char *rrdset_type_name(RRDSET_TYPE chart_type); - - // ---------------------------------------------------------------------------- // algorithms types @@ -279,7 +275,11 @@ void rrdlabels_destroy(DICTIONARY *labels_dict); void rrdlabels_add(DICTIONARY *dict, const char *name, const char *value, RRDLABEL_SRC ls); void rrdlabels_add_pair(DICTIONARY *dict, const char *string, RRDLABEL_SRC ls); void rrdlabels_get_value_to_buffer_or_null(DICTIONARY *labels, BUFFER *wb, const char *key, const char *quote, const char *null); -void rrdlabels_get_value_to_char_or_null(DICTIONARY *labels, char **value, const char *key); +void rrdlabels_value_to_buffer_array_item_or_null(DICTIONARY *labels, BUFFER *wb, const char *key); +void rrdlabels_get_value_strdup_or_null(DICTIONARY *labels, char **value, const char *key); +void rrdlabels_get_value_strcpyz(DICTIONARY *labels, char *dst, size_t dst_len, const char *key); +STRING *rrdlabels_get_value_string_dup(DICTIONARY *labels, const char *key); +STRING *rrdlabels_get_value_to_buffer_or_unset(DICTIONARY *labels, BUFFER *wb, const char *key, const char *unset); void rrdlabels_flush(DICTIONARY *labels_dict); void rrdlabels_unmark_all(DICTIONARY *labels); @@ -290,8 +290,10 @@ int rrdlabels_sorted_walkthrough_read(DICTIONARY *labels, int (*callback)(const void rrdlabels_log_to_buffer(DICTIONARY *labels, BUFFER *wb); bool rrdlabels_match_simple_pattern(DICTIONARY *labels, const char *simple_pattern_txt); -bool rrdlabels_match_simple_pattern_parsed(DICTIONARY *labels, SIMPLE_PATTERN *pattern, char equal); + +bool rrdlabels_match_simple_pattern_parsed(DICTIONARY *labels, SIMPLE_PATTERN *pattern, char equal, size_t *searches); int rrdlabels_to_buffer(DICTIONARY *labels, BUFFER *wb, const char *before_each, const char *equal, const char *quote, const char *between_them, bool (*filter_callback)(const char *name, const char *value, RRDLABEL_SRC ls, void *data), void *filter_data, void (*name_sanitizer)(char *dst, const char *src, size_t dst_size), void (*value_sanitizer)(char *dst, const char *src, size_t dst_size)); +void rrdlabels_to_buffer_json_members(DICTIONARY *labels, BUFFER *wb); void rrdlabels_migrate_to_these(DICTIONARY *dst, DICTIONARY *src); void rrdlabels_copy(DICTIONARY *dst, DICTIONARY *src); @@ -307,19 +309,20 @@ bool exporting_labels_filter_callback(const char *name, const char *value, RRDLA // ---------------------------------------------------------------------------- // engine-specific iterator state for dimension data collection -typedef struct storage_collect_handle STORAGE_COLLECT_HANDLE; +typedef struct storage_collect_handle { + STORAGE_ENGINE_BACKEND backend; +} STORAGE_COLLECT_HANDLE; // ---------------------------------------------------------------------------- // Storage tier data for every dimension struct rrddim_tier { STORAGE_POINT virtual_point; - size_t tier_grouping; + STORAGE_ENGINE_BACKEND backend; + uint32_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); @@ -354,7 +357,7 @@ struct rrddim { // ------------------------------------------------------------------------ // operational state members - ml_dimension_t *ml_dimension; // machine learning data about this dimension + rrd_ml_dimension_t *ml_dimension; // machine learning data about this dimension // ------------------------------------------------------------------------ // linking to siblings and parents @@ -417,82 +420,215 @@ size_t rrddim_memory_file_header_size(void); void rrddim_memory_file_save(RRDDIM *rd); -// ---------------------------------------------------------------------------- +// ------------------------------------------------------------------------ +// DATA COLLECTION STORAGE OPS -#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_s = 0; \ - (x).end_time_s = 0; \ - } while(0) - -#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_s = start_s; \ - (x).end_time_s = end_s; \ - } while(0) - -#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 } - -#define storage_point_is_unset(x) (!(x).count) -#define storage_point_is_gap(x) (!netdata_double_isnumber((x).sum)) +STORAGE_METRICS_GROUP *rrdeng_metrics_group_get(STORAGE_INSTANCE *db_instance, uuid_t *uuid); +STORAGE_METRICS_GROUP *rrddim_metrics_group_get(STORAGE_INSTANCE *db_instance, uuid_t *uuid); +static inline STORAGE_METRICS_GROUP *storage_engine_metrics_group_get(STORAGE_ENGINE_BACKEND backend __maybe_unused, STORAGE_INSTANCE *db_instance, uuid_t *uuid) { + internal_fatal(!is_valid_backend(backend), "STORAGE: invalid backend"); -// ------------------------------------------------------------------------ -// function pointers that handle data collection -struct storage_engine_collect_ops { - // an initialization function to run before starting collection - STORAGE_COLLECT_HANDLE *(*init)(STORAGE_METRIC_HANDLE *db_metric_handle, uint32_t update_every, STORAGE_METRICS_GROUP *smg); +#ifdef ENABLE_DBENGINE + if(likely(backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + return rrdeng_metrics_group_get(db_instance, uuid); +#endif + return rrddim_metrics_group_get(db_instance, uuid); +} + +void rrdeng_metrics_group_release(STORAGE_INSTANCE *db_instance, STORAGE_METRICS_GROUP *smg); +void rrddim_metrics_group_release(STORAGE_INSTANCE *db_instance, STORAGE_METRICS_GROUP *smg); +static inline void storage_engine_metrics_group_release(STORAGE_ENGINE_BACKEND backend __maybe_unused, STORAGE_INSTANCE *db_instance, STORAGE_METRICS_GROUP *smg) { + internal_fatal(!is_valid_backend(backend), "STORAGE: invalid backend"); + +#ifdef ENABLE_DBENGINE + if(likely(backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + rrdeng_metrics_group_release(db_instance, smg); + else +#endif + rrddim_metrics_group_release(db_instance, smg); +} - // run this to store each metric into the database - void (*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); +STORAGE_COLLECT_HANDLE *rrdeng_store_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, uint32_t update_every, STORAGE_METRICS_GROUP *smg); +STORAGE_COLLECT_HANDLE *rrddim_collect_init(STORAGE_METRIC_HANDLE *db_metric_handle, uint32_t update_every, STORAGE_METRICS_GROUP *smg); +static inline STORAGE_COLLECT_HANDLE *storage_metric_store_init(STORAGE_ENGINE_BACKEND backend __maybe_unused, STORAGE_METRIC_HANDLE *db_metric_handle, uint32_t update_every, STORAGE_METRICS_GROUP *smg) { + internal_fatal(!is_valid_backend(backend), "STORAGE: invalid backend"); - // run this to flush / reset the current data collection sequence - void (*flush)(STORAGE_COLLECT_HANDLE *collection_handle); +#ifdef ENABLE_DBENGINE + if(likely(backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + return rrdeng_store_metric_init(db_metric_handle, update_every, smg); +#endif + return rrddim_collect_init(db_metric_handle, update_every, smg); +} - // a finalization function to run after collection is over - // returns 1 if it's safe to delete the dimension - int (*finalize)(STORAGE_COLLECT_HANDLE *collection_handle); +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); - void (*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_ut, + NETDATA_DOUBLE n, NETDATA_DOUBLE min_value, NETDATA_DOUBLE max_value, + uint16_t count, uint16_t anomaly_count, SN_FLAGS flags); + +static inline void storage_engine_store_metric( + 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) { + internal_fatal(!is_valid_backend(collection_handle->backend), "STORAGE: invalid backend"); + +#ifdef ENABLE_DBENGINE + if(likely(collection_handle->backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + return rrdeng_store_metric_next(collection_handle, point_in_time_ut, + n, min_value, max_value, + count, anomaly_count, flags); +#endif + return rrddim_collect_store_metric(collection_handle, point_in_time_ut, + n, min_value, max_value, + count, anomaly_count, flags); +} + +void rrdeng_store_metric_flush_current_page(STORAGE_COLLECT_HANDLE *collection_handle); +void rrddim_store_metric_flush(STORAGE_COLLECT_HANDLE *collection_handle); +static inline void storage_engine_store_flush(STORAGE_COLLECT_HANDLE *collection_handle) { + if(unlikely(!collection_handle)) + return; + + internal_fatal(!is_valid_backend(collection_handle->backend), "STORAGE: invalid backend"); + +#ifdef ENABLE_DBENGINE + if(likely(collection_handle->backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + rrdeng_store_metric_flush_current_page(collection_handle); + else +#endif + rrddim_store_metric_flush(collection_handle); +} + +int rrdeng_store_metric_finalize(STORAGE_COLLECT_HANDLE *collection_handle); +int rrddim_collect_finalize(STORAGE_COLLECT_HANDLE *collection_handle); +// a finalization function to run after collection is over +// returns 1 if it's safe to delete the dimension +static inline int storage_engine_store_finalize(STORAGE_COLLECT_HANDLE *collection_handle) { + internal_fatal(!is_valid_backend(collection_handle->backend), "STORAGE: invalid backend"); + +#ifdef ENABLE_DBENGINE + if(likely(collection_handle->backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + return rrdeng_store_metric_finalize(collection_handle); +#endif + + return rrddim_collect_finalize(collection_handle); +} + +void rrdeng_store_metric_change_collection_frequency(STORAGE_COLLECT_HANDLE *collection_handle, int update_every); +void rrddim_store_metric_change_collection_frequency(STORAGE_COLLECT_HANDLE *collection_handle, int update_every); +static inline void storage_engine_store_change_collection_frequency(STORAGE_COLLECT_HANDLE *collection_handle, int update_every) { + internal_fatal(!is_valid_backend(collection_handle->backend), "STORAGE: invalid backend"); + +#ifdef ENABLE_DBENGINE + if(likely(collection_handle->backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + rrdeng_store_metric_change_collection_frequency(collection_handle, update_every); + else +#endif + rrddim_store_metric_change_collection_frequency(collection_handle, update_every); +} - STORAGE_METRICS_GROUP *(*metrics_group_get)(STORAGE_INSTANCE *db_instance, uuid_t *uuid); - void (*metrics_group_release)(STORAGE_INSTANCE *db_instance, STORAGE_METRICS_GROUP *sa); -}; // ---------------------------------------------------------------------------- +// STORAGE ENGINE QUERY OPS + +time_t rrdeng_metric_oldest_time(STORAGE_METRIC_HANDLE *db_metric_handle); +time_t rrddim_query_oldest_time_s(STORAGE_METRIC_HANDLE *db_metric_handle); +static inline time_t storage_engine_oldest_time_s(STORAGE_ENGINE_BACKEND backend __maybe_unused, STORAGE_METRIC_HANDLE *db_metric_handle) { + internal_fatal(!is_valid_backend(backend), "STORAGE: invalid backend"); + +#ifdef ENABLE_DBENGINE + if(likely(backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + return rrdeng_metric_oldest_time(db_metric_handle); +#endif + return rrddim_query_oldest_time_s(db_metric_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_s, time_t end_time_s, STORAGE_PRIORITY priority); +time_t rrdeng_metric_latest_time(STORAGE_METRIC_HANDLE *db_metric_handle); +time_t rrddim_query_latest_time_s(STORAGE_METRIC_HANDLE *db_metric_handle); +static inline time_t storage_engine_latest_time_s(STORAGE_ENGINE_BACKEND backend __maybe_unused, STORAGE_METRIC_HANDLE *db_metric_handle) { + internal_fatal(!is_valid_backend(backend), "STORAGE: invalid backend"); - // run this to load each metric number from the database - STORAGE_POINT (*next_metric)(struct storage_engine_query_handle *handle); +#ifdef ENABLE_DBENGINE + if(likely(backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + return rrdeng_metric_latest_time(db_metric_handle); +#endif + return rrddim_query_latest_time_s(db_metric_handle); +} - // run this to test if the series of next_metric() database queries is finished - int (*is_finished)(struct storage_engine_query_handle *handle); +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); - // run this after finishing a series of load_metric() database queries - void (*finalize)(struct storage_engine_query_handle *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); - // get the timestamp of the last entry of this metric - time_t (*latest_time_s)(STORAGE_METRIC_HANDLE *db_metric_handle); +static inline void storage_engine_query_init( + STORAGE_ENGINE_BACKEND backend __maybe_unused, + 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) { + internal_fatal(!is_valid_backend(backend), "STORAGE: invalid backend"); - // get the timestamp of the first entry of this metric - time_t (*oldest_time_s)(STORAGE_METRIC_HANDLE *db_metric_handle); +#ifdef ENABLE_DBENGINE + if(likely(backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + rrdeng_load_metric_init(db_metric_handle, handle, start_time_s, end_time_s, priority); + else +#endif + rrddim_query_init(db_metric_handle, handle, start_time_s, end_time_s, priority); +} - // 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); -}; +STORAGE_POINT rrdeng_load_metric_next(struct storage_engine_query_handle *rrddim_handle); +STORAGE_POINT rrddim_query_next_metric(struct storage_engine_query_handle *handle); +static inline STORAGE_POINT storage_engine_query_next_metric(struct storage_engine_query_handle *handle) { + internal_fatal(!is_valid_backend(handle->backend), "STORAGE: invalid backend"); -typedef struct storage_engine STORAGE_ENGINE; +#ifdef ENABLE_DBENGINE + if(likely(handle->backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + return rrdeng_load_metric_next(handle); +#endif + return rrddim_query_next_metric(handle); +} + +int rrdeng_load_metric_is_finished(struct storage_engine_query_handle *rrddim_handle); +int rrddim_query_is_finished(struct storage_engine_query_handle *handle); +static inline int storage_engine_query_is_finished(struct storage_engine_query_handle *handle) { + internal_fatal(!is_valid_backend(handle->backend), "STORAGE: invalid backend"); + +#ifdef ENABLE_DBENGINE + if(likely(handle->backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + return rrdeng_load_metric_is_finished(handle); +#endif + return rrddim_query_is_finished(handle); +} + +void rrdeng_load_metric_finalize(struct storage_engine_query_handle *rrddim_handle); +void rrddim_query_finalize(struct storage_engine_query_handle *handle); +static inline void storage_engine_query_finalize(struct storage_engine_query_handle *handle) { + internal_fatal(!is_valid_backend(handle->backend), "STORAGE: invalid backend"); + +#ifdef ENABLE_DBENGINE + if(likely(handle->backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + rrdeng_load_metric_finalize(handle); + else +#endif + rrddim_query_finalize(handle); +} + +time_t rrdeng_load_align_to_optimal_before(struct storage_engine_query_handle *rrddim_handle); +time_t rrddim_query_align_to_optimal_before(struct storage_engine_query_handle *rrddim_handle); +static inline time_t storage_engine_align_to_optimal_before(struct storage_engine_query_handle *handle) { + internal_fatal(!is_valid_backend(handle->backend), "STORAGE: invalid backend"); + +#ifdef ENABLE_DBENGINE + if(likely(handle->backend == STORAGE_ENGINE_BACKEND_DBENGINE)) + return rrdeng_load_align_to_optimal_before(handle); +#endif + return rrddim_query_align_to_optimal_before(handle); +} // ------------------------------------------------------------------------ // function pointers for all APIs provided by a storage engine @@ -503,17 +639,14 @@ typedef struct storage_engine_api { 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; - struct storage_engine_query_ops query_ops; } STORAGE_ENGINE_API; -struct storage_engine { +typedef struct storage_engine { + STORAGE_ENGINE_BACKEND backend; RRD_MEMORY_MODE id; const char* name; STORAGE_ENGINE_API api; -}; +} STORAGE_ENGINE; STORAGE_ENGINE* storage_engine_get(RRD_MEMORY_MODE mmode); STORAGE_ENGINE* storage_engine_find(const char* name); @@ -617,7 +750,7 @@ struct rrdset { DICTIONARY *rrddimvar_root_index; // dimension variables // we use this dictionary to manage their allocation - ml_chart_t *ml_chart; + rrd_ml_chart_t *ml_chart; // ------------------------------------------------------------------------ // operational state members @@ -702,6 +835,13 @@ struct rrdset { RRDCALC *base; // double linked list of RRDCALC related to this RRDSET } alerts; + struct { + size_t pos; + size_t size; + size_t used; + RRDDIM_ACQUIRED **rda; + } pluginsd; + #ifdef NETDATA_LOG_REPLICATION_REQUESTS struct { bool log_next_data_collection; @@ -757,35 +897,41 @@ bool rrdset_memory_load_or_create_map_save(RRDSET *st_on_file, RRD_MEMORY_MODE m // and may lead to missing information. typedef enum __attribute__ ((__packed__)) rrdhost_flags { + + // Careful not to overlap with rrdhost_options to avoid bugs if + // rrdhost_flags_xxx is used instead of rrdhost_option_xxx or vice-versa // 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 - RRDHOST_FLAG_PENDING_OBSOLETE_CHARTS = (1 << 12), // the host has pending chart obsoletions - RRDHOST_FLAG_PENDING_OBSOLETE_DIMENSIONS = (1 << 13), // the host has pending dimension obsoletions + RRDHOST_FLAG_ORPHAN = (1 << 8), // this host is orphan (not receiving data) + RRDHOST_FLAG_ARCHIVED = (1 << 9), // The host is archived, no collected charts yet + RRDHOST_FLAG_PENDING_OBSOLETE_CHARTS = (1 << 10), // the host has pending chart obsoletions + RRDHOST_FLAG_PENDING_OBSOLETE_DIMENSIONS = (1 << 11), // the host has pending dimension obsoletions // Streaming sender - RRDHOST_FLAG_RRDPUSH_SENDER_INITIALIZED = (1 << 14), // the host has initialized rrdpush structures - RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN = (1 << 15), // When set, the sender thread is running - 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_INITIALIZED = (1 << 12), // the host has initialized rrdpush structures + RRDHOST_FLAG_RRDPUSH_SENDER_SPAWN = (1 << 13), // When set, the sender thread is running + RRDHOST_FLAG_RRDPUSH_SENDER_CONNECTED = (1 << 14), // When set, the host is connected to a parent + RRDHOST_FLAG_RRDPUSH_SENDER_READY_4_METRICS = (1 << 15), // when set, rrdset_done() should push metrics to parent + RRDHOST_FLAG_RRDPUSH_SENDER_LOGGED_STATUS = (1 << 16), // when set, we have logged the status of metrics streaming // Health - RRDHOST_FLAG_PENDING_HEALTH_INITIALIZATION = (1 << 20), // contains charts and dims with uninitialized variables - RRDHOST_FLAG_INITIALIZED_HEALTH = (1 << 21), // the host has initialized health structures + RRDHOST_FLAG_PENDING_HEALTH_INITIALIZATION = (1 << 17), // contains charts and dims with uninitialized variables + RRDHOST_FLAG_INITIALIZED_HEALTH = (1 << 18), // the host has initialized health structures // Exporting - RRDHOST_FLAG_EXPORTING_SEND = (1 << 22), // send it to external databases - RRDHOST_FLAG_EXPORTING_DONT_SEND = (1 << 23), // don't send it to external databases + RRDHOST_FLAG_EXPORTING_SEND = (1 << 19), // send it to external databases + RRDHOST_FLAG_EXPORTING_DONT_SEND = (1 << 20), // don't send it to external databases // ACLK - RRDHOST_FLAG_ACLK_STREAM_CONTEXTS = (1 << 24), // when set, we should send ACLK stream context updates + RRDHOST_FLAG_ACLK_STREAM_CONTEXTS = (1 << 21), // when set, we should send ACLK stream context updates + RRDHOST_FLAG_ACLK_STREAM_ALERTS = (1 << 22), // set when the receiver part is disconnected // 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_METADATA_UPDATE = (1 << 23), // metadata needs to be stored in the database + RRDHOST_FLAG_METADATA_LABELS = (1 << 24), // metadata needs to be stored in the database + RRDHOST_FLAG_METADATA_INFO = (1 << 25), // metadata needs to be stored in the database + RRDHOST_FLAG_PENDING_CONTEXT_LOAD = (1 << 26), // metadata needs to be stored in the database + RRDHOST_FLAG_CONTEXT_LOAD_IN_PROGRESS = (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 << 29), // set when the receiver part is disconnected } RRDHOST_FLAGS; @@ -954,6 +1100,8 @@ struct rrdhost_system_info { int mc_version; }; +struct rrdhost_system_info *rrdhost_labels_to_system_info(DICTIONARY *labels); + struct rrdhost { char machine_guid[GUID_LEN + 1]; // the unique ID of this host @@ -982,13 +1130,12 @@ struct rrdhost { // the actual per tier is at .db[tier].mode char *cache_dir; // the directory to save RRD cache files - char *varlib_dir; // the directory to save health log struct { RRD_MEMORY_MODE mode; // the db mode for this tier STORAGE_ENGINE *eng; // the storage engine API for this tier STORAGE_INSTANCE *instance; // the db instance for this tier - size_t tier_grouping; // tier 0 iterations aggregated on this tier + uint32_t tier_grouping; // tier 0 iterations aggregated on this tier } db[RRD_STORAGE_TIERS]; struct rrdhost_system_info *system_info; // information collected from the host environment @@ -1011,7 +1158,7 @@ struct rrdhost { struct sender_state *sender; netdata_thread_t rrdpush_sender_thread; // the sender thread size_t rrdpush_sender_replicating_charts; // the number of charts currently being replicated to a parent - void *dbsync_worker; + void *aclk_sync_host_config; // ------------------------------------------------------------------------ // streaming of data from remote hosts - rrdpush receiver @@ -1042,6 +1189,7 @@ struct rrdhost { uint32_t health_last_processed_id; // the last processed health id from the log uint32_t health_max_unique_id; // the max alarm log unique id given for the host uint32_t health_max_alarm_id; // the max alarm id given for the host + size_t health_transitions; // the number of times an alert changed state // ------------------------------------------------------------------------ // locks @@ -1050,7 +1198,7 @@ struct rrdhost { // ------------------------------------------------------------------------ // ML handle - ml_host_t *ml_host; + rrd_ml_host_t *ml_host; // ------------------------------------------------------------------------ // Support for host-level labels @@ -1070,9 +1218,11 @@ struct rrdhost { DICTIONARY *rrdvars; // the host's chart variables index // this includes custom host variables - RRDCONTEXTS *rrdctx_hub_queue; - RRDCONTEXTS *rrdctx_post_processing_queue; - RRDCONTEXTS *rrdctx; + struct { + DICTIONARY *contexts; + DICTIONARY *hub_queue; + DICTIONARY *pp_queue; + } rrdctx; uuid_t host_uuid; // Global GUID for this host uuid_t *node_id; // Cloud node_id @@ -1110,6 +1260,10 @@ extern RRDHOST *localhost; extern DICTIONARY *rrdhost_root_index; size_t rrdhost_hosts_available(void); +RRDHOST_ACQUIRED *rrdhost_find_and_acquire(const char *machine_guid); +RRDHOST *rrdhost_acquired_to_rrdhost(RRDHOST_ACQUIRED *rha); +void rrdhost_acquired_release(RRDHOST_ACQUIRED *rha); + // ---------------------------------------------------------------------------- #define rrdhost_foreach_read(var) \ @@ -1145,6 +1299,7 @@ int rrd_init(char *hostname, struct rrdhost_system_info *system_info, bool unitt RRDHOST *rrdhost_find_by_hostname(const char *hostname); RRDHOST *rrdhost_find_by_guid(const char *guid); +RRDHOST *find_host_by_node_id(char *node_id); RRDHOST *rrdhost_find_or_create( const char *hostname @@ -1217,6 +1372,11 @@ void rrdset_update_heterogeneous_flag(RRDSET *st); time_t rrdset_set_update_every_s(RRDSET *st, time_t update_every_s); RRDSET *rrdset_find(RRDHOST *host, const char *id); + +RRDSET_ACQUIRED *rrdset_find_and_acquire(RRDHOST *host, const char *id); +RRDSET *rrdset_acquired_to_rrdset(RRDSET_ACQUIRED *rsa); +void rrdset_acquired_release(RRDSET_ACQUIRED *rsa); + #define rrdset_find_localhost(id) rrdset_find(localhost, id) /* This will not return charts that are archived */ static inline RRDSET *rrdset_find_active_localhost(const char *id) @@ -1339,13 +1499,13 @@ void rrdset_delete_files(RRDSET *st); void rrdset_save(RRDSET *st); void rrdset_free(RRDSET *st); +void rrddim_free(RRDSET *st, RRDDIM *rd); + #ifdef NETDATA_RRD_INTERNALS 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); diff --git a/database/rrdcalc.c b/database/rrdcalc.c index 76263582..3ee8719c 100644 --- a/database/rrdcalc.c +++ b/database/rrdcalc.c @@ -95,12 +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 }; + char label_val[RRDCALC_VAR_MAX + RRDCALC_VAR_LABEL_LEN + 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, label_val); + rrdlabels_get_value_strdup_or_null(rc->rrdset->rrdlabels, &lbl_value, label_val); if (lbl_value) { char *buf = find_and_replace(temp, var, lbl_value, m); freez(temp); @@ -245,6 +245,8 @@ static void rrdcalc_link_to_rrdset(RRDSET *st, RRDCALC *rc) { if(!rc->units) rc->units = string_dup(st->units); + rrdvar_store_for_chart(host, st); + rrdcalc_update_info_using_rrdset_labels(rc); time_t now = now_realtime_sec(); @@ -357,13 +359,14 @@ static inline bool rrdcalc_check_if_it_matches_rrdset(RRDCALC *rc, RRDSET *st) { && (rc->chart != st->name)) return false; - if (rc->module_pattern && !simple_pattern_matches(rc->module_pattern, rrdset_module_name(st))) + if (rc->module_pattern && !simple_pattern_matches_string(rc->module_pattern, st->module_name)) return false; - if (rc->plugin_pattern && !simple_pattern_matches(rc->plugin_pattern, rrdset_plugin_name(st))) + if (rc->plugin_pattern && !simple_pattern_matches_string(rc->plugin_pattern, st->module_name)) return false; - if (st->rrdhost->rrdlabels && rc->host_labels_pattern && !rrdlabels_match_simple_pattern_parsed(st->rrdhost->rrdlabels, rc->host_labels_pattern, '=')) + if (st->rrdhost->rrdlabels && rc->host_labels_pattern && !rrdlabels_match_simple_pattern_parsed( + st->rrdhost->rrdlabels, rc->host_labels_pattern, '=', NULL)) return false; return true; @@ -739,7 +742,7 @@ void rrdcalc_delete_alerts_not_matching_host_labels_from_this_host(RRDHOST *host if (!rc->host_labels) continue; - if(!rrdlabels_match_simple_pattern_parsed(host->rrdlabels, rc->host_labels_pattern, '=')) { + if(!rrdlabels_match_simple_pattern_parsed(host->rrdlabels, rc->host_labels_pattern, '=', NULL)) { 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), @@ -752,18 +755,15 @@ void rrdcalc_delete_alerts_not_matching_host_labels_from_this_host(RRDHOST *host } void rrdcalc_delete_alerts_not_matching_host_labels_from_all_hosts() { - rrd_rdlock(); - RRDHOST *host; - rrdhost_foreach_read(host) { + dfe_start_reentrant(rrdhost_root_index, host) { if (unlikely(!host->health.health_enabled)) continue; if (host->rrdlabels) rrdcalc_delete_alerts_not_matching_host_labels_from_this_host(host); } - - rrd_unlock(); + dfe_done(host); } void rrdcalc_unlink_all_rrdset_alerts(RRDSET *st) { diff --git a/database/rrdcalc.h b/database/rrdcalc.h index 08d8beee..c6d6fd4e 100644 --- a/database/rrdcalc.h +++ b/database/rrdcalc.h @@ -31,7 +31,7 @@ typedef enum { typedef enum { // This list uses several other options from RRDR_OPTIONS for db lookups. // To add an item here, you need to reserve a bit in RRDR_OPTIONS. - RRDCALC_OPTION_NO_CLEAR_NOTIFICATION = 0x80000000, + RRDCALC_OPTION_NO_CLEAR_NOTIFICATION = RRDR_OPTION_HEALTH_RSRVD1, } RRDCALC_OPTIONS; #define RRDCALC_ALL_OPTIONS_EXCLUDING_THE_RRDR_ONES (RRDCALC_OPTION_NO_CLEAR_NOTIFICATION) @@ -77,7 +77,7 @@ struct rrdcalc { STRING *dimensions; // the chart dimensions STRING *foreach_dimension; // the group of dimensions that the `foreach` will be applied. SIMPLE_PATTERN *foreach_dimension_pattern; // used if and only if there is a simple pattern for the chart. - RRDR_GROUPING group; // grouping method: average, max, etc. + RRDR_TIME_GROUPING group; // grouping method: average, max, etc. int before; // ending point in time-series int after; // starting point in time-series RRDCALC_OPTIONS options; // configuration options diff --git a/database/rrdcalctemplate.c b/database/rrdcalctemplate.c index 4d7352b2..4dacb6c7 100644 --- a/database/rrdcalctemplate.c +++ b/database/rrdcalctemplate.c @@ -34,26 +34,29 @@ bool rrdcalctemplate_check_rrdset_conditions(RRDCALCTEMPLATE *rt, RRDSET *st, RR if(rt->foreach_dimension_pattern && !rrdset_number_of_dimensions(st)) return false; - if (rt->charts_pattern && !simple_pattern_matches(rt->charts_pattern, rrdset_name(st)) && !simple_pattern_matches(rt->charts_pattern, rrdset_id(st))) + if (rt->charts_pattern && !simple_pattern_matches_string(rt->charts_pattern, st->name) && !simple_pattern_matches_string(rt->charts_pattern, st->id)) return false; - if (rt->family_pattern && !simple_pattern_matches(rt->family_pattern, rrdset_family(st))) + if (rt->family_pattern && !simple_pattern_matches_string(rt->family_pattern, st->family)) return false; - if (rt->module_pattern && !simple_pattern_matches(rt->module_pattern, rrdset_module_name(st))) + if (rt->module_pattern && !simple_pattern_matches_string(rt->module_pattern, st->module_name)) return false; - if (rt->plugin_pattern && !simple_pattern_matches(rt->plugin_pattern, rrdset_plugin_name(st))) + if (rt->plugin_pattern && !simple_pattern_matches_string(rt->plugin_pattern, st->plugin_name)) return false; - if(host->rrdlabels && rt->host_labels_pattern && !rrdlabels_match_simple_pattern_parsed(host->rrdlabels, rt->host_labels_pattern, '=')) + if(host->rrdlabels && rt->host_labels_pattern && !rrdlabels_match_simple_pattern_parsed(host->rrdlabels, + rt->host_labels_pattern, + '=', NULL)) return false; return true; } void rrdcalctemplate_check_rrddim_conditions_and_link(RRDCALCTEMPLATE *rt, RRDSET *st, RRDDIM *rd, RRDHOST *host) { - if (simple_pattern_matches(rt->foreach_dimension_pattern, rrddim_id(rd)) || simple_pattern_matches(rt->foreach_dimension_pattern, rrddim_name(rd))) { + if (simple_pattern_matches_string(rt->foreach_dimension_pattern, rd->id) || + simple_pattern_matches_string(rt->foreach_dimension_pattern, rd->name)) { char *overwrite_alert_name = rrdcalc_alert_name_with_dimension( rrdcalctemplate_name(rt), string_strlen(rt->name), rrddim_name(rd), string_strlen(rd->name)); rrdcalc_add_from_rrdcalctemplate(host, rt, st, overwrite_alert_name, rrddim_name(rd)); diff --git a/database/rrdcalctemplate.h b/database/rrdcalctemplate.h index 6212a42d..22cfe06e 100644 --- a/database/rrdcalctemplate.h +++ b/database/rrdcalctemplate.h @@ -50,7 +50,7 @@ struct rrdcalctemplate { STRING *dimensions; // the chart dimensions STRING *foreach_dimension; // the group of dimensions that the lookup will be applied. SIMPLE_PATTERN *foreach_dimension_pattern; // used if and only if there is a simple pattern for the chart. - RRDR_GROUPING group; // grouping method: average, max, etc. + RRDR_TIME_GROUPING group; // grouping method: average, max, etc. int before; // ending point in time-series int after; // starting point in time-series RRDCALC_OPTIONS options; // configuration options diff --git a/database/rrdcontext.c b/database/rrdcontext.c deleted file mode 100644 index 3f1ce73f..00000000 --- a/database/rrdcontext.c +++ /dev/null @@ -1,3993 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "rrdcontext.h" -#include "sqlite/sqlite_context.h" -#include "aclk/schema-wrappers/context.h" -#include "aclk/aclk_contexts_api.h" -#include "aclk/aclk.h" -#include "storage_engine.h" - -#define MESSAGES_PER_BUNDLE_TO_SEND_TO_HUB_PER_HOST 5000 -#define FULL_RETENTION_SCAN_DELAY_AFTER_DB_ROTATION_SECS 120 -#define RRDCONTEXT_WORKER_THREAD_HEARTBEAT_USEC (1000 * USEC_PER_MS) -#define RRDCONTEXT_MINIMUM_ALLOWED_PRIORITY 10 - -#define LOG_TRANSITIONS false - -#define WORKER_JOB_HOSTS 1 -#define WORKER_JOB_CHECK 2 -#define WORKER_JOB_SEND 3 -#define WORKER_JOB_DEQUEUE 4 -#define WORKER_JOB_RETENTION 5 -#define WORKER_JOB_QUEUED 6 -#define WORKER_JOB_CLEANUP 7 -#define WORKER_JOB_CLEANUP_DELETE 8 -#define WORKER_JOB_PP_METRIC 9 // post-processing metrics -#define WORKER_JOB_PP_INSTANCE 10 // post-processing instances -#define WORKER_JOB_PP_CONTEXT 11 // post-processing contexts -#define WORKER_JOB_HUB_QUEUE_SIZE 12 -#define WORKER_JOB_PP_QUEUE_SIZE 13 - - -typedef enum __attribute__ ((__packed__)) { - RRD_FLAG_NONE = 0, - RRD_FLAG_DELETED = (1 << 0), // this is a deleted object (metrics, instances, contexts) - RRD_FLAG_COLLECTED = (1 << 1), // this object is currently being collected - RRD_FLAG_UPDATED = (1 << 2), // this object has updates to propagate - RRD_FLAG_ARCHIVED = (1 << 3), // this object is not currently being collected - RRD_FLAG_OWN_LABELS = (1 << 4), // this instance has its own labels - not linked to an RRDSET - RRD_FLAG_LIVE_RETENTION = (1 << 5), // we have got live retention from the database - RRD_FLAG_QUEUED_FOR_HUB = (1 << 6), // this context is currently queued to be dispatched to hub - RRD_FLAG_QUEUED_FOR_PP = (1 << 7), // this context is currently queued to be post-processed - RRD_FLAG_HIDDEN = (1 << 8), // don't expose this to the hub or the API - - RRD_FLAG_UPDATE_REASON_TRIGGERED = (1 << 9), // the update was triggered by the child object - RRD_FLAG_UPDATE_REASON_LOAD_SQL = (1 << 10), // this object has just been loaded from SQL - RRD_FLAG_UPDATE_REASON_NEW_OBJECT = (1 << 11), // this object has just been created - RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT = (1 << 12), // we received an update on this object - RRD_FLAG_UPDATE_REASON_CHANGED_LINKING = (1 << 13), // an instance or a metric switched RRDSET or RRDDIM - RRD_FLAG_UPDATE_REASON_CHANGED_METADATA = (1 << 14), // this context or instance changed uuid, name, units, title, family, chart type, priority, update every, rrd changed flags - RRD_FLAG_UPDATE_REASON_ZERO_RETENTION = (1 << 15), // this object has no retention - RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T = (1 << 16), // this object changed its oldest time in the db - RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T = (1 << 17), // this object change its latest time in the db - RRD_FLAG_UPDATE_REASON_STOPPED_BEING_COLLECTED = (1 << 18), // this object has stopped being collected - RRD_FLAG_UPDATE_REASON_STARTED_BEING_COLLECTED = (1 << 19), // this object has started being collected - RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD = (1 << 20), // this context belongs to a host that just disconnected - RRD_FLAG_UPDATE_REASON_UNUSED = (1 << 21), // this context is not used anymore - RRD_FLAG_UPDATE_REASON_DB_ROTATION = (1 << 22), // this context changed because of a db rotation - - // action to perform on an object - RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION = (1 << 30), // this object has to update its retention from the db -} RRD_FLAGS; - -#define RRD_FLAG_ALL_UPDATE_REASONS ( \ - RRD_FLAG_UPDATE_REASON_TRIGGERED \ - |RRD_FLAG_UPDATE_REASON_LOAD_SQL \ - |RRD_FLAG_UPDATE_REASON_NEW_OBJECT \ - |RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT \ - |RRD_FLAG_UPDATE_REASON_CHANGED_LINKING \ - |RRD_FLAG_UPDATE_REASON_CHANGED_METADATA \ - |RRD_FLAG_UPDATE_REASON_ZERO_RETENTION \ - |RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T \ - |RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T \ - |RRD_FLAG_UPDATE_REASON_STOPPED_BEING_COLLECTED \ - |RRD_FLAG_UPDATE_REASON_STARTED_BEING_COLLECTED \ - |RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD \ - |RRD_FLAG_UPDATE_REASON_DB_ROTATION \ - |RRD_FLAG_UPDATE_REASON_UNUSED \ - ) - -#define RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS ( \ - RRD_FLAG_ARCHIVED \ - |RRD_FLAG_HIDDEN \ - |RRD_FLAG_ALL_UPDATE_REASONS \ - ) - -#define RRD_FLAGS_REQUIRED_FOR_DELETIONS ( \ - RRD_FLAG_DELETED \ - |RRD_FLAG_LIVE_RETENTION \ -) - -#define RRD_FLAGS_PREVENTING_DELETIONS ( \ - RRD_FLAG_QUEUED_FOR_HUB \ - |RRD_FLAG_COLLECTED \ - |RRD_FLAG_QUEUED_FOR_PP \ -) - -// get all the flags of an object -#define rrd_flags_get(obj) __atomic_load_n(&((obj)->flags), __ATOMIC_SEQ_CST) - -// check if ANY of the given flags (bits) is set -#define rrd_flag_check(obj, flag) (rrd_flags_get(obj) & (flag)) - -// check if ALL the given flags (bits) are set -#define rrd_flag_check_all(obj, flag) (rrd_flag_check(obj, flag) == (flag)) - -// set one or more flags (bits) -#define rrd_flag_set(obj, flag) __atomic_or_fetch(&((obj)->flags), flag, __ATOMIC_SEQ_CST) - -// clear one or more flags (bits) -#define rrd_flag_clear(obj, flag) __atomic_and_fetch(&((obj)->flags), ~(flag), __ATOMIC_SEQ_CST) - -// replace the flags of an object, with the supplied ones -#define rrd_flags_replace(obj, all_flags) __atomic_store_n(&((obj)->flags), all_flags, __ATOMIC_SEQ_CST) - -static inline void -rrd_flag_add_remove_atomic(RRD_FLAGS *flags, RRD_FLAGS check, RRD_FLAGS conditionally_add, RRD_FLAGS always_remove) { - RRD_FLAGS expected, desired; - - do { - expected = *flags; - - desired = expected; - desired &= ~(always_remove); - - if(!(expected & check)) - desired |= (check | conditionally_add); - - } while(!__atomic_compare_exchange_n(flags, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)); -} - -#define rrd_flag_set_collected(obj) \ - rrd_flag_add_remove_atomic(&((obj)->flags) \ - /* check this flag */ \ - , RRD_FLAG_COLLECTED \ - \ - /* add these flags together with the above, if the above is not already set */ \ - , RRD_FLAG_UPDATE_REASON_STARTED_BEING_COLLECTED | RRD_FLAG_UPDATED \ - \ - /* always remove these flags */ \ - , RRD_FLAG_ARCHIVED \ - | RRD_FLAG_DELETED \ - | RRD_FLAG_UPDATE_REASON_STOPPED_BEING_COLLECTED \ - | RRD_FLAG_UPDATE_REASON_ZERO_RETENTION \ - | RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD \ - ) - -#define rrd_flag_set_archived(obj) \ - rrd_flag_add_remove_atomic(&((obj)->flags) \ - /* check this flag */ \ - , RRD_FLAG_ARCHIVED \ - \ - /* add these flags together with the above, if the above is not already set */ \ - , RRD_FLAG_UPDATE_REASON_STOPPED_BEING_COLLECTED | RRD_FLAG_UPDATED \ - \ - /* always remove these flags */ \ - , RRD_FLAG_COLLECTED \ - | RRD_FLAG_DELETED \ - | RRD_FLAG_UPDATE_REASON_STARTED_BEING_COLLECTED \ - | RRD_FLAG_UPDATE_REASON_ZERO_RETENTION \ - ) - -#define rrd_flag_set_deleted(obj, reason) \ - rrd_flag_add_remove_atomic(&((obj)->flags) \ - /* check this flag */ \ - , RRD_FLAG_DELETED \ - \ - /* add these flags together with the above, if the above is not already set */ \ - , RRD_FLAG_UPDATE_REASON_ZERO_RETENTION | RRD_FLAG_UPDATED | (reason) \ - \ - /* always remove these flags */ \ - , RRD_FLAG_ARCHIVED \ - | RRD_FLAG_COLLECTED \ - ) - -#define rrd_flag_is_collected(obj) rrd_flag_check(obj, RRD_FLAG_COLLECTED) -#define rrd_flag_is_archived(obj) rrd_flag_check(obj, RRD_FLAG_ARCHIVED) -#define rrd_flag_is_deleted(obj) rrd_flag_check(obj, RRD_FLAG_DELETED) -#define rrd_flag_is_updated(obj) rrd_flag_check(obj, RRD_FLAG_UPDATED) - -// mark an object as updated, providing reasons (additional bits) -#define rrd_flag_set_updated(obj, reason) rrd_flag_set(obj, RRD_FLAG_UPDATED | (reason)) - -// clear an object as being updated, clearing also all the reasons -#define rrd_flag_unset_updated(obj) rrd_flag_clear(obj, RRD_FLAG_UPDATED | RRD_FLAG_ALL_UPDATE_REASONS) - - -static struct rrdcontext_reason { - RRD_FLAGS flag; - const char *name; - usec_t delay_ut; -} rrdcontext_reasons[] = { - // context related - {RRD_FLAG_UPDATE_REASON_TRIGGERED, "triggered transition", 65 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_NEW_OBJECT, "object created", 65 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT, "object updated", 65 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_LOAD_SQL, "loaded from sql", 65 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_CHANGED_METADATA, "changed metadata", 65 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_ZERO_RETENTION, "has no retention", 65 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T, "updated first_time_t", 65 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T, "updated last_time_t", 65 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_STOPPED_BEING_COLLECTED, "stopped collected", 65 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_STARTED_BEING_COLLECTED, "started collected", 5 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_UNUSED, "unused", 5 * USEC_PER_SEC }, - - // not context related - {RRD_FLAG_UPDATE_REASON_CHANGED_LINKING, "changed rrd link", 65 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD, "child disconnected", 65 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_DB_ROTATION, "db rotation", 65 * USEC_PER_SEC }, - {RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION, "updated retention", 65 * USEC_PER_SEC }, - - // terminator - {0, NULL, 0 }, -}; - - -typedef struct rrdmetric { - uuid_t uuid; - - STRING *id; - STRING *name; - - RRDDIM *rrddim; - - time_t first_time_s; - time_t last_time_s; - RRD_FLAGS flags; - - struct rrdinstance *ri; -} RRDMETRIC; - -typedef struct rrdinstance { - uuid_t uuid; - - STRING *id; - STRING *name; - STRING *title; - STRING *units; - STRING *family; - uint32_t priority; - RRDSET_TYPE chart_type; - - RRD_FLAGS flags; // flags related to this instance - time_t first_time_s; - time_t last_time_s; - - 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 - - struct rrdcontext *rc; - DICTIONARY *rrdmetrics; - - struct { - uint32_t collected_metrics_count; // a temporary variable to detect BEGIN/END without SET - // don't use it for other purposes - // it goes up and then resets to zero, on every iteration - } internal; -} RRDINSTANCE; - -typedef struct rrdcontext { - uint64_t version; - - STRING *id; - STRING *title; - STRING *units; - STRING *family; - uint32_t priority; - RRDSET_TYPE chart_type; - - RRD_FLAGS flags; - time_t first_time_s; - time_t last_time_s; - - VERSIONED_CONTEXT_DATA hub; - - DICTIONARY *rrdinstances; - RRDHOST *rrdhost; - - struct { - RRD_FLAGS queued_flags; // the last flags that triggered the post-processing - usec_t queued_ut; // the last time this was queued - usec_t dequeued_ut; // the last time we sent (or deduplicated) this context - size_t executions; // how many times this context has been processed - } pp; - - struct { - RRD_FLAGS queued_flags; // the last flags that triggered the queueing - usec_t queued_ut; // the last time this was queued - usec_t delay_calc_ut; // the last time we calculated the scheduled_dispatched_ut - usec_t scheduled_dispatch_ut; // the time it was/is scheduled to be sent - usec_t dequeued_ut; // the last time we sent (or deduplicated) this context - size_t dispatches; // the number of times this has been dispatched to hub - } queue; - - netdata_mutex_t mutex; -} RRDCONTEXT; - -// ---------------------------------------------------------------------------- -// helper one-liners for RRDMETRIC - -static bool rrdmetric_update_retention(RRDMETRIC *rm); - -static inline RRDMETRIC *rrdmetric_acquired_value(RRDMETRIC_ACQUIRED *rma) { - return dictionary_acquired_item_value((DICTIONARY_ITEM *)rma); -} - -static inline RRDMETRIC_ACQUIRED *rrdmetric_acquired_dup(RRDMETRIC_ACQUIRED *rma) { - RRDMETRIC *rm = rrdmetric_acquired_value(rma); - return (RRDMETRIC_ACQUIRED *)dictionary_acquired_item_dup(rm->ri->rrdmetrics, (DICTIONARY_ITEM *)rma); -} - -static inline void rrdmetric_release(RRDMETRIC_ACQUIRED *rma) { - RRDMETRIC *rm = rrdmetric_acquired_value(rma); - dictionary_acquired_item_release(rm->ri->rrdmetrics, (DICTIONARY_ITEM *)rma); -} - -const char *rrdmetric_acquired_id(RRDMETRIC_ACQUIRED *rma) { - RRDMETRIC *rm = rrdmetric_acquired_value(rma); - return string2str(rm->id); -} - -const char *rrdmetric_acquired_name(RRDMETRIC_ACQUIRED *rma) { - RRDMETRIC *rm = rrdmetric_acquired_value(rma); - return string2str(rm->name); -} - -NETDATA_DOUBLE rrdmetric_acquired_last_stored_value(RRDMETRIC_ACQUIRED *rma) { - RRDMETRIC *rm = rrdmetric_acquired_value(rma); - - if(rm->rrddim) - return rm->rrddim->last_stored_value; - - return NAN; -} - -// ---------------------------------------------------------------------------- -// helper one-liners for RRDINSTANCE - -static inline RRDINSTANCE *rrdinstance_acquired_value(RRDINSTANCE_ACQUIRED *ria) { - return dictionary_acquired_item_value((DICTIONARY_ITEM *)ria); -} - -static inline RRDINSTANCE_ACQUIRED *rrdinstance_acquired_dup(RRDINSTANCE_ACQUIRED *ria) { - RRDINSTANCE *ri = rrdinstance_acquired_value(ria); - return (RRDINSTANCE_ACQUIRED *)dictionary_acquired_item_dup(ri->rc->rrdinstances, (DICTIONARY_ITEM *)ria); -} - -static inline void rrdinstance_release(RRDINSTANCE_ACQUIRED *ria) { - RRDINSTANCE *ri = rrdinstance_acquired_value(ria); - dictionary_acquired_item_release(ri->rc->rrdinstances, (DICTIONARY_ITEM *)ria); -} - -const char *rrdinstance_acquired_id(RRDINSTANCE_ACQUIRED *ria) { - RRDINSTANCE *ri = rrdinstance_acquired_value(ria); - return string2str(ri->id); -} - -const char *rrdinstance_acquired_name(RRDINSTANCE_ACQUIRED *ria) { - RRDINSTANCE *ri = rrdinstance_acquired_value(ria); - return string2str(ri->name); -} - -DICTIONARY *rrdinstance_acquired_labels(RRDINSTANCE_ACQUIRED *ria) { - RRDINSTANCE *ri = rrdinstance_acquired_value(ria); - return ri->rrdlabels; -} - -DICTIONARY *rrdinstance_acquired_functions(RRDINSTANCE_ACQUIRED *ria) { - RRDINSTANCE *ri = rrdinstance_acquired_value(ria); - if(!ri->rrdset) return NULL; - return ri->rrdset->functions_view; -} - -// ---------------------------------------------------------------------------- -// helper one-liners for RRDCONTEXT - -static inline RRDCONTEXT *rrdcontext_acquired_value(RRDCONTEXT_ACQUIRED *rca) { - return dictionary_acquired_item_value((DICTIONARY_ITEM *)rca); -} - -const char *rrdcontext_acquired_id(RRDCONTEXT_ACQUIRED *rca) { - RRDCONTEXT *rc = rrdcontext_acquired_value(rca); - return string2str(rc->id); -} - -static inline RRDCONTEXT_ACQUIRED *rrdcontext_acquired_dup(RRDCONTEXT_ACQUIRED *rca) { - RRDCONTEXT *rc = rrdcontext_acquired_value(rca); - return (RRDCONTEXT_ACQUIRED *)dictionary_acquired_item_dup((DICTIONARY *)rc->rrdhost->rrdctx, (DICTIONARY_ITEM *)rca); -} - -static inline void rrdcontext_release(RRDCONTEXT_ACQUIRED *rca) { - RRDCONTEXT *rc = rrdcontext_acquired_value(rca); - dictionary_acquired_item_release((DICTIONARY *)rc->rrdhost->rrdctx, (DICTIONARY_ITEM *)rca); -} - -static void rrdcontext_recalculate_context_retention(RRDCONTEXT *rc, RRD_FLAGS reason, bool worker_jobs); -static void rrdcontext_recalculate_host_retention(RRDHOST *host, RRD_FLAGS reason, bool worker_jobs); - -#define rrdcontext_version_hash(host) rrdcontext_version_hash_with_callback(host, NULL, false, NULL) -static uint64_t rrdcontext_version_hash_with_callback(RRDHOST *host, void (*callback)(RRDCONTEXT *, bool, void *), bool snapshot, void *bundle); - -static void rrdcontext_garbage_collect_single_host(RRDHOST *host, bool worker_jobs); -static void rrdcontext_garbage_collect_for_all_hosts(void); - -#define rrdcontext_lock(rc) netdata_mutex_lock(&((rc)->mutex)) -#define rrdcontext_unlock(rc) netdata_mutex_unlock(&((rc)->mutex)) - -// ---------------------------------------------------------------------------- -// Forward definitions - -static uint64_t rrdcontext_get_next_version(RRDCONTEXT *rc); -static bool check_if_cloud_version_changed_unsafe(RRDCONTEXT *rc, bool sending __maybe_unused); -static void rrdcontext_message_send_unsafe(RRDCONTEXT *rc, bool snapshot __maybe_unused, void *bundle __maybe_unused); - -static void rrdcontext_delete_from_sql_unsafe(RRDCONTEXT *rc); - -static void rrdcontext_dequeue_from_post_processing(RRDCONTEXT *rc); -static void rrdcontext_queue_for_post_processing(RRDCONTEXT *rc, const char *function, RRD_FLAGS flags); -static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAGS reason, bool worker_jobs); - -static void rrdmetric_trigger_updates(RRDMETRIC *rm, const char *function); -static void rrdinstance_trigger_updates(RRDINSTANCE *ri, const char *function); -static void rrdcontext_trigger_updates(RRDCONTEXT *rc, const char *function); - -// ---------------------------------------------------------------------------- -// visualizing flags - -static void rrd_flags_to_buffer(RRD_FLAGS flags, BUFFER *wb) { - if(flags & RRD_FLAG_QUEUED_FOR_HUB) - buffer_strcat(wb, "QUEUED "); - - if(flags & RRD_FLAG_DELETED) - buffer_strcat(wb, "DELETED "); - - if(flags & RRD_FLAG_COLLECTED) - buffer_strcat(wb, "COLLECTED "); - - if(flags & RRD_FLAG_UPDATED) - buffer_strcat(wb, "UPDATED "); - - if(flags & RRD_FLAG_ARCHIVED) - buffer_strcat(wb, "ARCHIVED "); - - if(flags & RRD_FLAG_OWN_LABELS) - buffer_strcat(wb, "OWN_LABELS "); - - if(flags & RRD_FLAG_LIVE_RETENTION) - buffer_strcat(wb, "LIVE_RETENTION "); - - if(flags & RRD_FLAG_HIDDEN) - buffer_strcat(wb, "HIDDEN "); - - if(flags & RRD_FLAG_QUEUED_FOR_PP) - buffer_strcat(wb, "PENDING_UPDATES "); -} - -static void rrd_reasons_to_buffer(RRD_FLAGS flags, BUFFER *wb) { - for(int i = 0, added = 0; rrdcontext_reasons[i].name ; i++) { - if (flags & rrdcontext_reasons[i].flag) { - if (added) - buffer_strcat(wb, ", "); - buffer_strcat(wb, rrdcontext_reasons[i].name); - added++; - } - } -} - -// ---------------------------------------------------------------------------- -// RRDMETRIC - -// free the contents of RRDMETRIC. -// RRDMETRIC itself is managed by DICTIONARY - no need to free it here. -static void rrdmetric_free(RRDMETRIC *rm) { - string_freez(rm->id); - string_freez(rm->name); - - rm->id = NULL; - rm->name = NULL; - rm->ri = NULL; -} - -// called when this rrdmetric is inserted to the rrdmetrics dictionary of a rrdinstance -// the constructor of the rrdmetric object -static void rrdmetric_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdinstance) { - RRDMETRIC *rm = value; - - // link it to its parent - rm->ri = rrdinstance; - - // remove flags that we need to figure out at runtime - rm->flags = rm->flags & RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS; // no need for atomics - - // signal the react callback to do the job - rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_NEW_OBJECT); -} - -// called when this rrdmetric is deleted from the rrdmetrics dictionary of a rrdinstance -// the destructor of the rrdmetric object -static void rrdmetric_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdinstance __maybe_unused) { - RRDMETRIC *rm = value; - - internal_error(rm->rrddim, "RRDMETRIC: '%s' is freed but there is a RRDDIM linked to it.", string2str(rm->id)); - - // free the resources - rrdmetric_free(rm); -} - -// called when the same rrdmetric is inserted again to the rrdmetrics dictionary of a rrdinstance -// while this is called, the dictionary is write locked, but there may be other users of the object -static bool rrdmetric_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *rrdinstance __maybe_unused) { - RRDMETRIC *rm = old_value; - RRDMETRIC *rm_new = new_value; - - internal_error(rm->id != rm_new->id, - "RRDMETRIC: '%s' cannot change id to '%s'", - string2str(rm->id), string2str(rm_new->id)); - - if(uuid_compare(rm->uuid, rm_new->uuid) != 0) { -#ifdef NETDATA_INTERNAL_CHECKS - char uuid1[UUID_STR_LEN], uuid2[UUID_STR_LEN]; - uuid_unparse(rm->uuid, uuid1); - uuid_unparse(rm_new->uuid, uuid2); - - time_t old_first_time_s = 0; - time_t old_last_time_s = 0; - if(rrdmetric_update_retention(rm)) { - 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_s = 0; - time_t new_last_time_s = 0; - if(rrdmetric_update_retention(rm)) { - new_first_time_s = rm->first_time_s; - new_last_time_s = rm->last_time_s; - } - - internal_error(true, - "RRDMETRIC: '%s' of instance '%s' of host '%s' changed UUID from '%s' (retention %ld to %ld, %ld secs) to '%s' (retention %ld to %ld, %ld secs)" - , string2str(rm->id) - , string2str(rm->ri->id) - , rrdhost_hostname(rm->ri->rc->rrdhost) - , 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); -#endif - rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - if(rm->rrddim && rm_new->rrddim && rm->rrddim != rm_new->rrddim) { - rm->rrddim = rm_new->rrddim; - rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_LINKING); - } - -#ifdef NETDATA_INTERNAL_CHECKS - if(rm->rrddim && uuid_compare(rm->uuid, rm->rrddim->metric_uuid) != 0) { - char uuid1[UUID_STR_LEN], uuid2[UUID_STR_LEN]; - uuid_unparse(rm->uuid, uuid1); - uuid_unparse(rm_new->uuid, uuid2); - internal_error(true, "RRDMETRIC: '%s' is linked to RRDDIM '%s' but they have different UUIDs. RRDMETRIC has '%s', RRDDIM has '%s'", string2str(rm->id), rrddim_id(rm->rrddim), uuid1, uuid2); - } -#endif - - if(rm->rrddim != rm_new->rrddim) - rm->rrddim = rm_new->rrddim; - - if(rm->name != rm_new->name) { - STRING *old = rm->name; - rm->name = string_dup(rm_new->name); - string_freez(old); - rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - 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_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); - } - - rrd_flag_set(rm, rm_new->flags & RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS); // no needs for atomics on rm_new - - if(rrd_flag_is_collected(rm) && rrd_flag_is_archived(rm)) - rrd_flag_set_collected(rm); - - if(rrd_flag_check(rm, RRD_FLAG_UPDATED)) - rrd_flag_set(rm, RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT); - - rrdmetric_free(rm_new); - - // the react callback will continue from here - return rrd_flag_is_updated(rm); -} - -// this is called after the insert or the conflict callbacks, -// but the dictionary is now unlocked -static void rrdmetric_react_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdinstance __maybe_unused) { - RRDMETRIC *rm = value; - rrdmetric_trigger_updates(rm, __FUNCTION__ ); -} - -static void rrdmetrics_create_in_rrdinstance(RRDINSTANCE *ri) { - if(unlikely(!ri)) return; - if(likely(ri->rrdmetrics)) return; - - 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); - dictionary_register_react_callback(ri->rrdmetrics, rrdmetric_react_callback, ri); -} - -static void rrdmetrics_destroy_from_rrdinstance(RRDINSTANCE *ri) { - if(unlikely(!ri || !ri->rrdmetrics)) return; - dictionary_destroy(ri->rrdmetrics); - ri->rrdmetrics = NULL; -} - -// trigger post-processing of the rrdmetric, escalating changes to the rrdinstance it belongs -static void rrdmetric_trigger_updates(RRDMETRIC *rm, const char *function) { - if(unlikely(rrd_flag_is_collected(rm)) && (!rm->rrddim || rrd_flag_check(rm, RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD))) - rrd_flag_set_archived(rm); - - if(rrd_flag_is_updated(rm) || !rrd_flag_check(rm, RRD_FLAG_LIVE_RETENTION)) { - rrd_flag_set_updated(rm->ri, RRD_FLAG_UPDATE_REASON_TRIGGERED); - rrdcontext_queue_for_post_processing(rm->ri->rc, function, rm->flags); - } -} - -// ---------------------------------------------------------------------------- -// RRDMETRIC HOOKS ON RRDDIM - -static inline void rrdmetric_from_rrddim(RRDDIM *rd) { - if(unlikely(!rd->rrdset)) - fatal("RRDMETRIC: rrddim '%s' does not have a rrdset.", rrddim_id(rd)); - - if(unlikely(!rd->rrdset->rrdhost)) - fatal("RRDMETRIC: rrdset '%s' does not have a rrdhost", rrdset_id(rd->rrdset)); - - if(unlikely(!rd->rrdset->rrdinstance)) - fatal("RRDMETRIC: rrdset '%s' does not have a rrdinstance", rrdset_id(rd->rrdset)); - - RRDINSTANCE *ri = rrdinstance_acquired_value(rd->rrdset->rrdinstance); - - RRDMETRIC trm = { - .id = string_dup(rd->id), - .name = string_dup(rd->name), - .flags = RRD_FLAG_NONE, // no need for atomics - .rrddim = rd, - }; - uuid_copy(trm.uuid, rd->metric_uuid); - - RRDMETRIC_ACQUIRED *rma = (RRDMETRIC_ACQUIRED *)dictionary_set_and_acquire_item(ri->rrdmetrics, string2str(trm.id), &trm, sizeof(trm)); - - if(rd->rrdmetric) - rrdmetric_release(rd->rrdmetric); - - rd->rrdmetric = rma; -} - -#define rrddim_get_rrdmetric(rd) rrddim_get_rrdmetric_with_trace(rd, __FUNCTION__) -static inline RRDMETRIC *rrddim_get_rrdmetric_with_trace(RRDDIM *rd, const char *function) { - if(unlikely(!rd->rrdmetric)) { - error("RRDMETRIC: RRDDIM '%s' is not linked to an RRDMETRIC at %s()", rrddim_id(rd), function); - return NULL; - } - - RRDMETRIC *rm = rrdmetric_acquired_value(rd->rrdmetric); - if(unlikely(!rm)) { - error("RRDMETRIC: RRDDIM '%s' lost the link to its RRDMETRIC at %s()", rrddim_id(rd), function); - return NULL; - } - - if(unlikely(rm->rrddim != rd)) - fatal("RRDMETRIC: '%s' is not linked to RRDDIM '%s' at %s()", string2str(rm->id), rrddim_id(rd), function); - - return rm; -} - -static inline void rrdmetric_rrddim_is_freed(RRDDIM *rd) { - RRDMETRIC *rm = rrddim_get_rrdmetric(rd); - if(unlikely(!rm)) return; - - if(unlikely(rrd_flag_is_collected(rm))) - rrd_flag_set_archived(rm); - - rm->rrddim = NULL; - rrdmetric_trigger_updates(rm, __FUNCTION__ ); - rrdmetric_release(rd->rrdmetric); - rd->rrdmetric = NULL; -} - -static inline void rrdmetric_updated_rrddim_flags(RRDDIM *rd) { - RRDMETRIC *rm = rrddim_get_rrdmetric(rd); - if(unlikely(!rm)) return; - - if(unlikely(rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED|RRDDIM_FLAG_OBSOLETE))) { - if(unlikely(rrd_flag_is_collected(rm))) - rrd_flag_set_archived(rm); - } - - rrdmetric_trigger_updates(rm, __FUNCTION__ ); -} - -static inline void rrdmetric_collected_rrddim(RRDDIM *rd) { - RRDMETRIC *rm = rrddim_get_rrdmetric(rd); - if(unlikely(!rm)) return; - - if(unlikely(!rrd_flag_is_collected(rm))) - rrd_flag_set_collected(rm); - - // we use this variable to detect BEGIN/END without SET - rm->ri->internal.collected_metrics_count++; - - rrdmetric_trigger_updates(rm, __FUNCTION__ ); -} - -// ---------------------------------------------------------------------------- -// RRDINSTANCE - -static void rrdinstance_free(RRDINSTANCE *ri) { - - if(rrd_flag_check(ri, RRD_FLAG_OWN_LABELS)) - dictionary_destroy(ri->rrdlabels); - - rrdmetrics_destroy_from_rrdinstance(ri); - string_freez(ri->id); - string_freez(ri->name); - string_freez(ri->title); - string_freez(ri->units); - string_freez(ri->family); - - ri->id = NULL; - ri->name = NULL; - ri->title = NULL; - ri->units = NULL; - ri->family = NULL; - ri->rc = NULL; - ri->rrdlabels = NULL; - ri->rrdmetrics = NULL; - ri->rrdset = NULL; -} - -static void rrdinstance_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdcontext) { - RRDINSTANCE *ri = value; - - // link it to its parent - ri->rc = rrdcontext; - - ri->flags = ri->flags & RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS; // no need for atomics - - if(!ri->name) - ri->name = string_dup(ri->id); - - if(ri->rrdset) { - ri->rrdlabels = ri->rrdset->rrdlabels; - ri->flags &= ~RRD_FLAG_OWN_LABELS; // no need of atomics at the constructor - } - else { - ri->rrdlabels = rrdlabels_create(); - ri->flags |= RRD_FLAG_OWN_LABELS; // no need of atomics at the constructor - } - - if(ri->rrdset) { - if(unlikely(rrdset_flag_check(ri->rrdset, RRDSET_FLAG_HIDDEN))) - ri->flags |= RRD_FLAG_HIDDEN; // no need of atomics at the constructor - else - 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 - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_NEW_OBJECT); -} - -static void rrdinstance_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdcontext __maybe_unused) { - RRDINSTANCE *ri = (RRDINSTANCE *)value; - - internal_error(ri->rrdset, "RRDINSTANCE: '%s' is freed but there is a RRDSET linked to it.", string2str(ri->id)); - - rrdinstance_free(ri); -} - -static bool rrdinstance_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *rrdcontext __maybe_unused) { - RRDINSTANCE *ri = (RRDINSTANCE *)old_value; - RRDINSTANCE *ri_new = (RRDINSTANCE *)new_value; - - internal_error(ri->id != ri_new->id, - "RRDINSTANCE: '%s' cannot change id to '%s'", - string2str(ri->id), string2str(ri_new->id)); - - if(uuid_compare(ri->uuid, ri_new->uuid) != 0) { -#ifdef NETDATA_INTERNAL_CHECKS - char uuid1[UUID_STR_LEN], uuid2[UUID_STR_LEN]; - uuid_unparse(ri->uuid, uuid1); - uuid_unparse(ri_new->uuid, uuid2); - internal_error(true, "RRDINSTANCE: '%s' of host '%s' changed UUID from '%s' to '%s'", - string2str(ri->id), rrdhost_hostname(ri->rc->rrdhost), uuid1, uuid2); -#endif - - uuid_copy(ri->uuid, ri_new->uuid); - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - if(ri->rrdset && ri_new->rrdset && ri->rrdset != ri_new->rrdset) { - ri->rrdset = ri_new->rrdset; - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LINKING); - } - -#ifdef NETDATA_INTERNAL_CHECKS - if(ri->rrdset && uuid_compare(ri->uuid, ri->rrdset->chart_uuid) != 0) { - char uuid1[UUID_STR_LEN], uuid2[UUID_STR_LEN]; - uuid_unparse(ri->uuid, uuid1); - uuid_unparse(ri->rrdset->chart_uuid, uuid2); - internal_error(true, "RRDINSTANCE: '%s' is linked to RRDSET '%s' but they have different UUIDs. RRDINSTANCE has '%s', RRDSET has '%s'", string2str(ri->id), rrdset_id(ri->rrdset), uuid1, uuid2); - } -#endif - - if(ri->name != ri_new->name) { - STRING *old = ri->name; - ri->name = string_dup(ri_new->name); - string_freez(old); - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - if(ri->title != ri_new->title) { - STRING *old = ri->title; - ri->title = string_dup(ri_new->title); - string_freez(old); - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - if(ri->units != ri_new->units) { - STRING *old = ri->units; - ri->units = string_dup(ri_new->units); - string_freez(old); - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - if(ri->family != ri_new->family) { - STRING *old = ri->family; - ri->family = string_dup(ri_new->family); - string_freez(old); - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - if(ri->chart_type != ri_new->chart_type) { - ri->chart_type = ri_new->chart_type; - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - if(ri->priority != ri_new->priority) { - ri->priority = ri_new->priority; - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - 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); - } - - if(ri->rrdset != ri_new->rrdset) { - ri->rrdset = ri_new->rrdset; - - if(ri->rrdset && rrd_flag_check(ri, RRD_FLAG_OWN_LABELS)) { - DICTIONARY *old = ri->rrdlabels; - ri->rrdlabels = ri->rrdset->rrdlabels; - rrd_flag_clear(ri, RRD_FLAG_OWN_LABELS); - rrdlabels_destroy(old); - } - else if(!ri->rrdset && !rrd_flag_check(ri, RRD_FLAG_OWN_LABELS)) { - ri->rrdlabels = rrdlabels_create(); - rrd_flag_set(ri, RRD_FLAG_OWN_LABELS); - } - } - - if(ri->rrdset) { - if(unlikely(rrdset_flag_check(ri->rrdset, RRDSET_FLAG_HIDDEN))) - rrd_flag_set(ri, RRD_FLAG_HIDDEN); - else - rrd_flag_clear(ri, RRD_FLAG_HIDDEN); - } - - rrd_flag_set(ri, ri_new->flags & RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS); // no need for atomics on ri_new - - if(rrd_flag_is_collected(ri) && rrd_flag_is_archived(ri)) - rrd_flag_set_collected(ri); - - if(rrd_flag_is_updated(ri)) - rrd_flag_set(ri, RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT); - - // free the new one - rrdinstance_free(ri_new); - - // the react callback will continue from here - return rrd_flag_is_updated(ri); -} - -static void rrdinstance_react_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdcontext __maybe_unused) { - RRDINSTANCE *ri = value; - - rrdinstance_trigger_updates(ri, __FUNCTION__ ); -} - -void rrdinstances_create_in_rrdcontext(RRDCONTEXT *rc) { - if(unlikely(!rc || rc->rrdinstances)) return; - - 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); - dictionary_register_react_callback(rc->rrdinstances, rrdinstance_react_callback, rc); -} - -void rrdinstances_destroy_from_rrdcontext(RRDCONTEXT *rc) { - if(unlikely(!rc || !rc->rrdinstances)) return; - - dictionary_destroy(rc->rrdinstances); - rc->rrdinstances = NULL; -} - -static void rrdinstance_trigger_updates(RRDINSTANCE *ri, const char *function) { - RRDSET *st = ri->rrdset; - - if(likely(st)) { - if(unlikely((unsigned int) st->priority != ri->priority)) { - ri->priority = st->priority; - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - 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); - } - } - else if(unlikely(rrd_flag_is_collected(ri))) { - // there is no rrdset, but we have it as collected! - - rrd_flag_set_archived(ri); - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LINKING); - } - - if(rrd_flag_is_updated(ri) || !rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION)) { - rrd_flag_set_updated(ri->rc, RRD_FLAG_UPDATE_REASON_TRIGGERED); - rrdcontext_queue_for_post_processing(ri->rc, function, ri->flags); - } -} - -// ---------------------------------------------------------------------------- -// RRDINSTANCE HOOKS ON RRDSET - -static inline void rrdinstance_from_rrdset(RRDSET *st) { - RRDCONTEXT trc = { - .id = string_dup(st->context), - .title = string_dup(st->title), - .units = string_dup(st->units), - .family = string_dup(st->family), - .priority = st->priority, - .chart_type = st->chart_type, - .flags = RRD_FLAG_NONE, // no need for atomics - .rrdhost = st->rrdhost, - }; - - RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_set_and_acquire_item((DICTIONARY *)st->rrdhost->rrdctx, string2str(trc.id), &trc, sizeof(trc)); - RRDCONTEXT *rc = rrdcontext_acquired_value(rca); - - RRDINSTANCE tri = { - .id = string_dup(st->id), - .name = string_dup(st->name), - .units = string_dup(st->units), - .family = string_dup(st->family), - .title = string_dup(st->title), - .chart_type = st->chart_type, - .priority = st->priority, - .update_every_s = st->update_every, - .flags = RRD_FLAG_NONE, // no need for atomics - .rrdset = st, - }; - uuid_copy(tri.uuid, st->chart_uuid); - - RRDINSTANCE_ACQUIRED *ria = (RRDINSTANCE_ACQUIRED *)dictionary_set_and_acquire_item(rc->rrdinstances, string2str(tri.id), &tri, sizeof(tri)); - - RRDCONTEXT_ACQUIRED *rca_old = st->rrdcontext; - RRDINSTANCE_ACQUIRED *ria_old = st->rrdinstance; - - st->rrdcontext = rca; - st->rrdinstance = ria; - - if(rca == rca_old) { - rrdcontext_release(rca_old); - rca_old = NULL; - } - - if(ria == ria_old) { - rrdinstance_release(ria_old); - ria_old = NULL; - } - - if(rca_old && ria_old) { - // Oops! The chart changed context! - - // RRDCONTEXT *rc_old = rrdcontext_acquired_value(rca_old); - RRDINSTANCE *ri_old = rrdinstance_acquired_value(ria_old); - - // migrate all dimensions to the new metrics - RRDDIM *rd; - rrddim_foreach_read(rd, st) { - if (!rd->rrdmetric) continue; - - 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_s = 0; - rm_old->last_time_s = 0; - - rrdmetric_release(rd->rrdmetric); - rd->rrdmetric = NULL; - - rrdmetric_from_rrddim(rd); - } - rrddim_foreach_done(rd); - - // mark the old instance, ready to be deleted - if(!rrd_flag_check(ri_old, RRD_FLAG_OWN_LABELS)) - ri_old->rrdlabels = rrdlabels_create(); - - 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_s = 0; - ri_old->last_time_s = 0; - - rrdinstance_trigger_updates(ri_old, __FUNCTION__ ); - rrdinstance_release(ria_old); - - /* - // trigger updates on the old context - 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_s = 0; - rc_old->last_time_s = 0; - rrdcontext_unlock(rc_old); - rrdcontext_trigger_updates(rc_old, __FUNCTION__ ); - } - else - rrdcontext_trigger_updates(rc_old, __FUNCTION__ ); - */ - - rrdcontext_release(rca_old); - rca_old = NULL; - ria_old = NULL; - } - - if(rca_old || ria_old) - fatal("RRDCONTEXT: cannot switch rrdcontext without switching rrdinstance too"); -} - -#define rrdset_get_rrdinstance(st) rrdset_get_rrdinstance_with_trace(st, __FUNCTION__); -static inline RRDINSTANCE *rrdset_get_rrdinstance_with_trace(RRDSET *st, const char *function) { - if(unlikely(!st->rrdinstance)) { - error("RRDINSTANCE: RRDSET '%s' is not linked to an RRDINSTANCE at %s()", rrdset_id(st), function); - return NULL; - } - - RRDINSTANCE *ri = rrdinstance_acquired_value(st->rrdinstance); - if(unlikely(!ri)) { - error("RRDINSTANCE: RRDSET '%s' lost its link to an RRDINSTANCE at %s()", rrdset_id(st), function); - return NULL; - } - - if(unlikely(ri->rrdset != st)) - fatal("RRDINSTANCE: '%s' is not linked to RRDSET '%s' at %s()", string2str(ri->id), rrdset_id(st), function); - - return ri; -} - -static inline void rrdinstance_rrdset_is_freed(RRDSET *st) { - RRDINSTANCE *ri = rrdset_get_rrdinstance(st); - if(unlikely(!ri)) return; - - rrd_flag_set_archived(ri); - - if(!rrd_flag_check(ri, RRD_FLAG_OWN_LABELS)) { - ri->rrdlabels = rrdlabels_create(); - rrdlabels_copy(ri->rrdlabels, st->rrdlabels); - rrd_flag_set(ri, RRD_FLAG_OWN_LABELS); - } - - ri->rrdset = NULL; - - rrdinstance_trigger_updates(ri, __FUNCTION__ ); - - rrdinstance_release(st->rrdinstance); - st->rrdinstance = NULL; - - rrdcontext_release(st->rrdcontext); - st->rrdcontext = NULL; -} - -static inline void rrdinstance_rrdset_has_updated_retention(RRDSET *st) { - RRDINSTANCE *ri = rrdset_get_rrdinstance(st); - if(unlikely(!ri)) return; - - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION); - rrdinstance_trigger_updates(ri, __FUNCTION__ ); -} - -static inline void rrdinstance_updated_rrdset_name(RRDSET *st) { - // the chart may not be initialized when this is called - if(unlikely(!st->rrdinstance)) return; - - RRDINSTANCE *ri = rrdset_get_rrdinstance(st); - if(unlikely(!ri)) return; - - if(st->name != ri->name) { - STRING *old = ri->name; - ri->name = string_dup(st->name); - string_freez(old); - - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - rrdinstance_trigger_updates(ri, __FUNCTION__ ); - } -} - -static inline void rrdinstance_updated_rrdset_flags_no_action(RRDINSTANCE *ri, RRDSET *st) { - if(unlikely(ri->rrdset != st)) - fatal("RRDCONTEXT: instance '%s' is not linked to chart '%s' on host '%s'", - string2str(ri->id), rrdset_id(st), rrdhost_hostname(st->rrdhost)); - - bool st_is_hidden = rrdset_flag_check(st, RRDSET_FLAG_HIDDEN); - bool ri_is_hidden = rrd_flag_check(ri, RRD_FLAG_HIDDEN); - - if(unlikely(st_is_hidden != ri_is_hidden)) { - if (unlikely(st_is_hidden && !ri_is_hidden)) - rrd_flag_set_updated(ri, RRD_FLAG_HIDDEN | RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - - else if (unlikely(!st_is_hidden && ri_is_hidden)) { - rrd_flag_clear(ri, RRD_FLAG_HIDDEN); - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - } -} - -static inline void rrdinstance_updated_rrdset_flags(RRDSET *st) { - RRDINSTANCE *ri = rrdset_get_rrdinstance(st); - if(unlikely(!ri)) return; - - if(unlikely(rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED|RRDSET_FLAG_OBSOLETE))) - rrd_flag_set_archived(ri); - - rrdinstance_updated_rrdset_flags_no_action(ri, st); - - rrdinstance_trigger_updates(ri, __FUNCTION__ ); -} - -static inline void rrdinstance_collected_rrdset(RRDSET *st) { - RRDINSTANCE *ri = rrdset_get_rrdinstance(st); - if(unlikely(!ri)) return; - - rrdinstance_updated_rrdset_flags_no_action(ri, st); - - if(unlikely(ri->internal.collected_metrics_count && !rrd_flag_is_collected(ri))) - rrd_flag_set_collected(ri); - - // we use this variable to detect BEGIN/END without SET - ri->internal.collected_metrics_count = 0; - - rrdinstance_trigger_updates(ri, __FUNCTION__ ); -} - -// ---------------------------------------------------------------------------- -// RRDCONTEXT - -static void rrdcontext_freez(RRDCONTEXT *rc) { - string_freez(rc->id); - string_freez(rc->title); - string_freez(rc->units); - string_freez(rc->family); -} - -static void rrdcontext_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdhost) { - RRDHOST *host = (RRDHOST *)rrdhost; - RRDCONTEXT *rc = (RRDCONTEXT *)value; - - rc->rrdhost = host; - rc->flags = rc->flags & RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS; // no need for atomics at constructor - - if(rc->hub.version) { - // we are loading data from the SQL database - - if(rc->version) - error("RRDCONTEXT: context '%s' is already initialized with version %"PRIu64", but it is loaded again from SQL with version %"PRIu64"", string2str(rc->id), rc->version, rc->hub.version); - - // IMPORTANT - // replace all string pointers in rc->hub with our own versions - // the originals are coming from a tmp allocation of sqlite - - string_freez(rc->id); - rc->id = string_strdupz(rc->hub.id); - rc->hub.id = string2str(rc->id); - - string_freez(rc->title); - rc->title = string_strdupz(rc->hub.title); - rc->hub.title = string2str(rc->title); - - string_freez(rc->units); - rc->units = string_strdupz(rc->hub.units); - rc->hub.units = string2str(rc->units); - - string_freez(rc->family); - rc->family = string_strdupz(rc->hub.family); - rc->hub.family = string2str(rc->family); - - rc->chart_type = rrdset_type_id(rc->hub.chart_type); - rc->hub.chart_type = rrdset_type_name(rc->chart_type); - - rc->version = rc->hub.version; - rc->priority = rc->hub.priority; - 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_s) - rrd_flag_set_deleted(rc, RRD_FLAG_NONE); - else { - if (rc->last_time_s == 0) - rrd_flag_set_collected(rc); - else - rrd_flag_set_archived(rc); - } - - rc->flags |= RRD_FLAG_UPDATE_REASON_LOAD_SQL; // no need for atomics at constructor - } - else { - // we are adding this context now for the first time - rc->version = now_realtime_sec(); - } - - rrdinstances_create_in_rrdcontext(rc); - netdata_mutex_init(&rc->mutex); - - // signal the react callback to do the job - rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_NEW_OBJECT); -} - -static void rrdcontext_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdhost __maybe_unused) { - - RRDCONTEXT *rc = (RRDCONTEXT *)value; - - rrdinstances_destroy_from_rrdcontext(rc); - netdata_mutex_destroy(&rc->mutex); - rrdcontext_freez(rc); -} - -static bool rrdcontext_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *rrdhost __maybe_unused) { - RRDCONTEXT *rc = (RRDCONTEXT *)old_value; - RRDCONTEXT *rc_new = (RRDCONTEXT *)new_value; - - //current rc is not archived, new_rc is archived, don't merge - if (!rrd_flag_is_archived(rc) && rrd_flag_is_archived(rc_new)) { - rrdcontext_freez(rc_new); - return false; - } - - rrdcontext_lock(rc); - - if(rc->title != rc_new->title) { - STRING *old_title = rc->title; - if (rrd_flag_is_archived(rc) && !rrd_flag_is_archived(rc_new)) - rc->title = string_dup(rc_new->title); - else - rc->title = string_2way_merge(rc->title, rc_new->title); - string_freez(old_title); - rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - if(rc->units != rc_new->units) { - STRING *old_units = rc->units; - rc->units = string_dup(rc_new->units); - string_freez(old_units); - rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - if(rc->family != rc_new->family) { - STRING *old_family = rc->family; - if (rrd_flag_is_archived(rc) && !rrd_flag_is_archived(rc_new)) - rc->family = string_dup(rc_new->family); - else - rc->family = string_2way_merge(rc->family, rc_new->family); - string_freez(old_family); - rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - if(rc->chart_type != rc_new->chart_type) { - rc->chart_type = rc_new->chart_type; - rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - if(rc->priority != rc_new->priority) { - rc->priority = rc_new->priority; - rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - - rrd_flag_set(rc, rc_new->flags & RRD_FLAGS_ALLOWED_EXTERNALLY_ON_NEW_OBJECTS); // no need for atomics on rc_new - - if(rrd_flag_is_collected(rc) && rrd_flag_is_archived(rc)) - rrd_flag_set_collected(rc); - - if(rrd_flag_is_updated(rc)) - rrd_flag_set(rc, RRD_FLAG_UPDATE_REASON_UPDATED_OBJECT); - - rrdcontext_unlock(rc); - - // free the resources of the new one - rrdcontext_freez(rc_new); - - // the react callback will continue from here - return rrd_flag_is_updated(rc); -} - -static void rrdcontext_react_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *rrdhost __maybe_unused) { - RRDCONTEXT *rc = (RRDCONTEXT *)value; - rrdcontext_trigger_updates(rc, __FUNCTION__ ); -} - -static void rrdcontext_trigger_updates(RRDCONTEXT *rc, const char *function) { - if(rrd_flag_is_updated(rc) || !rrd_flag_check(rc, RRD_FLAG_LIVE_RETENTION)) - rrdcontext_queue_for_post_processing(rc, function, rc->flags); -} - -static void rrdcontext_hub_queue_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *context, void *nothing __maybe_unused) { - RRDCONTEXT *rc = context; - rrd_flag_set(rc, RRD_FLAG_QUEUED_FOR_HUB); - rc->queue.queued_ut = now_realtime_usec(); - rc->queue.queued_flags = rrd_flags_get(rc); -} - -static void rrdcontext_hub_queue_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *context, void *nothing __maybe_unused) { - RRDCONTEXT *rc = context; - rrd_flag_clear(rc, RRD_FLAG_QUEUED_FOR_HUB); -} - -static bool rrdcontext_hub_queue_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *context, void *new_context __maybe_unused, void *nothing __maybe_unused) { - // context and new_context are the same - // we just need to update the timings - RRDCONTEXT *rc = context; - rrd_flag_set(rc, RRD_FLAG_QUEUED_FOR_HUB); - rc->queue.queued_ut = now_realtime_usec(); - rc->queue.queued_flags |= rrd_flags_get(rc); - - return true; -} - -static void rrdcontext_post_processing_queue_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *context, void *nothing __maybe_unused) { - RRDCONTEXT *rc = context; - rrd_flag_set(rc, RRD_FLAG_QUEUED_FOR_PP); - rc->pp.queued_flags = rc->flags; - rc->pp.queued_ut = now_realtime_usec(); -} - -static void rrdcontext_post_processing_queue_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *context, void *nothing __maybe_unused) { - RRDCONTEXT *rc = context; - rrd_flag_clear(rc, RRD_FLAG_QUEUED_FOR_PP); - rc->pp.dequeued_ut = now_realtime_usec(); -} - -static bool rrdcontext_post_processing_queue_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *context, void *new_context __maybe_unused, void *nothing __maybe_unused) { - RRDCONTEXT *rc = context; - bool changed = false; - - if(!(rc->flags & RRD_FLAG_QUEUED_FOR_PP)) { - rrd_flag_set(rc, RRD_FLAG_QUEUED_FOR_PP); - changed = true; - } - - if(rc->pp.queued_flags != rc->flags) { - rc->pp.queued_flags |= rc->flags; - changed = true; - } - - return changed; -} - -void rrdhost_create_rrdcontexts(RRDHOST *host) { - if(unlikely(!host)) return; - if(likely(host->rrdctx)) return; - - 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_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_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); -} - -void rrdhost_destroy_rrdcontexts(RRDHOST *host) { - if(unlikely(!host)) return; - if(unlikely(!host->rrdctx)) return; - - DICTIONARY *old; - - if(host->rrdctx_hub_queue) { - old = (DICTIONARY *)host->rrdctx_hub_queue; - host->rrdctx_hub_queue = NULL; - - RRDCONTEXT *rc; - dfe_start_write(old, rc) { - dictionary_del(old, string2str(rc->id)); - } - dfe_done(rc); - dictionary_destroy(old); - } - - if(host->rrdctx_post_processing_queue) { - old = (DICTIONARY *)host->rrdctx_post_processing_queue; - host->rrdctx_post_processing_queue = NULL; - - RRDCONTEXT *rc; - dfe_start_write(old, rc) { - dictionary_del(old, string2str(rc->id)); - } - dfe_done(rc); - dictionary_destroy(old); - } - - old = (DICTIONARY *)host->rrdctx; - host->rrdctx = NULL; - dictionary_destroy(old); -} - -// ---------------------------------------------------------------------------- -// public API - -void rrdcontext_updated_rrddim(RRDDIM *rd) { - rrdmetric_from_rrddim(rd); -} - -void rrdcontext_removed_rrddim(RRDDIM *rd) { - rrdmetric_rrddim_is_freed(rd); -} - -void rrdcontext_updated_rrddim_algorithm(RRDDIM *rd) { - rrdmetric_updated_rrddim_flags(rd); -} - -void rrdcontext_updated_rrddim_multiplier(RRDDIM *rd) { - rrdmetric_updated_rrddim_flags(rd); -} - -void rrdcontext_updated_rrddim_divisor(RRDDIM *rd) { - rrdmetric_updated_rrddim_flags(rd); -} - -void rrdcontext_updated_rrddim_flags(RRDDIM *rd) { - rrdmetric_updated_rrddim_flags(rd); -} - -void rrdcontext_collected_rrddim(RRDDIM *rd) { - rrdmetric_collected_rrddim(rd); -} - -void rrdcontext_updated_rrdset(RRDSET *st) { - rrdinstance_from_rrdset(st); -} - -void rrdcontext_removed_rrdset(RRDSET *st) { - rrdinstance_rrdset_is_freed(st); -} - -void rrdcontext_updated_retention_rrdset(RRDSET *st) { - rrdinstance_rrdset_has_updated_retention(st); -} - -void rrdcontext_updated_rrdset_name(RRDSET *st) { - rrdinstance_updated_rrdset_name(st); -} - -void rrdcontext_updated_rrdset_flags(RRDSET *st) { - rrdinstance_updated_rrdset_flags(st); -} - -void rrdcontext_collected_rrdset(RRDSET *st) { - rrdinstance_collected_rrdset(st); -} - -void rrdcontext_host_child_connected(RRDHOST *host) { - (void)host; - - // no need to do anything here - ; -} - -int rrdcontext_find_dimension_uuid(RRDSET *st, const char *id, uuid_t *store_uuid) { - if(!st->rrdhost) return 1; - if(!st->context) return 2; - - RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item((DICTIONARY *)st->rrdhost->rrdctx, string2str(st->context)); - if(!rca) return 3; - - RRDCONTEXT *rc = rrdcontext_acquired_value(rca); - - RRDINSTANCE_ACQUIRED *ria = (RRDINSTANCE_ACQUIRED *)dictionary_get_and_acquire_item(rc->rrdinstances, string2str(st->id)); - if(!ria) { - rrdcontext_release(rca); - return 4; - } - - RRDINSTANCE *ri = rrdinstance_acquired_value(ria); - - RRDMETRIC_ACQUIRED *rma = (RRDMETRIC_ACQUIRED *)dictionary_get_and_acquire_item(ri->rrdmetrics, id); - if(!rma) { - rrdinstance_release(ria); - rrdcontext_release(rca); - return 5; - } - - RRDMETRIC *rm = rrdmetric_acquired_value(rma); - - uuid_copy(*store_uuid, rm->uuid); - - rrdmetric_release(rma); - rrdinstance_release(ria); - rrdcontext_release(rca); - return 0; -} - -int rrdcontext_find_chart_uuid(RRDSET *st, uuid_t *store_uuid) { - if(!st->rrdhost) return 1; - if(!st->context) return 2; - - RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item((DICTIONARY *)st->rrdhost->rrdctx, string2str(st->context)); - if(!rca) return 3; - - RRDCONTEXT *rc = rrdcontext_acquired_value(rca); - - RRDINSTANCE_ACQUIRED *ria = (RRDINSTANCE_ACQUIRED *)dictionary_get_and_acquire_item(rc->rrdinstances, string2str(st->id)); - if(!ria) { - rrdcontext_release(rca); - return 4; - } - - RRDINSTANCE *ri = rrdinstance_acquired_value(ria); - uuid_copy(*store_uuid, ri->uuid); - - rrdinstance_release(ria); - rrdcontext_release(rca); - return 0; -} - -void rrdcontext_host_child_disconnected(RRDHOST *host) { - rrdcontext_recalculate_host_retention(host, RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD, false); -} - -static usec_t rrdcontext_next_db_rotation_ut = 0; -void rrdcontext_db_rotation(void) { - // called when the db rotates its database - rrdcontext_next_db_rotation_ut = now_realtime_usec() + FULL_RETENTION_SCAN_DELAY_AFTER_DB_ROTATION_SECS * USEC_PER_SEC; -} - -int rrdcontext_foreach_instance_with_rrdset_in_context(RRDHOST *host, const char *context, int (*callback)(RRDSET *st, void *data), void *data) { - if(unlikely(!host || !context || !*context || !callback)) - return -1; - - RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item((DICTIONARY *)host->rrdctx, context); - if(unlikely(!rca)) return -1; - - RRDCONTEXT *rc = rrdcontext_acquired_value(rca); - if(unlikely(!rc)) return -1; - - int ret = 0; - RRDINSTANCE *ri; - dfe_start_read(rc->rrdinstances, ri) { - if(ri->rrdset) { - int r = callback(ri->rrdset, data); - if(r >= 0) ret += r; - else { - ret = r; - break; - } - } - } - dfe_done(ri); - - rrdcontext_release(rca); - - return ret; -} - -// ---------------------------------------------------------------------------- -// ACLK interface - -static bool rrdhost_check_our_claim_id(const char *claim_id) { - if(!localhost->aclk_state.claimed_id) return false; - return (strcasecmp(claim_id, localhost->aclk_state.claimed_id) == 0) ? true : false; -} - -static RRDHOST *rrdhost_find_by_node_id(const char *node_id) { - uuid_t uuid; - if (uuid_parse(node_id, uuid)) - return NULL; - - RRDHOST *host = NULL; - - rrd_rdlock(); - rrdhost_foreach_read(host) { - if(!host->node_id) continue; - - if(uuid_compare(uuid, *host->node_id) == 0) - break; - } - rrd_unlock(); - - return host; -} - -void rrdcontext_hub_checkpoint_command(void *ptr) { - struct ctxs_checkpoint *cmd = ptr; - - if(!rrdhost_check_our_claim_id(cmd->claim_id)) { - error("RRDCONTEXT: received checkpoint command for claim_id '%s', node id '%s', but this is not our claim id. Ours '%s', received '%s'. Ignoring command.", - cmd->claim_id, cmd->node_id, - localhost->aclk_state.claimed_id?localhost->aclk_state.claimed_id:"NOT SET", - cmd->claim_id); - - return; - } - - RRDHOST *host = rrdhost_find_by_node_id(cmd->node_id); - if(!host) { - error("RRDCONTEXT: received checkpoint command for claim id '%s', node id '%s', but there is no node with such node id here. Ignoring command.", - cmd->claim_id, cmd->node_id); - - return; - } - - if(rrdhost_flag_check(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS)) { - info("RRDCONTEXT: received checkpoint command for claim id '%s', node id '%s', while node '%s' has an active context streaming.", - cmd->claim_id, cmd->node_id, rrdhost_hostname(host)); - - // disable it temporarily, so that our worker will not attempt to send messages in parallel - rrdhost_flag_clear(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS); - } - - uint64_t our_version_hash = rrdcontext_version_hash(host); - - if(cmd->version_hash != our_version_hash) { - error("RRDCONTEXT: received version hash %"PRIu64" for host '%s', does not match our version hash %"PRIu64". Sending snapshot of all contexts.", - cmd->version_hash, rrdhost_hostname(host), our_version_hash); - -#ifdef ENABLE_ACLK - // prepare the snapshot - char uuid[UUID_STR_LEN]; - uuid_unparse_lower(*host->node_id, uuid); - contexts_snapshot_t bundle = contexts_snapshot_new(cmd->claim_id, uuid, our_version_hash); - - // do a deep scan on every metric of the host to make sure all our data are updated - rrdcontext_recalculate_host_retention(host, RRD_FLAG_NONE, false); - - // calculate version hash and pack all the messages together in one go - our_version_hash = rrdcontext_version_hash_with_callback(host, rrdcontext_message_send_unsafe, true, bundle); - - // update the version - contexts_snapshot_set_version(bundle, our_version_hash); - - // send it - aclk_send_contexts_snapshot(bundle); -#endif - } - - internal_error(true, "RRDCONTEXT: host '%s' enabling streaming of contexts", rrdhost_hostname(host)); - rrdhost_flag_set(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS); - char node_str[UUID_STR_LEN]; - uuid_unparse_lower(*host->node_id, node_str); - log_access("ACLK REQ [%s (%s)]: STREAM CONTEXTS ENABLED", node_str, rrdhost_hostname(host)); -} - -void rrdcontext_hub_stop_streaming_command(void *ptr) { - struct stop_streaming_ctxs *cmd = ptr; - - if(!rrdhost_check_our_claim_id(cmd->claim_id)) { - error("RRDCONTEXT: received stop streaming command for claim_id '%s', node id '%s', but this is not our claim id. Ours '%s', received '%s'. Ignoring command.", - cmd->claim_id, cmd->node_id, - localhost->aclk_state.claimed_id?localhost->aclk_state.claimed_id:"NOT SET", - cmd->claim_id); - - return; - } - - RRDHOST *host = rrdhost_find_by_node_id(cmd->node_id); - if(!host) { - error("RRDCONTEXT: received stop streaming command for claim id '%s', node id '%s', but there is no node with such node id here. Ignoring command.", - cmd->claim_id, cmd->node_id); - - return; - } - - if(!rrdhost_flag_check(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS)) { - error("RRDCONTEXT: received stop streaming command for claim id '%s', node id '%s', but node '%s' does not have active context streaming. Ignoring command.", - cmd->claim_id, cmd->node_id, rrdhost_hostname(host)); - - return; - } - - internal_error(true, "RRDCONTEXT: host '%s' disabling streaming of contexts", rrdhost_hostname(host)); - rrdhost_flag_clear(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS); -} - -// ---------------------------------------------------------------------------- -// web API - -struct rrdcontext_to_json { - BUFFER *wb; - RRDCONTEXT_TO_JSON_OPTIONS options; - time_t after; - time_t before; - SIMPLE_PATTERN *chart_label_key; - SIMPLE_PATTERN *chart_labels_filter; - SIMPLE_PATTERN *chart_dimensions; - size_t written; - time_t now; - time_t combined_first_time_s; - time_t combined_last_time_s; - RRD_FLAGS combined_flags; -}; - -static inline int rrdmetric_to_json_callback(const DICTIONARY_ITEM *item, void *value, void *data) { - const char *id = dictionary_acquired_item_name(item); - struct rrdcontext_to_json * t = data; - RRDMETRIC *rm = value; - BUFFER *wb = t->wb; - RRDCONTEXT_TO_JSON_OPTIONS options = t->options; - time_t after = t->after; - time_t before = t->before; - - if(unlikely(rrd_flag_is_deleted(rm) && !(options & RRDCONTEXT_OPTION_SHOW_DELETED))) - return 0; - - if(after && (!rm->last_time_s || after > rm->last_time_s)) - return 0; - - if(before && (!rm->first_time_s || before < rm->first_time_s)) - return 0; - - if(t->chart_dimensions - && !simple_pattern_matches(t->chart_dimensions, string2str(rm->id)) - && !simple_pattern_matches(t->chart_dimensions, string2str(rm->name))) - return 0; - - if(t->written) { - buffer_strcat(wb, ",\n"); - 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_s = rm->first_time_s; - t->combined_last_time_s = rm->last_time_s; - t->combined_flags = rrd_flags_get(rm); - } - - buffer_sprintf(wb, "\t\t\t\t\t\t\"%s\": {", id); - - if(options & RRDCONTEXT_OPTION_SHOW_UUIDS) { - char uuid[UUID_STR_LEN]; - uuid_unparse(rm->uuid, uuid); - buffer_sprintf(wb, "\n\t\t\t\t\t\t\t\"uuid\":\"%s\",", uuid); - } - - buffer_sprintf(wb, - "\n\t\t\t\t\t\t\t\"name\":\"%s\"" - ",\n\t\t\t\t\t\t\t\"first_time_t\":%lld" - ",\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_s - , rrd_flag_is_collected(rm) ? (long long)t->now : (long long)rm->last_time_s - , rrd_flag_is_collected(rm) ? "true" : "false" - ); - - if(options & RRDCONTEXT_OPTION_SHOW_DELETED) { - buffer_sprintf(wb, - ",\n\t\t\t\t\t\t\t\"deleted\":%s" - , rrd_flag_is_deleted(rm) ? "true" : "false" - ); - } - - if(options & RRDCONTEXT_OPTION_SHOW_FLAGS) { - buffer_strcat(wb, ",\n\t\t\t\t\t\t\t\"flags\":\""); - rrd_flags_to_buffer(rrd_flags_get(rm), wb); - buffer_strcat(wb, "\""); - } - - buffer_strcat(wb, "\n\t\t\t\t\t\t}"); - t->written++; - return 1; -} - -static inline int rrdinstance_to_json_callback(const DICTIONARY_ITEM *item, void *value, void *data) { - const char *id = dictionary_acquired_item_name(item); - - struct rrdcontext_to_json *t_parent = data; - RRDINSTANCE *ri = value; - BUFFER *wb = t_parent->wb; - RRDCONTEXT_TO_JSON_OPTIONS options = t_parent->options; - time_t after = t_parent->after; - time_t before = t_parent->before; - bool has_filter = t_parent->chart_label_key || t_parent->chart_labels_filter || t_parent->chart_dimensions; - - if(unlikely(rrd_flag_is_deleted(ri) && !(options & RRDCONTEXT_OPTION_SHOW_DELETED))) - return 0; - - if(after && (!ri->last_time_s || after > ri->last_time_s)) - return 0; - - 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')) - return 0; - - if(t_parent->chart_labels_filter && !rrdlabels_match_simple_pattern_parsed(ri->rrdlabels, t_parent->chart_labels_filter, ':')) - return 0; - - 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, &netdata_buffers_statistics.buffers_api); - - struct rrdcontext_to_json t_metrics = { - .wb = wb_metrics, - .options = options, - .chart_label_key = t_parent->chart_label_key, - .chart_labels_filter = t_parent->chart_labels_filter, - .chart_dimensions = t_parent->chart_dimensions, - .after = after, - .before = before, - .written = 0, - .now = t_parent->now, - }; - dictionary_walkthrough_read(ri->rrdmetrics, rrdmetric_to_json_callback, &t_metrics); - - if(has_filter && !t_metrics.written) { - buffer_free(wb_metrics); - return 0; - } - - 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_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_s = first_time_s; - t_parent->combined_last_time_s = last_time_s; - t_parent->combined_flags = flags; - } - - buffer_sprintf(wb, "\t\t\t\t\"%s\": {", id); - - if(options & RRDCONTEXT_OPTION_SHOW_UUIDS) { - char uuid[UUID_STR_LEN]; - uuid_unparse(ri->uuid, uuid); - buffer_sprintf(wb,"\n\t\t\t\t\t\"uuid\":\"%s\",", uuid); - } - - buffer_sprintf(wb, - "\n\t\t\t\t\t\"name\":\"%s\"" - ",\n\t\t\t\t\t\"context\":\"%s\"" - ",\n\t\t\t\t\t\"title\":\"%s\"" - ",\n\t\t\t\t\t\"units\":\"%s\"" - ",\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\":%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" - , string2str(ri->name) - , string2str(ri->rc->id) - , string2str(ri->title) - , string2str(ri->units) - , string2str(ri->family) - , rrdset_type_name(ri->chart_type) - , ri->priority - , 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" - ); - - if(options & RRDCONTEXT_OPTION_SHOW_DELETED) { - buffer_sprintf(wb, - ",\n\t\t\t\t\t\"deleted\":%s" - , rrd_flag_is_deleted(ri) ? "true" : "false" - ); - } - - if(options & RRDCONTEXT_OPTION_SHOW_FLAGS) { - buffer_strcat(wb, ",\n\t\t\t\t\t\"flags\":\""); - rrd_flags_to_buffer(rrd_flags_get(ri), wb); - buffer_strcat(wb, "\""); - } - - if(options & RRDCONTEXT_OPTION_SHOW_LABELS && ri->rrdlabels && dictionary_entries(ri->rrdlabels)) { - buffer_sprintf(wb, ",\n\t\t\t\t\t\"labels\": {\n"); - rrdlabels_to_buffer(ri->rrdlabels, wb, "\t\t\t\t\t\t", ":", "\"", ",\n", NULL, NULL, NULL, NULL); - buffer_strcat(wb, "\n\t\t\t\t\t}"); - } - - if(wb_metrics) { - buffer_sprintf(wb, ",\n\t\t\t\t\t\"dimensions\": {"); - buffer_fast_strcat(wb, buffer_tostring(wb_metrics), buffer_strlen(wb_metrics)); - buffer_strcat(wb, "\n\t\t\t\t\t}"); - - buffer_free(wb_metrics); - } - - buffer_strcat(wb, "\n\t\t\t\t}"); - t_parent->written++; - return 1; -} - -static inline int rrdcontext_to_json_callback(const DICTIONARY_ITEM *item, void *value, void *data) { - const char *id = dictionary_acquired_item_name(item); - struct rrdcontext_to_json *t_parent = data; - RRDCONTEXT *rc = value; - BUFFER *wb = t_parent->wb; - RRDCONTEXT_TO_JSON_OPTIONS options = t_parent->options; - time_t after = t_parent->after; - time_t before = t_parent->before; - bool has_filter = t_parent->chart_label_key || t_parent->chart_labels_filter || t_parent->chart_dimensions; - - if(unlikely(rrd_flag_check(rc, RRD_FLAG_HIDDEN) && !(options & RRDCONTEXT_OPTION_SHOW_HIDDEN))) - return 0; - - if(unlikely(rrd_flag_is_deleted(rc) && !(options & RRDCONTEXT_OPTION_SHOW_DELETED))) - return 0; - - if(options & RRDCONTEXT_OPTION_DEEPSCAN) - rrdcontext_recalculate_context_retention(rc, RRD_FLAG_NONE, false); - - if(after && (!rc->last_time_s || after > rc->last_time_s)) - return 0; - - if(before && (!rc->first_time_s || before < rc->first_time_s)) - return 0; - - 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; - if((options & (RRDCONTEXT_OPTION_SHOW_LABELS|RRDCONTEXT_OPTION_SHOW_INSTANCES|RRDCONTEXT_OPTION_SHOW_METRICS)) - || t_parent->chart_label_key - || t_parent->chart_labels_filter - || t_parent->chart_dimensions) { - - wb_instances = buffer_create(4096, &netdata_buffers_statistics.buffers_api); - - struct rrdcontext_to_json t_instances = { - .wb = wb_instances, - .options = options, - .chart_label_key = t_parent->chart_label_key, - .chart_labels_filter = t_parent->chart_labels_filter, - .chart_dimensions = t_parent->chart_dimensions, - .after = after, - .before = before, - .written = 0, - .now = t_parent->now, - }; - dictionary_walkthrough_read(rc->rrdinstances, rrdinstance_to_json_callback, &t_instances); - - if(has_filter && !t_instances.written) { - buffer_free(wb_instances); - return 0; - } - - first_time_s = t_instances.combined_first_time_s; - last_time_s = t_instances.combined_last_time_s; - flags = t_instances.combined_flags; - } - - if(t_parent->written) - buffer_strcat(wb, ",\n"); - else - buffer_strcat(wb, "\n"); - - if(options & RRDCONTEXT_OPTION_SKIP_ID) - buffer_sprintf(wb, "\t\t\{"); - else - buffer_sprintf(wb, "\t\t\"%s\": {", id); - - rrdcontext_lock(rc); - - buffer_sprintf(wb, - "\n\t\t\t\"title\":\"%s\"" - ",\n\t\t\t\"units\":\"%s\"" - ",\n\t\t\t\"family\":\"%s\"" - ",\n\t\t\t\"chart_type\":\"%s\"" - ",\n\t\t\t\"priority\":%u" - ",\n\t\t\t\"first_time_t\":%lld" - ",\n\t\t\t\"last_time_t\":%lld" - ",\n\t\t\t\"collected\":%s" - , string2str(rc->title) - , string2str(rc->units) - , string2str(rc->family) - , rrdset_type_name(rc->chart_type) - , rc->priority - , (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" - ); - - if(options & RRDCONTEXT_OPTION_SHOW_DELETED) { - buffer_sprintf(wb, - ",\n\t\t\t\"deleted\":%s" - , rrd_flag_is_deleted(rc) ? "true" : "false" - ); - } - - if(options & RRDCONTEXT_OPTION_SHOW_FLAGS) { - buffer_strcat(wb, ",\n\t\t\t\"flags\":\""); - rrd_flags_to_buffer(rrd_flags_get(rc), wb); - buffer_strcat(wb, "\""); - } - - if(options & RRDCONTEXT_OPTION_SHOW_QUEUED) { - buffer_strcat(wb, ",\n\t\t\t\"queued_reasons\":\""); - rrd_reasons_to_buffer(rc->queue.queued_flags, wb); - buffer_strcat(wb, "\""); - - buffer_sprintf(wb, - ",\n\t\t\t\"last_queued\":%llu" - ",\n\t\t\t\"scheduled_dispatch\":%llu" - ",\n\t\t\t\"last_dequeued\":%llu" - ",\n\t\t\t\"dispatches\":%zu" - ",\n\t\t\t\"hub_version\":%"PRIu64"" - ",\n\t\t\t\"version\":%"PRIu64"" - , rc->queue.queued_ut / USEC_PER_SEC - , rc->queue.scheduled_dispatch_ut / USEC_PER_SEC - , rc->queue.dequeued_ut / USEC_PER_SEC - , rc->queue.dispatches - , rc->hub.version - , rc->version - ); - - buffer_strcat(wb, ",\n\t\t\t\"pp_reasons\":\""); - rrd_reasons_to_buffer(rc->pp.queued_flags, wb); - buffer_strcat(wb, "\""); - - buffer_sprintf(wb, - ",\n\t\t\t\"pp_last_queued\":%llu" - ",\n\t\t\t\"pp_last_dequeued\":%llu" - ",\n\t\t\t\"pp_executed\":%zu" - , rc->pp.queued_ut / USEC_PER_SEC - , rc->pp.dequeued_ut / USEC_PER_SEC - , rc->pp.executions - ); - } - - rrdcontext_unlock(rc); - - if(wb_instances) { - buffer_sprintf(wb, ",\n\t\t\t\"charts\": {"); - buffer_fast_strcat(wb, buffer_tostring(wb_instances), buffer_strlen(wb_instances)); - buffer_strcat(wb, "\n\t\t\t}"); - - buffer_free(wb_instances); - } - - buffer_strcat(wb, "\n\t\t}"); - t_parent->written++; - return 1; -} - -int rrdcontext_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before, RRDCONTEXT_TO_JSON_OPTIONS options, const char *context, SIMPLE_PATTERN *chart_label_key, SIMPLE_PATTERN *chart_labels_filter, SIMPLE_PATTERN *chart_dimensions) { - if(!host->rrdctx) { - error("%s(): request for host '%s' that does not have rrdcontexts initialized.", __FUNCTION__, rrdhost_hostname(host)); - return HTTP_RESP_NOT_FOUND; - } - - RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item((DICTIONARY *)host->rrdctx, context); - if(!rca) return HTTP_RESP_NOT_FOUND; - - RRDCONTEXT *rc = rrdcontext_acquired_value(rca); - - if(after != 0 && before != 0) - rrdr_relative_window_to_absolute(&after, &before); - - struct rrdcontext_to_json t_contexts = { - .wb = wb, - .options = options|RRDCONTEXT_OPTION_SKIP_ID, - .chart_label_key = chart_label_key, - .chart_labels_filter = chart_labels_filter, - .chart_dimensions = chart_dimensions, - .after = after, - .before = before, - .written = 0, - .now = now_realtime_sec(), - }; - rrdcontext_to_json_callback((DICTIONARY_ITEM *)rca, rc, &t_contexts); - - rrdcontext_release(rca); - - if(!t_contexts.written) - return HTTP_RESP_NOT_FOUND; - - return HTTP_RESP_OK; -} - -int rrdcontexts_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before, RRDCONTEXT_TO_JSON_OPTIONS options, SIMPLE_PATTERN *chart_label_key, SIMPLE_PATTERN *chart_labels_filter, SIMPLE_PATTERN *chart_dimensions) { - if(!host->rrdctx) { - error("%s(): request for host '%s' that does not have rrdcontexts initialized.", __FUNCTION__, rrdhost_hostname(host)); - return HTTP_RESP_NOT_FOUND; - } - - char node_uuid[UUID_STR_LEN] = ""; - - if(host->node_id) - uuid_unparse(*host->node_id, node_uuid); - - if(after != 0 && before != 0) - rrdr_relative_window_to_absolute(&after, &before); - - buffer_sprintf(wb, "{\n" - "\t\"hostname\": \"%s\"" - ",\n\t\"machine_guid\": \"%s\"" - ",\n\t\"node_id\": \"%s\"" - ",\n\t\"claim_id\": \"%s\"" - , rrdhost_hostname(host) - , host->machine_guid - , node_uuid - , host->aclk_state.claimed_id ? host->aclk_state.claimed_id : "" - ); - - if(options & RRDCONTEXT_OPTION_SHOW_LABELS) { - buffer_sprintf(wb, ",\n\t\"host_labels\": {\n"); - rrdlabels_to_buffer(host->rrdlabels, wb, "\t\t", ":", "\"", ",\n", NULL, NULL, NULL, NULL); - buffer_strcat(wb, "\n\t}"); - } - - buffer_sprintf(wb, ",\n\t\"contexts\": {"); - struct rrdcontext_to_json t_contexts = { - .wb = wb, - .options = options, - .chart_label_key = chart_label_key, - .chart_labels_filter = chart_labels_filter, - .chart_dimensions = chart_dimensions, - .after = after, - .before = before, - .written = 0, - .now = now_realtime_sec(), - }; - dictionary_walkthrough_read((DICTIONARY *)host->rrdctx, rrdcontext_to_json_callback, &t_contexts); - - // close contexts, close main - buffer_strcat(wb, "\n\t}\n}"); - - return HTTP_RESP_OK; -} - -// ---------------------------------------------------------------------------- -// weights API - -static void metric_entry_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) { - struct metric_entry *t = value; - t->rca = rrdcontext_acquired_dup(t->rca); - t->ria = rrdinstance_acquired_dup(t->ria); - t->rma = rrdmetric_acquired_dup(t->rma); -} -static void metric_entry_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) { - struct metric_entry *t = value; - rrdcontext_release(t->rca); - rrdinstance_release(t->ria); - rrdmetric_release(t->rma); -} -static bool metric_entry_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value __maybe_unused, void *new_value __maybe_unused, void *data __maybe_unused) { - fatal("RRDCONTEXT: %s() detected a conflict on a metric pointer!", __FUNCTION__); - return false; -} - -DICTIONARY *rrdcontext_all_metrics_to_dict(RRDHOST *host, SIMPLE_PATTERN *contexts) { - if(!host || !host->rrdctx) - return NULL; - - 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); - - RRDCONTEXT *rc; - dfe_start_reentrant((DICTIONARY *)host->rrdctx, rc) { - if(rrd_flag_is_deleted(rc)) - continue; - - if(contexts && !simple_pattern_matches(contexts, string2str(rc->id))) - continue; - - RRDINSTANCE *ri; - dfe_start_read(rc->rrdinstances, ri) { - if(rrd_flag_is_deleted(ri)) - continue; - - RRDMETRIC *rm; - dfe_start_read(ri->rrdmetrics, rm) { - if(rrd_flag_is_deleted(rm)) - continue; - - struct metric_entry tmp = { - .rca = (RRDCONTEXT_ACQUIRED *)rc_dfe.item, - .ria = (RRDINSTANCE_ACQUIRED *)ri_dfe.item, - .rma = (RRDMETRIC_ACQUIRED *)rm_dfe.item, - }; - - char buffer[20 + 1]; - ssize_t len = snprintfz(buffer, 20, "%p", rm); - dictionary_set_advanced(dict, buffer, len + 1, &tmp, sizeof(struct metric_entry), NULL); - } - dfe_done(rm); - } - dfe_done(ri); - } - dfe_done(rc); - - return dict; -} - -// ---------------------------------------------------------------------------- -// query API - -typedef struct query_target_locals { - time_t start_s; - - QUERY_TARGET *qt; - - RRDSET *st; - - const char *hosts; - const char *contexts; - const char *charts; - const char *dimensions; - const char *chart_label_key; - const char *charts_labels_filter; - - long long after; - long long before; - bool match_ids; - bool match_names; - - RRDHOST *host; - RRDCONTEXT_ACQUIRED *rca; - RRDINSTANCE_ACQUIRED *ria; - - size_t metrics_skipped_due_to_not_matching_timeframe; -} QUERY_TARGET_LOCALS; - -static __thread QUERY_TARGET thread_query_target = {}; -void query_target_release(QUERY_TARGET *qt) { - if(unlikely(!qt)) return; - if(unlikely(!qt->used)) return; - - simple_pattern_free(qt->hosts.pattern); - qt->hosts.pattern = NULL; - - simple_pattern_free(qt->contexts.pattern); - qt->contexts.pattern = NULL; - - simple_pattern_free(qt->instances.pattern); - qt->instances.pattern = NULL; - - simple_pattern_free(qt->instances.chart_label_key_pattern); - qt->instances.chart_label_key_pattern = NULL; - - simple_pattern_free(qt->instances.charts_labels_filter_pattern); - qt->instances.charts_labels_filter_pattern = NULL; - - simple_pattern_free(qt->query.pattern); - qt->query.pattern = NULL; - - // release the query - for(size_t i = 0, used = qt->query.used; i < used ;i++) { - string_freez(qt->query.array[i].dimension.id); - qt->query.array[i].dimension.id = NULL; - - string_freez(qt->query.array[i].dimension.name); - qt->query.array[i].dimension.name = NULL; - - string_freez(qt->query.array[i].chart.id); - qt->query.array[i].chart.id = NULL; - - 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; - } - } - } - - // release the metrics - for(size_t i = 0, used = qt->metrics.used; i < used ;i++) { - rrdmetric_release(qt->metrics.array[i]); - qt->metrics.array[i] = NULL; - } - - // release the instances - for(size_t i = 0, used = qt->instances.used; i < used ;i++) { - rrdinstance_release(qt->instances.array[i]); - qt->instances.array[i] = NULL; - } - - // release the contexts - for(size_t i = 0, used = qt->contexts.used; i < used ;i++) { - rrdcontext_release(qt->contexts.array[i]); - qt->contexts.array[i] = NULL; - } - - // release the hosts - for(size_t i = 0, used = qt->hosts.used; i < used ;i++) { - qt->hosts.array[i] = NULL; - } - - qt->query.used = 0; - qt->metrics.used = 0; - qt->instances.used = 0; - qt->contexts.used = 0; - qt->hosts.used = 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) { - QUERY_TARGET *qt = &thread_query_target; - - if(qt->used) - query_target_release(qt); - - __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; - - __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; - - __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; - - __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, - bool queryable_instance) { - QUERY_TARGET *qt = qtl->qt; - - RRDMETRIC *rm = rrdmetric_acquired_value(rma); - if(rrd_flag_is_deleted(rm)) - 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; - 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_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_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_s = (time_t) (qtl->host->db[tier].tier_grouping * ri->update_every_s); - - 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_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_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_s) - common_last_time_s = tier_retention[tier].db_last_time_s; - else - common_last_time_s = MAX(common_last_time_s, tier_retention[tier].db_last_time_s); - - 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_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_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) { - RRDR_DIMENSION_FLAGS options = RRDR_DIMENSION_DEFAULT; - - if (rrd_flag_check(rm, RRD_FLAG_HIDDEN) - || (rm->rrddim && rrddim_option_check(rm->rrddim, RRDDIM_OPTION_HIDDEN))) { - options |= RRDR_DIMENSION_HIDDEN; - options &= ~RRDR_DIMENSION_QUERIED; - } - - if (qt->query.pattern) { - // we have a dimensions pattern - // lets see if this dimension is selected - - if ((qtl->match_ids && simple_pattern_matches(qt->query.pattern, string2str(rm->id))) - || (qtl->match_names && simple_pattern_matches(qt->query.pattern, string2str(rm->name))) - ) { - // it matches the pattern - options |= (RRDR_DIMENSION_QUERIED | RRDR_DIMENSION_NONZERO); - options &= ~RRDR_DIMENSION_HIDDEN; - } - else { - // it does not match the pattern - options |= RRDR_DIMENSION_HIDDEN; - options &= ~RRDR_DIMENSION_QUERIED; - } - } - else { - // we don't have a dimensions pattern - // so this is a selected dimension - // if it is not hidden - if(!(options & RRDR_DIMENSION_HIDDEN)) - options |= RRDR_DIMENSION_QUERIED; - } - - if((options & RRDR_DIMENSION_HIDDEN) && (options & RRDR_DIMENSION_QUERIED)) - options &= ~RRDR_DIMENSION_HIDDEN; - - if(!(options & RRDR_DIMENSION_HIDDEN) || (qt->request.options & RRDR_OPTION_PERCENTAGE)) { - // we have a non-hidden dimension - // let's add it to the query metrics - - if(ri->rrdset) - 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; - 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; - qm->link.rca = qtl->rca; - qm->link.ria = qtl->ria; - qm->link.rma = rma; - - qm->chart.id = string_dup(ri->id); - qm->chart.name = string_dup(ri->name); - - qm->dimension.id = string_dup(rm->id); - qm->dimension.name = string_dup(rm->name); - - 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_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_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; - } - } - else - qtl->metrics_skipped_due_to_not_matching_timeframe++; - - if(release_retention) { - // cleanup anything we allocated to the retention we will not use - for(size_t tier = 0; tier < storage_tiers ;tier++) { - if (tier_retention[tier].db_metric_handle) - tier_retention[tier].eng->api.metric_release(tier_retention[tier].db_metric_handle); - } - } -} - -static void query_target_add_instance(QUERY_TARGET_LOCALS *qtl, RRDINSTANCE_ACQUIRED *ria, bool queryable_instance) { - QUERY_TARGET *qt = qtl->qt; - - RRDINSTANCE *ri = rrdinstance_acquired_value(ria); - if(rrd_flag_is_deleted(ri)) - 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; - 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_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, ':')) || - (qt->instances.charts_labels_filter_pattern && !rrdlabels_match_simple_pattern_parsed(ri->rrdlabels, qt->instances.charts_labels_filter_pattern, ':'))) - queryable_instance = false; - } - - size_t added = 0; - - if(unlikely(qt->request.rma)) { - query_target_add_metric(qtl, qt->request.rma, ri, queryable_instance); - added++; - } - else { - RRDMETRIC *rm; - dfe_start_read(ri->rrdmetrics, rm) { - query_target_add_metric(qtl, (RRDMETRIC_ACQUIRED *) rm_dfe.item, ri, queryable_instance); - added++; - } - dfe_done(rm); - } - - if(!added) { - qt->instances.used--; - rrdinstance_release(ria); - } -} - -static void query_target_add_context(QUERY_TARGET_LOCALS *qtl, RRDCONTEXT_ACQUIRED *rca) { - QUERY_TARGET *qt = qtl->qt; - - RRDCONTEXT *rc = rrdcontext_acquired_value(rca); - if(rrd_flag_is_deleted(rc)) - 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; - 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); - - size_t added = 0; - if(unlikely(qt->request.ria)) { - query_target_add_instance(qtl, qt->request.ria, true); - added++; - } - else if(unlikely(qtl->st && qtl->st->rrdcontext == rca && qtl->st->rrdinstance)) { - query_target_add_instance(qtl, qtl->st->rrdinstance, true); - added++; - } - else { - RRDINSTANCE *ri; - dfe_start_read(rc->rrdinstances, ri) { - bool queryable_instance = false; - if(!qt->instances.pattern - || (qtl->match_ids && simple_pattern_matches(qt->instances.pattern, string2str(ri->id))) - || (qtl->match_names && simple_pattern_matches(qt->instances.pattern, string2str(ri->name))) - ) - queryable_instance = true; - - query_target_add_instance(qtl, (RRDINSTANCE_ACQUIRED *)ri_dfe.item, queryable_instance); - added++; - } - dfe_done(ri); - } - - if(!added) { - qt->contexts.used--; - rrdcontext_release(rca); - } -} - -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; - 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; - - // is the chart given valid? - if(unlikely(qtl->st && (!qtl->st->rrdinstance || !qtl->st->rrdcontext))) { - error("QUERY TARGET: RRDSET '%s' given, because it is not linked to rrdcontext structures. Switching to context query.", rrdset_name(qtl->st)); - - if(!is_valid_sp(qtl->charts)) - qtl->charts = rrdset_name(qtl->st); - - qtl->st = NULL; - } - - size_t added = 0; - if(unlikely(qt->request.rca)) { - query_target_add_context(qtl, qt->request.rca); - added++; - } - else if(unlikely(qtl->st)) { - // single chart data queries - query_target_add_context(qtl, qtl->st->rrdcontext); - added++; - } - else { - // context pattern queries - RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item((DICTIONARY *)qtl->host->rrdctx, qtl->contexts); - if(likely(rca)) { - // we found it! - query_target_add_context(qtl, rca); - rrdcontext_release(rca); - added++; - } - else { - // Probably it is a pattern, we need to search for it... - RRDCONTEXT *rc; - dfe_start_read((DICTIONARY *)qtl->host->rrdctx, rc) { - if(qt->contexts.pattern && !simple_pattern_matches(qt->contexts.pattern, string2str(rc->id))) - continue; - - query_target_add_context(qtl, (RRDCONTEXT_ACQUIRED *)rc_dfe.item); - added++; - } - dfe_done(rc); - } - } - - if(!added) { - qt->hosts.used--; - } -} - -void query_target_generate_name(QUERY_TARGET *qt) { - char options_buffer[100 + 1]; - web_client_api_request_v1_data_options_to_string(options_buffer, 100, qt->request.options); - - char resampling_buffer[20 + 1] = ""; - if(qt->request.resampling_time > 1) - snprintfz(resampling_buffer, 20, "/resampling:%lld", (long long)qt->request.resampling_time); - - char tier_buffer[20 + 1] = ""; - if(qt->request.options & RRDR_OPTION_SELECTED_TIER) - snprintfz(tier_buffer, 20, "/tier:%zu", qt->request.tier); - - if(qt->request.st) - snprintfz(qt->id, MAX_QUERY_TARGET_ID_LENGTH, "chart://host:%s/instance:%s/dimensions:%s/after:%lld/before:%lld/points:%zu/group:%s%s/options:%s%s%s" - , rrdhost_hostname(qt->request.st->rrdhost) - , rrdset_name(qt->request.st) - , (qt->request.dimensions) ? qt->request.dimensions : "*" - , (long long)qt->request.after - , (long long)qt->request.before - , qt->request.points - , web_client_api_request_v1_data_group_to_string(qt->request.group_method) - , qt->request.group_options?qt->request.group_options:"" - , options_buffer - , resampling_buffer - , tier_buffer - ); - else if(qt->request.host && qt->request.rca && qt->request.ria && qt->request.rma) - snprintfz(qt->id, MAX_QUERY_TARGET_ID_LENGTH, "metric://host:%s/context:%s/instance:%s/dimension:%s/after:%lld/before:%lld/points:%zu/group:%s%s/options:%s%s%s" - , rrdhost_hostname(qt->request.host) - , rrdcontext_acquired_id(qt->request.rca) - , rrdinstance_acquired_id(qt->request.ria) - , rrdmetric_acquired_id(qt->request.rma) - , (long long)qt->request.after - , (long long)qt->request.before - , qt->request.points - , web_client_api_request_v1_data_group_to_string(qt->request.group_method) - , qt->request.group_options?qt->request.group_options:"" - , options_buffer - , resampling_buffer - , tier_buffer - ); - else - snprintfz(qt->id, MAX_QUERY_TARGET_ID_LENGTH, "context://host:%s/contexts:%s/instances:%s/dimensions:%s/after:%lld/before:%lld/points:%zu/group:%s%s/options:%s%s%s" - , (qt->request.host) ? rrdhost_hostname(qt->request.host) : ((qt->request.hosts) ? qt->request.hosts : "*") - , (qt->request.contexts) ? qt->request.contexts : "*" - , (qt->request.charts) ? qt->request.charts : "*" - , (qt->request.dimensions) ? qt->request.dimensions : "*" - , (long long)qt->request.after - , (long long)qt->request.before - , qt->request.points - , web_client_api_request_v1_data_group_to_string(qt->request.group_method) - , qt->request.group_options?qt->request.group_options:"" - , options_buffer - , resampling_buffer - , tier_buffer - ); - - json_fix_string(qt->id); -} - -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) - fatal("QUERY TARGET: this query target is already used (%zu queries made with this QUERY_TARGET so far).", qt->queries); - - qt->used = true; - qt->queries++; - - // copy the request into query_thread_target - qt->request = *qtr; - - query_target_generate_name(qt); - qt->window.after = qt->request.after; - qt->window.before = qt->request.before; - rrdr_relative_window_to_absolute(&qt->window.after, &qt->window.before); - - // prepare our local variables - we need these across all these functions - QUERY_TARGET_LOCALS qtl = { - .qt = qt, - .start_s = now_realtime_sec(), - .host = qt->request.host, - .st = qt->request.st, - .hosts = qt->request.hosts, - .contexts = qt->request.contexts, - .charts = qt->request.charts, - .dimensions = qt->request.dimensions, - .chart_label_key = qt->request.chart_label_key, - .charts_labels_filter = qt->request.charts_labels_filter, - }; - - 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; - qt->contexts.pattern = is_valid_sp(qtl.contexts) ? simple_pattern_create(qtl.contexts, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL; - qt->instances.pattern = is_valid_sp(qtl.charts) ? simple_pattern_create(qtl.charts, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL; - qt->query.pattern = is_valid_sp(qtl.dimensions) ? simple_pattern_create(qtl.dimensions, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL; - qt->instances.chart_label_key_pattern = is_valid_sp(qtl.chart_label_key) ? simple_pattern_create(qtl.chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL; - qt->instances.charts_labels_filter_pattern = is_valid_sp(qtl.charts_labels_filter) ? simple_pattern_create(qtl.charts_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL; - - qtl.match_ids = qt->request.options & RRDR_OPTION_MATCH_IDS; - qtl.match_names = qt->request.options & RRDR_OPTION_MATCH_NAMES; - if(likely(!qtl.match_ids && !qtl.match_names)) - qtl.match_ids = qtl.match_names = true; - - // verify that the chart belongs to the host we are interested - if(qtl.st) { - if (!qtl.host) { - // It is NULL, set it ourselves. - qtl.host = qtl.st->rrdhost; - } - else if (unlikely(qtl.host != qtl.st->rrdhost)) { - // Oops! A different host! - error("QUERY TARGET: RRDSET '%s' given does not belong to host '%s'. Switching query host to '%s'", - rrdset_name(qtl.st), rrdhost_hostname(qtl.host), rrdhost_hostname(qtl.st->rrdhost)); - qtl.host = qtl.st->rrdhost; - } - } - - if(qtl.host) { - // single host query - query_target_add_host(&qtl, qtl.host); - qtl.hosts = rrdhost_hostname(qtl.host); - } - else { - // multi host query - rrd_rdlock(); - rrdhost_foreach_read(qtl.host) { - if(!qt->hosts.pattern || simple_pattern_matches(qt->hosts.pattern, rrdhost_hostname(qtl.host))) - query_target_add_host(&qtl, qtl.host); - } - rrd_unlock(); - } - - // make sure everything is good - if(!qt->query.used || !qt->metrics.used || !qt->instances.used || !qt->contexts.used || !qt->hosts.used) { - internal_error( - true - , "QUERY TARGET: query '%s' does not have all the data required. " - "Matched %u hosts, %u contexts, %u instances, %u dimensions, %u metrics to query, " - "%zu metrics skipped because they don't have data in the desired time-frame. " - "Aborting it." - , qt->id - , qt->hosts.used - , qt->contexts.used - , qt->instances.used - , qt->metrics.used - , qt->query.used - , qtl.metrics_skipped_due_to_not_matching_timeframe - ); - - query_target_release(qt); - return NULL; - } - - if(!query_target_calculate_window(qt)) { - query_target_release(qt); - return NULL; - } - - return qt; -} - - -// ---------------------------------------------------------------------------- -// load from SQL - -static void rrdinstance_load_clabel(SQL_CLABEL_DATA *sld, void *data) { - RRDINSTANCE *ri = data; - rrdlabels_add(ri->rrdlabels, sld->label_key, sld->label_value, sld->label_source); -} - -static void rrdinstance_load_dimension(SQL_DIMENSION_DATA *sd, void *data) { - RRDINSTANCE *ri = data; - - RRDMETRIC trm = { - .id = string_strdupz(sd->id), - .name = string_strdupz(sd->name), - .flags = RRD_FLAG_ARCHIVED | RRD_FLAG_UPDATE_REASON_LOAD_SQL, // no need for atomic - }; - if(sd->hidden) trm.flags |= RRD_FLAG_HIDDEN; - - uuid_copy(trm.uuid, sd->dim_id); - - dictionary_set(ri->rrdmetrics, string2str(trm.id), &trm, sizeof(trm)); -} - -static void rrdinstance_load_chart_callback(SQL_CHART_DATA *sc, void *data) { - RRDHOST *host = data; - - RRDCONTEXT tc = { - .id = string_strdupz(sc->context), - .title = string_strdupz(sc->title), - .units = string_strdupz(sc->units), - .family = string_strdupz(sc->family), - .priority = sc->priority, - .chart_type = sc->chart_type, - .flags = RRD_FLAG_ARCHIVED | RRD_FLAG_UPDATE_REASON_LOAD_SQL, // no need for atomics - .rrdhost = host, - }; - - RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_set_and_acquire_item((DICTIONARY *)host->rrdctx, string2str(tc.id), &tc, sizeof(tc)); - RRDCONTEXT *rc = rrdcontext_acquired_value(rca); - - RRDINSTANCE tri = { - .id = string_strdupz(sc->id), - .name = string_strdupz(sc->name), - .title = string_strdupz(sc->title), - .units = string_strdupz(sc->units), - .family = string_strdupz(sc->family), - .chart_type = sc->chart_type, - .priority = sc->priority, - .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); - - RRDINSTANCE_ACQUIRED *ria = (RRDINSTANCE_ACQUIRED *)dictionary_set_and_acquire_item(rc->rrdinstances, sc->id, &tri, sizeof(tri)); - RRDINSTANCE *ri = rrdinstance_acquired_value(ria); - - ctx_get_dimension_list(&ri->uuid, rrdinstance_load_dimension, ri); - ctx_get_label_list(&ri->uuid, rrdinstance_load_clabel, ri); - rrdinstance_trigger_updates(ri, __FUNCTION__ ); - rrdinstance_release(ria); - rrdcontext_release(rca); -} - -static void rrdcontext_load_context_callback(VERSIONED_CONTEXT_DATA *ctx_data, void *data) { - RRDHOST *host = data; - (void)host; - - RRDCONTEXT trc = { - .id = string_strdupz(ctx_data->id), - .flags = RRD_FLAG_ARCHIVED | RRD_FLAG_UPDATE_REASON_LOAD_SQL, // no need for atomics - - // no need to set more data here - // we only need the hub data - - .hub = *ctx_data, - }; - dictionary_set((DICTIONARY *)host->rrdctx, string2str(trc.id), &trc, sizeof(trc)); -} - -void rrdhost_load_rrdcontext_data(RRDHOST *host) { - if(host->rrdctx) return; - - rrdhost_create_rrdcontexts(host); - ctx_get_context_list(&host->host_uuid, rrdcontext_load_context_callback, host); - ctx_get_chart_list(&host->host_uuid, rrdinstance_load_chart_callback, host); - - RRDCONTEXT *rc; - dfe_start_read((DICTIONARY *)host->rrdctx, rc) { - rrdcontext_trigger_updates(rc, __FUNCTION__ ); - } - dfe_done(rc); - - rrdcontext_garbage_collect_single_host(host, false); -} - -// ---------------------------------------------------------------------------- -// version hash calculation - -static uint64_t rrdcontext_version_hash_with_callback( - RRDHOST *host, - void (*callback)(RRDCONTEXT *, bool, void *), - bool snapshot, - void *bundle) { - - if(unlikely(!host || !host->rrdctx)) return 0; - - RRDCONTEXT *rc; - uint64_t hash = 0; - - // loop through all contexts of the host - dfe_start_read((DICTIONARY *)host->rrdctx, rc) { - - rrdcontext_lock(rc); - - if(unlikely(rrd_flag_check(rc, RRD_FLAG_HIDDEN))) { - rrdcontext_unlock(rc); - continue; - } - - if(unlikely(callback)) - callback(rc, snapshot, bundle); - - // skip any deleted contexts - if(unlikely(rrd_flag_is_deleted(rc))) { - rrdcontext_unlock(rc); - continue; - } - - // we use rc->hub.* which has the latest - // metadata we have sent to the hub - - // if a context is currently queued, rc->hub.* does NOT - // reflect the queued changes. rc->hub.* is updated with - // their metadata, after messages are dispatched to hub. - - // when the context is being collected, - // rc->hub.last_time_t is already zero - - hash += rc->hub.version + rc->hub.last_time_s - rc->hub.first_time_s; - - rrdcontext_unlock(rc); - - } - dfe_done(rc); - - return hash; -} - -// ---------------------------------------------------------------------------- -// retention recalculation - -static void rrdcontext_recalculate_context_retention(RRDCONTEXT *rc, RRD_FLAGS reason, bool worker_jobs) { - rrdcontext_post_process_updates(rc, true, reason, worker_jobs); -} - -static void rrdcontext_recalculate_host_retention(RRDHOST *host, RRD_FLAGS reason, bool worker_jobs) { - if(unlikely(!host || !host->rrdctx)) return; - - RRDCONTEXT *rc; - dfe_start_read((DICTIONARY *)host->rrdctx, rc) { - rrdcontext_recalculate_context_retention(rc, reason, worker_jobs); - } - dfe_done(rc); -} - -static void rrdcontext_recalculate_retention_all_hosts(void) { - rrdcontext_next_db_rotation_ut = 0; - rrd_rdlock(); - RRDHOST *host; - rrdhost_foreach_read(host) { - worker_is_busy(WORKER_JOB_RETENTION); - rrdcontext_recalculate_host_retention(host, RRD_FLAG_UPDATE_REASON_DB_ROTATION, true); - } - rrd_unlock(); -} - -// ---------------------------------------------------------------------------- -// garbage collector - -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_s(rm->rrddim); - max_last_time_t = rrddim_last_entry_s(rm->rrddim); - } - else { - RRDHOST *rrdhost = rm->ri->rc->rrdhost; - for (size_t tier = 0; tier < storage_tiers; tier++) { - STORAGE_ENGINE *eng = rrdhost->db[tier].eng; - - time_t first_time_t, last_time_t; - 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; - - if (last_time_t > max_last_time_t) - max_last_time_t = last_time_t; - } - } - } - - if((min_first_time_t == LONG_MAX || min_first_time_t == 0) && max_last_time_t == 0) - return false; - - 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, 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; - } - - // check if retention changed - - 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_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_s && !rm->last_time_s)) - rrd_flag_set_deleted(rm, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); - - rrd_flag_set(rm, RRD_FLAG_LIVE_RETENTION); - - return true; -} - -static inline bool rrdmetric_should_be_deleted(RRDMETRIC *rm) { - if(likely(!rrd_flag_check(rm, RRD_FLAGS_REQUIRED_FOR_DELETIONS))) - return false; - - if(likely(rrd_flag_check(rm, RRD_FLAGS_PREVENTING_DELETIONS))) - return false; - - if(likely(rm->rrddim)) - return false; - - rrdmetric_update_retention(rm); - if(rm->first_time_s || rm->last_time_s) - return false; - - return true; -} - -static inline bool rrdinstance_should_be_deleted(RRDINSTANCE *ri) { - if(likely(!rrd_flag_check(ri, RRD_FLAGS_REQUIRED_FOR_DELETIONS))) - return false; - - if(likely(rrd_flag_check(ri, RRD_FLAGS_PREVENTING_DELETIONS))) - return false; - - if(likely(ri->rrdset)) - return false; - - if(unlikely(dictionary_referenced_items(ri->rrdmetrics) != 0)) - return false; - - if(unlikely(dictionary_entries(ri->rrdmetrics) != 0)) - return false; - - if(ri->first_time_s || ri->last_time_s) - return false; - - return true; -} - -static inline bool rrdcontext_should_be_deleted(RRDCONTEXT *rc) { - if(likely(!rrd_flag_check(rc, RRD_FLAGS_REQUIRED_FOR_DELETIONS))) - return false; - - if(likely(rrd_flag_check(rc, RRD_FLAGS_PREVENTING_DELETIONS))) - return false; - - if(unlikely(dictionary_referenced_items(rc->rrdinstances) != 0)) - return false; - - if(unlikely(dictionary_entries(rc->rrdinstances) != 0)) - return false; - - if(unlikely(rc->first_time_s || rc->last_time_s)) - return false; - - return true; -} - -void rrdcontext_delete_from_sql_unsafe(RRDCONTEXT *rc) { - // we need to refresh the string pointers in rc->hub - // in case the context changed values - rc->hub.id = string2str(rc->id); - rc->hub.title = string2str(rc->title); - rc->hub.units = string2str(rc->units); - rc->hub.family = string2str(rc->family); - - // delete it from SQL - if(ctx_delete_context(&rc->rrdhost->host_uuid, &rc->hub) != 0) - error("RRDCONTEXT: failed to delete context '%s' version %"PRIu64" from SQL.", rc->hub.id, rc->hub.version); -} - -static void rrdcontext_garbage_collect_single_host(RRDHOST *host, bool worker_jobs) { - - internal_error(true, "RRDCONTEXT: garbage collecting context structures of host '%s'", rrdhost_hostname(host)); - - RRDCONTEXT *rc; - dfe_start_reentrant((DICTIONARY *)host->rrdctx, rc) { - if(unlikely(!service_running(SERVICE_CONTEXT))) break; - - if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP); - - rrdcontext_lock(rc); - - RRDINSTANCE *ri; - dfe_start_reentrant(rc->rrdinstances, ri) { - if(unlikely(!service_running(SERVICE_CONTEXT))) break; - - RRDMETRIC *rm; - dfe_start_write(ri->rrdmetrics, rm) { - if(rrdmetric_should_be_deleted(rm)) { - if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP_DELETE); - if(!dictionary_del(ri->rrdmetrics, string2str(rm->id))) - error("RRDCONTEXT: metric '%s' of instance '%s' of context '%s' of host '%s', failed to be deleted from rrdmetrics dictionary.", - string2str(rm->id), - string2str(ri->id), - string2str(rc->id), - rrdhost_hostname(host)); - else - internal_error( - true, - "RRDCONTEXT: metric '%s' of instance '%s' of context '%s' of host '%s', deleted from rrdmetrics dictionary.", - string2str(rm->id), - string2str(ri->id), - string2str(rc->id), - rrdhost_hostname(host)); - } - } - dfe_done(rm); - - if(rrdinstance_should_be_deleted(ri)) { - if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP_DELETE); - if(!dictionary_del(rc->rrdinstances, string2str(ri->id))) - error("RRDCONTEXT: instance '%s' of context '%s' of host '%s', failed to be deleted from rrdmetrics dictionary.", - string2str(ri->id), - string2str(rc->id), - rrdhost_hostname(host)); - else - internal_error( - true, - "RRDCONTEXT: instance '%s' of context '%s' of host '%s', deleted from rrdmetrics dictionary.", - string2str(ri->id), - string2str(rc->id), - rrdhost_hostname(host)); - } - } - dfe_done(ri); - - if(unlikely(rrdcontext_should_be_deleted(rc))) { - if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP_DELETE); - rrdcontext_dequeue_from_post_processing(rc); - rrdcontext_delete_from_sql_unsafe(rc); - - if(!dictionary_del((DICTIONARY *)host->rrdctx, string2str(rc->id))) - error("RRDCONTEXT: context '%s' of host '%s', failed to be deleted from rrdmetrics dictionary.", - string2str(rc->id), - rrdhost_hostname(host)); - else - internal_error( - true, - "RRDCONTEXT: context '%s' of host '%s', deleted from rrdmetrics dictionary.", - string2str(rc->id), - rrdhost_hostname(host)); - } - - // the item is referenced in the dictionary - // so, it is still here to unlock, even if we have deleted it - rrdcontext_unlock(rc); - } - dfe_done(rc); -} - -static void rrdcontext_garbage_collect_for_all_hosts(void) { - rrd_rdlock(); - RRDHOST *host; - rrdhost_foreach_read(host) { - rrdcontext_garbage_collect_single_host(host, true); - } - rrd_unlock(); -} - -// ---------------------------------------------------------------------------- -// post processing - -static void rrdmetric_process_updates(RRDMETRIC *rm, bool force, RRD_FLAGS reason, bool worker_jobs) { - if(reason != RRD_FLAG_NONE) - rrd_flag_set_updated(rm, reason); - - if(!force && !rrd_flag_is_updated(rm) && rrd_flag_check(rm, RRD_FLAG_LIVE_RETENTION) && !rrd_flag_check(rm, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION)) - return; - - if(worker_jobs) - worker_is_busy(WORKER_JOB_PP_METRIC); - - if(reason & RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD) { - rrd_flag_set_archived(rm); - rrd_flag_set(rm, RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD); - } - if(rrd_flag_is_deleted(rm) && (reason & RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION)) - rrd_flag_set_archived(rm); - - rrdmetric_update_retention(rm); - - rrd_flag_unset_updated(rm); -} - -static void rrdinstance_post_process_updates(RRDINSTANCE *ri, bool force, RRD_FLAGS reason, bool worker_jobs) { - if(reason != RRD_FLAG_NONE) - rrd_flag_set_updated(ri, reason); - - if(!force && !rrd_flag_is_updated(ri) && rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION)) - return; - - if(worker_jobs) - worker_is_busy(WORKER_JOB_PP_INSTANCE); - - time_t min_first_time_t = LONG_MAX, max_last_time_t = 0; - size_t metrics_active = 0, metrics_deleted = 0; - bool live_retention = true, currently_collected = false; - if(dictionary_entries(ri->rrdmetrics) > 0) { - RRDMETRIC *rm; - dfe_start_read((DICTIONARY *)ri->rrdmetrics, rm) { - if(unlikely(!service_running(SERVICE_CONTEXT))) break; - - RRD_FLAGS reason_to_pass = reason; - if(rrd_flag_check(ri, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION)) - reason_to_pass |= RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION; - - rrdmetric_process_updates(rm, force, reason_to_pass, worker_jobs); - - if(unlikely(!rrd_flag_check(rm, RRD_FLAG_LIVE_RETENTION))) - live_retention = false; - - if (unlikely((rrdmetric_should_be_deleted(rm)))) { - metrics_deleted++; - continue; - } - - if(!currently_collected && rrd_flag_check(rm, RRD_FLAG_COLLECTED) && rm->first_time_s) - currently_collected = true; - - metrics_active++; - - 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_s && rm->last_time_s > max_last_time_t) - max_last_time_t = rm->last_time_s; - } - dfe_done(rm); - } - - if(unlikely(live_retention && !rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION))) - rrd_flag_set(ri, RRD_FLAG_LIVE_RETENTION); - else if(unlikely(!live_retention && rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION))) - rrd_flag_clear(ri, RRD_FLAG_LIVE_RETENTION); - - if(unlikely(!metrics_active)) { - // no metrics available - - 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_s) { - ri->last_time_s = 0; - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T); - } - - rrd_flag_set_deleted(ri, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); - } - else { - // we have active metrics... - - if (unlikely(min_first_time_t == LONG_MAX)) - min_first_time_t = 0; - - if (unlikely(min_first_time_t == 0 || max_last_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_s) { - ri->last_time_s = 0; - rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T); - } - - if(likely(live_retention)) - rrd_flag_set_deleted(ri, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); - } - else { - rrd_flag_clear(ri, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); - - 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_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); - } - - if(likely(currently_collected)) - rrd_flag_set_collected(ri); - else - rrd_flag_set_archived(ri); - } - } - - rrd_flag_unset_updated(ri); -} - -static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAGS reason, bool worker_jobs) { - if(reason != RRD_FLAG_NONE) - rrd_flag_set_updated(rc, reason); - - 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; - bool live_retention = true, currently_collected = false, hidden = true; - if(dictionary_entries(rc->rrdinstances) > 0) { - RRDINSTANCE *ri; - dfe_start_reentrant(rc->rrdinstances, ri) { - if(unlikely(!service_running(SERVICE_CONTEXT))) break; - - RRD_FLAGS reason_to_pass = reason; - if(rrd_flag_check(rc, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION)) - reason_to_pass |= RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION; - - rrdinstance_post_process_updates(ri, force, reason_to_pass, worker_jobs); - - if(unlikely(hidden && !rrd_flag_check(ri, RRD_FLAG_HIDDEN))) - hidden = false; - - if(unlikely(live_retention && !rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION))) - live_retention = false; - - if (unlikely(rrdinstance_should_be_deleted(ri))) { - instances_deleted++; - continue; - } - - if(unlikely(!currently_collected && rrd_flag_is_collected(ri) && ri->first_time_s)) - currently_collected = true; - - internal_error(rc->units != ri->units, - "RRDCONTEXT: '%s' rrdinstance '%s' has different units, context '%s', instance '%s'", - string2str(rc->id), string2str(ri->id), - string2str(rc->units), string2str(ri->units)); - - instances_active++; - - 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_s && ri->first_time_s < min_first_time_t) - min_first_time_t = ri->first_time_s; - - 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; - } - - { - bool previous_hidden = rrd_flag_check(rc, RRD_FLAG_HIDDEN); - if (hidden != previous_hidden) { - if (hidden && !rrd_flag_check(rc, RRD_FLAG_HIDDEN)) - rrd_flag_set(rc, RRD_FLAG_HIDDEN); - else if (!hidden && rrd_flag_check(rc, RRD_FLAG_HIDDEN)) - rrd_flag_clear(rc, RRD_FLAG_HIDDEN); - } - - bool previous_live_retention = rrd_flag_check(rc, RRD_FLAG_LIVE_RETENTION); - if (live_retention != previous_live_retention) { - if (live_retention && !rrd_flag_check(rc, RRD_FLAG_LIVE_RETENTION)) - rrd_flag_set(rc, RRD_FLAG_LIVE_RETENTION); - else if (!live_retention && rrd_flag_check(rc, RRD_FLAG_LIVE_RETENTION)) - rrd_flag_clear(rc, RRD_FLAG_LIVE_RETENTION); - } - } - - rrdcontext_lock(rc); - rc->pp.executions++; - - if(unlikely(!instances_active)) { - // we had some instances, but they are gone now... - - 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_s) { - rc->last_time_s = 0; - rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T); - } - - rrd_flag_set_deleted(rc, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); - } - else { - // we have some active instances... - - if (unlikely(min_first_time_t == LONG_MAX)) - min_first_time_t = 0; - - if (unlikely(min_first_time_t == 0 && max_last_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_s) { - rc->last_time_s = 0; - rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T); - } - - rrd_flag_set_deleted(rc, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); - } - else { - rrd_flag_clear(rc, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION); - - 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_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); - } - - if(likely(currently_collected)) - rrd_flag_set_collected(rc); - else - rrd_flag_set_archived(rc); - } - - if (min_priority != LONG_MAX && rc->priority != min_priority) { - rc->priority = min_priority; - rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA); - } - } - - if(unlikely(rrd_flag_is_updated(rc) && rc->rrdhost->rrdctx_hub_queue)) { - if(check_if_cloud_version_changed_unsafe(rc, false)) { - rc->version = rrdcontext_get_next_version(rc); - dictionary_set((DICTIONARY *)rc->rrdhost->rrdctx_hub_queue, - string2str(rc->id), rc, sizeof(*rc)); - } - } - - rrd_flag_unset_updated(rc); - rrdcontext_unlock(rc); -} - -static void rrdcontext_queue_for_post_processing(RRDCONTEXT *rc, const char *function __maybe_unused, RRD_FLAGS flags __maybe_unused) { - if(unlikely(!rc->rrdhost->rrdctx_post_processing_queue)) return; - - if(!rrd_flag_check(rc, RRD_FLAG_QUEUED_FOR_PP)) { - dictionary_set((DICTIONARY *)rc->rrdhost->rrdctx_post_processing_queue, - string2str(rc->id), - rc, - sizeof(*rc)); - -#if(defined(NETDATA_INTERNAL_CHECKS) && defined(LOG_POST_PROCESSING_QUEUE_INSERTIONS)) - { - BUFFER *wb_flags = buffer_create(1000); - rrd_flags_to_buffer(flags, wb_flags); - - BUFFER *wb_reasons = buffer_create(1000); - rrd_reasons_to_buffer(flags, wb_reasons); - - internal_error(true, "RRDCONTEXT: '%s' update triggered by function %s(), due to flags: %s, reasons: %s", - string2str(rc->id), function, - buffer_tostring(wb_flags), - buffer_tostring(wb_reasons)); - - buffer_free(wb_reasons); - buffer_free(wb_flags); - } -#endif - } -} - -static void rrdcontext_dequeue_from_post_processing(RRDCONTEXT *rc) { - if(unlikely(!rc->rrdhost->rrdctx_post_processing_queue)) return; - dictionary_del((DICTIONARY *)rc->rrdhost->rrdctx_post_processing_queue, string2str(rc->id)); -} - -static void rrdcontext_post_process_queued_contexts(RRDHOST *host) { - if(unlikely(!host->rrdctx_post_processing_queue)) return; - - RRDCONTEXT *rc; - dfe_start_reentrant((DICTIONARY *)host->rrdctx_post_processing_queue, rc) { - if(unlikely(!service_running(SERVICE_CONTEXT))) break; - - rrdcontext_dequeue_from_post_processing(rc); - rrdcontext_post_process_updates(rc, false, RRD_FLAG_NONE, true); - } - dfe_done(rc); -} - -// ---------------------------------------------------------------------------- -// dispatching contexts to cloud - -static uint64_t rrdcontext_get_next_version(RRDCONTEXT *rc) { - time_t now = now_realtime_sec(); - uint64_t version = MAX(rc->version, rc->hub.version); - version = MAX((uint64_t)now, version); - version++; - return version; -} - -static void rrdcontext_message_send_unsafe(RRDCONTEXT *rc, bool snapshot __maybe_unused, void *bundle __maybe_unused) { - - // save it, so that we know the last version we sent to hub - rc->version = rc->hub.version = rrdcontext_get_next_version(rc); - rc->hub.id = string2str(rc->id); - rc->hub.title = string2str(rc->title); - rc->hub.units = string2str(rc->units); - 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_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 - struct context_updated message = { - .id = rc->hub.id, - .version = rc->hub.version, - .title = rc->hub.title, - .units = rc->hub.units, - .family = rc->hub.family, - .chart_type = rc->hub.chart_type, - .priority = rc->hub.priority, - .first_entry = rc->hub.first_time_s, - .last_entry = rc->hub.last_time_s, - .deleted = rc->hub.deleted, - }; - - if(likely(!rrd_flag_check(rc, RRD_FLAG_HIDDEN))) { - if (snapshot) { - if (!rc->hub.deleted) - contexts_snapshot_add_ctx_update(bundle, &message); - } - else - contexts_updated_add_ctx_update(bundle, &message); - } -#endif - - // store it to SQL - - if(rrd_flag_is_deleted(rc)) - rrdcontext_delete_from_sql_unsafe(rc); - - else if (ctx_store_context(&rc->rrdhost->host_uuid, &rc->hub) != 0) - error("RRDCONTEXT: failed to save context '%s' version %"PRIu64" to SQL.", rc->hub.id, rc->hub.version); -} - -static bool check_if_cloud_version_changed_unsafe(RRDCONTEXT *rc, bool sending __maybe_unused) { - bool id_changed = false, - title_changed = false, - units_changed = false, - family_changed = false, - chart_type_changed = false, - priority_changed = false, - first_time_changed = false, - last_time_changed = false, - deleted_changed = false; - - RRD_FLAGS flags = rrd_flags_get(rc); - - if(unlikely(string2str(rc->id) != rc->hub.id)) - id_changed = true; - - if(unlikely(string2str(rc->title) != rc->hub.title)) - title_changed = true; - - if(unlikely(string2str(rc->units) != rc->hub.units)) - units_changed = true; - - if(unlikely(string2str(rc->family) != rc->hub.family)) - family_changed = true; - - if(unlikely(rrdset_type_name(rc->chart_type) != rc->hub.chart_type)) - chart_type_changed = true; - - if(unlikely(rc->priority != rc->hub.priority)) - priority_changed = true; - - 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_s) != rc->hub.last_time_s)) - last_time_changed = true; - - if(unlikely(((flags & RRD_FLAG_DELETED) ? true : false) != rc->hub.deleted)) - deleted_changed = true; - - if(unlikely(id_changed || title_changed || units_changed || family_changed || chart_type_changed || priority_changed || first_time_changed || last_time_changed || deleted_changed)) { - - internal_error(LOG_TRANSITIONS, - "RRDCONTEXT: %s NEW VERSION '%s'%s of host '%s', version %"PRIu64", title '%s'%s, units '%s'%s, family '%s'%s, chart type '%s'%s, priority %u%s, first_time_t %ld%s, last_time_t %ld%s, deleted '%s'%s, (queued for %llu ms, expected %llu ms)", - sending?"SENDING":"QUEUE", - string2str(rc->id), id_changed ? " (CHANGED)" : "", - rrdhost_hostname(rc->rrdhost), - rc->version, - string2str(rc->title), title_changed ? " (CHANGED)" : "", - string2str(rc->units), units_changed ? " (CHANGED)" : "", - string2str(rc->family), family_changed ? " (CHANGED)" : "", - rrdset_type_name(rc->chart_type), chart_type_changed ? " (CHANGED)" : "", - rc->priority, priority_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 - ); - - return true; - } - - return false; -} - -static inline usec_t rrdcontext_calculate_queued_dispatch_time_ut(RRDCONTEXT *rc, usec_t now_ut) { - - if(likely(rc->queue.delay_calc_ut >= rc->queue.queued_ut)) - return rc->queue.scheduled_dispatch_ut; - - RRD_FLAGS flags = rc->queue.queued_flags; - - usec_t delay = LONG_MAX; - int i; - struct rrdcontext_reason *reason; - for(i = 0, reason = &rrdcontext_reasons[i]; reason->name ; reason = &rrdcontext_reasons[++i]) { - if(unlikely(flags & reason->flag)) { - if(reason->delay_ut < delay) - delay = reason->delay_ut; - } - } - - if(unlikely(delay == LONG_MAX)) { - internal_error(true, "RRDCONTEXT: '%s', cannot find minimum delay of flags %x", string2str(rc->id), (unsigned int)flags); - delay = 60 * USEC_PER_SEC; - } - - rc->queue.delay_calc_ut = now_ut; - usec_t dispatch_ut = rc->queue.scheduled_dispatch_ut = rc->queue.queued_ut + delay; - return dispatch_ut; -} - -static void rrdcontext_dequeue_from_hub_queue(RRDCONTEXT *rc) { - dictionary_del((DICTIONARY *)rc->rrdhost->rrdctx_hub_queue, string2str(rc->id)); -} - -static void rrdcontext_dispatch_queued_contexts_to_hub(RRDHOST *host, usec_t now_ut) { - - // check if we have received a streaming command for this host - if(!rrdhost_flag_check(host, RRDHOST_FLAG_ACLK_STREAM_CONTEXTS) || !aclk_connected || !host->rrdctx_hub_queue) - return; - - // check if there are queued items to send - if(!dictionary_entries((DICTIONARY *)host->rrdctx_hub_queue)) - return; - - if(!host->node_id) - return; - - size_t messages_added = 0; - contexts_updated_t bundle = NULL; - - RRDCONTEXT *rc; - dfe_start_reentrant((DICTIONARY *)host->rrdctx_hub_queue, rc) { - if(unlikely(!service_running(SERVICE_CONTEXT))) break; - - if(unlikely(messages_added >= MESSAGES_PER_BUNDLE_TO_SEND_TO_HUB_PER_HOST)) - break; - - worker_is_busy(WORKER_JOB_QUEUED); - usec_t dispatch_ut = rrdcontext_calculate_queued_dispatch_time_ut(rc, now_ut); - char *claim_id = get_agent_claimid(); - - if(unlikely(now_ut >= dispatch_ut) && claim_id) { - worker_is_busy(WORKER_JOB_CHECK); - - rrdcontext_lock(rc); - - if(check_if_cloud_version_changed_unsafe(rc, true)) { - worker_is_busy(WORKER_JOB_SEND); - -#ifdef ENABLE_ACLK - if(!bundle) { - // prepare the bundle to send the messages - char uuid[UUID_STR_LEN]; - uuid_unparse_lower(*host->node_id, uuid); - - bundle = contexts_updated_new(claim_id, uuid, 0, now_ut); - } -#endif - // update the hub data of the context, give a new version, pack the message - // and save an update to SQL - rrdcontext_message_send_unsafe(rc, false, bundle); - messages_added++; - - rc->queue.dispatches++; - rc->queue.dequeued_ut = now_ut; - } - else - rc->version = rc->hub.version; - - // remove it from the queue - worker_is_busy(WORKER_JOB_DEQUEUE); - rrdcontext_dequeue_from_hub_queue(rc); - - if(unlikely(rrdcontext_should_be_deleted(rc))) { - // this is a deleted context - delete it forever... - - worker_is_busy(WORKER_JOB_CLEANUP_DELETE); - - rrdcontext_dequeue_from_post_processing(rc); - rrdcontext_delete_from_sql_unsafe(rc); - - STRING *id = string_dup(rc->id); - rrdcontext_unlock(rc); - - // delete it from the master dictionary - if(!dictionary_del((DICTIONARY *)host->rrdctx, string2str(rc->id))) - error("RRDCONTEXT: '%s' of host '%s' failed to be deleted from rrdcontext dictionary.", - string2str(id), rrdhost_hostname(host)); - - string_freez(id); - } - else - rrdcontext_unlock(rc); - } - freez(claim_id); - } - dfe_done(rc); - -#ifdef ENABLE_ACLK - if(service_running(SERVICE_CONTEXT) && bundle) { - // we have a bundle to send messages - - // update the version hash - contexts_updated_update_version_hash(bundle, rrdcontext_version_hash(host)); - - // send it - aclk_send_contexts_updated(bundle); - } - else if(bundle) - contexts_updated_delete(bundle); -#endif - -} - -// ---------------------------------------------------------------------------- -// worker thread - -static void rrdcontext_main_cleanup(void *ptr) { - struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr; - static_thread->enabled = NETDATA_MAIN_THREAD_EXITING; - - // custom code - worker_unregister(); - - static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; -} - -void *rrdcontext_main(void *ptr) { - netdata_thread_cleanup_push(rrdcontext_main_cleanup, ptr); - - worker_register("RRDCONTEXT"); - worker_register_job_name(WORKER_JOB_HOSTS, "hosts"); - worker_register_job_name(WORKER_JOB_CHECK, "dedup checks"); - worker_register_job_name(WORKER_JOB_SEND, "sent contexts"); - worker_register_job_name(WORKER_JOB_DEQUEUE, "deduplicated contexts"); - worker_register_job_name(WORKER_JOB_RETENTION, "metrics retention"); - worker_register_job_name(WORKER_JOB_QUEUED, "queued contexts"); - worker_register_job_name(WORKER_JOB_CLEANUP, "cleanups"); - worker_register_job_name(WORKER_JOB_CLEANUP_DELETE, "deletes"); - worker_register_job_name(WORKER_JOB_PP_METRIC, "check metrics"); - worker_register_job_name(WORKER_JOB_PP_INSTANCE, "check instances"); - worker_register_job_name(WORKER_JOB_PP_CONTEXT, "check contexts"); - - worker_register_job_custom_metric(WORKER_JOB_HUB_QUEUE_SIZE, "hub queue size", "contexts", WORKER_METRIC_ABSOLUTE); - worker_register_job_custom_metric(WORKER_JOB_PP_QUEUE_SIZE, "post processing queue size", "contexts", WORKER_METRIC_ABSOLUTE); - - heartbeat_t hb; - heartbeat_init(&hb); - usec_t step = RRDCONTEXT_WORKER_THREAD_HEARTBEAT_USEC; - - while (service_running(SERVICE_CONTEXT)) { - worker_is_idle(); - heartbeat_next(&hb, step); - - if(unlikely(!service_running(SERVICE_CONTEXT))) break; - - usec_t now_ut = now_realtime_usec(); - - if(rrdcontext_next_db_rotation_ut && now_ut > rrdcontext_next_db_rotation_ut) { - rrdcontext_recalculate_retention_all_hosts(); - rrdcontext_garbage_collect_for_all_hosts(); - rrdcontext_next_db_rotation_ut = 0; - } - - size_t hub_queued_contexts_for_all_hosts = 0; - size_t pp_queued_contexts_for_all_hosts = 0; - - rrd_rdlock(); - RRDHOST *host; - rrdhost_foreach_read(host) { - if(unlikely(!service_running(SERVICE_CONTEXT))) break; - - worker_is_busy(WORKER_JOB_HOSTS); - - if(host->rrdctx_post_processing_queue) { - pp_queued_contexts_for_all_hosts += - dictionary_entries((DICTIONARY *)host->rrdctx_post_processing_queue); - rrdcontext_post_process_queued_contexts(host); - } - - if(host->rrdctx_hub_queue) { - hub_queued_contexts_for_all_hosts += dictionary_entries((DICTIONARY *)host->rrdctx_hub_queue); - rrdcontext_dispatch_queued_contexts_to_hub(host, now_ut); - } - } - rrd_unlock(); - - worker_set_metric(WORKER_JOB_HUB_QUEUE_SIZE, (NETDATA_DOUBLE)hub_queued_contexts_for_all_hosts); - worker_set_metric(WORKER_JOB_PP_QUEUE_SIZE, (NETDATA_DOUBLE)pp_queued_contexts_for_all_hosts); - } - - netdata_thread_cleanup_pop(1); - return NULL; -} diff --git a/database/rrdcontext.h b/database/rrdcontext.h deleted file mode 100644 index eae37036..00000000 --- a/database/rrdcontext.h +++ /dev/null @@ -1,273 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_RRDCONTEXT_H -#define NETDATA_RRDCONTEXT_H 1 - -// ---------------------------------------------------------------------------- -// RRDMETRIC - -typedef struct rrdmetric_acquired RRDMETRIC_ACQUIRED; - -// ---------------------------------------------------------------------------- -// RRDINSTANCE - -typedef struct rrdinstance_acquired RRDINSTANCE_ACQUIRED; - -// ---------------------------------------------------------------------------- -// RRDCONTEXT - -typedef struct rrdcontexts_dictionary RRDCONTEXTS; -typedef struct rrdcontext_acquired RRDCONTEXT_ACQUIRED; - -// ---------------------------------------------------------------------------- - -#include "rrd.h" - -const char *rrdmetric_acquired_id(RRDMETRIC_ACQUIRED *rma); -const char *rrdmetric_acquired_name(RRDMETRIC_ACQUIRED *rma); -NETDATA_DOUBLE rrdmetric_acquired_last_stored_value(RRDMETRIC_ACQUIRED *rma); - -const char *rrdinstance_acquired_id(RRDINSTANCE_ACQUIRED *ria); -const char *rrdinstance_acquired_name(RRDINSTANCE_ACQUIRED *ria); -DICTIONARY *rrdinstance_acquired_labels(RRDINSTANCE_ACQUIRED *ria); -DICTIONARY *rrdinstance_acquired_functions(RRDINSTANCE_ACQUIRED *ria); - -// ---------------------------------------------------------------------------- -// public API for rrdhost - -void rrdhost_load_rrdcontext_data(RRDHOST *host); -void rrdhost_create_rrdcontexts(RRDHOST *host); -void rrdhost_destroy_rrdcontexts(RRDHOST *host); - -void rrdcontext_host_child_connected(RRDHOST *host); -void rrdcontext_host_child_disconnected(RRDHOST *host); - -int rrdcontext_foreach_instance_with_rrdset_in_context(RRDHOST *host, const char *context, int (*callback)(RRDSET *st, void *data), void *data); - -typedef enum { - RRDCONTEXT_OPTION_NONE = 0, - RRDCONTEXT_OPTION_SHOW_METRICS = (1 << 0), - RRDCONTEXT_OPTION_SHOW_INSTANCES = (1 << 1), - RRDCONTEXT_OPTION_SHOW_LABELS = (1 << 2), - RRDCONTEXT_OPTION_SHOW_QUEUED = (1 << 3), - RRDCONTEXT_OPTION_SHOW_FLAGS = (1 << 4), - RRDCONTEXT_OPTION_SHOW_DELETED = (1 << 5), - RRDCONTEXT_OPTION_DEEPSCAN = (1 << 6), - RRDCONTEXT_OPTION_SHOW_UUIDS = (1 << 7), - RRDCONTEXT_OPTION_SHOW_HIDDEN = (1 << 8), - RRDCONTEXT_OPTION_SKIP_ID = (1 << 31), // internal use -} RRDCONTEXT_TO_JSON_OPTIONS; - -#define RRDCONTEXT_OPTIONS_ALL (RRDCONTEXT_OPTION_SHOW_METRICS|RRDCONTEXT_OPTION_SHOW_INSTANCES|RRDCONTEXT_OPTION_SHOW_LABELS|RRDCONTEXT_OPTION_SHOW_QUEUED|RRDCONTEXT_OPTION_SHOW_FLAGS|RRDCONTEXT_OPTION_SHOW_DELETED|RRDCONTEXT_OPTION_SHOW_UUIDS|RRDCONTEXT_OPTION_SHOW_HIDDEN) - -int rrdcontext_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before, RRDCONTEXT_TO_JSON_OPTIONS options, const char *context, SIMPLE_PATTERN *chart_label_key, SIMPLE_PATTERN *chart_labels_filter, SIMPLE_PATTERN *chart_dimensions); -int rrdcontexts_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before, RRDCONTEXT_TO_JSON_OPTIONS options, SIMPLE_PATTERN *chart_label_key, SIMPLE_PATTERN *chart_labels_filter, SIMPLE_PATTERN *chart_dimensions); - -// ---------------------------------------------------------------------------- -// public API for rrdcontexts - -const char *rrdcontext_acquired_id(RRDCONTEXT_ACQUIRED *rca); - -// ---------------------------------------------------------------------------- -// public API for rrddims - -void rrdcontext_updated_rrddim(RRDDIM *rd); -void rrdcontext_removed_rrddim(RRDDIM *rd); -void rrdcontext_updated_rrddim_algorithm(RRDDIM *rd); -void rrdcontext_updated_rrddim_multiplier(RRDDIM *rd); -void rrdcontext_updated_rrddim_divisor(RRDDIM *rd); -void rrdcontext_updated_rrddim_flags(RRDDIM *rd); -void rrdcontext_collected_rrddim(RRDDIM *rd); -int rrdcontext_find_dimension_uuid(RRDSET *st, const char *id, uuid_t *store_uuid); - -// ---------------------------------------------------------------------------- -// public API for rrdsets - -void rrdcontext_updated_rrdset(RRDSET *st); -void rrdcontext_removed_rrdset(RRDSET *st); -void rrdcontext_updated_rrdset_name(RRDSET *st); -void rrdcontext_updated_rrdset_flags(RRDSET *st); -void rrdcontext_updated_retention_rrdset(RRDSET *st); -void rrdcontext_collected_rrdset(RRDSET *st); -int rrdcontext_find_chart_uuid(RRDSET *st, uuid_t *store_uuid); - -// ---------------------------------------------------------------------------- -// public API for ACLK - -void rrdcontext_hub_checkpoint_command(void *cmd); -void rrdcontext_hub_stop_streaming_command(void *cmd); - - -// ---------------------------------------------------------------------------- -// public API for threads - -void rrdcontext_db_rotation(void); -void *rrdcontext_main(void *); - -// ---------------------------------------------------------------------------- -// public API for weights - -struct metric_entry { - RRDCONTEXT_ACQUIRED *rca; - RRDINSTANCE_ACQUIRED *ria; - RRDMETRIC_ACQUIRED *rma; -}; - -DICTIONARY *rrdcontext_all_metrics_to_dict(RRDHOST *host, SIMPLE_PATTERN *contexts); - -// ---------------------------------------------------------------------------- -// 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_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; - RRDMETRIC_ACQUIRED *rma; - } link; - - struct { - STRING *id; - STRING *name; - RRDR_DIMENSION_FLAGS options; - } dimension; - - struct { - STRING *id; - STRING *name; - } chart; - -} QUERY_METRIC; - -#define MAX_QUERY_TARGET_ID_LENGTH 255 - -typedef struct query_target_request { - RRDHOST *host; // the host to be queried (can be NULL, hosts will be used) - RRDCONTEXT_ACQUIRED *rca; // the context to be queried (can be NULL) - RRDINSTANCE_ACQUIRED *ria; // the instance to be queried (can be NULL) - RRDMETRIC_ACQUIRED *rma; // the metric to be queried (can be NULL) - RRDSET *st; // the chart to be queried (NULL, for context queries) - const char *hosts; // hosts simple pattern - const char *contexts; // contexts simple pattern (context queries) - const char *charts; // charts simple pattern (for context queries) - const char *dimensions; // dimensions simple pattern - const char *chart_label_key; // select only the chart having this label key - const char *charts_labels_filter; // select only the charts having this combo of label key:value - time_t after; // the requested timeframe - time_t before; // the requested timeframe - size_t points; // the requested number of points - time_t timeout; // the timeout of the query in seconds - uint32_t format; // DATASOURCE_FORMAT - RRDR_OPTIONS options; - RRDR_GROUPING group_method; - const char *group_options; - time_t resampling_time; - size_t tier; - QUERY_SOURCE query_source; - STORAGE_PRIORITY priority; -} QUERY_TARGET_REQUEST; - -typedef struct query_target { - char id[MAX_QUERY_TARGET_ID_LENGTH + 1]; // query identifier (for logging) - QUERY_TARGET_REQUEST request; - - bool used; // when true, this query is currently being used - size_t queries; // how many query we have done so far - - struct { - bool relative; // true when the request made with relative timestamps, true if it was absolute - bool aligned; - time_t after; // the absolute timestamp this query is about - time_t before; // the absolute timestamp this query is about - time_t query_granularity; - size_t points; // the number of points the query will return (maybe different from the request) - size_t group; - RRDR_GROUPING group_method; - const char *group_options; - size_t resampling_group; - NETDATA_DOUBLE resampling_divisor; - RRDR_OPTIONS options; - size_t tier; - } window; - - struct { - 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 { - QUERY_METRIC *array; // the metrics to be queried (all of them should be queried, no exceptions) - uint32_t used; // how many items of the array are used - uint32_t size; // the size of the array - SIMPLE_PATTERN *pattern; - } query; - - struct { - RRDMETRIC_ACQUIRED **array; - uint32_t used; // how many items of the array are used - uint32_t size; // the size of the array - } metrics; - - struct { - RRDINSTANCE_ACQUIRED **array; - uint32_t used; // how many items of the array are used - uint32_t size; // the size of the array - SIMPLE_PATTERN *pattern; - SIMPLE_PATTERN *chart_label_key_pattern; - SIMPLE_PATTERN *charts_labels_filter_pattern; - } instances; - - struct { - RRDCONTEXT_ACQUIRED **array; - uint32_t used; // how many items of the array are used - uint32_t size; // the size of the array - SIMPLE_PATTERN *pattern; - } contexts; - - struct { - RRDHOST **array; - uint32_t used; // how many items of the array are used - uint32_t size; // the size of the array - SIMPLE_PATTERN *pattern; - } hosts; - -} QUERY_TARGET; - -void query_target_free(void); -void query_target_release(QUERY_TARGET *qt); - -QUERY_TARGET *query_target_create(QUERY_TARGET_REQUEST *qtr); - -#endif // NETDATA_RRDCONTEXT_H - diff --git a/database/rrddim.c b/database/rrddim.c index b8059b3c..496fdc61 100644 --- a/database/rrddim.c +++ b/database/rrddim.c @@ -94,9 +94,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++) { STORAGE_ENGINE *eng = host->db[tier].eng; + rd->tiers[tier].backend = eng->backend; 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++; @@ -116,7 +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].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]); + rd->tiers[tier].db_collection_handle = + storage_metric_store_init(rd->tiers[tier].backend, rd->tiers[tier].db_metric_handle, st->rrdhost->db[tier].tier_grouping * st->update_every, rd->rrdset->storage_metrics_groups[tier]); initialized++; } } @@ -175,7 +175,7 @@ bool rrddim_finalize_collection_and_check_retention(RRDDIM *rd) { tiers_available++; - if(rd->tiers[tier].collect_ops->finalize(rd->tiers[tier].db_collection_handle)) + if(storage_engine_store_finalize(rd->tiers[tier].db_collection_handle)) tiers_said_no_retention++; rd->tiers[tier].db_collection_handle = NULL; @@ -253,7 +253,7 @@ static bool rrddim_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, for(size_t tier = 0; tier < storage_tiers ;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]); + storage_metric_store_init(rd->tiers[tier].backend, 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)) { @@ -320,7 +320,7 @@ inline RRDDIM *rrddim_find(RRDSET *st, const char *id) { } inline RRDDIM_ACQUIRED *rrddim_find_and_acquire(RRDSET *st, const char *id) { - debug(D_RRD_CALLS, "rrddim_find() for chart %s, dimension %s", rrdset_name(st), id); + debug(D_RRD_CALLS, "rrddim_find_and_acquire() for chart %s, dimension %s", rrdset_name(st), id); return (RRDDIM_ACQUIRED *)dictionary_get_and_acquire_item(st->rrddim_root_index, id); } @@ -416,7 +416,7 @@ 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); + return storage_engine_latest_time_s(rd->tiers[tier].backend, rd->tiers[tier].db_metric_handle); } // get the timestamp of the last entry in the round-robin database @@ -438,7 +438,7 @@ 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_s(rd->tiers[tier].db_metric_handle); + return storage_engine_oldest_time_s(rd->tiers[tier].backend, rd->tiers[tier].db_metric_handle); } time_t rrddim_first_entry_s(RRDDIM *rd) { diff --git a/database/rrdfunctions.c b/database/rrdfunctions.c index a8341f87..cdba221a 100644 --- a/database/rrdfunctions.c +++ b/database/rrdfunctions.c @@ -496,7 +496,7 @@ static void rrd_function_call_wait_free(struct rrd_function_call_wait *tmp) { struct { const char *format; - uint8_t content_type; + HTTP_CONTENT_TYPE content_type; } function_formats[] = { { .format = "application/json", CT_APPLICATION_JSON }, { .format = "text/plain", CT_TEXT_PLAIN }, @@ -523,7 +523,7 @@ uint8_t functions_format_to_content_type(const char *format) { return CT_TEXT_PLAIN; } -const char *functions_content_type_to_format(uint8_t content_type) { +const char *functions_content_type_to_format(HTTP_CONTENT_TYPE content_type) { for (int i = 0; function_formats[i].format; i++) if (function_formats[i].content_type == content_type) return function_formats[i].format; @@ -537,7 +537,7 @@ int rrd_call_function_error(BUFFER *wb, const char *msg, int code) { buffer_flush(wb); buffer_sprintf(wb, "{\"status\":%d,\"error_message\":\"%s\"}", code, buffer); - wb->contenttype = CT_APPLICATION_JSON; + wb->content_type = CT_APPLICATION_JSON; buffer_no_cacheable(wb); return code; } @@ -632,7 +632,7 @@ int rrd_call_function_and_wait(RRDHOST *host, BUFFER *wb, int timeout, const cha bool we_should_free = true; 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; + temp_wb->content_type = wb->content_type; code = rdcf->function(temp_wb, timeout, key, rdcf->collector_data, rrd_call_function_signal_when_ready, tmp); if (code == HTTP_RESP_OK) { netdata_mutex_lock(&tmp->mutex); @@ -647,7 +647,7 @@ int rrd_call_function_and_wait(RRDHOST *host, BUFFER *wb, int timeout, const cha if (tmp->data_are_ready) { // we have a response buffer_fast_strcat(wb, buffer_tostring(temp_wb), buffer_strlen(temp_wb)); - wb->contenttype = temp_wb->contenttype; + wb->content_type = temp_wb->content_type; wb->expires = temp_wb->expires; if(wb->expires) @@ -738,14 +738,29 @@ void chart_functions2json(RRDSET *st, BUFFER *wb, int tabs, const char *kq, cons functions2json(st->functions_view, wb, ident, kq, sq); } -void host_functions2json(RRDHOST *host, BUFFER *wb, int tabs, const char *kq, const char *sq) { +void host_functions2json(RRDHOST *host, BUFFER *wb) { if(!host || !host->functions) return; - char ident[tabs + 1]; - ident[tabs] = '\0'; - while(tabs) ident[--tabs] = '\t'; + buffer_json_member_add_object(wb, "functions"); + + struct rrd_collector_function *t; + dfe_start_read(host->functions, t) { + if(!t->collector->running) continue; + + buffer_json_member_add_object(wb, t_dfe.name); + buffer_json_member_add_string(wb, "help", string2str(t->help)); + buffer_json_member_add_int64(wb, "timeout", t->timeout); + buffer_json_member_add_array(wb, "options"); + if(t->options & RRD_FUNCTION_GLOBAL) + buffer_json_add_array_item_string(wb, "GLOBAL"); + if(t->options & RRD_FUNCTION_LOCAL) + buffer_json_add_array_item_string(wb, "LOCAL"); + buffer_json_array_close(wb); + buffer_json_object_close(wb); + } + dfe_done(t); - functions2json(host->functions, wb, ident, kq, sq); + buffer_json_object_close(wb); } void chart_functions_to_dict(DICTIONARY *rrdset_functions_view, DICTIONARY *dst) { diff --git a/database/rrdfunctions.h b/database/rrdfunctions.h index f031ec34..920ada8d 100644 --- a/database/rrdfunctions.h +++ b/database/rrdfunctions.h @@ -26,10 +26,10 @@ void rrd_functions_expose_rrdpush(RRDSET *st, BUFFER *wb); void chart_functions2json(RRDSET *st, BUFFER *wb, int tabs, const char *kq, const char *sq); void chart_functions_to_dict(DICTIONARY *rrdset_functions_view, DICTIONARY *dst); -void host_functions2json(RRDHOST *host, BUFFER *wb, int tabs, const char *kq, const char *sq); +void host_functions2json(RRDHOST *host, BUFFER *wb); uint8_t functions_format_to_content_type(const char *format); -const char *functions_content_type_to_format(uint8_t content_type); +const char *functions_content_type_to_format(HTTP_CONTENT_TYPE content_type); int rrd_call_function_error(BUFFER *wb, const char *msg, int code); #endif // NETDATA_RRDFUNCTIONS_H diff --git a/database/rrdhost.c b/database/rrdhost.c index 60b14c13..88e411de 100644 --- a/database/rrdhost.c +++ b/database/rrdhost.c @@ -41,6 +41,22 @@ bool is_storage_engine_shared(STORAGE_INSTANCE *engine __maybe_unused) { return false; } +RRDHOST *find_host_by_node_id(char *node_id) { + uuid_t node_uuid; + if (unlikely(!node_id || uuid_parse(node_id, node_uuid))) + return NULL; + + RRDHOST *host, *ret = NULL; + dfe_start_read(rrdhost_root_index, host) { + if (host->node_id && !(uuid_memcmp(host->node_id, &node_uuid))) { + ret = host; + break; + } + } + dfe_done(host); + + return ret; +} // ---------------------------------------------------------------------------- // RRDHOST indexes management @@ -62,6 +78,26 @@ static inline void rrdhost_init() { } } +RRDHOST_ACQUIRED *rrdhost_find_and_acquire(const char *machine_guid) { + debug(D_RRD_CALLS, "rrdhost_find_and_acquire() host %s", machine_guid); + + return (RRDHOST_ACQUIRED *)dictionary_get_and_acquire_item(rrdhost_root_index, machine_guid); +} + +RRDHOST *rrdhost_acquired_to_rrdhost(RRDHOST_ACQUIRED *rha) { + if(unlikely(!rha)) + return NULL; + + return (RRDHOST *) dictionary_acquired_item_value((const DICTIONARY_ITEM *)rha); +} + +void rrdhost_acquired_release(RRDHOST_ACQUIRED *rha) { + if(unlikely(!rha)) + return; + + dictionary_acquired_item_release(rrdhost_root_index, (const DICTIONARY_ITEM *)rha); +} + // ---------------------------------------------------------------------------- // RRDHOST index by UUID @@ -104,6 +140,17 @@ inline RRDHOST *rrdhost_find_by_hostname(const char *hostname) { return dictionary_get(rrdhost_root_index_hostname, hostname); } +static inline void rrdhost_index_del_hostname(RRDHOST *host) { + if(unlikely(!host->hostname)) return; + + if(rrdhost_option_check(host, RRDHOST_OPTION_INDEXED_HOSTNAME)) { + if(!dictionary_del(rrdhost_root_index_hostname, rrdhost_hostname(host))) + error("RRDHOST: %s() failed to delete hostname '%s' from index", __FUNCTION__, rrdhost_hostname(host)); + + rrdhost_option_clear(host, RRDHOST_OPTION_INDEXED_HOSTNAME); + } +} + static inline RRDHOST *rrdhost_index_add_hostname(RRDHOST *host) { if(!host->hostname) return host; @@ -111,24 +158,17 @@ static inline RRDHOST *rrdhost_index_add_hostname(RRDHOST *host) { if(ret_hostname == host) rrdhost_option_set(host, RRDHOST_OPTION_INDEXED_HOSTNAME); else { - rrdhost_option_clear(host, RRDHOST_OPTION_INDEXED_HOSTNAME); - error("RRDHOST: %s() host with hostname '%s' is already indexed", __FUNCTION__, rrdhost_hostname(host)); + //have the same hostname but it's not the same host + //keep the new one only if the old one is orphan or archived + if (rrdhost_flag_check(ret_hostname, RRDHOST_FLAG_ORPHAN) || rrdhost_flag_check(ret_hostname, RRDHOST_FLAG_ARCHIVED)) { + rrdhost_index_del_hostname(ret_hostname); + rrdhost_index_add_hostname(host); + } } return host; } -static inline void rrdhost_index_del_hostname(RRDHOST *host) { - if(unlikely(!host->hostname)) return; - - if(rrdhost_option_check(host, RRDHOST_OPTION_INDEXED_HOSTNAME)) { - if(!dictionary_del(rrdhost_root_index_hostname, rrdhost_hostname(host))) - error("RRDHOST: %s() failed to delete hostname '%s' from index", __FUNCTION__, rrdhost_hostname(host)); - - rrdhost_option_clear(host, RRDHOST_OPTION_INDEXED_HOSTNAME); - } -} - // ---------------------------------------------------------------------------- // RRDHOST - internal helpers @@ -225,7 +265,8 @@ static void rrdhost_initialize_rrdpush_sender(RRDHOST *host, rrdpush_destinations_init(host); host->rrdpush_send_api_key = strdupz(rrdpush_api_key); - host->rrdpush_send_charts_matching = simple_pattern_create(rrdpush_send_charts_matching, NULL, SIMPLE_PATTERN_EXACT); + host->rrdpush_send_charts_matching = simple_pattern_create(rrdpush_send_charts_matching, NULL, + SIMPLE_PATTERN_EXACT, true); rrdhost_option_set(host, RRDHOST_OPTION_SENDER_ENABLED); } @@ -329,10 +370,8 @@ int is_legacy = 1; rrdhost_option_set(host, RRDHOST_OPTION_DELETE_ORPHAN_HOST); char filename[FILENAME_MAX + 1]; - if(is_localhost) { + if(is_localhost) host->cache_dir = strdupz(netdata_configured_cache_dir); - host->varlib_dir = strdupz(netdata_configured_varlib_dir); - } else { // this is not localhost - append our GUID to localhost path if (is_in_multihost) { // don't append to cache dir in multihost @@ -349,9 +388,6 @@ int is_legacy = 1; if(r != 0 && errno != EEXIST) error("Host '%s': cannot create directory '%s'", rrdhost_hostname(host), host->cache_dir); } - - snprintfz(filename, FILENAME_MAX, "%s/%s", netdata_configured_varlib_dir, host->machine_guid); - host->varlib_dir = strdupz(filename); } // this is also needed for custom host variables - not only health @@ -366,7 +402,6 @@ int is_legacy = 1; rrdfamily_index_init(host); rrdcalctemplate_index_init(host); rrdcalc_rrdhost_index_init(host); - metaqueue_host_update_info(host); if (host->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) { #ifdef ENABLE_DBENGINE @@ -498,7 +533,6 @@ int is_legacy = 1; " (to '%s' with api key '%s')" ", health %s" ", cache_dir '%s'" - ", varlib_dir '%s'" ", alarms default handler '%s'" ", alarms default recipient '%s'" , rrdhost_hostname(host) @@ -517,20 +551,17 @@ int is_legacy = 1; , host->rrdpush_send_api_key?host->rrdpush_send_api_key:"" , host->health.health_enabled?"enabled":"disabled" , host->cache_dir - , host->varlib_dir , string2str(host->health.health_default_exec) , string2str(host->health.health_default_recipient) ); - if(!archived) - rrdhost_flag_set(host,RRDHOST_FLAG_METADATA_INFO | RRDHOST_FLAG_METADATA_UPDATE); - - rrdhost_load_rrdcontext_data(host); - if (!archived) { + if(!archived) { + metaqueue_host_update_info(host); + rrdhost_load_rrdcontext_data(host); +// rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_INFO | RRDHOST_FLAG_METADATA_UPDATE); ml_host_new(host); - ml_start_anomaly_detection_threads(host); } else - rrdhost_flag_set(host, RRDHOST_FLAG_ARCHIVED | RRDHOST_FLAG_ORPHAN); + rrdhost_flag_set(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD | RRDHOST_FLAG_ARCHIVED | RRDHOST_FLAG_ORPHAN); return host; } @@ -582,6 +613,8 @@ static 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, true); + } else { + rrdhost_index_add_hostname(host); } if(strcmp(rrdhost_program_name(host), program_name) != 0) { @@ -643,7 +676,6 @@ static void rrdhost_update(RRDHOST *host host->rrdpush_replication_step = rrdpush_replication_step; 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)); @@ -681,6 +713,10 @@ RRDHOST *rrdhost_find_or_create( RRDHOST *host = rrdhost_find_by_guid(guid); if (unlikely(host && host->rrd_memory_mode != mode && rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED))) { + + if (likely(!archived && rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD))) + return host; + /* 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)); @@ -720,31 +756,30 @@ RRDHOST *rrdhost_find_or_create( ); } else { - - rrdhost_update(host - , hostname - , registry_hostname - , guid - , os - , timezone - , abbrev_timezone - , utc_offset - , tags - , program_name - , program_version - , update_every - , history - , mode - , health_enabled - , rrdpush_enabled - , rrdpush_destination - , rrdpush_api_key - , rrdpush_send_charts_matching - , rrdpush_enable_replication - , rrdpush_seconds_to_replicate - , rrdpush_replication_step - , system_info); - + if (likely(!rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD))) + rrdhost_update(host + , hostname + , registry_hostname + , guid + , os + , timezone + , abbrev_timezone + , utc_offset + , tags + , program_name + , program_version + , update_every + , history + , mode + , health_enabled + , rrdpush_enabled + , rrdpush_destination + , rrdpush_api_key + , rrdpush_send_charts_matching + , rrdpush_enable_replication + , rrdpush_seconds_to_replicate + , rrdpush_replication_step + , system_info); } return host; @@ -811,20 +846,6 @@ void dbengine_init(char *hostname) { 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); - default_rrdeng_page_fetch_timeout = 1; - config_set_number(CONFIG_SECTION_DB, "dbengine page fetch timeout secs", default_rrdeng_page_fetch_timeout); - } - - default_rrdeng_page_fetch_retries = (int) config_get_number(CONFIG_SECTION_DB, "dbengine page fetch retries", MAX_PAGE_CACHE_FETCH_RETRIES); - if (default_rrdeng_page_fetch_retries < 1) { - info("\"dbengine page fetch retries\" found in netdata.conf cannot be %d, using 1", default_rrdeng_page_fetch_retries); - default_rrdeng_page_fetch_retries = 1; - config_set_number(CONFIG_SECTION_DB, "dbengine page fetch retries", default_rrdeng_page_fetch_retries); - } - struct dbengine_initialization tiers_init[RRD_STORAGE_TIERS] = {}; size_t created_tiers = 0; @@ -938,8 +959,10 @@ int rrd_init(char *hostname, struct rrdhost_system_info *system_info, bool unitt rrdhost_init(); if (unlikely(sql_init_database(DB_CHECK_NONE, system_info ? 0 : 1))) { - if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) + if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) { + set_late_global_environment(system_info); fatal("Failed to initialize SQLite"); + } info("Skipping SQLITE metadata initialization since memory mode is not dbengine"); } @@ -1071,7 +1094,7 @@ static void rrdhost_streaming_sender_structures_init(RRDHOST *host) 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; + host->sender->capabilities = stream_our_capabilities(); host->sender->rrdpush_sender_pipe[PIPE_READ] = -1; host->sender->rrdpush_sender_pipe[PIPE_WRITE] = -1; @@ -1160,7 +1183,6 @@ void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host, bool force) { rrdcalctemplate_index_destroy(host); // cleanup ML resources - ml_stop_anomaly_detection_threads(host); ml_host_delete(host); freez(host->exporting_flags); @@ -1182,21 +1204,6 @@ void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host, bool force) { return; } -#ifdef ENABLE_ACLK - struct aclk_database_worker_config *wc = host->dbsync_worker; - if (wc && !netdata_exit) { - struct aclk_database_cmd cmd; - memset(&cmd, 0, sizeof(cmd)); - cmd.opcode = ACLK_DATABASE_ORPHAN_HOST; - struct aclk_completion compl ; - init_aclk_completion(&compl ); - cmd.completion = &compl ; - aclk_database_enq_cmd(wc, &cmd); - wait_for_aclk_completion(&compl ); - destroy_aclk_completion(&compl ); - } -#endif - // ------------------------------------------------------------------------ // free it @@ -1212,7 +1219,6 @@ void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host, bool force) { string_freez(host->program_version); rrdhost_system_info_free(host->system_info); freez(host->cache_dir); - freez(host->varlib_dir); freez(host->rrdpush_send_api_key); freez(host->rrdpush_send_destination); rrdpush_destinations_free(host); @@ -1226,16 +1232,14 @@ void rrdhost_free___while_having_rrd_wrlock(RRDHOST *host, bool force) { rrdfamily_index_destroy(host); rrdfunctions_destroy(host); rrdvariables_destroy(host->rrdvars); + if (host == localhost) + rrdvariables_destroy(health_rrdvars); 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 } void rrdhost_free_all(void) { @@ -1253,11 +1257,10 @@ void rrdhost_free_all(void) { void rrd_finalize_collection_for_all_hosts(void) { RRDHOST *host; - rrd_wrlock(); - rrdhost_foreach_read(host) { + dfe_start_reentrant(rrdhost_root_index, host) { rrdhost_finalize_collection(host); } - rrd_unlock(); + dfe_done(host); } // ---------------------------------------------------------------------------- @@ -1278,6 +1281,33 @@ void rrdhost_save_charts(RRDHOST *host) { rrdset_foreach_done(st); } +struct rrdhost_system_info *rrdhost_labels_to_system_info(DICTIONARY *labels) { + struct rrdhost_system_info *info = callocz(1, sizeof(struct rrdhost_system_info)); + info->hops = 1; + + rrdlabels_get_value_strdup_or_null(labels, &info->cloud_provider_type, "_cloud_provider_type"); + rrdlabels_get_value_strdup_or_null(labels, &info->cloud_instance_type, "_cloud_instance_type"); + rrdlabels_get_value_strdup_or_null(labels, &info->cloud_instance_region, "_cloud_instance_region"); + rrdlabels_get_value_strdup_or_null(labels, &info->host_os_name, "_os_name"); + rrdlabels_get_value_strdup_or_null(labels, &info->host_os_version, "_os_version"); + rrdlabels_get_value_strdup_or_null(labels, &info->kernel_version, "_kernel_version"); + rrdlabels_get_value_strdup_or_null(labels, &info->host_cores, "_system_cores"); + rrdlabels_get_value_strdup_or_null(labels, &info->host_cpu_freq, "_system_cpu_freq"); + rrdlabels_get_value_strdup_or_null(labels, &info->host_ram_total, "_system_ram_total"); + rrdlabels_get_value_strdup_or_null(labels, &info->host_disk_space, "_system_disk_space"); + rrdlabels_get_value_strdup_or_null(labels, &info->architecture, "_architecture"); + rrdlabels_get_value_strdup_or_null(labels, &info->virtualization, "_virtualization"); + rrdlabels_get_value_strdup_or_null(labels, &info->container, "_container"); + rrdlabels_get_value_strdup_or_null(labels, &info->container_detection, "_container_detection"); + rrdlabels_get_value_strdup_or_null(labels, &info->virt_detection, "_virt_detection"); + rrdlabels_get_value_strdup_or_null(labels, &info->is_k8s_node, "_is_k8s_node"); + rrdlabels_get_value_strdup_or_null(labels, &info->install_type, "_install_type"); + rrdlabels_get_value_strdup_or_null(labels, &info->prebuilt_arch, "_prebuilt_arch"); + rrdlabels_get_value_strdup_or_null(labels, &info->prebuilt_dist, "_prebuilt_dist"); + + return info; +} + static void rrdhost_load_auto_labels(void) { DICTIONARY *labels = localhost->rrdlabels; @@ -1288,8 +1318,7 @@ static void rrdhost_load_auto_labels(void) { rrdlabels_add(labels, "_cloud_instance_type", localhost->system_info->cloud_instance_type, RRDLABEL_SRC_AUTO); if (localhost->system_info->cloud_instance_region) - rrdlabels_add( - labels, "_cloud_instance_region", localhost->system_info->cloud_instance_region, RRDLABEL_SRC_AUTO); + rrdlabels_add(labels, "_cloud_instance_region", localhost->system_info->cloud_instance_region, RRDLABEL_SRC_AUTO); if (localhost->system_info->host_os_name) rrdlabels_add(labels, "_os_name", localhost->system_info->host_os_name, RRDLABEL_SRC_AUTO); @@ -1353,13 +1382,12 @@ void rrdhost_set_is_parent_label(int count) { DICTIONARY *labels = localhost->rrdlabels; if (count == 0 || count == 1) { - rrdlabels_add( - labels, "_is_parent", (count) ? "true" : "false", RRDLABEL_SRC_AUTO); + rrdlabels_add(labels, "_is_parent", (count) ? "true" : "false", RRDLABEL_SRC_AUTO); //queue a node info #ifdef ENABLE_ACLK if (netdata_cloud_setting) { - aclk_queue_node_info(localhost); + aclk_queue_node_info(localhost, false); } #endif } @@ -1430,7 +1458,7 @@ void rrdhost_finalize_collection(RRDHOST *host) { info("RRD: 'host:%s' stopping data collection...", rrdhost_hostname(host)); RRDSET *st; - rrdset_foreach_write(st, host) + rrdset_foreach_read(st, host) rrdset_finalize_collection(st, true); rrdset_foreach_done(st); } diff --git a/database/rrdlabels.c b/database/rrdlabels.c index 4a9a6dae..f6abd602 100644 --- a/database/rrdlabels.c +++ b/database/rrdlabels.c @@ -399,7 +399,7 @@ size_t text_sanitize(unsigned char *dst, const unsigned char *src, size_t dst_si // find how big this character is (2-4 bytes) size_t utf_character_size = 2; - while(utf_character_size <= 4 && src[utf_character_size] && IS_UTF8_BYTE(src[utf_character_size]) && !IS_UTF8_STARTBYTE(src[utf_character_size])) + while(utf_character_size < 4 && src[utf_character_size] && IS_UTF8_BYTE(src[utf_character_size]) && !IS_UTF8_STARTBYTE(src[utf_character_size])) utf_character_size++; if(utf) { @@ -651,10 +651,24 @@ void rrdlabels_get_value_to_buffer_or_null(DICTIONARY *labels, BUFFER *wb, const dictionary_acquired_item_release(labels, acquired_item); } +void rrdlabels_value_to_buffer_array_item_or_null(DICTIONARY *labels, BUFFER *wb, const char *key) { + if(!labels) return; + + const DICTIONARY_ITEM *acquired_item = dictionary_get_and_acquire_item(labels, key); + RRDLABEL *lb = dictionary_acquired_item_value(acquired_item); + + if(lb && lb->label_value) + buffer_json_add_array_item_string(wb, string2str(lb->label_value)); + else + buffer_json_add_array_item_string(wb, NULL); + + dictionary_acquired_item_release(labels, acquired_item); +} + // ---------------------------------------------------------------------------- // rrdlabels_get_value_to_char_or_null() -void rrdlabels_get_value_to_char_or_null(DICTIONARY *labels, char **value, const char *key) { +void rrdlabels_get_value_strdup_or_null(DICTIONARY *labels, char **value, const char *key) { const DICTIONARY_ITEM *acquired_item = dictionary_get_and_acquire_item(labels, key); RRDLABEL *lb = dictionary_acquired_item_value(acquired_item); @@ -663,6 +677,46 @@ void rrdlabels_get_value_to_char_or_null(DICTIONARY *labels, char **value, const dictionary_acquired_item_release(labels, acquired_item); } +void rrdlabels_get_value_strcpyz(DICTIONARY *labels, char *dst, size_t dst_len, const char *key) { + const DICTIONARY_ITEM *acquired_item = dictionary_get_and_acquire_item(labels, key); + RRDLABEL *lb = dictionary_acquired_item_value(acquired_item); + + if(lb && lb->label_value) + strncpyz(dst, string2str(lb->label_value), dst_len); + else + dst[0] = '\0'; + + dictionary_acquired_item_release(labels, acquired_item); +} + +STRING *rrdlabels_get_value_string_dup(DICTIONARY *labels, const char *key) { + const DICTIONARY_ITEM *acquired_item = dictionary_get_and_acquire_item(labels, key); + RRDLABEL *lb = dictionary_acquired_item_value(acquired_item); + + STRING *ret = NULL; + if(lb && lb->label_value) + ret = string_dup(lb->label_value); + + dictionary_acquired_item_release(labels, acquired_item); + + return ret; +} + +STRING *rrdlabels_get_value_to_buffer_or_unset(DICTIONARY *labels, BUFFER *wb, const char *key, const char *unset) { + const DICTIONARY_ITEM *acquired_item = dictionary_get_and_acquire_item(labels, key); + RRDLABEL *lb = dictionary_acquired_item_value(acquired_item); + + STRING *ret = NULL; + if(lb && lb->label_value) + buffer_strcat(wb, string2str(lb->label_value)); + else + buffer_strcat(wb, unset); + + dictionary_acquired_item_release(labels, acquired_item); + + return ret; +} + // ---------------------------------------------------------------------------- // rrdlabels_unmark_all() // remove labels RRDLABEL_FLAG_OLD and RRDLABEL_FLAG_NEW from all dictionary items @@ -778,6 +832,7 @@ void rrdlabels_copy(DICTIONARY *dst, DICTIONARY *src) { // returns true when there are keys in the dictionary matching a simple pattern struct simple_pattern_match_name_value { + size_t searches; SIMPLE_PATTERN *pattern; char equal; }; @@ -788,6 +843,7 @@ static int simple_pattern_match_name_only_callback(const DICTIONARY_ITEM *item, (void)value; // we return -1 to stop the walkthrough on first match + t->searches++; if(simple_pattern_matches(t->pattern, name)) return -1; return 0; @@ -799,6 +855,7 @@ static int simple_pattern_match_name_and_value_callback(const DICTIONARY_ITEM *i RRDLABEL *lb = (RRDLABEL *)value; // we return -1 to stop the walkthrough on first match + t->searches++; if(simple_pattern_matches(t->pattern, name)) return -1; size_t len = RRDLABELS_MAX_NAME_LENGTH + RRDLABELS_MAX_VALUE_LENGTH + 2; // +1 for =, +1 for \0 @@ -817,28 +874,34 @@ static int simple_pattern_match_name_and_value_callback(const DICTIONARY_ITEM *i // terminate it *dst = '\0'; - if(simple_pattern_matches(t->pattern, tmp)) return -1; + t->searches++; + if(simple_pattern_matches_length_extract(t->pattern, tmp, dst - tmp, NULL, 0) == SP_MATCHED_POSITIVE) + return -1; return 0; } -bool rrdlabels_match_simple_pattern_parsed(DICTIONARY *labels, SIMPLE_PATTERN *pattern, char equal) { +bool rrdlabels_match_simple_pattern_parsed(DICTIONARY *labels, SIMPLE_PATTERN *pattern, char equal, size_t *searches) { if (!labels) return false; struct simple_pattern_match_name_value t = { + .searches = 0, .pattern = pattern, .equal = equal }; int ret = dictionary_walkthrough_read(labels, equal?simple_pattern_match_name_and_value_callback:simple_pattern_match_name_only_callback, &t); + if(searches) + *searches = t.searches; + return (ret == -1)?true:false; } bool rrdlabels_match_simple_pattern(DICTIONARY *labels, const char *simple_pattern_txt) { if (!labels) return false; - SIMPLE_PATTERN *pattern = simple_pattern_create(simple_pattern_txt, " ,|\t\r\n\f\v", SIMPLE_PATTERN_EXACT); + SIMPLE_PATTERN *pattern = simple_pattern_create(simple_pattern_txt, " ,|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, true); char equal = '\0'; const char *s; @@ -849,7 +912,7 @@ bool rrdlabels_match_simple_pattern(DICTIONARY *labels, const char *simple_patte } } - bool ret = rrdlabels_match_simple_pattern_parsed(labels, pattern, equal); + bool ret = rrdlabels_match_simple_pattern_parsed(labels, pattern, equal, NULL); simple_pattern_free(pattern); @@ -959,6 +1022,14 @@ int rrdlabels_to_buffer(DICTIONARY *labels, BUFFER *wb, const char *before_each, return dictionary_walkthrough_read(labels, label_to_buffer_callback, (void *)&tmp); } +void rrdlabels_to_buffer_json_members(DICTIONARY *labels, BUFFER *wb) { + RRDLABEL *lb; + dfe_start_read(labels, lb) { + buffer_json_member_add_string(wb, lb_dfe.name, string2str(lb->label_value)); + } + dfe_done(lb); +} + void rrdset_update_rrdlabels(RRDSET *st, DICTIONARY *new_rrdlabels) { if(!st->rrdlabels) st->rrdlabels = rrdlabels_create(); diff --git a/database/rrdset.c b/database/rrdset.c index 57f962cd..2843bb33 100644 --- a/database/rrdset.c +++ b/database/rrdset.c @@ -158,7 +158,7 @@ static void rrdset_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v STORAGE_ENGINE *eng = st->rrdhost->db[tier].eng; if(!eng) continue; - st->storage_metrics_groups[tier] = eng->api.collect_ops.metrics_group_get(host->db[tier].instance, &st->chart_uuid); + st->storage_metrics_groups[tier] = storage_engine_metrics_group_get(eng->backend, host->db[tier].instance, &st->chart_uuid); } } @@ -184,6 +184,8 @@ static void rrdset_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, v ml_chart_new(st); } +void pluginsd_rrdset_cleanup(RRDSET *st); + void rrdset_finalize_collection(RRDSET *st, bool dimensions_too) { RRDHOST *host = st->rrdhost; @@ -201,10 +203,12 @@ void rrdset_finalize_collection(RRDSET *st, bool dimensions_too) { 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]); + storage_engine_metrics_group_release(eng->backend, host->db[tier].instance, st->storage_metrics_groups[tier]); st->storage_metrics_groups[tier] = NULL; } } + + pluginsd_rrdset_cleanup(st); } // the destructor - the dictionary is write locked while this runs @@ -475,6 +479,27 @@ inline RRDSET *rrdset_find_byname(RRDHOST *host, const char *name) { return(st); } +RRDSET_ACQUIRED *rrdset_find_and_acquire(RRDHOST *host, const char *id) { + debug(D_RRD_CALLS, "rrdset_find_and_acquire() for host %s, chart %s", rrdhost_hostname(host), id); + + return (RRDSET_ACQUIRED *)dictionary_get_and_acquire_item(host->rrdset_root_index, id); +} + +RRDSET *rrdset_acquired_to_rrdset(RRDSET_ACQUIRED *rsa) { + if(unlikely(!rsa)) + return NULL; + + return (RRDSET *) dictionary_acquired_item_value((const DICTIONARY_ITEM *)rsa); +} + +void rrdset_acquired_release(RRDSET_ACQUIRED *rsa) { + if(unlikely(!rsa)) + return; + + RRDSET *rs = rrdset_acquired_to_rrdset(rsa); + dictionary_acquired_item_release(rs->rrdhost->rrdset_root_index, (const DICTIONARY_ITEM *)rsa); +} + // ---------------------------------------------------------------------------- // RRDSET - rename charts @@ -737,10 +762,8 @@ void rrdset_reset(RRDSET *st) { rd->collections_counter = 0; if(!rrddim_flag_check(rd, RRDDIM_FLAG_ARCHIVED)) { - for(size_t tier = 0; tier < storage_tiers ;tier++) { - if(rd->tiers[tier].db_collection_handle) - rd->tiers[tier].collect_ops->flush(rd->tiers[tier].db_collection_handle); - } + for(size_t tier = 0; tier < storage_tiers ;tier++) + storage_engine_store_flush(rd->tiers[tier].db_collection_handle); } } rrddim_foreach_done(rd); @@ -1116,7 +1139,7 @@ void store_metric_at_tier(RRDDIM *rd, size_t tier, struct rrddim_tier *t, STORAG if (likely(!storage_point_is_unset(t->virtual_point))) { - t->collect_ops->store_metric( + storage_engine_store_metric( t->db_collection_handle, t->next_point_end_time_s * USEC_PER_SEC, t->virtual_point.sum, @@ -1127,7 +1150,7 @@ void store_metric_at_tier(RRDDIM *rd, size_t tier, struct rrddim_tier *t, STORAG t->virtual_point.flags); } else { - t->collect_ops->store_metric( + storage_engine_store_metric( t->db_collection_handle, t->next_point_end_time_s * USEC_PER_SEC, NAN, @@ -1199,7 +1222,10 @@ 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); + storage_engine_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_s = (time_t)(point_end_time_ut / USEC_PER_SEC); @@ -1229,6 +1255,8 @@ void rrddim_store_metric(RRDDIM *rd, usec_t point_end_time_ut, NETDATA_DOUBLE n, store_metric_at_tier(rd, tier, t, sp, point_end_time_ut); } + + rrdcontext_collected_rrddim(rd); } void store_metric_collection_completed() { @@ -1269,7 +1297,8 @@ void rrdset_thread_rda_free(void) { } static inline size_t rrdset_done_interpolate( - RRDSET *st + RRDSET_STREAM_BUFFER *rsb + , RRDSET *st , struct rda_item *rda_base , size_t rda_slots , usec_t update_every_ut @@ -1399,32 +1428,38 @@ static inline size_t rrdset_done_interpolate( time_t current_time_s = (time_t) (next_store_ut / USEC_PER_SEC); if(unlikely(!store_this_entry)) { - (void) ml_is_anomalous(rd, current_time_s, 0, false); + (void) ml_dimension_is_anomalous(rd, current_time_s, 0, false); + + if(rsb->wb && rsb->v2) + rrddim_push_metrics_v2(rsb, rd, next_store_ut, NAN, SN_FLAG_NONE); 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 < gap_when_lost_iterations_above)) { uint32_t dim_storage_flags = storage_flags; - if (ml_is_anomalous(rd, current_time_s, new_value, true)) { + if (ml_dimension_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); } + if(rsb->wb && rsb->v2) + rrddim_push_metrics_v2(rsb, rd, next_store_ut, new_value, dim_storage_flags); + rrddim_store_metric(rd, next_store_ut, new_value, dim_storage_flags); - rrdcontext_collected_rrddim(rd); rd->last_stored_value = new_value; } else { - (void) ml_is_anomalous(rd, current_time_s, 0, false); + (void) ml_dimension_is_anomalous(rd, current_time_s, 0, false); rrdset_debug(st, "%s: STORE[%ld] = NON EXISTING ", rrddim_name(rd), current_entry); + if(rsb->wb && rsb->v2) + rrddim_push_metrics_v2(rsb, rd, next_store_ut, NAN, SN_FLAG_NONE); + rrddim_store_metric(rd, next_store_ut, NAN, SN_FLAG_NONE); - rrdcontext_collected_rrddim(rd); rd->last_stored_value = NAN; } @@ -1468,6 +1503,10 @@ void rrdset_done(RRDSET *st) { void rrdset_timed_done(RRDSET *st, struct timeval now, bool pending_rrdset_next) { if(unlikely(!service_running(SERVICE_COLLECTORS))) return; + RRDSET_STREAM_BUFFER stream_buffer = { .wb = NULL, }; + if(unlikely(rrdhost_has_rrdpush_sender_enabled(st->rrdhost))) + stream_buffer = rrdset_push_metric_initialize(st, now.tv_sec); + netdata_spinlock_lock(&st->data_collection_lock); if (pending_rrdset_next) @@ -1489,10 +1528,10 @@ void rrdset_timed_done(RRDSET *st, struct timeval now, bool pending_rrdset_next) 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)) + if(unlikely(rrdset_flags & RRDSET_FLAG_COLLECTION_FINISHED)) { + netdata_spinlock_unlock(&st->data_collection_lock); return; - - netdata_thread_disable_cancelability(); + } if (unlikely(rrdset_flags & RRDSET_FLAG_OBSOLETE)) { error("Chart '%s' has the OBSOLETE flag set, but it is collected.", rrdset_id(st)); @@ -1500,8 +1539,7 @@ void rrdset_timed_done(RRDSET *st, struct timeval now, bool pending_rrdset_next) } // check if the chart has a long time to be updated - if(unlikely(st->usec_since_last_update > st->entries * update_every_ut && - st->rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE && st->rrd_memory_mode != RRD_MEMORY_MODE_NONE)) { + if(unlikely(st->usec_since_last_update > MAX(st->entries, 60) * update_every_ut)) { info("host '%s', chart '%s': took too long to be updated (counter #%zu, update #%zu, %0.3" NETDATA_DOUBLE_MODIFIER " secs). Resetting it.", rrdhost_hostname(st->rrdhost), rrdset_id(st), st->counter, st->counter_done, (NETDATA_DOUBLE)st->usec_since_last_update / USEC_PER_SEC); rrdset_reset(st); @@ -1527,9 +1565,6 @@ void rrdset_timed_done(RRDSET *st, struct timeval now, bool pending_rrdset_next) // calculate the proper last_collected_time, using usec_since_last_update last_collect_ut = rrdset_update_last_collected_time(st); } - if (unlikely(st->rrd_memory_mode == RRD_MEMORY_MODE_NONE)) { - goto after_first_database_work; - } // if this set has not been updated in the past // we fake the last_update time to be = now - usec_since_last_update @@ -1592,11 +1627,10 @@ void rrdset_timed_done(RRDSET *st, struct timeval now, bool pending_rrdset_next) } } -after_first_database_work: st->counter_done++; - if(unlikely(rrdhost_has_rrdpush_sender_enabled(st->rrdhost))) - rrdset_done_push(st); + if(stream_buffer.wb && !stream_buffer.v2) + rrdset_push_metrics_v1(&stream_buffer, st); uint32_t has_reset_value = 0; @@ -1654,9 +1688,6 @@ after_first_database_work: rrddim_foreach_done(rd); rda_slots = dimensions; - if (unlikely(st->rrd_memory_mode == RRD_MEMORY_MODE_NONE)) - goto after_second_database_work; - rrdset_debug(st, "last_collect_ut = %0.3" NETDATA_DOUBLE_MODIFIER " (last collection time)", (NETDATA_DOUBLE)last_collect_ut/USEC_PER_SEC); rrdset_debug(st, "now_collect_ut = %0.3" NETDATA_DOUBLE_MODIFIER " (current collection time)", (NETDATA_DOUBLE)now_collect_ut/USEC_PER_SEC); rrdset_debug(st, "last_stored_ut = %0.3" NETDATA_DOUBLE_MODIFIER " (last updated time)", (NETDATA_DOUBLE)last_stored_ut/USEC_PER_SEC); @@ -1857,7 +1888,8 @@ after_first_database_work: // #endif rrdset_done_interpolate( - st + &stream_buffer + , st , rda_base , rda_slots , update_every_ut @@ -1869,7 +1901,6 @@ after_first_database_work: , has_reset_value ); -after_second_database_work: for(dim_id = 0, rda = rda_base ; dim_id < rda_slots ; ++dim_id, ++rda) { rd = rda->rd; if(unlikely(!rd)) continue; @@ -1928,6 +1959,7 @@ after_second_database_work: } netdata_spinlock_unlock(&st->data_collection_lock); + rrdset_push_metrics_finished(&stream_buffer, st); // ALL DONE ABOUT THE DATA UPDATE // -------------------------------------------------------------------- @@ -1955,8 +1987,6 @@ after_second_database_work: rrdcontext_collected_rrdset(st); - netdata_thread_enable_cancelability(); - store_metric_collection_completed(); } @@ -1965,18 +1995,20 @@ 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_s); - time_t prev_update_every_s = st->update_every; - st->update_every = update_every_s; + time_t prev_update_every_s = (time_t) st->update_every; + st->update_every = (int) 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].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)); + storage_engine_store_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_s && + assert(rd->update_every == (int) prev_update_every_s && "chart's update every differs from the update every of its dimensions"); rd->update_every = st->update_every; } diff --git a/database/rrdvar.c b/database/rrdvar.c index 72decbd4..914a5d6e 100644 --- a/database/rrdvar.c +++ b/database/rrdvar.c @@ -93,6 +93,15 @@ DICTIONARY *rrdvariables_create(void) { return dict; } +DICTIONARY *health_rrdvariables_create(void) { + DICTIONARY *dict = dictionary_create_advanced(DICT_OPTION_NONE, &dictionary_stats_category_rrdhealth, 0); + + dictionary_register_insert_callback(dict, rrdvar_insert_callback, NULL); + dictionary_register_delete_callback(dict, rrdvar_delete_callback, NULL); + + return dict; +} + void rrdvariables_destroy(DICTIONARY *dict) { dictionary_destroy(dict); } @@ -124,6 +133,19 @@ inline const RRDVAR_ACQUIRED *rrdvar_add_and_acquire(const char *scope __maybe_u return (const RRDVAR_ACQUIRED *)dictionary_set_and_acquire_item_advanced(dict, string2str(name), (ssize_t)string_strlen(name) + 1, NULL, sizeof(RRDVAR), &tmp); } +inline void rrdvar_add(const char *scope __maybe_unused, DICTIONARY *dict, STRING *name, RRDVAR_TYPE type, RRDVAR_FLAGS options, void *value) { + if(unlikely(!dict || !name)) return; + + struct rrdvar_constructor tmp = { + .name = name, + .value = value, + .type = type, + .options = options, + .react_action = RRDVAR_REACT_NONE, + }; + dictionary_set_advanced(dict, string2str(name), (ssize_t)string_strlen(name) + 1, NULL, sizeof(RRDVAR), &tmp); +} + void rrdvar_delete_all(DICTIONARY *dict) { dictionary_flush(dict); } @@ -211,6 +233,52 @@ NETDATA_DOUBLE rrdvar2number(const RRDVAR_ACQUIRED *rva) { } } +int health_variable_check(DICTIONARY *dict, RRDSET *st, RRDDIM *rd) { + if (!dict || !st || !rd) return 0; + + STRING *helper_str; + char helper[RRDVAR_MAX_LENGTH + 1]; + snprintfz(helper, RRDVAR_MAX_LENGTH, "%s.%s", string2str(st->name), string2str(rd->name)); + helper_str = string_strdupz(helper); + + const RRDVAR_ACQUIRED *rva; + rva = rrdvar_get_and_acquire(dict, helper_str); + if(rva) { + dictionary_acquired_item_release(dict, (const DICTIONARY_ITEM *)rva); + string_freez(helper_str); + return 1; + } + + string_freez(helper_str); + + return 0; +} + +void rrdvar_store_for_chart(RRDHOST *host, RRDSET *st) { + if (!st) return; + + if(!st->rrdfamily) + st->rrdfamily = rrdfamily_add_and_acquire(host, rrdset_family(st)); + + if(!st->rrdvars) + st->rrdvars = rrdvariables_create(); + + rrddimvar_index_init(st); + + rrdsetvar_add_and_leave_released(st, "last_collected_t", RRDVAR_TYPE_TIME_T, &st->last_collected_time.tv_sec, RRDVAR_FLAG_NONE); + rrdsetvar_add_and_leave_released(st, "green", RRDVAR_TYPE_CALCULATED, &st->green, RRDVAR_FLAG_NONE); + rrdsetvar_add_and_leave_released(st, "red", RRDVAR_TYPE_CALCULATED, &st->red, RRDVAR_FLAG_NONE); + rrdsetvar_add_and_leave_released(st, "update_every", RRDVAR_TYPE_INT, &st->update_every, RRDVAR_FLAG_NONE); + + RRDDIM *rd; + rrddim_foreach_read(rd, st) { + rrddimvar_add_and_leave_released(rd, RRDVAR_TYPE_CALCULATED, NULL, NULL, &rd->last_stored_value, RRDVAR_FLAG_NONE); + rrddimvar_add_and_leave_released(rd, RRDVAR_TYPE_COLLECTED, NULL, "_raw", &rd->last_collected_value, RRDVAR_FLAG_NONE); + rrddimvar_add_and_leave_released(rd, RRDVAR_TYPE_TIME_T, NULL, "_last_collected_t", &rd->last_collected_time.tv_sec, RRDVAR_FLAG_NONE); + } + rrddim_foreach_done(rd); +} + int health_variable_lookup(STRING *variable, RRDCALC *rc, NETDATA_DOUBLE *result) { RRDSET *st = rc->rrdset; if(!st) return 0; diff --git a/database/rrdvar.h b/database/rrdvar.h index a511c732..3b6e9cb9 100644 --- a/database/rrdvar.h +++ b/database/rrdvar.h @@ -26,6 +26,7 @@ typedef enum rrdvar_options { RRDVAR_FLAG_RRDCALC_FAMILY_VAR = (1 << 4), // this is a an alarm variable, attached to a family RRDVAR_FLAG_RRDCALC_HOST_CHARTID_VAR = (1 << 5), // this is a an alarm variable, attached to the host, using the chart id RRDVAR_FLAG_RRDCALC_HOST_CHARTNAME_VAR = (1 << 6), // this is a an alarm variable, attached to the host, using the chart name + RRDVAR_FLAG_CONFIG_VAR = (1 << 7), // this is a an alarm variable, read from alarm config // this is 24 bit // to increase it you have to set change the bitfield in @@ -47,6 +48,7 @@ int rrdvar_fix_name(char *variable); STRING *rrdvar_name_to_string(const char *name); const RRDVAR_ACQUIRED *rrdvar_custom_host_variable_add_and_acquire(RRDHOST *host, const char *name); +void rrdvar_add(const char *scope __maybe_unused, DICTIONARY *dict, STRING *name, RRDVAR_TYPE type, RRDVAR_FLAGS options, void *value); void rrdvar_custom_host_variable_set(RRDHOST *host, const RRDVAR_ACQUIRED *rva, NETDATA_DOUBLE value); int rrdvar_walkthrough_read(DICTIONARY *dict, int (*callback)(const DICTIONARY_ITEM *item, void *rrdvar, void *data), void *data); @@ -60,8 +62,12 @@ const RRDVAR_ACQUIRED *rrdvar_add_and_acquire(const char *scope, DICTIONARY *dic void rrdvar_release_and_del(DICTIONARY *dict, const RRDVAR_ACQUIRED *rva); DICTIONARY *rrdvariables_create(void); +DICTIONARY *health_rrdvariables_create(void); void rrdvariables_destroy(DICTIONARY *dict); +void rrdvar_store_for_chart(RRDHOST *host, RRDSET *st); +int health_variable_check(DICTIONARY *dict, RRDSET *st, RRDDIM *rd); + void rrdvar_delete_all(DICTIONARY *dict); const char *rrdvar_name(const RRDVAR_ACQUIRED *rva); diff --git a/database/sqlite/sqlite_aclk.c b/database/sqlite/sqlite_aclk.c index 3b0c4052..a33e09f5 100644 --- a/database/sqlite/sqlite_aclk.c +++ b/database/sqlite/sqlite_aclk.c @@ -5,58 +5,176 @@ #include "sqlite_aclk_node.h" +struct aclk_sync_config_s { + uv_thread_t thread; + uv_loop_t loop; + uv_timer_t timer_req; + time_t cleanup_after; // Start a cleanup after this timestamp + uv_async_t async; + /* FIFO command queue */ + uv_mutex_t cmd_mutex; + uv_cond_t cmd_cond; + bool initialized; + volatile unsigned queue_size; + struct aclk_database_cmdqueue cmd_queue; +} aclk_sync_config = { 0 }; + + void sanity_check(void) { // make sure the compiler will stop on misconfigurations BUILD_BUG_ON(WORKER_UTILIZATION_MAX_JOB_TYPES < ACLK_MAX_ENUMERATIONS_DEFINED); } -static int sql_check_aclk_table(void *data, int argc, char **argv, char **column) + +int aclk_database_enq_cmd_noblock(struct aclk_database_cmd *cmd) { - struct aclk_database_worker_config *wc = data; - UNUSED(argc); - UNUSED(column); + unsigned queue_size; - 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); + /* wait for free space in queue */ + uv_mutex_lock(&aclk_sync_config.cmd_mutex); + if ((queue_size = aclk_sync_config.queue_size) == ACLK_DATABASE_CMD_Q_MAX_SIZE) { + uv_mutex_unlock(&aclk_sync_config.cmd_mutex); + return 1; + } + + fatal_assert(queue_size < ACLK_DATABASE_CMD_Q_MAX_SIZE); + /* enqueue command */ + aclk_sync_config.cmd_queue.cmd_array[aclk_sync_config.cmd_queue.tail] = *cmd; + aclk_sync_config.cmd_queue.tail = aclk_sync_config.cmd_queue.tail != ACLK_DATABASE_CMD_Q_MAX_SIZE - 1 ? + aclk_sync_config.cmd_queue.tail + 1 : 0; + aclk_sync_config.queue_size = queue_size + 1; + uv_mutex_unlock(&aclk_sync_config.cmd_mutex); 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) +static void aclk_database_enq_cmd(struct aclk_database_cmd *cmd) { - 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); + unsigned queue_size; + + /* wait for free space in queue */ + uv_mutex_lock(&aclk_sync_config.cmd_mutex); + while ((queue_size = aclk_sync_config.queue_size) == ACLK_DATABASE_CMD_Q_MAX_SIZE) { + uv_cond_wait(&aclk_sync_config.cmd_cond, &aclk_sync_config.cmd_mutex); } + fatal_assert(queue_size < ACLK_DATABASE_CMD_Q_MAX_SIZE); + /* enqueue command */ + aclk_sync_config.cmd_queue.cmd_array[aclk_sync_config.cmd_queue.tail] = *cmd; + aclk_sync_config.cmd_queue.tail = aclk_sync_config.cmd_queue.tail != ACLK_DATABASE_CMD_Q_MAX_SIZE - 1 ? + aclk_sync_config.cmd_queue.tail + 1 : 0; + aclk_sync_config.queue_size = queue_size + 1; + uv_mutex_unlock(&aclk_sync_config.cmd_mutex); + + /* wake up event loop */ + int rc = uv_async_send(&aclk_sync_config.async); + if (unlikely(rc)) + debug(D_ACLK_SYNC, "Failed to wake up event loop"); } -static void sql_maint_aclk_sync_database(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd) +enum { + IDX_HOST_ID, + IDX_HOSTNAME, + IDX_REGISTRY, + IDX_UPDATE_EVERY, + IDX_OS, + IDX_TIMEZONE, + IDX_TAGS, + IDX_HOPS, + IDX_MEMORY_MODE, + IDX_ABBREV_TIMEZONE, + IDX_UTC_OFFSET, + IDX_PROGRAM_NAME, + IDX_PROGRAM_VERSION, + IDX_ENTRIES, + IDX_HEALTH_ENABLED, +}; + +static int create_host_callback(void *data, int argc, char **argv, char **column) { - UNUSED(cmd); + int *number_of_chidren = data; + UNUSED(argc); + UNUSED(column); - debug(D_ACLK, "Checking database for %s", wc->host_guid); + char guid[UUID_STR_LEN]; + uuid_unparse_lower(*(uuid_t *)argv[IDX_HOST_ID], guid); - BUFFER *sql = buffer_create(ACLK_SYNC_QUERY_SIZE, &netdata_buffers_statistics.buffers_sqlite); + 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); - 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)); + system_info->hops = str2i((const char *) argv[IDX_HOPS]); - buffer_free(sql); + sql_build_host_system_info((uuid_t *)argv[IDX_HOST_ID], system_info); + + RRDHOST *host = rrdhost_find_or_create( + (const char *) argv[IDX_HOSTNAME] + , (const char *) argv[IDX_REGISTRY] + , guid + , (const char *) argv[IDX_OS] + , (const char *) argv[IDX_TIMEZONE] + , (const char *) argv[IDX_ABBREV_TIMEZONE] + , (int32_t) (argv[IDX_UTC_OFFSET] ? str2uint32_t(argv[IDX_UTC_OFFSET], NULL) : 0) + , (const char *) argv[IDX_TAGS] + , (const char *) (argv[IDX_PROGRAM_NAME] ? argv[IDX_PROGRAM_NAME] : "unknown") + , (const char *) (argv[IDX_PROGRAM_VERSION] ? argv[IDX_PROGRAM_VERSION] : "unknown") + , argv[IDX_UPDATE_EVERY] ? str2i(argv[IDX_UPDATE_EVERY]) : 1 + , argv[IDX_ENTRIES] ? str2i(argv[IDX_ENTRIES]) : 0 + , default_rrd_memory_mode + , 0 // health + , 0 // rrdpush enabled + , NULL //destination + , NULL // api key + , NULL // send charts matching + , false // rrdpush_enable_replication + , 0 // rrdpush_seconds_to_replicate + , 0 // rrdpush_replication_step + , system_info + , 1 + ); + if (likely(host)) + host->rrdlabels = sql_load_host_labels((uuid_t *)argv[IDX_HOST_ID]); + + (*number_of_chidren)++; + +#ifdef NETDATA_INTERNAL_CHECKS + char node_str[UUID_STR_LEN] = ""; + if (likely(host->node_id)) + uuid_unparse_lower(*host->node_id, node_str); + internal_error(true, "Adding archived host \"%s\" with GUID \"%s\" node id = \"%s\"", rrdhost_hostname(host), host->machine_guid, node_str); +#endif + return 0; } +#ifdef ENABLE_ACLK +static struct aclk_database_cmd aclk_database_deq_cmd(void) +{ + struct aclk_database_cmd ret; + unsigned queue_size; -#define SQL_SELECT_HOST_BY_UUID "SELECT host_id FROM host WHERE host_id = @host_id;" + uv_mutex_lock(&aclk_sync_config.cmd_mutex); + queue_size = aclk_sync_config.queue_size; + if (queue_size == 0) { + memset(&ret, 0, sizeof(ret)); + ret.opcode = ACLK_DATABASE_NOOP; + ret.completion = NULL; + + } else { + /* dequeue command */ + ret = aclk_sync_config.cmd_queue.cmd_array[aclk_sync_config.cmd_queue.head]; + if (queue_size == 1) { + aclk_sync_config.cmd_queue.head = aclk_sync_config.cmd_queue.tail = 0; + } else { + aclk_sync_config.cmd_queue.head = aclk_sync_config.cmd_queue.head != ACLK_DATABASE_CMD_Q_MAX_SIZE - 1 ? + aclk_sync_config.cmd_queue.head + 1 : 0; + } + aclk_sync_config.queue_size = queue_size - 1; + /* wake up producers */ + uv_cond_signal(&aclk_sync_config.cmd_cond); + } + uv_mutex_unlock(&aclk_sync_config.cmd_mutex); + return ret; +} + +#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; @@ -76,7 +194,7 @@ static int is_host_available(uuid_t *host_id) 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"); + error_report("Failed to bind host_id parameter to check host existence"); goto failed; } rc = sqlite3_step_monitored(res); @@ -89,15 +207,13 @@ failed: } // OPCODE: ACLK_DATABASE_DELETE_HOST -void sql_delete_aclk_table_list(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd) +static void sql_delete_aclk_table_list(char *host_guid) { - UNUSED(wc); - char uuid_str[GUID_LEN + 1]; - char host_str[GUID_LEN + 1]; + char uuid_str[UUID_STR_LEN]; + char host_str[UUID_STR_LEN]; int rc; uuid_t host_uuid; - char *host_guid = (char *)cmd.data; if (unlikely(!host_guid)) return; @@ -139,273 +255,67 @@ void sql_delete_aclk_table_list(struct aclk_database_worker_config *wc, struct a if (unlikely(rc != SQLITE_OK)) error_report("Failed to finalize statement to clean up aclk tables, rc = %d", rc); - db_execute(buffer_tostring(sql)); + rc = db_execute(db_meta, buffer_tostring(sql)); + if (unlikely(rc)) + error("Failed to drop unused ACLK tables"); fail: buffer_free(sql); } -uv_mutex_t aclk_async_lock; -struct aclk_database_worker_config *aclk_thread_head = NULL; - -int claimed() +static int sql_check_aclk_table(void *data __maybe_unused, int argc __maybe_unused, char **argv __maybe_unused, char **column __maybe_unused) { - int rc; - rrdhost_aclk_state_lock(localhost); - rc = (localhost->aclk_state.claimed_id != NULL); - rrdhost_aclk_state_unlock(localhost); - return rc; -} - -void aclk_add_worker_thread(struct aclk_database_worker_config *wc) -{ - if (unlikely(!wc)) - return; - - uv_mutex_lock(&aclk_async_lock); - if (unlikely(!wc->host)) { - wc->next = aclk_thread_head; - aclk_thread_head = wc; - } - uv_mutex_unlock(&aclk_async_lock); + 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.param[0] = strdupz((char *) argv[0]); + aclk_database_enq_cmd_noblock(&cmd); + return 0; } -void aclk_del_worker_thread(struct aclk_database_worker_config *wc) -{ - if (unlikely(!wc)) - return; - - uv_mutex_lock(&aclk_async_lock); - struct aclk_database_worker_config **tmp = &aclk_thread_head; - while (*tmp && (*tmp) != wc) - tmp = &(*tmp)->next; - if (*tmp) - *tmp = wc->next; - uv_mutex_unlock(&aclk_async_lock); -} +#define SQL_SELECT_ACLK_ACTIVE_LIST "SELECT REPLACE(SUBSTR(name,19),'_','-') FROM sqlite_schema " \ + "WHERE name LIKE 'aclk_chart_latest_%' AND type IN ('table');" -int aclk_worker_thread_exists(char *guid) +static void sql_check_aclk_table_list(void) { - int rc = 0; - uv_mutex_lock(&aclk_async_lock); - - struct aclk_database_worker_config *tmp = aclk_thread_head; - - while (tmp && !rc) { - rc = strcmp(tmp->uuid_str, guid) == 0; - tmp = tmp->next; + 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, NULL, &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); } - uv_mutex_unlock(&aclk_async_lock); - return rc; } -void aclk_database_init_cmd_queue(struct aclk_database_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)); -} +#define SQL_ALERT_CLEANUP "DELETE FROM aclk_alert_%s WHERE date_submitted IS NOT NULL AND CAST(date_cloud_ack AS INT) < unixepoch()-%d;" -int aclk_database_enq_cmd_noblock(struct aclk_database_worker_config *wc, struct aclk_database_cmd *cmd) +static int sql_maint_aclk_sync_database(void *data __maybe_unused, int argc __maybe_unused, char **argv, char **column __maybe_unused) { - unsigned queue_size; - - /* wait for free space in queue */ - uv_mutex_lock(&wc->cmd_mutex); - if ((queue_size = wc->queue_size) == ACLK_DATABASE_CMD_Q_MAX_SIZE || wc->is_shutting_down) { - uv_mutex_unlock(&wc->cmd_mutex); - return 1; - } - - fatal_assert(queue_size < ACLK_DATABASE_CMD_Q_MAX_SIZE); - /* enqueue command */ - wc->cmd_queue.cmd_array[wc->cmd_queue.tail] = *cmd; - wc->cmd_queue.tail = wc->cmd_queue.tail != ACLK_DATABASE_CMD_Q_MAX_SIZE - 1 ? - wc->cmd_queue.tail + 1 : 0; - wc->queue_size = queue_size + 1; - uv_mutex_unlock(&wc->cmd_mutex); + char sql[512]; + snprintfz(sql,511, SQL_ALERT_CLEANUP, (char *) argv[0], ACLK_DELETE_ACK_ALERTS_INTERNAL); + if (unlikely(db_execute(db_meta, sql))) + error_report("Failed to clean stale ACLK alert entries"); return 0; } -void aclk_database_enq_cmd(struct aclk_database_worker_config *wc, struct aclk_database_cmd *cmd) -{ - unsigned queue_size; - - /* wait for free space in queue */ - uv_mutex_lock(&wc->cmd_mutex); - if (wc->is_shutting_down) { - uv_mutex_unlock(&wc->cmd_mutex); - return; - } - while ((queue_size = wc->queue_size) == ACLK_DATABASE_CMD_Q_MAX_SIZE) { - uv_cond_wait(&wc->cmd_cond, &wc->cmd_mutex); - } - fatal_assert(queue_size < ACLK_DATABASE_CMD_Q_MAX_SIZE); - /* enqueue command */ - wc->cmd_queue.cmd_array[wc->cmd_queue.tail] = *cmd; - wc->cmd_queue.tail = wc->cmd_queue.tail != ACLK_DATABASE_CMD_Q_MAX_SIZE - 1 ? - wc->cmd_queue.tail + 1 : 0; - wc->queue_size = queue_size + 1; - uv_mutex_unlock(&wc->cmd_mutex); - - /* wake up event loop */ - int rc = uv_async_send(&wc->async); - if (unlikely(rc)) - debug(D_ACLK_SYNC, "Failed to wake up event loop"); -} - -struct aclk_database_cmd aclk_database_deq_cmd(struct aclk_database_worker_config* wc) -{ - struct aclk_database_cmd ret; - unsigned queue_size; - uv_mutex_lock(&wc->cmd_mutex); - queue_size = wc->queue_size; - if (queue_size == 0 || wc->is_shutting_down) { - memset(&ret, 0, sizeof(ret)); - ret.opcode = ACLK_DATABASE_NOOP; - ret.completion = NULL; - if (wc->is_shutting_down) - uv_cond_signal(&wc->cmd_cond); - } 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 != ACLK_DATABASE_CMD_Q_MAX_SIZE - 1 ? - wc->cmd_queue.head + 1 : 0; - } - wc->queue_size = queue_size - 1; - /* wake up producers */ - uv_cond_signal(&wc->cmd_cond); - } - uv_mutex_unlock(&wc->cmd_mutex); - - return ret; -} +#define SQL_SELECT_ACLK_ALERT_LIST "SELECT SUBSTR(name,12) FROM sqlite_schema WHERE name LIKE 'aclk_alert_%' AND type IN ('table');" -struct aclk_database_worker_config *find_inactive_wc_by_node_id(char *node_id) +static void sql_maint_aclk_sync_database_all(void) { - if (unlikely(!node_id)) - return NULL; - - uv_mutex_lock(&aclk_async_lock); - struct aclk_database_worker_config *wc = aclk_thread_head; - - while (wc) { - if (!strcmp(wc->node_id, node_id)) - break; - wc = wc->next; + 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_ALERT_LIST, sql_maint_aclk_sync_database, NULL, &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); } - uv_mutex_unlock(&aclk_async_lock); - - return (wc); } -void aclk_sync_exit_all() -{ - rrd_rdlock(); - RRDHOST *host; - rrdhost_foreach_read(host) { - struct aclk_database_worker_config *wc = host->dbsync_worker; - if (wc) { - wc->is_shutting_down = 1; - (void) aclk_database_deq_cmd(wc); - uv_cond_signal(&wc->cmd_cond); - } - } - rrd_unlock(); - - uv_mutex_lock(&aclk_async_lock); - struct aclk_database_worker_config *wc = aclk_thread_head; - while (wc) { - wc->is_shutting_down = 1; - wc = wc->next; - } - uv_mutex_unlock(&aclk_async_lock); -} - -enum { - IDX_HOST_ID, - IDX_HOSTNAME, - IDX_REGISTRY, - IDX_UPDATE_EVERY, - IDX_OS, - IDX_TIMEZONE, - IDX_TAGS, - IDX_HOPS, - IDX_MEMORY_MODE, - IDX_ABBREV_TIMEZONE, - IDX_UTC_OFFSET, - IDX_PROGRAM_NAME, - IDX_PROGRAM_VERSION, - IDX_ENTRIES, - IDX_HEALTH_ENABLED, -}; - -static int create_host_callback(void *data, int argc, char **argv, char **column) -{ - UNUSED(data); - UNUSED(argc); - UNUSED(column); - - char guid[UUID_STR_LEN]; - 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); - - RRDHOST *host = rrdhost_find_or_create( - (const char *) argv[IDX_HOSTNAME] - , (const char *) argv[IDX_REGISTRY] - , guid - , (const char *) argv[IDX_OS] - , (const char *) argv[IDX_TIMEZONE] - , (const char *) argv[IDX_ABBREV_TIMEZONE] - , argv[IDX_UTC_OFFSET] ? str2uint32_t(argv[IDX_UTC_OFFSET]) : 0 - , (const char *) argv[IDX_TAGS] - , (const char *) (argv[IDX_PROGRAM_NAME] ? argv[IDX_PROGRAM_NAME] : "unknown") - , (const char *) (argv[IDX_PROGRAM_VERSION] ? argv[IDX_PROGRAM_VERSION] : "unknown") - , argv[3] ? str2i(argv[IDX_UPDATE_EVERY]) : 1 - , argv[13] ? str2i(argv[IDX_ENTRIES]) : 0 - , default_rrd_memory_mode - , 0 // health - , 0 // rrdpush enabled - , NULL //destination - , NULL // api key - , NULL // send charts matching - , false // rrdpush_enable_replication - , 0 // rrdpush_seconds_to_replicate - , 0 // rrdpush_replication_step - , system_info - , 1 - ); - if (likely(host)) - host->rrdlabels = sql_load_host_labels((uuid_t *)argv[IDX_HOST_ID]); - -#ifdef NETDATA_INTERNAL_CHECKS - char node_str[UUID_STR_LEN] = ""; - if (likely(host->node_id)) - uuid_unparse_lower(*host->node_id, node_str); - internal_error(true, "Adding archived host \"%s\" with GUID \"%s\" node id = \"%s\"", rrdhost_hostname(host), host->machine_guid, node_str); -#endif - return 0; -} - -#ifdef ENABLE_ACLK -static int aclk_start_sync_thread(void *data, int argc, char **argv, char **column) +static int aclk_config_parameters(void *data __maybe_unused, int argc __maybe_unused, char **argv, char **column __maybe_unused) { char uuid_str[GUID_LEN + 1]; - UNUSED(data); - UNUSED(argc); - UNUSED(column); - uuid_unparse_lower(*((uuid_t *) argv[0]), uuid_str); RRDHOST *host = rrdhost_find_by_guid(uuid_str); @@ -415,156 +325,81 @@ static int aclk_start_sync_thread(void *data, int argc, char **argv, char **colu sql_create_aclk_table(host, (uuid_t *) argv[0], (uuid_t *) argv[1]); return 0; } -#endif -void sql_aclk_sync_init(void) -{ - char *err_msg = NULL; - int rc; - - if (unlikely(!db_meta)) { - if (default_rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE) { - return; - } - error_report("Database has not been initialized"); - return; - } - - 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;", - create_host_callback, NULL, &err_msg); - if (rc != SQLITE_OK) { - error_report("SQLite error when loading archived hosts, rc = %d (%s)", rc, err_msg); - 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 -} static void async_cb(uv_async_t *handle) { uv_stop(handle->loop); uv_update_time(handle->loop); - debug(D_ACLK_SYNC, "%s called, active=%d.", __func__, uv_is_active((uv_handle_t *)handle)); } #define TIMER_PERIOD_MS (1000) -static void timer_cb(uv_timer_t* handle) +static void timer_cb(uv_timer_t *handle) { uv_stop(handle->loop); uv_update_time(handle->loop); -#ifdef ENABLE_ACLK - struct aclk_database_worker_config *wc = handle->data; + struct aclk_sync_config_s *config = handle->data; struct aclk_database_cmd cmd; memset(&cmd, 0, sizeof(cmd)); - cmd.opcode = ACLK_DATABASE_TIMER; - aclk_database_enq_cmd_noblock(wc, &cmd); time_t now = now_realtime_sec(); - if (wc->cleanup_after && wc->cleanup_after < now) { + if (config->cleanup_after && config->cleanup_after < now) { cmd.opcode = ACLK_DATABASE_CLEANUP; - if (!aclk_database_enq_cmd_noblock(wc, &cmd)) - wc->cleanup_after += ACLK_DATABASE_CLEANUP_INTERVAL; + if (!aclk_database_enq_cmd_noblock(&cmd)) + config->cleanup_after += ACLK_DATABASE_CLEANUP_INTERVAL; } if (aclk_connected) { - if (wc->alert_updates && !wc->pause_alert_updates) { - cmd.opcode = ACLK_DATABASE_PUSH_ALERT; - cmd.count = ACLK_MAX_ALERT_UPDATES; - aclk_database_enq_cmd_noblock(wc, &cmd); - } + cmd.opcode = ACLK_DATABASE_PUSH_ALERT; + aclk_database_enq_cmd_noblock(&cmd); + + aclk_check_node_info_and_collectors(); } -#endif } -static void aclk_database_worker(void *arg) +static void aclk_synchronization(void *arg __maybe_unused) { - service_register(SERVICE_THREAD_TYPE_EVENT_LOOP, NULL, NULL, NULL, true); + struct aclk_sync_config_s *config = arg; + uv_thread_set_name_np(config->thread, "ACLKSYNC"); worker_register("ACLKSYNC"); + service_register(SERVICE_THREAD_TYPE_EVENT_LOOP, NULL, NULL, NULL, true); + worker_register_job_name(ACLK_DATABASE_NOOP, "noop"); - worker_register_job_name(ACLK_DATABASE_ORPHAN_HOST, "node orphan"); - worker_register_job_name(ACLK_DATABASE_ALARM_HEALTH_LOG, "alert log"); worker_register_job_name(ACLK_DATABASE_CLEANUP, "cleanup"); worker_register_job_name(ACLK_DATABASE_DELETE_HOST, "node delete"); - worker_register_job_name(ACLK_DATABASE_NODE_INFO, "node info"); - worker_register_job_name(ACLK_DATABASE_NODE_COLLECTORS, "node collectors"); + worker_register_job_name(ACLK_DATABASE_NODE_STATE, "node state"); worker_register_job_name(ACLK_DATABASE_PUSH_ALERT, "alert push"); worker_register_job_name(ACLK_DATABASE_PUSH_ALERT_CONFIG, "alert conf push"); + worker_register_job_name(ACLK_DATABASE_PUSH_ALERT_CHECKPOINT,"alert checkpoint"); worker_register_job_name(ACLK_DATABASE_PUSH_ALERT_SNAPSHOT, "alert snapshot"); worker_register_job_name(ACLK_DATABASE_QUEUE_REMOVED_ALERTS, "alerts check"); worker_register_job_name(ACLK_DATABASE_TIMER, "timer"); - struct aclk_database_worker_config *wc = arg; - uv_loop_t *loop; - int ret; - enum aclk_database_opcode opcode; - uv_timer_t timer_req; - struct aclk_database_cmd cmd; + uv_loop_t *loop = &config->loop; + fatal_assert(0 == uv_loop_init(loop)); + fatal_assert(0 == uv_async_init(loop, &config->async, async_cb)); - char threadname[NETDATA_THREAD_NAME_MAX+1]; - if (wc->host) - snprintfz(threadname, NETDATA_THREAD_NAME_MAX, "ACLK[%s]", rrdhost_hostname(wc->host)); - else { - snprintfz(threadname, NETDATA_THREAD_NAME_MAX, "ACLK[%s]", wc->uuid_str); - threadname[11] = '\0'; - } - uv_thread_set_name_np(wc->thread, threadname); - - 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; + fatal_assert(0 == uv_timer_init(loop, &config->timer_req)); + config->timer_req.data = config; + fatal_assert(0 == uv_timer_start(&config->timer_req, timer_cb, TIMER_PERIOD_MS, TIMER_PERIOD_MS)); - 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_timer_init(loop, &timer_req); - if (ret) { - error("uv_timer_init(): %s", uv_strerror(ret)); - goto error_after_timer_init; - } - timer_req.data = wc; - fatal_assert(0 == uv_timer_start(&timer_req, timer_cb, TIMER_PERIOD_MS, TIMER_PERIOD_MS)); - - wc->node_info_send = 1; - info("Starting ACLK sync thread for host %s -- scratch area %lu bytes", wc->host_guid, (unsigned long int) sizeof(*wc)); - - memset(&cmd, 0, sizeof(cmd)); + info("Starting ACLK synchronization thread"); - wc->startup_time = now_realtime_sec(); - wc->cleanup_after = wc->startup_time + ACLK_DATABASE_CLEANUP_FIRST; + config->cleanup_after = now_realtime_sec() + ACLK_DATABASE_CLEANUP_FIRST; + config->initialized = true; - debug(D_ACLK_SYNC,"Node %s reports pending message count = %u", wc->node_id, wc->chart_payload_count); - - while (likely(!netdata_exit)) { + while (likely(service_running(SERVICE_ACLKSYNC))) { + enum aclk_database_opcode opcode; worker_is_idle(); uv_run(loop, UV_RUN_DEFAULT); /* wait for commands */ do { - cmd = aclk_database_deq_cmd(wc); + struct aclk_database_cmd cmd = aclk_database_deq_cmd(); - if (netdata_exit) + if (unlikely(!service_running(SERVICE_ACLKSYNC))) break; opcode = cmd.opcode; @@ -576,201 +411,216 @@ static void aclk_database_worker(void *arg) case ACLK_DATABASE_NOOP: /* the command queue was empty, do nothing */ break; - // MAINTENANCE case ACLK_DATABASE_CLEANUP: - debug(D_ACLK_SYNC, "Database cleanup for %s", wc->host_guid); - - if (wc->startup_time + ACLK_DATABASE_CLEANUP_FIRST + 2 < now_realtime_sec() && claimed() && aclk_connected) { - cmd.opcode = ACLK_DATABASE_NODE_INFO; - cmd.completion = NULL; - (void) aclk_database_enq_cmd_noblock(wc, &cmd); - } - - sql_maint_aclk_sync_database(wc, cmd); - if (wc->host == localhost) - sql_check_aclk_table_list(wc); + // Scan all aclk_alert_ tables and cleanup as needed + sql_maint_aclk_sync_database_all(); + sql_check_aclk_table_list(); break; case ACLK_DATABASE_DELETE_HOST: - debug(D_ACLK_SYNC,"Cleaning ACLK tables for %s", (char *) cmd.data); - sql_delete_aclk_table_list(wc, cmd); + sql_delete_aclk_table_list(cmd.param[0]); + break; +// NODE STATE + case ACLK_DATABASE_NODE_STATE:; + RRDHOST *host = cmd.param[0]; + int live = (host == localhost || host->receiver || !(rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) ? 1 : 0; + struct aclk_sync_host_config *ahc = host->aclk_sync_host_config; + if (unlikely(!ahc)) + sql_create_aclk_table(host, &host->host_uuid, host->node_id); + aclk_host_state_update(host, live); break; - // ALERTS case ACLK_DATABASE_PUSH_ALERT_CONFIG: - debug(D_ACLK_SYNC,"Pushing chart config info to the cloud for %s", wc->host_guid); - aclk_push_alert_config_event(wc, cmd); + aclk_push_alert_config_event(cmd.param[0], cmd.param[1]); break; case ACLK_DATABASE_PUSH_ALERT: - debug(D_ACLK_SYNC, "Pushing alert info to the cloud for %s", wc->host_guid); - aclk_push_alert_event(wc, cmd); - break; - case ACLK_DATABASE_ALARM_HEALTH_LOG: - debug(D_ACLK_SYNC, "Pushing alarm health log to the cloud for %s", wc->host_guid); - aclk_push_alarm_health_log(wc, cmd); + aclk_push_alert_events_for_all_hosts(); break; - case ACLK_DATABASE_PUSH_ALERT_SNAPSHOT: - debug(D_ACLK_SYNC, "Pushing alert snapshot to the cloud for node %s", wc->host_guid); - aclk_push_alert_snapshot_event(wc, cmd); + case ACLK_DATABASE_PUSH_ALERT_SNAPSHOT:; + aclk_push_alert_snapshot_event(cmd.param[0]); break; case ACLK_DATABASE_QUEUE_REMOVED_ALERTS: - debug(D_ACLK_SYNC, "Queueing removed alerts for node %s", wc->host_guid); - sql_process_queue_removed_alerts_to_aclk(wc, cmd); - break; - -// NODE OPERATIONS - case ACLK_DATABASE_NODE_INFO: - debug(D_ACLK_SYNC,"Sending node info for %s", wc->uuid_str); - sql_build_node_info(wc, cmd); - break; - case ACLK_DATABASE_NODE_COLLECTORS: - debug(D_ACLK_SYNC,"Sending node collectors info for %s", wc->uuid_str); - sql_build_node_collectors(wc); - break; -#ifdef ENABLE_ACLK - -// NODE_INSTANCE DETECTION - case ACLK_DATABASE_ORPHAN_HOST: - wc->host = NULL; - wc->is_orphan = 1; - aclk_add_worker_thread(wc); - break; -#endif - case ACLK_DATABASE_TIMER: - if (unlikely(localhost && !wc->host && !wc->is_orphan)) { - if (claimed()) { - 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, "ACLK[%s]", rrdhost_hostname(wc->host)); - uv_thread_set_name_np(wc->thread, threadname); - wc->host->dbsync_worker = wc; - if (unlikely(!wc->hostname)) - wc->hostname = strdupz(rrdhost_hostname(wc->host)); - aclk_del_worker_thread(wc); - wc->node_info_send = 1; - } - } - } - if (wc->node_info_send && localhost && claimed() && aclk_connected) { - cmd.opcode = ACLK_DATABASE_NODE_INFO; - cmd.completion = NULL; - wc->node_info_send = aclk_database_enq_cmd_noblock(wc, &cmd); - } - if (wc->node_collectors_send && wc->node_collectors_send + 30 < now_realtime_sec()) { - cmd.opcode = ACLK_DATABASE_NODE_COLLECTORS; - cmd.completion = NULL; - wc->node_collectors_send = aclk_database_enq_cmd_noblock(wc, &cmd); - } - if (localhost == wc->host) - (void) sqlite3_wal_checkpoint(db_meta, NULL); + sql_process_queue_removed_alerts_to_aclk(cmd.param[0]); break; default: debug(D_ACLK_SYNC, "%s: default.", __func__); break; } if (cmd.completion) - aclk_complete(cmd.completion); + completion_mark_complete(cmd.completion); } while (opcode != ACLK_DATABASE_NOOP); } - if (!uv_timer_stop(&timer_req)) - uv_close((uv_handle_t *)&timer_req, NULL); - - /* cleanup operations of the event loop */ - //info("Shutting down ACLK sync event loop for %s", wc->host_guid); + if (!uv_timer_stop(&config->timer_req)) + uv_close((uv_handle_t *)&config->timer_req, NULL); - /* - * uv_async_send after uv_close does not seem to crash in linux at the moment, - * it is however undocumented behaviour we need to be aware if this becomes - * an issue in the future. - */ - uv_close((uv_handle_t *)&wc->async, NULL); - uv_run(loop, UV_RUN_DEFAULT); + uv_close((uv_handle_t *)&config->async, NULL); +// uv_close((uv_handle_t *)&config->async_exit, NULL); + uv_cond_destroy(&config->cmd_cond); + (void) uv_loop_close(loop); - 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); - - int rc; - do { - rc = uv_loop_close(loop); - } while (rc != UV_EBUSY); - - freez(loop); + worker_unregister(); + service_exits(); + info("ACLK SYNC: Shutting down ACLK synchronization event loop"); +} - rrd_rdlock(); - if (likely(wc->host)) - wc->host->dbsync_worker = NULL; - freez(wc->hostname); - freez(wc); - rrd_unlock(); +static void aclk_synchronization_init(void) +{ + aclk_sync_config.cmd_queue.head = aclk_sync_config.cmd_queue.tail = 0; + aclk_sync_config.queue_size = 0; + fatal_assert(0 == uv_cond_init(&aclk_sync_config.cmd_cond)); + fatal_assert(0 == uv_mutex_init(&aclk_sync_config.cmd_mutex)); - 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); - worker_unregister(); + fatal_assert(0 == uv_thread_create(&aclk_sync_config.thread, aclk_synchronization, &aclk_sync_config)); } +#endif // ------------------------------------------------------------- -void sql_create_aclk_table(RRDHOST *host, uuid_t *host_uuid, uuid_t *node_id) +void sql_create_aclk_table(RRDHOST *host __maybe_unused, uuid_t *host_uuid __maybe_unused, uuid_t *node_id __maybe_unused) { #ifdef ENABLE_ACLK char uuid_str[GUID_LEN + 1]; char host_guid[GUID_LEN + 1]; + int rc; uuid_unparse_lower_fix(host_uuid, uuid_str); - - if (aclk_worker_thread_exists(uuid_str)) - return; - uuid_unparse_lower(*host_uuid, host_guid); - BUFFER *sql = buffer_create(ACLK_SYNC_QUERY_SIZE, &netdata_buffers_statistics.buffers_sqlite); + char sql[ACLK_SYNC_QUERY_SIZE]; - buffer_sprintf(sql, TABLE_ACLK_ALERT, uuid_str); - db_execute(buffer_tostring(sql)); - buffer_flush(sql); - - buffer_sprintf(sql, INDEX_ACLK_ALERT, uuid_str, uuid_str); - db_execute(buffer_tostring(sql)); - - buffer_free(sql); + snprintfz(sql, ACLK_SYNC_QUERY_SIZE-1, TABLE_ACLK_ALERT, uuid_str); + rc = db_execute(db_meta, sql); + if (unlikely(rc)) + error_report("Failed to create ACLK alert table for host %s", host ? rrdhost_hostname(host) : host_guid); + else { + snprintfz(sql, ACLK_SYNC_QUERY_SIZE -1, INDEX_ACLK_ALERT, uuid_str, uuid_str); + rc = db_execute(db_meta, sql); + if (unlikely(rc)) + error_report("Failed to create ACLK alert table index for host %s", host ? string2str(host->hostname) : host_guid); + } + if (likely(host) && unlikely(host->aclk_sync_host_config)) + return; - if (likely(host) && unlikely(host->dbsync_worker)) + if (unlikely(!host)) return; - struct aclk_database_worker_config *wc = callocz(1, sizeof(struct aclk_database_worker_config)); + struct aclk_sync_host_config *wc = callocz(1, sizeof(struct aclk_sync_host_config)); if (node_id && !uuid_is_null(*node_id)) uuid_unparse_lower(*node_id, wc->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); - } + + host->aclk_sync_host_config = (void *)wc; + 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); + wc->host = host; strcpy(wc->uuid_str, uuid_str); - strcpy(wc->host_guid, host_guid); wc->alert_updates = 0; - aclk_database_init_cmd_queue(wc); - aclk_add_worker_thread(wc); - fatal_assert(0 == uv_thread_create(&(wc->thread), aclk_database_worker, wc)); -#else - UNUSED(host); - UNUSED(host_uuid); - UNUSED(node_id); + time_t now = now_realtime_sec(); + wc->node_info_send_time = (host == localhost || NULL == localhost) ? now - 25 : now; #endif -} \ No newline at end of file +} + +#define SQL_FETCH_ALL_HOSTS "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;" + +#define SQL_FETCH_ALL_INSTANCES "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; " +void sql_aclk_sync_init(void) +{ + char *err_msg = NULL; + int rc; + + if (unlikely(!db_meta)) { + if (default_rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE) { + return; + } + error_report("Database has not been initialized"); + return; + } + + info("Creating archived hosts"); + int number_of_children = 0; + rc = sqlite3_exec_monitored(db_meta, SQL_FETCH_ALL_HOSTS, create_host_callback, &number_of_children, &err_msg); + + if (rc != SQLITE_OK) { + error_report("SQLite error when loading archived hosts, rc = %d (%s)", rc, err_msg); + sqlite3_free(err_msg); + } + + info("Created %d archived hosts", number_of_children); + // Trigger host context load for hosts that have been created + metadata_queue_load_host_context(NULL); + +#ifdef ENABLE_ACLK + if (!number_of_children) + aclk_queue_node_info(localhost, true); + + rc = sqlite3_exec_monitored(db_meta, SQL_FETCH_ALL_INSTANCES,aclk_config_parameters, NULL,&err_msg); + + if (rc != SQLITE_OK) { + error_report("SQLite error when configuring host ACLK synchonization parameters, rc = %d (%s)", rc, err_msg); + sqlite3_free(err_msg); + } + aclk_synchronization_init(); + + info("ACLK sync initialization completed"); +#endif +} + +// Public + +static inline void queue_aclk_sync_cmd(enum aclk_database_opcode opcode, const void *param0, const void *param1) +{ + struct aclk_database_cmd cmd; + cmd.opcode = opcode; + cmd.param[0] = (void *) param0; + cmd.param[1] = (void *) param1; + cmd.completion = NULL; + aclk_database_enq_cmd(&cmd); +} + +// Public +void aclk_push_alert_config(const char *node_id, const char *config_hash) +{ + if (unlikely(!aclk_sync_config.initialized)) + return; + + queue_aclk_sync_cmd(ACLK_DATABASE_PUSH_ALERT_CONFIG, strdupz(node_id), strdupz(config_hash)); +} + +void aclk_push_node_alert_snapshot(const char *node_id) +{ + if (unlikely(!aclk_sync_config.initialized)) + return; + + queue_aclk_sync_cmd(ACLK_DATABASE_PUSH_ALERT_SNAPSHOT, strdupz(node_id), NULL); +} + + +void aclk_push_node_removed_alerts(const char *node_id) +{ + if (unlikely(!aclk_sync_config.initialized)) + return; + + queue_aclk_sync_cmd(ACLK_DATABASE_QUEUE_REMOVED_ALERTS, strdupz(node_id), NULL); +} + +void schedule_node_info_update(RRDHOST *host __maybe_unused) +{ +#ifdef ENABLE_ACLK + if (unlikely(!host)) + return; + + struct aclk_database_cmd cmd; + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = ACLK_DATABASE_NODE_STATE; + cmd.param[0] = host; + cmd.completion = NULL; + aclk_database_enq_cmd(&cmd); +#endif +} diff --git a/database/sqlite/sqlite_aclk.h b/database/sqlite/sqlite_aclk.h index 208177e4..d555a0ce 100644 --- a/database/sqlite/sqlite_aclk.h +++ b/database/sqlite/sqlite_aclk.h @@ -18,45 +18,6 @@ #define ACLK_DELETE_ACK_ALERTS_INTERNAL (86400) #define ACLK_SYNC_QUERY_SIZE 512 -struct aclk_completion { - uv_mutex_t mutex; - uv_cond_t cond; - volatile unsigned completed; -}; - -static inline void init_aclk_completion(struct aclk_completion *p) -{ - p->completed = 0; - fatal_assert(0 == uv_cond_init(&p->cond)); - fatal_assert(0 == uv_mutex_init(&p->mutex)); -} - -static inline void destroy_aclk_completion(struct aclk_completion *p) -{ - uv_cond_destroy(&p->cond); - uv_mutex_destroy(&p->mutex); -} - -static inline void wait_for_aclk_completion(struct aclk_completion *p) -{ - uv_mutex_lock(&p->mutex); - while (0 == p->completed) { - uv_cond_wait(&p->cond, &p->mutex); - } - fatal_assert(1 == p->completed); - uv_mutex_unlock(&p->mutex); -} - -static inline void aclk_complete(struct aclk_completion *p) -{ - uv_mutex_lock(&p->mutex); - p->completed = 1; - uv_mutex_unlock(&p->mutex); - uv_cond_broadcast(&p->cond); -} - -extern uv_mutex_t aclk_async_lock; - static inline void uuid_unparse_lower_fix(uuid_t *uuid, char *out) { uuid_unparse_lower(*uuid, out); @@ -66,6 +27,12 @@ static inline void uuid_unparse_lower_fix(uuid_t *uuid, char *out) out[23] = '_'; } +static inline int claimed() +{ + return localhost->aclk_state.claimed_id != NULL; +} + + #define TABLE_ACLK_ALERT "CREATE TABLE IF NOT EXISTS aclk_alert_%s (sequence_id INTEGER PRIMARY KEY, " \ "alert_unique_id, date_created, date_submitted, date_cloud_ack, filtered_alert_unique_id NOT NULL, " \ "unique(alert_unique_id));" @@ -74,16 +41,14 @@ static inline void uuid_unparse_lower_fix(uuid_t *uuid, char *out) enum aclk_database_opcode { ACLK_DATABASE_NOOP = 0, - ACLK_DATABASE_ORPHAN_HOST, - ACLK_DATABASE_ALARM_HEALTH_LOG, ACLK_DATABASE_CLEANUP, ACLK_DATABASE_DELETE_HOST, - ACLK_DATABASE_NODE_INFO, + ACLK_DATABASE_NODE_STATE, ACLK_DATABASE_PUSH_ALERT, ACLK_DATABASE_PUSH_ALERT_CONFIG, ACLK_DATABASE_PUSH_ALERT_SNAPSHOT, + ACLK_DATABASE_PUSH_ALERT_CHECKPOINT, ACLK_DATABASE_QUEUE_REMOVED_ALERTS, - ACLK_DATABASE_NODE_COLLECTORS, ACLK_DATABASE_TIMER, // leave this last @@ -93,10 +58,8 @@ enum aclk_database_opcode { struct aclk_database_cmd { enum aclk_database_opcode opcode; - void *data; - void *data_param; - int count; - struct aclk_completion *completion; + void *param[2]; + struct completion *completion; }; #define ACLK_DATABASE_CMD_Q_MAX_SIZE (1024) @@ -106,67 +69,27 @@ struct aclk_database_cmdqueue { struct aclk_database_cmd cmd_array[ACLK_DATABASE_CMD_Q_MAX_SIZE]; }; -struct aclk_database_worker_config { - uv_thread_t thread; - char uuid_str[GUID_LEN + 1]; - char node_id[GUID_LEN + 1]; - char host_guid[GUID_LEN + 1]; - char *hostname; // hostname to avoid constant lookups - time_t cleanup_after; // Start a cleanup after this timestamp - time_t startup_time; // When the sync thread started - uint64_t alerts_batch_id; // batch id for alerts to use - uint64_t alerts_start_seq_id; // cloud has asked to start streaming from - uint64_t alert_sequence_id; // last alert sequence_id - int pause_alert_updates; - uint32_t chart_payload_count; - uint64_t alerts_snapshot_id; //will contain the snapshot_id value if snapshot was requested - uint64_t alerts_ack_sequence_id; //last sequence_id ack'ed from cloud via sendsnapshot message - uv_loop_t *loop; +struct aclk_sync_host_config { RRDHOST *host; - uv_async_t async; - /* FIFO command queue */ - uv_mutex_t cmd_mutex; - uv_cond_t cmd_cond; - volatile unsigned queue_size; - struct aclk_database_cmdqueue cmd_queue; int alert_updates; - int node_info_send; + int alert_checkpoint_req; + int alert_queue_removed; + time_t node_info_send_time; time_t node_collectors_send; - volatile unsigned is_shutting_down; - volatile unsigned is_orphan; - struct aclk_database_worker_config *next; + char uuid_str[UUID_STR_LEN]; + char node_id[UUID_STR_LEN]; + char *alerts_snapshot_uuid; // will contain the snapshot_uuid value if snapshot was requested }; -static inline RRDHOST *find_host_by_node_id(char *node_id) -{ - uuid_t node_uuid; - if (unlikely(!node_id)) - return NULL; - - if (uuid_parse(node_id, node_uuid)) - return NULL; - - rrd_rdlock(); - RRDHOST *host, *ret = NULL; - rrdhost_foreach_read(host) { - if (host->node_id && !(uuid_compare(*host->node_id, node_uuid))) { - ret = host; - break; - } - } - rrd_unlock(); - - return ret; -} - - extern sqlite3 *db_meta; -int aclk_database_enq_cmd_noblock(struct aclk_database_worker_config *wc, struct aclk_database_cmd *cmd); -void aclk_database_enq_cmd(struct aclk_database_worker_config *wc, struct aclk_database_cmd *cmd); +int aclk_database_enq_cmd_noblock(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); -int claimed(); -void aclk_sync_exit_all(); -struct aclk_database_worker_config *find_inactive_wc_by_node_id(char *node_id); +void aclk_push_alert_config(const char *node_id, const char *config_hash); +void aclk_push_node_alert_snapshot(const char *node_id); +void aclk_push_node_health_log(const char *node_id); +void aclk_push_node_removed_alerts(const char *node_id); +void schedule_node_info_update(RRDHOST *host); + #endif //NETDATA_SQLITE_ACLK_H diff --git a/database/sqlite/sqlite_aclk_alert.c b/database/sqlite/sqlite_aclk_alert.c index ce284ebc..62f1df29 100644 --- a/database/sqlite/sqlite_aclk_alert.c +++ b/database/sqlite/sqlite_aclk_alert.c @@ -5,20 +5,20 @@ #ifdef ENABLE_ACLK #include "../../aclk/aclk_alarm_api.h" -#include "../../aclk/aclk.h" #endif +#define SQL_GET_ALERT_REMOVE_TIME "SELECT when_key FROM health_log_%s WHERE alarm_id = %u " \ + "AND unique_id > %u AND unique_id < %u " \ + "AND new_status = -2;" + time_t removed_when(uint32_t alarm_id, uint32_t before_unique_id, uint32_t after_unique_id, char *uuid_str) { sqlite3_stmt *res = NULL; - int rc = 0; time_t when = 0; char sql[ACLK_SYNC_QUERY_SIZE]; - snprintfz(sql,ACLK_SYNC_QUERY_SIZE-1, "select when_key from health_log_%s where alarm_id = %u " \ - "and unique_id > %u and unique_id < %u " \ - "and new_status = -2;", uuid_str, alarm_id, after_unique_id, before_unique_id); + snprintfz(sql,ACLK_SYNC_QUERY_SIZE-1, SQL_GET_ALERT_REMOVE_TIME, uuid_str, alarm_id, after_unique_id, before_unique_id); - rc = sqlite3_prepare_v2(db_meta, sql, -1, &res, 0); + int rc = sqlite3_prepare_v2(db_meta, sql, -1, &res, 0); if (rc != SQLITE_OK) { error_report("Failed to prepare statement when trying to find removed gap."); return 0; @@ -36,22 +36,26 @@ time_t removed_when(uint32_t alarm_id, uint32_t before_unique_id, uint32_t after return when; } +#define SQL_UPDATE_FILTERED_ALERT "UPDATE aclk_alert_%s SET filtered_alert_unique_id = %u where filtered_alert_unique_id = %u" + void update_filtered(ALARM_ENTRY *ae, uint32_t unique_id, char *uuid_str) { char sql[ACLK_SYNC_QUERY_SIZE]; - snprintfz(sql, ACLK_SYNC_QUERY_SIZE-1, "UPDATE aclk_alert_%s SET filtered_alert_unique_id = %u where filtered_alert_unique_id = %u", uuid_str, ae->unique_id, unique_id); + snprintfz(sql, ACLK_SYNC_QUERY_SIZE-1, SQL_UPDATE_FILTERED_ALERT, uuid_str, ae->unique_id, unique_id); sqlite3_exec_monitored(db_meta, sql, 0, 0, NULL); ae->flags |= HEALTH_ENTRY_FLAG_ACLK_QUEUED; } +#define SQL_SELECT_ALERT_BY_UNIQUE_ID "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;" + 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); + snprintfz(sql,ACLK_SYNC_QUERY_SIZE-1, SQL_SELECT_ALERT_BY_UNIQUE_ID, uuid_str, unique_id); rc = sqlite3_prepare_v2(db_meta, sql, -1, &res, 0); if (rc != SQLITE_OK) { @@ -73,12 +77,18 @@ static inline bool is_event_from_alert_variable_config(uint32_t unique_id, char #define MAX_REMOVED_PERIOD 86400 //decide if some events should be sent or not + +#define SQL_SELECT_ALERT_BY_ID "SELECT hl.new_status, hl.config_hash_id, hl.unique_id FROM health_log_%s hl, aclk_alert_%s aa " \ + "WHERE hl.unique_id = aa.filtered_alert_unique_id " \ + "AND hl.alarm_id = %u " \ + "ORDER BY alarm_event_id DESC LIMIT 1;" + int should_send_to_cloud(RRDHOST *host, ALARM_ENTRY *ae) { sqlite3_stmt *res = NULL; - char uuid_str[GUID_LEN + 1]; + char uuid_str[UUID_STR_LEN]; uuid_unparse_lower_fix(&host->host_uuid, uuid_str); - int send = 1, rc = 0; + int send = 1; if (ae->new_status == RRDCALC_STATUS_REMOVED || ae->new_status == RRDCALC_STATUS_UNINITIALIZED) { return 0; @@ -87,9 +97,6 @@ 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; @@ -97,12 +104,9 @@ int should_send_to_cloud(RRDHOST *host, ALARM_ENTRY *ae) //get the previous sent event of this alarm_id //base the search on the last filtered event - snprintfz(sql,ACLK_SYNC_QUERY_SIZE-1, "select hl.new_status, hl.config_hash_id, hl.unique_id from health_log_%s hl, aclk_alert_%s aa \ - where hl.unique_id = aa.filtered_alert_unique_id \ - and hl.alarm_id = %u \ - order by alarm_event_id desc LIMIT 1;", uuid_str, uuid_str, ae->alarm_id); + snprintfz(sql,ACLK_SYNC_QUERY_SIZE-1, SQL_SELECT_ALERT_BY_ID, uuid_str, uuid_str, ae->alarm_id); - rc = sqlite3_prepare_v2(db_meta, sql, -1, &res, 0); + int rc = sqlite3_prepare_v2(db_meta, sql, -1, &res, 0); if (rc != SQLITE_OK) { error_report("Failed to prepare statement when trying to filter alert events."); send = 1; @@ -126,7 +130,7 @@ int should_send_to_cloud(RRDHOST *host, ALARM_ENTRY *ae) goto done; } - if (uuid_compare(ae->config_hash_id, config_hash_id)) { + if (uuid_memcmp(&ae->config_hash_id, &config_hash_id)) { send = 1; goto done; } @@ -162,6 +166,10 @@ done: // will replace call to aclk_update_alarm in health/health_log.c // and handle both cases + +#define SQL_QUEUE_ALERT_TO_CLOUD "INSERT INTO aclk_alert_%s (alert_unique_id, date_created, filtered_alert_unique_id) " \ + "VALUES (@alert_unique_id, unixepoch(), @alert_unique_id) ON CONFLICT (alert_unique_id) do nothing;" + int sql_queue_alarm_to_aclk(RRDHOST *host, ALARM_ENTRY *ae, int skip_filter) { if(!service_running(SERVICE_ACLK)) @@ -182,27 +190,24 @@ int sql_queue_alarm_to_aclk(RRDHOST *host, ALARM_ENTRY *ae, int skip_filter) } } - int rc = 0; - sqlite3_stmt *res_alert = NULL; - char uuid_str[GUID_LEN + 1]; + char uuid_str[UUID_STR_LEN]; uuid_unparse_lower_fix(&host->host_uuid, uuid_str); - BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite); + if (is_event_from_alert_variable_config(ae->unique_id, uuid_str)) + return 0; + + sqlite3_stmt *res_alert = NULL; + char sql[ACLK_SYNC_QUERY_SIZE]; - buffer_sprintf( - sql, - "INSERT INTO aclk_alert_%s (alert_unique_id, date_created, filtered_alert_unique_id) " - "VALUES (@alert_unique_id, unixepoch(), @alert_unique_id) on conflict (alert_unique_id) do nothing; ", - uuid_str); + snprintfz(sql, ACLK_SYNC_QUERY_SIZE - 1, SQL_QUEUE_ALERT_TO_CLOUD, uuid_str); - rc = sqlite3_prepare_v2(db_meta, buffer_tostring(sql), -1, &res_alert, 0); + int rc = sqlite3_prepare_v2(db_meta, sql, -1, &res_alert, 0); if (unlikely(rc != SQLITE_OK)) { error_report("Failed to prepare statement to store alert event"); - buffer_free(sql); return 1; } - rc = sqlite3_bind_int(res_alert, 1, ae->unique_id); + rc = sqlite3_bind_int(res_alert, 1, (int) ae->unique_id); if (unlikely(rc != SQLITE_OK)) goto bind_fail; @@ -213,16 +218,12 @@ int sql_queue_alarm_to_aclk(RRDHOST *host, ALARM_ENTRY *ae, int skip_filter) } ae->flags |= HEALTH_ENTRY_FLAG_ACLK_QUEUED; - struct aclk_database_worker_config *wc = (struct aclk_database_worker_config *)host->dbsync_worker; - if (wc) { - wc->pause_alert_updates = 0; - } + rrdhost_flag_set(host, RRDHOST_FLAG_ACLK_STREAM_ALERTS); bind_fail: if (unlikely(sqlite3_finalize(res_alert) != SQLITE_OK)) error_report("Failed to reset statement in store alert event, rc = %d", rc); - buffer_free(sql); return 0; } @@ -254,11 +255,10 @@ int rrdcalc_status_to_proto_enum(RRDCALC_STATUS status) #endif } -void aclk_push_alert_event(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd) +void aclk_push_alert_event(struct aclk_sync_host_config *wc) { #ifndef ENABLE_ACLK UNUSED(wc); - UNUSED(cmd); #else int rc; @@ -278,26 +278,7 @@ void aclk_push_alert_event(struct aclk_database_worker_config *wc, struct aclk_d BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite); - if (wc->alerts_start_seq_id != 0) { - buffer_sprintf( - sql, - "UPDATE aclk_alert_%s SET date_submitted = NULL, date_cloud_ack = NULL WHERE sequence_id >= %"PRIu64 - "; UPDATE aclk_alert_%s SET date_cloud_ack = unixepoch() WHERE sequence_id < %"PRIu64 - " and date_cloud_ack is null " - "; UPDATE aclk_alert_%s SET date_submitted = unixepoch() WHERE sequence_id < %"PRIu64 - " and date_submitted is null", - wc->uuid_str, - wc->alerts_start_seq_id, - wc->uuid_str, - wc->alerts_start_seq_id, - wc->uuid_str, - wc->alerts_start_seq_id); - db_execute(buffer_tostring(sql)); - buffer_reset(sql); - wc->alerts_start_seq_id = 0; - } - - int limit = cmd.count > 0 ? cmd.count : 1; + int limit = ACLK_MAX_ALERT_UPDATES; sqlite3_stmt *res = NULL; @@ -318,10 +299,15 @@ void aclk_push_alert_event(struct aclk_database_worker_config *wc, struct aclk_d 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)); + rc = db_execute(db_meta, buffer_tostring(sql_fix)); + if (unlikely(rc)) + error_report("Failed to create ACLK alert table for host %s", rrdhost_hostname(wc->host)); + else { + buffer_flush(sql_fix); + buffer_sprintf(sql_fix, INDEX_ACLK_ALERT, wc->uuid_str, wc->uuid_str); + if (unlikely(db_execute(db_meta, buffer_tostring(sql_fix)))) + error_report("Failed to create ACLK alert table for host %s", rrdhost_hostname(wc->host)); + } buffer_free(sql_fix); // Try again @@ -353,8 +339,8 @@ void aclk_push_alert_event(struct aclk_database_worker_config *wc, struct aclk_d alarm_log.name = strdupz((char *)sqlite3_column_text(res, 11)); alarm_log.family = sqlite3_column_bytes(res, 13) > 0 ? strdupz((char *)sqlite3_column_text(res, 13)) : NULL; - alarm_log.batch_id = wc->alerts_batch_id; - alarm_log.sequence_id = (uint64_t) sqlite3_column_int64(res, 0); + //alarm_log.batch_id = wc->alerts_batch_id; + //alarm_log.sequence_id = (uint64_t) sqlite3_column_int64(res, 0); alarm_log.when = (time_t) sqlite3_column_int64(res, 5); uuid_unparse_lower(*((uuid_t *) sqlite3_column_blob(res, 3)), uuid_str); @@ -429,19 +415,23 @@ void aclk_push_alert_event(struct aclk_database_worker_config *wc, struct aclk_d buffer_sprintf(sql, "UPDATE aclk_alert_%s SET date_submitted=unixepoch() " "WHERE date_submitted IS NULL AND sequence_id BETWEEN %" PRIu64 " AND %" PRIu64 ";", wc->uuid_str, first_sequence_id, last_sequence_id); - db_execute(buffer_tostring(sql)); + + if (unlikely(db_execute(db_meta, buffer_tostring(sql)))) + error_report("Failed to mark ACLK alert entries as submitted for host %s", rrdhost_hostname(wc->host)); + + // Mark to do one more check + rrdhost_flag_set(wc->host, RRDHOST_FLAG_ACLK_STREAM_ALERTS); + } else { if (log_first_sequence_id) log_access( - "ACLK RES [%s (%s)]: ALERTS SENT from %" PRIu64 " to %" PRIu64 " batch=%" PRIu64, + "ACLK RES [%s (%s)]: ALERTS SENT from %" PRIu64 " to %" PRIu64 "", wc->node_id, wc->host ? rrdhost_hostname(wc->host) : "N/A", log_first_sequence_id, - log_last_sequence_id, - wc->alerts_batch_id); + log_last_sequence_id); log_first_sequence_id = 0; log_last_sequence_id = 0; - wc->pause_alert_updates = 1; } rc = sqlite3_finalize(res); @@ -451,8 +441,24 @@ void aclk_push_alert_event(struct aclk_database_worker_config *wc, struct aclk_d freez(claim_id); buffer_free(sql); #endif +} + +void aclk_push_alert_events_for_all_hosts(void) +{ + RRDHOST *host; + + dfe_start_reentrant(rrdhost_root_index, host) { + if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED) || !rrdhost_flag_check(host, RRDHOST_FLAG_ACLK_STREAM_ALERTS)) + continue; - return; + internal_error(true, "ACLK SYNC: Scanning host %s", rrdhost_hostname(host)); + rrdhost_flag_clear(host, RRDHOST_FLAG_ACLK_STREAM_ALERTS); + + struct aclk_sync_host_config *wc = host->aclk_sync_host_config; + if (likely(wc)) + aclk_push_alert_event(wc); + } + dfe_done(host); } void sql_queue_existing_alerts_to_aclk(RRDHOST *host) @@ -467,137 +473,15 @@ void sql_queue_existing_alerts_to_aclk(RRDHOST *host) "where new_status <> 0 and new_status <> -2 and config_hash_id is not null and updated_by_id = 0 " \ "order by unique_id asc on conflict (alert_unique_id) do nothing;", uuid_str, uuid_str, uuid_str); - db_execute(buffer_tostring(sql)); - - buffer_free(sql); - - struct aclk_database_worker_config *wc = (struct aclk_database_worker_config *)host->dbsync_worker; - if (wc) { - wc->pause_alert_updates = 0; - } -} - -void aclk_send_alarm_health_log(char *node_id) -{ - if (unlikely(!node_id)) - return; - - struct aclk_database_worker_config *wc = find_inactive_wc_by_node_id(node_id); - - if (likely(!wc)) { - RRDHOST *host = find_host_by_node_id(node_id); - if (likely(host)) - wc = (struct aclk_database_worker_config *)host->dbsync_worker; - } - - if (!wc) { - log_access("ACLK REQ [%s (N/A)]: HEALTH LOG REQUEST RECEIVED FOR INVALID NODE", node_id); - return; - } - - log_access("ACLK REQ [%s (%s)]: HEALTH LOG REQUEST RECEIVED", node_id, wc->hostname ? wc->hostname : "N/A"); - - struct aclk_database_cmd cmd; - memset(&cmd, 0, sizeof(cmd)); - cmd.opcode = ACLK_DATABASE_ALARM_HEALTH_LOG; - - aclk_database_enq_cmd(wc, &cmd); - return; -} - -void aclk_push_alarm_health_log(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd) -{ - UNUSED(cmd); -#ifndef ENABLE_ACLK - UNUSED(wc); -#else - int rc; - - char *claim_id = get_agent_claimid(); - if (unlikely(!claim_id)) - return; - - RRDHOST *host = wc->host; - if (unlikely(!host)) { - host = find_host_by_node_id(wc->node_id); - - if (unlikely(!host)) { - log_access( - "AC [%s (N/A)]: ACLK synchronization thread for %s is not yet linked to HOST.", - wc->node_id, - wc->host_guid); - freez(claim_id); - return; - } - } - - uint64_t first_sequence = 0; - uint64_t last_sequence = 0; - struct timeval first_timestamp; - struct timeval last_timestamp; - - BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite); - - sqlite3_stmt *res = NULL; - - //TODO: make this better: include info from health log too - buffer_sprintf(sql, "SELECT MIN(sequence_id), MIN(date_created), MAX(sequence_id), MAX(date_created) " \ - "FROM aclk_alert_%s;", wc->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 get health log statistics from the database"); - buffer_free(sql); - freez(claim_id); - return; - } - - first_timestamp.tv_sec = 0; - first_timestamp.tv_usec = 0; - last_timestamp.tv_sec = 0; - last_timestamp.tv_usec = 0; - - while (sqlite3_step_monitored(res) == SQLITE_ROW) { - first_sequence = sqlite3_column_bytes(res, 0) > 0 ? (uint64_t) sqlite3_column_int64(res, 0) : 0; - if (sqlite3_column_bytes(res, 1) > 0) { - first_timestamp.tv_sec = sqlite3_column_int64(res, 1); - } - - last_sequence = sqlite3_column_bytes(res, 2) > 0 ? (uint64_t) sqlite3_column_int64(res, 2) : 0; - if (sqlite3_column_bytes(res, 3) > 0) { - last_timestamp.tv_sec = sqlite3_column_int64(res, 3); - } - } - - struct alarm_log_entries log_entries; - log_entries.first_seq_id = first_sequence; - log_entries.first_when = first_timestamp; - log_entries.last_seq_id = last_sequence; - log_entries.last_when = last_timestamp; - - struct alarm_log_health alarm_log; - alarm_log.claim_id = claim_id; - 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.health_enabled; - - wc->alert_sequence_id = last_sequence; + netdata_rwlock_rdlock(&host->health_log.alarm_log_rwlock); - aclk_send_alarm_log_health(&alarm_log); - log_access("ACLK RES [%s (%s)]: HEALTH LOG SENT from %"PRIu64" to %"PRIu64, wc->node_id, wc->hostname ? wc->hostname : "N/A", first_sequence, last_sequence); + if (unlikely(db_execute(db_meta, buffer_tostring(sql)))) + error_report("Failed to queue existing ACLK alert events for host %s", rrdhost_hostname(host)); - rc = sqlite3_finalize(res); - if (unlikely(rc != SQLITE_OK)) - error_report("Failed to reset statement to get health log statistics from the database, rc = %d", rc); + netdata_rwlock_unlock(&host->health_log.alarm_log_rwlock); - freez(claim_id); buffer_free(sql); - - aclk_alert_reloaded = 1; -#endif - - return; + rrdhost_flag_set(host, RRDHOST_FLAG_ACLK_STREAM_ALERTS); } void aclk_send_alarm_configuration(char *config_hash) @@ -605,22 +489,14 @@ void aclk_send_alarm_configuration(char *config_hash) if (unlikely(!config_hash)) return; - struct aclk_database_worker_config *wc = (struct aclk_database_worker_config *) localhost->dbsync_worker; + struct aclk_sync_host_config *wc = (struct aclk_sync_host_config *) localhost->aclk_sync_host_config; - if (unlikely(!wc)) { + if (unlikely(!wc)) return; - } log_access("ACLK REQ [%s (%s)]: Request to send alert config %s.", wc->node_id, wc->host ? rrdhost_hostname(wc->host) : "N/A", config_hash); - struct aclk_database_cmd cmd; - memset(&cmd, 0, sizeof(cmd)); - cmd.opcode = ACLK_DATABASE_PUSH_ALERT_CONFIG; - cmd.data_param = (void *) strdupz(config_hash); - cmd.completion = NULL; - aclk_database_enq_cmd(wc, &cmd); - - return; + aclk_push_alert_config(wc->node_id, config_hash); } #define SQL_SELECT_ALERT_CONFIG "SELECT alarm, template, on_key, class, type, component, os, hosts, plugin," \ @@ -628,19 +504,31 @@ void aclk_send_alarm_configuration(char *config_hash) "options, host_labels, p_db_lookup_dimensions, p_db_lookup_method, p_db_lookup_options, p_db_lookup_after," \ "p_db_lookup_before, p_update_every FROM alert_hash WHERE hash_id = @hash_id;" -int aclk_push_alert_config_event(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd) +int aclk_push_alert_config_event(char *node_id __maybe_unused, char *config_hash __maybe_unused) { - UNUSED(wc); -#ifndef ENABLE_ACLK - UNUSED(cmd); -#else int rc = 0; +#ifdef ENABLE_ACLK + CHECK_SQLITE_CONNECTION(db_meta); sqlite3_stmt *res = NULL; - char *config_hash = (char *) cmd.data_param; + struct aclk_sync_host_config *wc = NULL; + RRDHOST *host = find_host_by_node_id(node_id); + + if (unlikely(!host)) { + freez(config_hash); + freez(node_id); + return 1; + } + + wc = (struct aclk_sync_host_config *)host->aclk_sync_host_config; + if (unlikely(!wc)) { + freez(config_hash); + freez(node_id); + return 1; + } rc = sqlite3_prepare_v2(db_meta, SQL_SELECT_ALERT_CONFIG, -1, &res, 0); if (rc != SQLITE_OK) { @@ -723,7 +611,6 @@ int aclk_push_alert_config_event(struct aclk_database_worker_config *wc, struct if (likely(p_alarm_config.cfg_hash)) { log_access("ACLK RES [%s (%s)]: Sent alert config %s.", wc->node_id, wc->host ? rrdhost_hostname(wc->host) : "N/A", config_hash); aclk_send_provide_alarm_cfg(&p_alarm_config); - freez((char *) cmd.data_param); freez(p_alarm_config.cfg_hash); destroy_aclk_alarm_configuration(&alarm_config); } @@ -735,150 +622,125 @@ bind_fail: if (unlikely(rc != SQLITE_OK)) error_report("Failed to reset statement when pushing alarm config hash, rc = %d", rc); - return rc; + freez(config_hash); + freez(node_id); #endif - return 0; + return rc; } // Start streaming alerts -void aclk_start_alert_streaming(char *node_id, uint64_t batch_id, uint64_t start_seq_id) +void aclk_start_alert_streaming(char *node_id, bool resets) { if (unlikely(!node_id)) return; - //log_access("ACLK REQ [%s (N/A)]: ALERTS STREAM from %"PRIu64" batch=%"PRIu64".", node_id, start_seq_id, batch_id); - uuid_t node_uuid; if (uuid_parse(node_id, node_uuid)) return; - struct aclk_database_worker_config *wc = NULL; RRDHOST *host = find_host_by_node_id(node_id); - if (likely(host)) { - wc = (struct aclk_database_worker_config *)host->dbsync_worker ? - (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.health_enabled)) { - log_access("ACLK STA [%s (N/A)]: Ignoring request to stream alert state changes, health is disabled.", node_id); - return; - } + if (unlikely(!host)) + return; - if (unlikely(batch_id == 1) && unlikely(start_seq_id == 1)) - sql_queue_existing_alerts_to_aclk(host); - } else - wc = (struct aclk_database_worker_config *)find_inactive_wc_by_node_id(node_id); - - if (likely(wc)) { - log_access("ACLK REQ [%s (%s)]: ALERTS STREAM from %"PRIu64" batch=%"PRIu64, node_id, wc->host ? rrdhost_hostname(wc->host) : "N/A", start_seq_id, batch_id); - __sync_synchronize(); - wc->alerts_batch_id = batch_id; - wc->alerts_start_seq_id = start_seq_id; - wc->alert_updates = 1; - wc->pause_alert_updates = 0; - __sync_synchronize(); + struct aclk_sync_host_config *wc = host->aclk_sync_host_config; + + if (unlikely(!wc)) + return; + + 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; } - else - log_access("ACLK STA [%s (N/A)]: ACLK synchronization thread is not active.", node_id); - return; + if (resets) { + log_access("ACLK REQ [%s (%s)]: STREAM ALERTS ENABLED (RESET REQUESTED)", node_id, wc->host ? rrdhost_hostname(wc->host) : "N/A"); + sql_queue_existing_alerts_to_aclk(host); + } else + log_access("ACLK REQ [%s (%s)]: STREAM ALERTS ENABLED", node_id, wc->host ? rrdhost_hostname(wc->host) : "N/A"); + + wc->alert_updates = 1; + wc->alert_queue_removed = SEND_REMOVED_AFTER_HEALTH_LOOPS; } -void sql_process_queue_removed_alerts_to_aclk(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd) -{ - UNUSED(cmd); +#define SQL_QUEUE_REMOVE_ALERTS "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 " \ + "WHERE new_status = -2 AND updated_by_id = 0 AND unique_id NOT IN " \ + "(SELECT alert_unique_id FROM aclk_alert_%s) " \ + "AND config_hash_id NOT IN (select hash_id from alert_hash where warn is null and crit is null) " \ + "ORDER BY unique_id ASC " \ + "ON CONFLICT (alert_unique_id) DO NOTHING;" - BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite); +void sql_process_queue_removed_alerts_to_aclk(char *node_id) +{ + struct aclk_sync_host_config *wc; + RRDHOST *host = find_host_by_node_id(node_id); + freez(node_id); - 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 " \ - "where new_status = -2 and updated_by_id = 0 and unique_id not in " \ - "(select alert_unique_id from aclk_alert_%s) order by unique_id asc " \ - "on conflict (alert_unique_id) do nothing;", wc->uuid_str, wc->uuid_str, wc->uuid_str); + if (unlikely(!host || !(wc = host->aclk_sync_host_config))) + return; - db_execute(buffer_tostring(sql)); + char sql[ACLK_SYNC_QUERY_SIZE * 2]; - log_access("ACLK STA [%s (%s)]: QUEUED REMOVED ALERTS", wc->node_id, wc->host ? rrdhost_hostname(wc->host) : "N/A"); + snprintfz(sql,ACLK_SYNC_QUERY_SIZE * 2 - 1, SQL_QUEUE_REMOVE_ALERTS, wc->uuid_str, wc->uuid_str, wc->uuid_str); - buffer_free(sql); + if (unlikely(db_execute(db_meta, sql))) { + log_access("ACLK STA [%s (%s)]: QUEUED REMOVED ALERTS FAILED", wc->node_id, rrdhost_hostname(wc->host)); + error_report("Failed to queue ACLK alert removed entries for host %s", rrdhost_hostname(wc->host)); + } + else + log_access("ACLK STA [%s (%s)]: QUEUED REMOVED ALERTS", wc->node_id, rrdhost_hostname(wc->host)); - wc->pause_alert_updates = 0; - return; + rrdhost_flag_set(wc->host, RRDHOST_FLAG_ACLK_STREAM_ALERTS); + wc->alert_queue_removed = 0; } void sql_queue_removed_alerts_to_aclk(RRDHOST *host) { - if (unlikely(!host->dbsync_worker)) + if (unlikely(!host->aclk_sync_host_config)) return; - if (!claimed()) + if (!claimed() || !host->node_id) return; - struct aclk_database_cmd cmd; - memset(&cmd, 0, sizeof(cmd)); - cmd.opcode = ACLK_DATABASE_QUEUE_REMOVED_ALERTS; - cmd.data = NULL; - cmd.data_param = NULL; - cmd.completion = NULL; - aclk_database_enq_cmd((struct aclk_database_worker_config *) host->dbsync_worker, &cmd); + char node_id[UUID_STR_LEN]; + uuid_unparse_lower(*host->node_id, node_id); + + aclk_push_node_removed_alerts(node_id); } -void aclk_process_send_alarm_snapshot(char *node_id, char *claim_id, uint64_t snapshot_id, uint64_t sequence_id) +void aclk_process_send_alarm_snapshot(char *node_id, char *claim_id __maybe_unused, char *snapshot_uuid) { - UNUSED(claim_id); - if (unlikely(!node_id)) - return; - uuid_t node_uuid; - if (uuid_parse(node_id, node_uuid)) + if (unlikely(!node_id || uuid_parse(node_id, node_uuid))) return; - struct aclk_database_worker_config *wc = NULL; RRDHOST *host = find_host_by_node_id(node_id); - if (likely(host)) - wc = (struct aclk_database_worker_config *)host->dbsync_worker; - - if (likely(wc)) { - log_access( - "IN [%s (%s)]: Request to send alerts snapshot, snapshot_id %" PRIu64 " and ack_sequence_id %" PRIu64, - wc->node_id, - wc->host ? rrdhost_hostname(wc->host) : "N/A", - snapshot_id, - sequence_id); - if (wc->alerts_snapshot_id == snapshot_id) - return; - __sync_synchronize(); - wc->alerts_snapshot_id = snapshot_id; - wc->alerts_ack_sequence_id = sequence_id; - __sync_synchronize(); - - struct aclk_database_cmd cmd; - memset(&cmd, 0, sizeof(cmd)); - cmd.opcode = ACLK_DATABASE_PUSH_ALERT_SNAPSHOT; - cmd.data_param = NULL; - cmd.completion = NULL; - aclk_database_enq_cmd(wc, &cmd); - } else - log_access("ACLK STA [%s (N/A)]: ACLK synchronization thread is not active.", node_id); - - return; -} + if (unlikely(!host)) { + log_access("ACLK STA [%s (N/A)]: ACLK node id does not exist", node_id); + return; + } -void aclk_mark_alert_cloud_ack(char *uuid_str, uint64_t alerts_ack_sequence_id) -{ - BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite); + struct aclk_sync_host_config *wc = (struct aclk_sync_host_config *)host->aclk_sync_host_config; - if (alerts_ack_sequence_id != 0) { - buffer_sprintf( - sql, - "UPDATE aclk_alert_%s SET date_cloud_ack = unixepoch() WHERE sequence_id <= %" PRIu64 "", - uuid_str, - alerts_ack_sequence_id); - db_execute(buffer_tostring(sql)); + if (unlikely(!wc)) { + log_access("ACLK STA [%s (N/A)]: ACLK node id does not exist", node_id); + return; } - buffer_free(sql); + log_access( + "IN [%s (%s)]: Request to send alerts snapshot, snapshot_uuid %s", + node_id, + wc->host ? rrdhost_hostname(wc->host) : "N/A", + snapshot_uuid); + if (wc->alerts_snapshot_uuid && !strcmp(wc->alerts_snapshot_uuid,snapshot_uuid)) + return; + __sync_synchronize(); + wc->alerts_snapshot_uuid = strdupz(snapshot_uuid); + __sync_synchronize(); + + aclk_push_node_alert_snapshot(node_id); } #ifdef ENABLE_ACLK @@ -949,37 +811,41 @@ static int have_recent_alarm(RRDHOST *host, uint32_t alarm_id, uint32_t mark) #endif #define ALARM_EVENTS_PER_CHUNK 10 -void aclk_push_alert_snapshot_event(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd) +void aclk_push_alert_snapshot_event(char *node_id __maybe_unused) { -#ifndef ENABLE_ACLK - UNUSED(wc); - UNUSED(cmd); -#else - UNUSED(cmd); - // we perhaps we don't need this for snapshots - if (unlikely(!wc->alert_updates)) { - log_access("ACLK STA [%s (%s)]: Ignoring alert snapshot event, updates have been turned off for this node.", wc->node_id, wc->host ? rrdhost_hostname(wc->host) : "N/A"); +#ifdef ENABLE_ACLK + RRDHOST *host = find_host_by_node_id(node_id); + + if (unlikely(!host)) { + log_access("AC [%s (N/A)]: Node id not found", node_id); + freez(node_id); return; } + freez(node_id); - if (unlikely(!wc->host)) { - error_report("ACLK synchronization thread for %s is not linked to HOST", wc->host_guid); + struct aclk_sync_host_config *wc = host->aclk_sync_host_config; + + // we perhaps we don't need this for snapshots + if (unlikely(!wc->alert_updates)) { + log_access( + "ACLK STA [%s (%s)]: Ignoring alert snapshot event, updates have been turned off for this node.", + wc->node_id, + wc->host ? rrdhost_hostname(wc->host) : "N/A"); return; } - if (unlikely(!wc->alerts_snapshot_id)) + if (unlikely(!wc->alerts_snapshot_uuid)) return; char *claim_id = get_agent_claimid(); if (unlikely(!claim_id)) return; - log_access("ACLK REQ [%s (%s)]: Sending alerts snapshot, snapshot_id %" PRIu64, wc->node_id, wc->host ? rrdhost_hostname(wc->host) : "N/A", wc->alerts_snapshot_id); + log_access("ACLK REQ [%s (%s)]: Sending alerts snapshot, snapshot_uuid %s", wc->node_id, rrdhost_hostname(wc->host), wc->alerts_snapshot_uuid); - aclk_mark_alert_cloud_ack(wc->uuid_str, wc->alerts_ack_sequence_id); - - RRDHOST *host = wc->host; uint32_t cnt = 0; + char uuid_str[UUID_STR_LEN]; + uuid_unparse_lower_fix(&host->host_uuid, uuid_str); netdata_rwlock_rdlock(&host->health_log.alarm_log_rwlock); @@ -995,6 +861,9 @@ void aclk_push_alert_snapshot_event(struct aclk_database_worker_config *wc, stru if (have_recent_alarm(host, ae->alarm_id, ae->unique_id)) continue; + if (is_event_from_alert_variable_config(ae->unique_id, uuid_str)) + continue; + cnt++; } @@ -1008,7 +877,7 @@ void aclk_push_alert_snapshot_event(struct aclk_database_worker_config *wc, stru struct alarm_snapshot alarm_snap; alarm_snap.node_id = wc->node_id; alarm_snap.claim_id = claim_id; - alarm_snap.snapshot_id = wc->alerts_snapshot_id; + alarm_snap.snapshot_uuid = wc->alerts_snapshot_uuid; alarm_snap.chunks = chunks; alarm_snap.chunk = chunk; @@ -1024,6 +893,9 @@ void aclk_push_alert_snapshot_event(struct aclk_database_worker_config *wc, stru if (have_recent_alarm(host, ae->alarm_id, ae->unique_id)) continue; + if (is_event_from_alert_variable_config(ae->unique_id, uuid_str)) + continue; + cnt++; struct alarm_log_entry alarm_log; @@ -1047,7 +919,7 @@ void aclk_push_alert_snapshot_event(struct aclk_database_worker_config *wc, stru struct alarm_snapshot alarm_snap; alarm_snap.node_id = wc->node_id; alarm_snap.claim_id = claim_id; - alarm_snap.snapshot_id = wc->alerts_snapshot_id; + alarm_snap.snapshot_uuid = wc->alerts_snapshot_uuid; alarm_snap.chunks = chunks; alarm_snap.chunk = chunk; @@ -1061,73 +933,204 @@ void aclk_push_alert_snapshot_event(struct aclk_database_worker_config *wc, stru } netdata_rwlock_unlock(&host->health_log.alarm_log_rwlock); - wc->alerts_snapshot_id = 0; + wc->alerts_snapshot_uuid = NULL; freez(claim_id); #endif - return; } +#define SQL_DELETE_ALERT_ENTRIES "DELETE FROM aclk_alert_%s WHERE filtered_alert_unique_id NOT IN (SELECT unique_id FROM health_log_%s);" + void sql_aclk_alert_clean_dead_entries(RRDHOST *host) { if (!claimed()) return; - char uuid_str[GUID_LEN + 1]; + char uuid_str[UUID_STR_LEN]; uuid_unparse_lower_fix(&host->host_uuid, uuid_str); - BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite); + char sql[512]; + snprintfz(sql,511,SQL_DELETE_ALERT_ENTRIES, uuid_str, uuid_str); - 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); - char *err_msg = NULL; - int rc = sqlite3_exec_monitored(db_meta, buffer_tostring(sql), NULL, NULL, &err_msg); + int rc = sqlite3_exec_monitored(db_meta, sql, NULL, NULL, &err_msg); if (rc != SQLITE_OK) { - error_report("Failed when trying to clean stale ACLK alert entries from aclk_alert_%s, error message \"%s""", - uuid_str, err_msg); + error_report("Failed when trying to clean stale ACLK alert entries from aclk_alert_%s, error message \"%s\"", uuid_str, err_msg); sqlite3_free(err_msg); } - buffer_free(sql); } +#define SQL_GET_MIN_MAX_ALERT_SEQ "SELECT MIN(sequence_id), MAX(sequence_id), " \ + "(SELECT MAX(sequence_id) FROM aclk_alert_%s WHERE date_submitted IS NOT NULL) " \ + "FROM aclk_alert_%s WHERE date_submitted IS NULL;" + int get_proto_alert_status(RRDHOST *host, struct proto_alert_status *proto_alert_status) { int rc; - struct aclk_database_worker_config *wc = NULL; - wc = (struct aclk_database_worker_config *)host->dbsync_worker; + struct aclk_sync_host_config *wc = NULL; + wc = (struct aclk_sync_host_config *)host->aclk_sync_host_config; if (!wc) return 1; proto_alert_status->alert_updates = wc->alert_updates; - proto_alert_status->alerts_batch_id = wc->alerts_batch_id; - BUFFER *sql = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite); + char sql[ACLK_SYNC_QUERY_SIZE]; sqlite3_stmt *res = NULL; - buffer_sprintf(sql, "SELECT MIN(sequence_id), MAX(sequence_id), " \ - "(select MAX(sequence_id) from aclk_alert_%s where date_cloud_ack is not NULL), " \ - "(select MAX(sequence_id) from aclk_alert_%s where date_submitted is not NULL) " \ - "FROM aclk_alert_%s where date_submitted is null;", wc->uuid_str, wc->uuid_str, wc->uuid_str); + snprintfz(sql, ACLK_SYNC_QUERY_SIZE - 1, SQL_GET_MIN_MAX_ALERT_SEQ, wc->uuid_str, wc->uuid_str); - rc = sqlite3_prepare_v2(db_meta, buffer_tostring(sql), -1, &res, 0); + rc = sqlite3_prepare_v2(db_meta, sql, -1, &res, 0); if (rc != SQLITE_OK) { error_report("Failed to prepare statement to get alert log status from the database."); - buffer_free(sql); return 1; } while (sqlite3_step_monitored(res) == SQLITE_ROW) { proto_alert_status->pending_min_sequence_id = sqlite3_column_bytes(res, 0) > 0 ? (uint64_t) sqlite3_column_int64(res, 0) : 0; proto_alert_status->pending_max_sequence_id = sqlite3_column_bytes(res, 1) > 0 ? (uint64_t) sqlite3_column_int64(res, 1) : 0; - proto_alert_status->last_acked_sequence_id = sqlite3_column_bytes(res, 2) > 0 ? (uint64_t) sqlite3_column_int64(res, 2) : 0; - proto_alert_status->last_submitted_sequence_id = sqlite3_column_bytes(res, 3) > 0 ? (uint64_t) sqlite3_column_int64(res, 3) : 0; + proto_alert_status->last_submitted_sequence_id = sqlite3_column_bytes(res, 2) > 0 ? (uint64_t) sqlite3_column_int64(res, 2) : 0; } rc = sqlite3_finalize(res); if (unlikely(rc != SQLITE_OK)) error_report("Failed to finalize statement to get alert log status from the database, rc = %d", rc); - buffer_free(sql); return 0; } + +void aclk_send_alarm_checkpoint(char *node_id, char *claim_id __maybe_unused) +{ + if (unlikely(!node_id)) + return; + + struct aclk_sync_host_config *wc = NULL; + RRDHOST *host = find_host_by_node_id(node_id); + + if (unlikely(!host)) + return; + + wc = (struct aclk_sync_host_config *)host->aclk_sync_host_config; + if (unlikely(!wc)) { + log_access("ACLK REQ [%s (N/A)]: ALERTS CHECKPOINT REQUEST RECEIVED FOR INVALID NODE", node_id); + return; + } + + log_access("ACLK REQ [%s (%s)]: ALERTS CHECKPOINT REQUEST RECEIVED", node_id, rrdhost_hostname(host)); + + wc->alert_checkpoint_req = SEND_CHECKPOINT_AFTER_HEALTH_LOOPS; +} + +typedef struct active_alerts { + char *name; + char *chart; + RRDCALC_STATUS status; +} active_alerts_t; + +static inline int compare_active_alerts(const void * a, const void * b) { + active_alerts_t *active_alerts_a = (active_alerts_t *)a; + active_alerts_t *active_alerts_b = (active_alerts_t *)b; + + if( !(strcmp(active_alerts_a->name, active_alerts_b->name)) ) + { + return strcmp(active_alerts_a->chart, active_alerts_b->chart); + } + else + return strcmp(active_alerts_a->name, active_alerts_b->name); +} + +void aclk_push_alarm_checkpoint(RRDHOST *host __maybe_unused) +{ +#ifdef ENABLE_ACLK + struct aclk_sync_host_config *wc = host->aclk_sync_host_config; + if (unlikely(!wc)) { + log_access("ACLK REQ [%s (N/A)]: ALERTS CHECKPOINT REQUEST RECEIVED FOR INVALID NODE", rrdhost_hostname(host)); + return; + } + + //TODO: make sure all pending events are sent. + if (rrdhost_flag_check(host, RRDHOST_FLAG_ACLK_STREAM_ALERTS)) { + //postpone checkpoint send + wc->alert_checkpoint_req++; + log_access("ACLK REQ [%s (N/A)]: ALERTS CHECKPOINT POSTPONED", rrdhost_hostname(host)); + return; + } + + //TODO: lock rc here, or make sure it's called when health decides + //count them + RRDCALC *rc; + uint32_t cnt = 0; + size_t len = 0; + active_alerts_t *active_alerts = NULL; + + foreach_rrdcalc_in_rrdhost_read(host, rc) { + if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec)) + continue; + + if (rc->status == RRDCALC_STATUS_WARNING || + rc->status == RRDCALC_STATUS_CRITICAL) { + + cnt++; + } + } + foreach_rrdcalc_in_rrdhost_done(rc); + + if (cnt) { + active_alerts = callocz(cnt, sizeof(active_alerts_t)); + cnt = 0; + foreach_rrdcalc_in_rrdhost_read(host, rc) { + if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec)) + continue; + + if (rc->status == RRDCALC_STATUS_WARNING || + rc->status == RRDCALC_STATUS_CRITICAL) { + + active_alerts[cnt].name = (char *)rrdcalc_name(rc); + len += string_strlen(rc->name); + active_alerts[cnt].chart = (char *)rrdcalc_chart_name(rc); + len += string_strlen(rc->chart); + active_alerts[cnt].status = rc->status; + len++; + cnt++; + } + } + foreach_rrdcalc_in_rrdhost_done(rc); + } + + BUFFER *alarms_to_hash; + if (cnt) { + qsort (active_alerts, cnt, sizeof(active_alerts_t), compare_active_alerts); + + alarms_to_hash = buffer_create(len, NULL); + for (uint32_t i=0;inode_id; + alarm_checkpoint.checksum = (char *)hash; + + aclk_send_provide_alarm_checkpoint(&alarm_checkpoint); + log_access("ACLK RES [%s (%s)]: ALERTS CHECKPOINT SENT", wc->node_id, rrdhost_hostname(host)); + } else { + log_access("ACLK RES [%s (%s)]: FAILED TO CREATE ALERTS CHECKPOINT HASH", wc->node_id, rrdhost_hostname(host)); + } + wc->alert_checkpoint_req = 0; + buffer_free(alarms_to_hash); +#endif +} diff --git a/database/sqlite/sqlite_aclk_alert.h b/database/sqlite/sqlite_aclk_alert.h index 88a939e8..d7252aad 100644 --- a/database/sqlite/sqlite_aclk_alert.h +++ b/database/sqlite/sqlite_aclk_alert.h @@ -5,27 +5,31 @@ extern sqlite3 *db_meta; +#define SEND_REMOVED_AFTER_HEALTH_LOOPS 3 +#define SEND_CHECKPOINT_AFTER_HEALTH_LOOPS 4 + struct proto_alert_status { int alert_updates; - uint64_t alerts_batch_id; - uint64_t last_acked_sequence_id; uint64_t pending_min_sequence_id; uint64_t pending_max_sequence_id; uint64_t last_submitted_sequence_id; }; -int aclk_add_alert_event(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd); -void aclk_push_alert_event(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd); -void aclk_send_alarm_health_log(char *node_id); -void aclk_push_alarm_health_log(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd); +int aclk_add_alert_event(struct aclk_sync_host_config *wc, struct aclk_database_cmd cmd); +void aclk_push_alert_event(struct aclk_sync_host_config *wc); void aclk_send_alarm_configuration (char *config_hash); -int aclk_push_alert_config_event(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd); -void aclk_start_alert_streaming(char *node_id, uint64_t batch_id, uint64_t start_seq_id); +int aclk_push_alert_config_event(char *node_id, char *config_hash); +void aclk_start_alert_streaming(char *node_id, bool resets); void sql_queue_removed_alerts_to_aclk(RRDHOST *host); -void sql_process_queue_removed_alerts_to_aclk(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd); -void aclk_push_alert_snapshot_event(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd); -void aclk_process_send_alarm_snapshot(char *node_id, char *claim_id, uint64_t snapshot_id, uint64_t sequence_id); +void sql_process_queue_removed_alerts_to_aclk(char *node_id); +void aclk_send_alarm_checkpoint(char *node_id, char *claim_id); +void aclk_push_alarm_checkpoint(RRDHOST *host); + +void aclk_push_alert_snapshot_event(char *node_id); +void aclk_process_send_alarm_snapshot(char *node_id, char *claim_id, char *snapshot_uuid); int get_proto_alert_status(RRDHOST *host, struct proto_alert_status *proto_alert_status); int sql_queue_alarm_to_aclk(RRDHOST *host, ALARM_ENTRY *ae, int skip_filter); +void aclk_push_alert_events_for_all_hosts(void); + #endif //NETDATA_SQLITE_ACLK_ALERT_H diff --git a/database/sqlite/sqlite_aclk_node.c b/database/sqlite/sqlite_aclk_node.c index afe77499..3817296d 100644 --- a/database/sqlite/sqlite_aclk_node.c +++ b/database/sqlite/sqlite_aclk_node.c @@ -25,12 +25,17 @@ DICTIONARY *collectors_from_charts(RRDHOST *host, DICTIONARY *dict) { return dict; } -#endif -void sql_build_node_collectors(struct aclk_database_worker_config *wc) +static void build_node_collectors(char *node_id __maybe_unused) { -#ifdef ENABLE_ACLK - if (!wc->host) + + RRDHOST *host = find_host_by_node_id(node_id); + + if (unlikely(!host)) + return; + + struct aclk_sync_host_config *wc = (struct aclk_sync_host_config *) host->aclk_sync_host_config; + if (unlikely(!wc)) return; struct update_node_collectors upd_node_collectors; @@ -39,48 +44,51 @@ void sql_build_node_collectors(struct aclk_database_worker_config *wc) upd_node_collectors.node_id = wc->node_id; upd_node_collectors.claim_id = get_agent_claimid(); - upd_node_collectors.node_collectors = collectors_from_charts(wc->host, dict); + upd_node_collectors.node_collectors = collectors_from_charts(host, dict); aclk_update_node_collectors(&upd_node_collectors); dictionary_destroy(dict); freez(upd_node_collectors.claim_id); - log_access("ACLK RES [%s (%s)]: NODE COLLECTORS SENT", wc->node_id, rrdhost_hostname(wc->host)); -#else - UNUSED(wc); -#endif - return; + log_access("ACLK RES [%s (%s)]: NODE COLLECTORS SENT", node_id, rrdhost_hostname(host)); + + freez(node_id); } -void sql_build_node_info(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd) +static void build_node_info(char *node_id __maybe_unused) { - UNUSED(cmd); - -#ifdef ENABLE_ACLK struct update_node_info node_info; - if (!wc->host) { - wc->node_info_send = 1; + RRDHOST *host = find_host_by_node_id(node_id); + + if (unlikely((!host))) { + freez(node_id); + return; + } + + struct aclk_sync_host_config *wc = (struct aclk_sync_host_config *) host->aclk_sync_host_config; + + if (unlikely(!wc)) { + freez(node_id); return; } rrd_rdlock(); node_info.node_id = wc->node_id; node_info.claim_id = get_agent_claimid(); - node_info.machine_guid = wc->host_guid; + node_info.machine_guid = host->machine_guid; node_info.child = (wc->host != localhost); - node_info.ml_info.ml_capable = ml_capable(localhost); + node_info.ml_info.ml_capable = ml_capable(); node_info.ml_info.ml_enabled = ml_enabled(wc->host); node_info.node_instance_capabilities = aclk_get_node_instance_capas(wc->host); now_realtime_timeval(&node_info.updated_at); - RRDHOST *host = wc->host; char *host_version = NULL; if (host != localhost) { netdata_mutex_lock(&host->receiver_lock); - host_version = strdupz(host->receiver && host->receiver->program_version ? host->receiver->program_version : "unknown"); + host_version = strdupz(host->receiver && host->receiver->program_version ? host->receiver->program_version : rrdhost_program_version(host)); netdata_mutex_unlock(&host->receiver_lock); } @@ -91,7 +99,7 @@ void sql_build_node_info(struct aclk_database_worker_config *wc, struct aclk_dat node_info.data.kernel_name = host->system_info->kernel_name; node_info.data.kernel_version = host->system_info->kernel_version; node_info.data.architecture = host->system_info->architecture; - node_info.data.cpus = host->system_info->host_cores ? str2uint32_t(host->system_info->host_cores) : 0; + node_info.data.cpus = host->system_info->host_cores ? str2uint32_t(host->system_info->host_cores, NULL) : 0; node_info.data.cpu_frequency = host->system_info->host_cpu_freq ? host->system_info->host_cpu_freq : "0"; node_info.data.memory = host->system_info->host_ram_total ? host->system_info->host_ram_total : "0"; node_info.data.disk_space = host->system_info->host_disk_space ? host->system_info->host_disk_space : "0"; @@ -101,7 +109,7 @@ void sql_build_node_info(struct aclk_database_worker_config *wc, struct aclk_dat node_info.data.virtualization_type = host->system_info->virtualization ? host->system_info->virtualization : "unknown"; node_info.data.container_type = host->system_info->container ? host->system_info->container : "unknown"; node_info.data.custom_info = config_get(CONFIG_SECTION_WEB, "custom dashboard_info.js", ""); - node_info.data.machine_guid = wc->host_guid; + node_info.data.machine_guid = host->machine_guid; struct capability node_caps[] = { { .name = "ml", .version = host->system_info->ml_capable, .enabled = host->system_info->ml_enabled }, @@ -116,7 +124,7 @@ void sql_build_node_info(struct aclk_database_worker_config *wc, struct aclk_dat node_info.data.host_labels_ptr = host->rrdlabels; aclk_update_node_info(&node_info); - log_access("ACLK RES [%s (%s)]: NODE INFO SENT for guid [%s] (%s)", wc->node_id, rrdhost_hostname(wc->host), wc->host_guid, wc->host == localhost ? "parent" : "child"); + log_access("ACLK RES [%s (%s)]: NODE INFO SENT for guid [%s] (%s)", wc->node_id, rrdhost_hostname(wc->host), host->machine_guid, wc->host == localhost ? "parent" : "child"); rrd_unlock(); freez(node_info.claim_id); @@ -124,9 +132,47 @@ void sql_build_node_info(struct aclk_database_worker_config *wc, struct aclk_dat freez(host_version); wc->node_collectors_send = now_realtime_sec(); -#else - UNUSED(wc); -#endif + freez(node_id); + +} + - return; +void aclk_check_node_info_and_collectors(void) +{ + RRDHOST *host; + + if (unlikely(!aclk_connected)) + return; + + size_t pending = 0; + dfe_start_reentrant(rrdhost_root_index, host) { + + struct aclk_sync_host_config *wc = host->aclk_sync_host_config; + if (unlikely(!wc)) + continue; + + if (unlikely(rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD))) { + internal_error(true, "ACLK SYNC: Context still pending for %s", rrdhost_hostname(host)); + pending++; + continue; + } + + if (wc->node_info_send_time && wc->node_info_send_time + 30 < now_realtime_sec()) { + wc->node_info_send_time = 0; + build_node_info(strdupz(wc->node_id)); + internal_error(true, "ACLK SYNC: Sending node info for %s", rrdhost_hostname(host)); + } + + if (wc->node_collectors_send && wc->node_collectors_send + 30 < now_realtime_sec()) { + build_node_collectors(strdupz(wc->node_id)); + internal_error(true, "ACLK SYNC: Sending collectors for %s", rrdhost_hostname(host)); + wc->node_collectors_send = 0; + } + } + dfe_done(host); + + if(pending) + info("ACLK: %zu nodes are pending for contexts to load, skipped sending node info for them", pending); } + +#endif diff --git a/database/sqlite/sqlite_aclk_node.h b/database/sqlite/sqlite_aclk_node.h index c2c54f8c..6afdf8d7 100644 --- a/database/sqlite/sqlite_aclk_node.h +++ b/database/sqlite/sqlite_aclk_node.h @@ -3,6 +3,5 @@ #ifndef NETDATA_SQLITE_ACLK_NODE_H #define NETDATA_SQLITE_ACLK_NODE_H -void sql_build_node_info(struct aclk_database_worker_config *wc, struct aclk_database_cmd cmd); -void sql_build_node_collectors(struct aclk_database_worker_config *wc); +void aclk_check_node_info_and_collectors(void); #endif //NETDATA_SQLITE_ACLK_NODE_H diff --git a/database/sqlite/sqlite_context.c b/database/sqlite/sqlite_context.c index 892292cc..b72726dc 100644 --- a/database/sqlite/sqlite_context.c +++ b/database/sqlite/sqlite_context.c @@ -117,7 +117,6 @@ void sql_close_context_database(void) rc = sqlite3_close_v2(db_context_meta); if (unlikely(rc != SQLITE_OK)) error_report("Error %d while closing the context SQLite database, %s", rc, sqlite3_errstr(rc)); - return; } // @@ -243,8 +242,6 @@ failed: rc = sqlite3_reset(res); if (rc != SQLITE_OK) error_report("Failed to reset statement that fetches chart label data, rc = %d", rc); - - return; } // CONTEXT LIST @@ -372,9 +369,9 @@ int ctx_store_context(uuid_t *host_uuid, VERSIONED_CONTEXT_DATA *context_data) goto skip_store; } - rc = sqlite3_bind_int(res, 10, (time_t) context_data->deleted); + rc = sqlite3_bind_int(res, 10, context_data->deleted); if (unlikely(rc != SQLITE_OK)) { - error_report("Failed to bind last_time_t to store context details"); + error_report("Failed to bind deleted flag to store context details"); goto skip_store; } @@ -431,11 +428,11 @@ int ctx_delete_context(uuid_t *host_uuid, VERSIONED_CONTEXT_DATA *context_data) if (rc_stored != SQLITE_DONE) error_report("Failed to delete context %s, rc = %d", context_data->id, rc_stored); #ifdef NETDATA_INTERNAL_CHECKS - else { - char host_uuid_str[UUID_STR_LEN]; - uuid_unparse_lower(*host_uuid, host_uuid_str); - info("%s: Deleted context %s under host %s", __FUNCTION__ , context_data->id, host_uuid_str); - } + else { + char host_uuid_str[UUID_STR_LEN]; + uuid_unparse_lower(*host_uuid, host_uuid_str); + info("%s: Deleted context %s under host %s", __FUNCTION__, context_data->id, host_uuid_str); + } #endif skip_delete: @@ -449,6 +446,10 @@ skip_delete: int sql_context_cache_stats(int op) { int count, dummy; + + if (unlikely(!db_context_meta)) + return 0; + netdata_thread_disable_cancelability(); sqlite3_db_status(db_context_meta, op, &count, &dummy, 0); netdata_thread_enable_cancelability(); @@ -489,6 +490,8 @@ int ctx_unittest(void) uuid_t host_uuid; uuid_generate(host_uuid); + initialize_thread_key_pool(); + int rc = sql_init_context_database(1); if (rc != SQLITE_OK) @@ -556,6 +559,7 @@ int ctx_unittest(void) freez((void *)context_data.title); freez((void *)context_data.chart_type); freez((void *)context_data.family); + freez((void *)context_data.units); // The list should be empty info("List context start after delete"); diff --git a/database/sqlite/sqlite_db_migration.c b/database/sqlite/sqlite_db_migration.c index 8b1d0159..3132ae2d 100644 --- a/database/sqlite/sqlite_db_migration.c +++ b/database/sqlite/sqlite_db_migration.c @@ -7,7 +7,7 @@ static int return_int_cb(void *data, int argc, char **argv, char **column) int *status = data; UNUSED(argc); UNUSED(column); - *status = str2uint32_t(argv[0]); + *status = str2uint32_t(argv[0], NULL); return 0; } @@ -49,7 +49,7 @@ static int column_exists_in_table(const char *table, const char *column) } const char *database_migrate_v1_v2[] = { - "ALTER TABLE host ADD hops INTEGER;", + "ALTER TABLE host ADD hops INTEGER NOT NULL DEFAULT 0;", NULL }; diff --git a/database/sqlite/sqlite_functions.c b/database/sqlite/sqlite_functions.c index 1d03cfc2..2fca2dfc 100644 --- a/database/sqlite/sqlite_functions.c +++ b/database/sqlite/sqlite_functions.c @@ -49,7 +49,6 @@ const char *database_config[] = { const char *database_cleanup[] = { "DELETE FROM chart WHERE chart_id NOT IN (SELECT chart_id FROM dimension);", "DELETE FROM host WHERE host_id NOT IN (SELECT host_id FROM chart);", - "DELETE FROM chart_label WHERE chart_id NOT IN (SELECT chart_id FROM chart);", "DELETE FROM node_instance WHERE host_id NOT IN (SELECT host_id FROM host);", "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);", @@ -117,7 +116,6 @@ int execute_insert(sqlite3_stmt *res) break; } } - return rc; } @@ -156,6 +154,12 @@ static void release_statement(void *statement) error_report("Failed to finalize statement, rc = %d", rc); } +void initialize_thread_key_pool(void) +{ + for (int i = 0; i < MAX_PREPARED_STATEMENTS; i++) + (void)pthread_key_create(&key_pool[i], release_statement); +} + int prepare_statement(sqlite3 *database, const char *query, sqlite3_stmt **statement) { static __thread uint32_t keys_used = 0; @@ -448,8 +452,7 @@ int sql_init_database(db_check_action_type_t rebuild, int memory) info("SQLite database initialization completed"); - for (int i = 0; i < MAX_PREPARED_STATEMENTS; i++) - (void)pthread_key_create(&key_pool[i], release_statement); + initialize_thread_key_pool(); rc = sqlite3_create_function(db_meta, "u2h", 1, SQLITE_ANY | SQLITE_DETERMINISTIC, 0, sqlite_uuid_parse, 0, 0); if (unlikely(rc != SQLITE_OK)) @@ -505,14 +508,15 @@ skip: error_report("Failed to finalize statement %s, rc = %d", sql, rc); return result; } - -void db_execute(const char *cmd) +// Return 0 OK +// Return 1 Failed +int db_execute(sqlite3 *db, const char *cmd) { int rc; int cnt = 0; while (cnt < SQL_MAX_RETRY) { char *err_msg; - rc = sqlite3_exec_monitored(db_meta, cmd, 0, 0, &err_msg); + rc = sqlite3_exec_monitored(db, cmd, 0, 0, &err_msg); if (rc != SQLITE_OK) { error_report("Failed to execute '%s', rc = %d (%s) -- attempt %d", cmd, rc, err_msg, cnt); sqlite3_free(err_msg); @@ -527,6 +531,7 @@ void db_execute(const char *cmd) ++cnt; } + return (rc != SQLITE_OK); } static inline void set_host_node_id(RRDHOST *host, uuid_t *node_id) @@ -540,7 +545,7 @@ static inline void set_host_node_id(RRDHOST *host, uuid_t *node_id) return; } - struct aclk_database_worker_config *wc = host->dbsync_worker; + struct aclk_sync_host_config *wc = host->aclk_sync_host_config; if (unlikely(!host->node_id)) host->node_id = mallocz(sizeof(*host->node_id)); @@ -594,7 +599,7 @@ int update_node_id(uuid_t *host_id, uuid_t *node_id) rrd_wrlock(); host = rrdhost_find_by_guid(host_guid); if (likely(host)) - set_host_node_id(host, node_id); + set_host_node_id(host, node_id); rrd_unlock(); failed: @@ -604,48 +609,6 @@ failed: return rc - 1; } -#define SQL_SELECT_HOSTNAME_BY_NODE_ID "SELECT h.hostname FROM node_instance ni, " \ -"host h WHERE ni.host_id = h.host_id AND ni.node_id = @node_id;" - -char *get_hostname_by_node_id(char *node) -{ - sqlite3_stmt *res = NULL; - char *hostname = NULL; - int rc; - - if (unlikely(!db_meta)) { - if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) - error_report("Database has not been initialized"); - return NULL; - } - - uuid_t node_id; - if (uuid_parse(node, node_id)) - return NULL; - - rc = sqlite3_prepare_v2(db_meta, SQL_SELECT_HOSTNAME_BY_NODE_ID, -1, &res, 0); - if (unlikely(rc != SQLITE_OK)) { - error_report("Failed to prepare statement to fetch hostname by node id"); - return NULL; - } - - rc = sqlite3_bind_blob(res, 1, &node_id, sizeof(node_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); - if (likely(rc == SQLITE_ROW)) - hostname = strdupz((char *)sqlite3_column_text(res, 0)); - -failed: - if (unlikely(sqlite3_finalize(res) != SQLITE_OK)) - error_report("Failed to finalize the prepared statement when search for hostname by node id"); - - return hostname; -} - #define SQL_SELECT_HOST_BY_NODE_ID "select host_id from node_instance where node_id = @node_id;" int get_host_id(uuid_t *node_id, uuid_t *host_id) @@ -684,7 +647,7 @@ failed: return (rc == SQLITE_ROW) ? 0 : -1; } -#define SQL_SELECT_NODE_ID "select node_id from node_instance where host_id = @host_id and node_id not null;" +#define SQL_SELECT_NODE_ID "SELECT node_id FROM node_instance WHERE host_id = @host_id AND node_id IS NOT NULL;" int get_node_id(uuid_t *host_id, uuid_t *node_id) { @@ -720,8 +683,8 @@ failed: return (rc == SQLITE_ROW) ? 0 : -1; } -#define SQL_INVALIDATE_NODE_INSTANCES "update node_instance set node_id = NULL where exists " \ - "(select host_id from node_instance where host_id = @host_id and (@claim_id is null or claim_id <> @claim_id));" +#define SQL_INVALIDATE_NODE_INSTANCES "UPDATE node_instance SET node_id = NULL WHERE EXISTS " \ + "(SELECT host_id FROM node_instance WHERE host_id = @host_id AND (@claim_id IS NULL OR claim_id <> @claim_id));" void invalidate_node_instances(uuid_t *host_id, uuid_t *claim_id) { @@ -765,8 +728,8 @@ failed: error_report("Failed to finalize the prepared statement when invalidating node instance information"); } -#define SQL_GET_NODE_INSTANCE_LIST "select ni.node_id, ni.host_id, h.hostname " \ - "from node_instance ni, host h where ni.host_id = h.host_id;" +#define SQL_GET_NODE_INSTANCE_LIST "SELECT ni.node_id, ni.host_id, h.hostname " \ + "FROM node_instance ni, host h WHERE ni.host_id = h.host_id AND h.hops >=0;" struct node_instance_list *get_node_list(void) { @@ -805,13 +768,18 @@ struct node_instance_list *get_node_list(void) uuid_copy(node_list[row].node_id, *((uuid_t *)sqlite3_column_blob(res, 0))); if (sqlite3_column_bytes(res, 1) == sizeof(uuid_t)) { uuid_t *host_id = (uuid_t *)sqlite3_column_blob(res, 1); - uuid_copy(node_list[row].host_id, *host_id); - node_list[row].queryable = 1; uuid_unparse_lower(*host_id, host_guid); RRDHOST *host = rrdhost_find_by_guid(host_guid); - node_list[row].live = host && (host == localhost || host->receiver) ? 1 : 0; + if (rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD)) { + info("ACLK: 'host:%s' skipping get node list because context is initializing", rrdhost_hostname(host)); + continue; + } + uuid_copy(node_list[row].host_id, *host_id); + node_list[row].queryable = 1; + node_list[row].live = (host && (host == localhost || host->receiver + || !(rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN)))) ? 1 : 0; node_list[row].hops = (host && host->system_info) ? host->system_info->hops : - uuid_compare(*host_id, localhost->host_uuid) ? 1 : 0; + uuid_memcmp(host_id, &localhost->host_uuid) ? 1 : 0; node_list[row].hostname = sqlite3_column_bytes(res, 2) ? strdupz((char *)sqlite3_column_text(res, 2)) : NULL; } @@ -950,6 +918,10 @@ int bind_text_null(sqlite3_stmt *res, int position, const char *text, bool can_b int sql_metadata_cache_stats(int op) { int count, dummy; + + if (unlikely(!db_meta)) + return 0; + netdata_thread_disable_cancelability(); sqlite3_db_status(db_meta, op, &count, &dummy, 0); netdata_thread_enable_cancelability(); diff --git a/database/sqlite/sqlite_functions.h b/database/sqlite/sqlite_functions.h index 40abd010..ee63a397 100644 --- a/database/sqlite/sqlite_functions.h +++ b/database/sqlite/sqlite_functions.h @@ -55,7 +55,8 @@ int bind_text_null(sqlite3_stmt *res, int position, const char *text, bool can_b int prepare_statement(sqlite3 *database, const char *query, sqlite3_stmt **statement); int execute_insert(sqlite3_stmt *res); int exec_statement_with_uuid(const char *sql, uuid_t *uuid); -void db_execute(const char *cmd); +int db_execute(sqlite3 *database, const char *cmd); +void initialize_thread_key_pool(void); // Look up functions int get_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 471fa3ad..dd08f63e 100644 --- a/database/sqlite/sqlite_health.c +++ b/database/sqlite/sqlite_health.c @@ -12,7 +12,7 @@ #define SQL_CREATE_HEALTH_LOG_TABLE(guid) "CREATE TABLE IF NOT EXISTS health_log_%s(hostname text, unique_id int, alarm_id int, alarm_event_id int, config_hash_id blob, updated_by_id int, updates_id int, when_key int, duration int, non_clear_duration int, flags int, exec_run_timestamp int, delay_up_to_timestamp int, name text, chart text, family text, exec text, recipient text, source text, units text, info text, exec_code int, new_status real, old_status real, delay int, new_value double, old_value double, last_repeat int, class text, component text, type text, chart_context text);", guid int sql_create_health_log_table(RRDHOST *host) { int rc; - char *err_msg = NULL, command[MAX_HEALTH_SQL_SIZE + 1]; + char command[MAX_HEALTH_SQL_SIZE + 1]; if (unlikely(!db_meta)) { if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) @@ -25,18 +25,17 @@ int sql_create_health_log_table(RRDHOST *host) { snprintfz(command, MAX_HEALTH_SQL_SIZE, SQL_CREATE_HEALTH_LOG_TABLE(uuid_str)); - rc = sqlite3_exec_monitored(db_meta, command, 0, 0, &err_msg); - if (rc != SQLITE_OK) { - error_report("HEALTH [%s]: SQLite error during creation of health log table, rc = %d (%s)", rrdhost_hostname(host), rc, err_msg); - sqlite3_free(err_msg); - return 1; + rc = db_execute(db_meta, command); + if (unlikely(rc)) + error_report("HEALTH [%s]: SQLite error during creation of health log table", rrdhost_hostname(host)); + else { + snprintfz(command, MAX_HEALTH_SQL_SIZE, "CREATE INDEX IF NOT EXISTS health_log_index_%s ON health_log_%s (unique_id); ", uuid_str, uuid_str); + rc = db_execute(db_meta, command); + if (unlikely(unlikely(rc))) + error_report("HEALTH [%s]: SQLite error during creation of health log table index", rrdhost_hostname(host)); } - snprintfz(command, MAX_HEALTH_SQL_SIZE, "CREATE INDEX IF NOT EXISTS " - "health_log_index_%s ON health_log_%s (unique_id); ", uuid_str, uuid_str); - db_execute(command); - - return 0; + return rc; } /* Health related SQL queries @@ -104,7 +103,7 @@ void sql_health_alarm_log_update(RRDHOST *host, ALARM_ENTRY *ae) { error_report("HEALTH [%s]: Failed to update health log, rc = %d", rrdhost_hostname(host), rc); } - failed: +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)); } @@ -345,7 +344,7 @@ void sql_health_alarm_log_insert(RRDHOST *host, ALARM_ENTRY *ae) { ae->flags |= HEALTH_ENTRY_FLAG_SAVED; host->health.health_log_entries_written++; - failed: +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)); } @@ -452,7 +451,7 @@ void sql_health_alarm_log_count(RRDHOST *host) { #define SQL_INJECT_REMOVED_UPDATE(guid) "update health_log_%s set flags = flags | ?1, updated_by_id = ?2 where unique_id = ?3; ", guid void sql_inject_removed_status(char *uuid_str, uint32_t alarm_id, uint32_t alarm_event_id, uint32_t unique_id, uint32_t max_unique_id) { - int rc = 0; + int rc; char command[MAX_HEALTH_SQL_SIZE + 1]; if (!alarm_id || !alarm_event_id || !unique_id || !max_unique_id) @@ -546,7 +545,7 @@ failed: #define SQL_SELECT_MAX_UNIQUE_ID(guid) "SELECT MAX(unique_id) from health_log_%s", guid uint32_t sql_get_max_unique_id (char *uuid_str) { - int rc = 0; + int rc; char command[MAX_HEALTH_SQL_SIZE + 1]; uint32_t max_unique_id = 0; @@ -573,10 +572,9 @@ uint32_t sql_get_max_unique_id (char *uuid_str) #define SQL_SELECT_LAST_STATUSES(guid) "SELECT new_status, unique_id, alarm_id, alarm_event_id from health_log_%s group by alarm_id having max(alarm_event_id)", guid void sql_check_removed_alerts_state(char *uuid_str) { - int rc = 0; + int rc; char command[MAX_HEALTH_SQL_SIZE + 1]; - RRDCALC_STATUS status; - uint32_t alarm_id = 0, alarm_event_id = 0, unique_id = 0, max_unique_id = 0; + uint32_t max_unique_id = 0; sqlite3_stmt *res = NULL; @@ -588,6 +586,9 @@ void sql_check_removed_alerts_state(char *uuid_str) } while (sqlite3_step_monitored(res) == SQLITE_ROW) { + uint32_t alarm_id, alarm_event_id, unique_id; + RRDCALC_STATUS status; + status = (RRDCALC_STATUS) sqlite3_column_int(res, 0); unique_id = (uint32_t) sqlite3_column_int64(res, 1); alarm_id = (uint32_t) sqlite3_column_int64(res, 2); @@ -683,8 +684,7 @@ void sql_health_alarm_log_load(RRDHOST *host) { } // Check if we got last_repeat field - time_t last_repeat = 0; - last_repeat = (time_t)sqlite3_column_int64(res, 27); + time_t last_repeat = (time_t)sqlite3_column_int64(res, 27); rc = dictionary_get(all_rrdcalcs, (char *) sqlite3_column_text(res, 14)); if(unlikely(rc)) { @@ -847,196 +847,161 @@ int sql_store_alert_config_hash(uuid_t *hash_id, struct alert_config *cfg) } } - param++; - rc = sqlite3_bind_blob(res, param, hash_id, sizeof(*hash_id), SQLITE_STATIC); + rc = sqlite3_bind_blob(res, ++param, hash_id, sizeof(*hash_id), SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->alarm, param); + rc = sqlite3_bind_string_or_null(res, cfg->alarm, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->template_key, param); + rc = sqlite3_bind_string_or_null(res, cfg->template_key, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->on, param); + rc = sqlite3_bind_string_or_null(res, cfg->on, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->classification, param); + rc = sqlite3_bind_string_or_null(res, cfg->classification, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->component, param); + rc = sqlite3_bind_string_or_null(res, cfg->component, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->type, param); + rc = sqlite3_bind_string_or_null(res, cfg->type, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->os, param); + rc = sqlite3_bind_string_or_null(res, cfg->os, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->host, param); + rc = sqlite3_bind_string_or_null(res, cfg->host, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->lookup, param); + rc = sqlite3_bind_string_or_null(res, cfg->lookup, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->every, param); + rc = sqlite3_bind_string_or_null(res, cfg->every, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->units, param); + rc = sqlite3_bind_string_or_null(res, cfg->units, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->calc, param); + rc = sqlite3_bind_string_or_null(res, cfg->calc, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->families, param); + rc = sqlite3_bind_string_or_null(res, cfg->families, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->plugin, param); + rc = sqlite3_bind_string_or_null(res, cfg->plugin, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->module, param); + rc = sqlite3_bind_string_or_null(res, cfg->module, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->charts, param); + rc = sqlite3_bind_string_or_null(res, cfg->charts, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->green, param); + rc = sqlite3_bind_string_or_null(res, cfg->green, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->red, param); + rc = sqlite3_bind_string_or_null(res, cfg->red, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->warn, param); + rc = sqlite3_bind_string_or_null(res, cfg->warn, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->crit, param); + rc = sqlite3_bind_string_or_null(res, cfg->crit, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->exec, param); + rc = sqlite3_bind_string_or_null(res, cfg->exec, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->to, param); + rc = sqlite3_bind_string_or_null(res, cfg->to, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->info, param); + rc = sqlite3_bind_string_or_null(res, cfg->info, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->delay, param); + rc = sqlite3_bind_string_or_null(res, cfg->delay, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->options, param); + rc = sqlite3_bind_string_or_null(res, cfg->options, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->repeat, param); + rc = sqlite3_bind_string_or_null(res, cfg->repeat, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->host_labels, param); + rc = sqlite3_bind_string_or_null(res, cfg->host_labels, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; if (cfg->p_db_lookup_after) { - param++; - rc = sqlite3_bind_string_or_null(res, cfg->p_db_lookup_dimensions, param); + rc = sqlite3_bind_string_or_null(res, cfg->p_db_lookup_dimensions, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_string_or_null(res, cfg->p_db_lookup_method, param); + rc = sqlite3_bind_string_or_null(res, cfg->p_db_lookup_method, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_int(res, 31, cfg->p_db_lookup_options); + rc = sqlite3_bind_int(res, ++param, (int) cfg->p_db_lookup_options); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_int(res, 32, cfg->p_db_lookup_after); + rc = sqlite3_bind_int(res, ++param, (int) cfg->p_db_lookup_after); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_int(res, 33, cfg->p_db_lookup_before); + rc = sqlite3_bind_int(res, ++param, (int) cfg->p_db_lookup_before); if (unlikely(rc != SQLITE_OK)) goto bind_fail; } else { - param++; - rc = sqlite3_bind_null(res, 29); + rc = sqlite3_bind_null(res, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_null(res, 30); + + rc = sqlite3_bind_null(res, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_null(res, 31); + + rc = sqlite3_bind_null(res, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_null(res, 32); + + rc = sqlite3_bind_null(res, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_null(res, 33); + + rc = sqlite3_bind_null(res, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; } - param++; - rc = sqlite3_bind_int(res, 34, cfg->p_update_every); + rc = sqlite3_bind_int(res, ++param, cfg->p_update_every); if (unlikely(rc != SQLITE_OK)) goto bind_fail; @@ -1050,7 +1015,7 @@ int sql_store_alert_config_hash(uuid_t *hash_id, struct alert_config *cfg) return 0; - bind_fail: +bind_fail: error_report("Failed to bind parameter %d to store alert hash_id, rc = %d", param, rc); rc = sqlite3_reset(res); if (unlikely(rc != SQLITE_OK)) diff --git a/database/sqlite/sqlite_metadata.c b/database/sqlite/sqlite_metadata.c index 35f928ff..607d789a 100644 --- a/database/sqlite/sqlite_metadata.c +++ b/database/sqlite/sqlite_metadata.c @@ -64,9 +64,11 @@ enum metadata_opcode { METADATA_STORE_CLAIM_ID, METADATA_ADD_HOST_INFO, METADATA_SCAN_HOSTS, + METADATA_LOAD_HOST_CONTEXT, METADATA_MAINTENANCE, METADATA_SYNC_SHUTDOWN, METADATA_UNITTEST, + METADATA_ML_LOAD_MODELS, // leave this last // we need it to check for worker utilization METADATA_MAX_ENUMERATIONS_DEFINED @@ -154,19 +156,43 @@ static int chart_label_store_to_sql_callback(const char *name, const char *value return 1; } -static void check_and_update_chart_labels(RRDSET *st, BUFFER *work_buffer) +#define SQL_DELETE_CHART_LABEL "DELETE FROM chart_label WHERE chart_id = @chart_id;" +#define SQL_DELETE_CHART_LABEL_HISTORY "DELETE FROM chart_label WHERE date_created < %ld AND chart_id = @chart_id;" + +static void clean_old_chart_labels(RRDSET *st) +{ + char sql[512]; + time_t first_time_s = rrdset_first_entry_s(st); + + if (unlikely(!first_time_s)) + snprintfz(sql, 511,SQL_DELETE_CHART_LABEL); + else + snprintfz(sql, 511,SQL_DELETE_CHART_LABEL_HISTORY, first_time_s); + + int rc = exec_statement_with_uuid(sql, &st->chart_uuid); + if (unlikely(rc)) + error_report("METADATA: 'host:%s' Failed to clean old labels for chart %s", rrdhost_hostname(st->rrdhost), rrdset_name(st)); +} + +static int check_and_update_chart_labels(RRDSET *st, BUFFER *work_buffer, size_t *query_counter) { size_t old_version = st->rrdlabels_last_saved_version; size_t new_version = dictionary_version(st->rrdlabels); - if(new_version != old_version) { - buffer_flush(work_buffer); - struct query_build tmp = {.sql = work_buffer, .count = 0}; - uuid_unparse_lower(st->chart_uuid, tmp.uuid_str); - rrdlabels_walkthrough_read(st->rrdlabels, chart_label_store_to_sql_callback, &tmp); + if (new_version == old_version) + return 0; + + struct query_build tmp = {.sql = work_buffer, .count = 0}; + uuid_unparse_lower(st->chart_uuid, tmp.uuid_str); + rrdlabels_walkthrough_read(st->rrdlabels, chart_label_store_to_sql_callback, &tmp); + int rc = db_execute(db_meta, buffer_tostring(work_buffer)); + if (likely(!rc)) { st->rrdlabels_last_saved_version = new_version; - db_execute(buffer_tostring(work_buffer)); + (*query_counter)++; } + + clean_old_chart_labels(st); + return rc; } // Migrate all hosts with hops zero to this host_uuid @@ -177,12 +203,13 @@ void migrate_localhost(uuid_t *host_uuid) rc = exec_statement_with_uuid(MIGRATE_LOCALHOST_TO_NEW_MACHINE_GUID, host_uuid); if (!rc) rc = exec_statement_with_uuid(DELETE_NON_EXISTING_LOCALHOST, host_uuid); - if (!rc) - db_execute(DELETE_MISSING_NODE_INSTANCES); - + if (!rc) { + if (unlikely(db_execute(db_meta, DELETE_MISSING_NODE_INSTANCES))) + error_report("Failed to remove deleted hosts from node instances"); + } } -static void store_claim_id(uuid_t *host_id, uuid_t *claim_id) +static int store_claim_id(uuid_t *host_id, uuid_t *claim_id) { sqlite3_stmt *res = NULL; int rc; @@ -190,18 +217,18 @@ static void store_claim_id(uuid_t *host_id, uuid_t *claim_id) if (unlikely(!db_meta)) { if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) error_report("Database has not been initialized"); - return; + return 1; } 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; + error_report("Failed to prepare statement to store host claim id"); + 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 store node instance information"); + error_report("Failed to bind host_id parameter to store claim id"); goto failed; } @@ -210,17 +237,19 @@ static void store_claim_id(uuid_t *host_id, uuid_t *claim_id) 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"); + error_report("Failed to bind claim_id parameter to host claim id"); goto failed; } rc = execute_insert(res); if (unlikely(rc != SQLITE_DONE)) - error_report("Failed to store node instance information, rc = %d", rc); + error_report("Failed to store host claim id rc = %d", rc); failed: if (unlikely(sqlite3_finalize(res) != SQLITE_OK)) - error_report("Failed to finalize the prepared statement when storing node instance information"); + error_report("Failed to finalize the prepared statement when storing a host claim id"); + + return rc != SQLITE_DONE; } static void delete_dimension_uuid(uuid_t *dimension_uuid) @@ -252,7 +281,7 @@ skip_execution: // // Store host and host system info information in the database -static int sql_store_host_info(RRDHOST *host) +static int store_host_metadata(RRDHOST *host) { static __thread sqlite3_stmt *res = NULL; int rc, param = 0; @@ -340,7 +369,7 @@ static int sql_store_host_info(RRDHOST *host) if (unlikely(rc != SQLITE_OK)) error_report("Failed to reset statement to store host %s, rc = %d", rrdhost_hostname(host), rc); - return !(store_rc == SQLITE_DONE); + return store_rc != SQLITE_DONE; bind_fail: error_report("Failed to bind %d parameter to store host %s, rc = %d", param, rrdhost_hostname(host), rc); rc = sqlite3_reset(res); @@ -349,7 +378,7 @@ bind_fail: return 1; } -static void sql_store_host_system_info_key_value(const char *name, const char *value, void *data) +static void add_host_sysinfo_key_value(const char *name, const char *value, void *data) { struct query_build *lb = data; @@ -365,44 +394,43 @@ static void sql_store_host_system_info_key_value(const char *name, const char *v lb->count++; } -static BUFFER *sql_store_host_system_info(RRDHOST *host) +static bool build_host_system_info_statements(RRDHOST *host, BUFFER *work_buffer) { struct rrdhost_system_info *system_info = host->system_info; if (unlikely(!system_info)) - return NULL; - - BUFFER *work_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite); + return false; + buffer_flush(work_buffer); struct query_build key_data = {.sql = work_buffer, .count = 0}; uuid_unparse_lower(host->host_uuid, key_data.uuid_str); - sql_store_host_system_info_key_value("NETDATA_CONTAINER_OS_NAME", system_info->container_os_name, &key_data); - sql_store_host_system_info_key_value("NETDATA_CONTAINER_OS_ID", system_info->container_os_id, &key_data); - sql_store_host_system_info_key_value("NETDATA_CONTAINER_OS_ID_LIKE", system_info->container_os_id_like, &key_data); - sql_store_host_system_info_key_value("NETDATA_CONTAINER_OS_VERSION", system_info->container_os_version, &key_data); - sql_store_host_system_info_key_value("NETDATA_CONTAINER_OS_VERSION_ID", system_info->container_os_version_id, &key_data); - sql_store_host_system_info_key_value("NETDATA_CONTAINER_OS_DETECTION", system_info->host_os_detection, &key_data); - sql_store_host_system_info_key_value("NETDATA_HOST_OS_NAME", system_info->host_os_name, &key_data); - sql_store_host_system_info_key_value("NETDATA_HOST_OS_ID", system_info->host_os_id, &key_data); - sql_store_host_system_info_key_value("NETDATA_HOST_OS_ID_LIKE", system_info->host_os_id_like, &key_data); - sql_store_host_system_info_key_value("NETDATA_HOST_OS_VERSION", system_info->host_os_version, &key_data); - sql_store_host_system_info_key_value("NETDATA_HOST_OS_VERSION_ID", system_info->host_os_version_id, &key_data); - sql_store_host_system_info_key_value("NETDATA_HOST_OS_DETECTION", system_info->host_os_detection, &key_data); - sql_store_host_system_info_key_value("NETDATA_SYSTEM_KERNEL_NAME", system_info->kernel_name, &key_data); - sql_store_host_system_info_key_value("NETDATA_SYSTEM_CPU_LOGICAL_CPU_COUNT", system_info->host_cores, &key_data); - sql_store_host_system_info_key_value("NETDATA_SYSTEM_CPU_FREQ", system_info->host_cpu_freq, &key_data); - sql_store_host_system_info_key_value("NETDATA_SYSTEM_TOTAL_RAM", system_info->host_ram_total, &key_data); - sql_store_host_system_info_key_value("NETDATA_SYSTEM_TOTAL_DISK_SIZE", system_info->host_disk_space, &key_data); - sql_store_host_system_info_key_value("NETDATA_SYSTEM_KERNEL_VERSION", system_info->kernel_version, &key_data); - sql_store_host_system_info_key_value("NETDATA_SYSTEM_ARCHITECTURE", system_info->architecture, &key_data); - sql_store_host_system_info_key_value("NETDATA_SYSTEM_VIRTUALIZATION", system_info->virtualization, &key_data); - sql_store_host_system_info_key_value("NETDATA_SYSTEM_VIRT_DETECTION", system_info->virt_detection, &key_data); - sql_store_host_system_info_key_value("NETDATA_SYSTEM_CONTAINER", system_info->container, &key_data); - sql_store_host_system_info_key_value("NETDATA_SYSTEM_CONTAINER_DETECTION", system_info->container_detection, &key_data); - sql_store_host_system_info_key_value("NETDATA_HOST_IS_K8S_NODE", system_info->is_k8s_node, &key_data); - - return work_buffer; + add_host_sysinfo_key_value("NETDATA_CONTAINER_OS_NAME", system_info->container_os_name, &key_data); + add_host_sysinfo_key_value("NETDATA_CONTAINER_OS_ID", system_info->container_os_id, &key_data); + add_host_sysinfo_key_value("NETDATA_CONTAINER_OS_ID_LIKE", system_info->container_os_id_like, &key_data); + add_host_sysinfo_key_value("NETDATA_CONTAINER_OS_VERSION", system_info->container_os_version, &key_data); + add_host_sysinfo_key_value("NETDATA_CONTAINER_OS_VERSION_ID", system_info->container_os_version_id, &key_data); + add_host_sysinfo_key_value("NETDATA_CONTAINER_OS_DETECTION", system_info->host_os_detection, &key_data); + add_host_sysinfo_key_value("NETDATA_HOST_OS_NAME", system_info->host_os_name, &key_data); + add_host_sysinfo_key_value("NETDATA_HOST_OS_ID", system_info->host_os_id, &key_data); + add_host_sysinfo_key_value("NETDATA_HOST_OS_ID_LIKE", system_info->host_os_id_like, &key_data); + add_host_sysinfo_key_value("NETDATA_HOST_OS_VERSION", system_info->host_os_version, &key_data); + add_host_sysinfo_key_value("NETDATA_HOST_OS_VERSION_ID", system_info->host_os_version_id, &key_data); + add_host_sysinfo_key_value("NETDATA_HOST_OS_DETECTION", system_info->host_os_detection, &key_data); + add_host_sysinfo_key_value("NETDATA_SYSTEM_KERNEL_NAME", system_info->kernel_name, &key_data); + add_host_sysinfo_key_value("NETDATA_SYSTEM_CPU_LOGICAL_CPU_COUNT", system_info->host_cores, &key_data); + add_host_sysinfo_key_value("NETDATA_SYSTEM_CPU_FREQ", system_info->host_cpu_freq, &key_data); + add_host_sysinfo_key_value("NETDATA_SYSTEM_TOTAL_RAM", system_info->host_ram_total, &key_data); + add_host_sysinfo_key_value("NETDATA_SYSTEM_TOTAL_DISK_SIZE", system_info->host_disk_space, &key_data); + add_host_sysinfo_key_value("NETDATA_SYSTEM_KERNEL_VERSION", system_info->kernel_version, &key_data); + add_host_sysinfo_key_value("NETDATA_SYSTEM_ARCHITECTURE", system_info->architecture, &key_data); + add_host_sysinfo_key_value("NETDATA_SYSTEM_VIRTUALIZATION", system_info->virtualization, &key_data); + add_host_sysinfo_key_value("NETDATA_SYSTEM_VIRT_DETECTION", system_info->virt_detection, &key_data); + add_host_sysinfo_key_value("NETDATA_SYSTEM_CONTAINER", system_info->container, &key_data); + add_host_sysinfo_key_value("NETDATA_SYSTEM_CONTAINER_DETECTION", system_info->container_detection, &key_data); + add_host_sysinfo_key_value("NETDATA_HOST_IS_K8S_NODE", system_info->is_k8s_node, &key_data); + + return true; } @@ -410,13 +438,10 @@ static BUFFER *sql_store_host_system_info(RRDHOST *host) * Store a chart in the database */ -static int sql_store_chart( - uuid_t *chart_uuid, uuid_t *host_uuid, const char *type, const char *id, const char *name, const char *family, - const char *context, const char *title, const char *units, const char *plugin, const char *module, long priority, - int update_every, int chart_type, int memory_mode, long history_entries) +static int store_chart_metadata(RRDSET *st) { static __thread sqlite3_stmt *res = NULL; - int rc, param = 0; + int rc, param = 0, store_rc = 0; if (unlikely(!db_meta)) { if (default_rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE) @@ -433,98 +458,83 @@ static int sql_store_chart( } } - param++; - rc = sqlite3_bind_blob(res, 1, chart_uuid, sizeof(*chart_uuid), SQLITE_STATIC); + rc = sqlite3_bind_blob(res, ++param, &st->chart_uuid, sizeof(st->chart_uuid), SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_blob(res, 2, host_uuid, sizeof(*host_uuid), SQLITE_STATIC); + rc = sqlite3_bind_blob(res, ++param, &st->rrdhost->host_uuid, sizeof(st->rrdhost->host_uuid), SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_text(res, 3, type, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(res, ++param, string2str(st->parts.type), -1, SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_text(res, 4, id, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(res, ++param, string2str(st->parts.id), -1, SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; + const char *name = string2str(st->parts.name); if (name && *name) - rc = sqlite3_bind_text(res, 5, name, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(res, ++param, name, -1, SQLITE_STATIC); else - rc = sqlite3_bind_null(res, 5); + rc = sqlite3_bind_null(res, ++param); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_text(res, 6, family, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(res, ++param, rrdset_family(st), -1, SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_text(res, 7, context, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(res, ++param, rrdset_context(st), -1, SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_text(res, 8, title, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(res, ++param, rrdset_title(st), -1, SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_text(res, 9, units, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(res, ++param, rrdset_units(st), -1, SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_text(res, 10, plugin, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(res, ++param, rrdset_plugin_name(st), -1, SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_text(res, 11, module, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(res, ++param, rrdset_module_name(st), -1, SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_int(res, 12, (int) priority); + rc = sqlite3_bind_int(res, ++param, (int) st->priority); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_int(res, 13, update_every); + rc = sqlite3_bind_int(res, ++param, st->update_every); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_int(res, 14, chart_type); + rc = sqlite3_bind_int(res, ++param, st->chart_type); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_int(res, 15, memory_mode); + rc = sqlite3_bind_int(res, ++param, st->rrd_memory_mode); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - param++; - rc = sqlite3_bind_int(res, 16, (int) history_entries); + rc = sqlite3_bind_int(res, ++param, (int) st->entries); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - rc = execute_insert(res); - if (unlikely(rc != SQLITE_DONE)) - error_report("Failed to store chart, rc = %d", rc); + store_rc = execute_insert(res); + if (unlikely(store_rc != SQLITE_DONE)) + error_report("Failed to store chart, rc = %d", store_rc); rc = sqlite3_reset(res); if (unlikely(rc != SQLITE_OK)) error_report("Failed to reset statement in chart store function, rc = %d", rc); - return 0; + return store_rc != SQLITE_DONE; bind_fail: error_report("Failed to bind parameter %d to store chart, rc = %d", param, rc); @@ -537,9 +547,7 @@ bind_fail: /* * Store a dimension */ -static int sql_store_dimension( - uuid_t *dim_uuid, uuid_t *chart_uuid, const char *id, const char *name, collected_number multiplier, - collected_number divisor, int algorithm, bool hidden) +static int store_dimension_metadata(RRDDIM *rd) { static __thread sqlite3_stmt *res = NULL; int rc, param = 0; @@ -559,35 +567,35 @@ static int sql_store_dimension( } } - rc = sqlite3_bind_blob(res, ++param, dim_uuid, sizeof(*dim_uuid), SQLITE_STATIC); + rc = sqlite3_bind_blob(res, ++param, &rd->metric_uuid, sizeof(rd->metric_uuid), SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - rc = sqlite3_bind_blob(res, ++param, chart_uuid, sizeof(*chart_uuid), SQLITE_STATIC); + rc = sqlite3_bind_blob(res, ++param, &rd->rrdset->chart_uuid, sizeof(rd->rrdset->chart_uuid), SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - rc = sqlite3_bind_text(res, ++param, id, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(res, ++param, string2str(rd->id), -1, SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - rc = sqlite3_bind_text(res, ++param, name, -1, SQLITE_STATIC); + rc = sqlite3_bind_text(res, ++param, string2str(rd->name), -1, SQLITE_STATIC); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - rc = sqlite3_bind_int(res, ++param, (int) multiplier); + rc = sqlite3_bind_int(res, ++param, (int) rd->multiplier); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - rc = sqlite3_bind_int(res, ++param, (int ) divisor); + rc = sqlite3_bind_int(res, ++param, (int ) rd->divisor); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - rc = sqlite3_bind_int(res, ++param, algorithm); + rc = sqlite3_bind_int(res, ++param, rd->algorithm); if (unlikely(rc != SQLITE_OK)) goto bind_fail; - if (hidden) + if (rrddim_option_check(rd, RRDDIM_OPTION_HIDDEN)) rc = sqlite3_bind_text(res, ++param, "hidden", -1, SQLITE_STATIC); else rc = sqlite3_bind_null(res, ++param); @@ -700,7 +708,6 @@ static void cleanup_health_log(void) // // EVENT LOOP STARTS HERE // -static uv_mutex_t metadata_async_lock; static void metadata_init_cmd_queue(struct metadata_wc *wc) { @@ -856,6 +863,7 @@ static void start_metadata_cleanup(uv_work_t *req) struct metadata_wc *wc = req->data; check_dimension_metadata(wc); cleanup_health_log(); + (void) sqlite3_wal_checkpoint(db_meta, NULL); worker_is_idle(); } @@ -863,9 +871,125 @@ struct scan_metadata_payload { uv_work_t request; struct metadata_wc *wc; struct completion *completion; + BUFFER *work_buffer; uint32_t max_count; }; +struct host_context_load_thread { + uv_thread_t thread; + RRDHOST *host; + bool busy; + bool finished; +}; + +static void restore_host_context(void *arg) +{ + struct host_context_load_thread *hclt = arg; + RRDHOST *host = hclt->host; + + usec_t started_ut = now_monotonic_usec(); (void)started_ut; + rrdhost_load_rrdcontext_data(host); + usec_t ended_ut = now_monotonic_usec(); (void)ended_ut; + + rrdhost_flag_clear(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD | RRDHOST_FLAG_CONTEXT_LOAD_IN_PROGRESS); + +#ifdef ENABLE_ACLK + aclk_queue_node_info(host, false); +#endif + + internal_error(true, "METADATA: 'host:%s' context load in %0.2f ms", rrdhost_hostname(host), + (double)(ended_ut - started_ut) / USEC_PER_MS); + + __atomic_store_n(&hclt->finished, true, __ATOMIC_RELEASE); +} + +// Callback after scan of hosts is done +static void after_start_host_load_context(uv_work_t *req, int status __maybe_unused) +{ + struct scan_metadata_payload *data = req->data; + freez(data); +} + +#define MAX_FIND_THREAD_RETRIES (10) + +static void cleanup_finished_threads(struct host_context_load_thread *hclt, size_t max_thread_slots, bool wait) +{ + for (size_t index = 0; index < max_thread_slots; index++) { + if (__atomic_load_n(&(hclt[index].finished), __ATOMIC_RELAXED) + || (wait && __atomic_load_n(&(hclt[index].busy), __ATOMIC_ACQUIRE))) { + int rc = uv_thread_join(&(hclt[index].thread)); + if (rc) + error("Failed to join thread, rc = %d",rc); + __atomic_store_n(&(hclt[index].busy), false, __ATOMIC_RELEASE); + __atomic_store_n(&(hclt[index].finished), false, __ATOMIC_RELEASE); + } + } +} + +static size_t find_available_thread_slot(struct host_context_load_thread *hclt, size_t max_thread_slots, size_t *found_index) +{ + size_t retries = MAX_FIND_THREAD_RETRIES; + while (retries--) { + size_t index = 0; + while (index < max_thread_slots) { + if (false == __atomic_load_n(&(hclt[index].busy), __ATOMIC_ACQUIRE)) { + *found_index = index; + return true; + } + index++; + } + sleep_usec(10 * USEC_PER_MS); + } + return false; +} + +static void start_all_host_load_context(uv_work_t *req __maybe_unused) +{ + register_libuv_worker_jobs(); + + struct scan_metadata_payload *data = req->data; + UNUSED(data); + + worker_is_busy(UV_EVENT_HOST_CONTEXT_LOAD); + usec_t started_ut = now_monotonic_usec(); (void)started_ut; + + RRDHOST *host; + + size_t max_threads = MIN(get_netdata_cpus() / 2, 6); + struct host_context_load_thread *hclt = callocz(max_threads, sizeof(*hclt)); + + size_t thread_index; + dfe_start_reentrant(rrdhost_root_index, host) { + if (rrdhost_flag_check(host, RRDHOST_FLAG_CONTEXT_LOAD_IN_PROGRESS) || + !rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD)) + continue; + + rrdhost_flag_set(host, RRDHOST_FLAG_CONTEXT_LOAD_IN_PROGRESS); + internal_error(true, "METADATA: 'host:%s' loading context", rrdhost_hostname(host)); + + cleanup_finished_threads(hclt, max_threads, false); + bool found_slot = find_available_thread_slot(hclt, max_threads, &thread_index); + + if (unlikely(!found_slot)) { + struct host_context_load_thread hclt_sync = {.host = host}; + restore_host_context(&hclt_sync); + } + else { + __atomic_store_n(&hclt[thread_index].busy, true, __ATOMIC_RELAXED); + hclt[thread_index].host = host; + assert(0 == uv_thread_create(&hclt[thread_index].thread, restore_host_context, &hclt[thread_index])); + } + } + dfe_done(host); + + cleanup_finished_threads(hclt, max_threads, true); + freez(hclt); + usec_t ended_ut = now_monotonic_usec(); (void)ended_ut; + internal_error(true, "METADATA: 'host:ALL' contexts loaded in %0.2f ms", (double)(ended_ut - started_ut) / USEC_PER_MS); + + worker_is_idle(); +} + // Callback after scan of hosts is done static void after_metadata_hosts(uv_work_t *req, int status __maybe_unused) { @@ -881,13 +1005,15 @@ 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, size_t *query_counter) { +static bool metadata_scan_host(RRDHOST *host, uint32_t max_count, bool use_transaction, BUFFER *work_buffer, size_t *query_counter) { RRDSET *st; int rc; bool more_to_do = false; uint32_t scan_count = 1; - BUFFER *work_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite); + + if (use_transaction) + (void)db_execute(db_meta, "BEGIN TRANSACTION;"); rrdset_foreach_reentrant(st, host) { if (scan_count == max_count) { @@ -900,27 +1026,16 @@ static bool metadata_scan_host(RRDHOST *host, uint32_t max_count, size_t *query_ rrdset_flag_clear(st, RRDSET_FLAG_METADATA_UPDATE); scan_count++; - check_and_update_chart_labels(st, work_buffer); - - 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); + buffer_flush(work_buffer); + rc = check_and_update_chart_labels(st, work_buffer, query_counter); + if (unlikely(rc)) + error_report("METADATA: 'host:%s': Failed to update labels for chart %s", rrdhost_hostname(host), rrdset_name(st)); + else + (*query_counter)++; + + rc = store_chart_metadata(st); if (unlikely(rc)) - internal_error(true, "METADATA: Failed to store chart metadata %s", string2str(st->id)); + error_report("METADATA: 'host:%s': Failed to store metadata for chart %s", rrdhost_hostname(host), rrdset_name(st)); } RRDDIM *rd; @@ -935,116 +1050,145 @@ static bool metadata_scan_host(RRDHOST *host, uint32_t max_count, size_t *query_ else rrddim_flag_clear(rd, RRDDIM_FLAG_META_HIDDEN); - 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)); - + rc = store_dimension_metadata(rd); if (unlikely(rc)) - error_report("METADATA: Failed to store dimension %s", string2str(rd->id)); + error_report("METADATA: 'host:%s': Failed to dimension metadata for chart %s. dimension %s", + rrdhost_hostname(host), rrdset_name(st), + rrddim_name(rd)); } } rrddim_foreach_done(rd); } rrdset_foreach_done(st); - buffer_free(work_buffer); + if (use_transaction) + (void)db_execute(db_meta, "COMMIT TRANSACTION;"); + return more_to_do; } +static void store_host_and_system_info(RRDHOST *host, BUFFER *work_buffer, size_t *query_counter) +{ + bool free_work_buffer = (NULL == work_buffer); + + if (unlikely(free_work_buffer)) + work_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite); + + if (build_host_system_info_statements(host, work_buffer)) { + int rc = db_execute(db_meta, buffer_tostring(work_buffer)); + if (unlikely(rc)) { + error_report("METADATA: 'host:%s': Failed to store host updated information in the database", rrdhost_hostname(host)); + rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_INFO | RRDHOST_FLAG_METADATA_UPDATE); + } + else { + if (likely(query_counter)) + (*query_counter)++; + } + } + + if (unlikely(store_host_metadata(host))) { + error_report("METADATA: 'host:%s': Failed to store host info in the database", rrdhost_hostname(host)); + rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_INFO | RRDHOST_FLAG_METADATA_UPDATE); + } + else { + if (likely(query_counter)) + (*query_counter)++; + } + + if (unlikely(free_work_buffer)) + buffer_free(work_buffer); +} + // 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; + int transaction_started = 0; struct scan_metadata_payload *data = req->data; struct metadata_wc *wc = data->wc; + BUFFER *work_buffer = data->work_buffer; usec_t all_started_ut = now_monotonic_usec(); (void)all_started_ut; internal_error(true, "METADATA: checking all hosts..."); + usec_t started_ut = now_monotonic_usec(); (void)started_ut; bool run_again = false; worker_is_busy(UV_EVENT_METADATA_STORE); if (!data->max_count) - db_execute("BEGIN TRANSACTION;"); + transaction_started = !db_execute(db_meta, "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; 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(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); + if (likely(!rc)) { + query_counter++; + + 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)); - buffer_free(work_buffer); - query_counter++; + rc = db_execute(db_meta, buffer_tostring(work_buffer)); + + if (unlikely(rc)) { + error_report("METADATA: 'host:%s': failed to update metadata host labels", rrdhost_hostname(host)); + rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_LABELS | RRDHOST_FLAG_METADATA_UPDATE); + } + else + query_counter++; + } else { + error_report("METADATA: 'host:%s': failed to delete old host labels", rrdhost_hostname(host)); + rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_LABELS | RRDHOST_FLAG_METADATA_UPDATE); } } if (unlikely(rrdhost_flag_check(host, RRDHOST_FLAG_METADATA_CLAIMID))) { rrdhost_flag_clear(host, RRDHOST_FLAG_METADATA_CLAIMID); uuid_t uuid; - + int rc; if (likely(host->aclk_state.claimed_id && !uuid_parse(host->aclk_state.claimed_id, uuid))) - store_claim_id(&host->host_uuid, &uuid); + rc = 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++; - } + rc = store_claim_id(&host->host_uuid, NULL); - int rc = sql_store_host_info(host); if (unlikely(rc)) - error_report("METADATA: 'host:%s': failed to store host info", string2str(host->hostname)); + rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_CLAIMID | RRDHOST_FLAG_METADATA_UPDATE); else query_counter++; } + if (unlikely(rrdhost_flag_check(host, RRDHOST_FLAG_METADATA_INFO))) { + rrdhost_flag_clear(host, RRDHOST_FLAG_METADATA_INFO); + store_host_and_system_info(host, work_buffer, &query_counter); + } - if (data->max_count) - db_execute("BEGIN TRANSACTION;"); - if (unlikely(metadata_scan_host(host, data->max_count, &query_counter))) { + // For clarity + bool use_transaction = data->max_count; + if (unlikely(metadata_scan_host(host, data->max_count, use_transaction, work_buffer, &query_counter))) { run_again = true; rrdhost_flag_set(host,RRDHOST_FLAG_METADATA_UPDATE); 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;"); + + if (!data->max_count && transaction_started) + transaction_started = db_execute(db_meta, "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", @@ -1059,7 +1203,6 @@ static void start_metadata_hosts(uv_work_t *req __maybe_unused) 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"); @@ -1067,6 +1210,7 @@ static void metadata_event_loop(void *arg) worker_register_job_name(METADATA_STORE_CLAIM_ID, "add claim id"); worker_register_job_name(METADATA_ADD_HOST_INFO, "add host info"); worker_register_job_name(METADATA_MAINTENANCE, "maintenance"); + worker_register_job_name(METADATA_ML_LOAD_MODELS, "ml load models"); int ret; uv_loop_t *loop; @@ -1076,6 +1220,7 @@ static void metadata_event_loop(void *arg) uv_work_t metadata_cleanup_worker; uv_thread_set_name_np(wc->thread, "METASYNC"); +// service_register(SERVICE_THREAD_TYPE_EVENT_LOOP, NULL, NULL, NULL, true); loop = wc->loop = mallocz(sizeof(uv_loop_t)); ret = uv_loop_init(loop); if (ret) { @@ -1112,11 +1257,12 @@ static void metadata_event_loop(void *arg) int shutdown = 0; wc->row_id = 0; completion_mark_complete(&wc->init_complete); + BUFFER *work_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite); + struct scan_metadata_payload *data; while (shutdown == 0 || (wc->flags & METADATA_WORKER_BUSY)) { uuid_t *uuid; RRDHOST *host = NULL; - int rc; worker_is_idle(); uv_run(loop, UV_RUN_DEFAULT); @@ -1145,6 +1291,11 @@ static void metadata_event_loop(void *arg) case METADATA_DATABASE_TIMER: break; + case METADATA_ML_LOAD_MODELS: { + RRDDIM *rd = (RRDDIM *) cmd.param[0]; + ml_dimension_load_models(rd); + break; + } case METADATA_DEL_DIMENSION: uuid = (uuid_t *) cmd.param[0]; if (likely(dimension_can_be_deleted(uuid))) @@ -1158,9 +1309,7 @@ static void metadata_event_loop(void *arg) break; 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)); + store_host_and_system_info(host, NULL, NULL); break; case METADATA_SCAN_HOSTS: if (unlikely(metadata_flag_check(wc, METADATA_FLAG_SCANNING_HOSTS))) @@ -1169,10 +1318,11 @@ static void metadata_event_loop(void *arg) if (unittest_running) break; - struct scan_metadata_payload *data = mallocz(sizeof(*data)); + data = mallocz(sizeof(*data)); data->request.data = data; data->wc = wc; data->completion = cmd.completion; // Completion by the worker + data->work_buffer = work_buffer; if (unlikely(cmd.completion)) { data->max_count = 0; // 0 will process all pending updates @@ -1192,6 +1342,19 @@ static void metadata_event_loop(void *arg) metadata_flag_clear(wc, METADATA_FLAG_SCANNING_HOSTS); } break; + case METADATA_LOAD_HOST_CONTEXT:; + if (unittest_running) + break; + + data = callocz(1,sizeof(*data)); + data->request.data = data; + data->wc = wc; + if (unlikely( + uv_queue_work(loop,&data->request, start_all_host_load_context, + after_start_host_load_context))) { + freez(data); + } + break; case METADATA_MAINTENANCE: if (unlikely(metadata_flag_check(wc, METADATA_FLAG_CLEANUP))) break; @@ -1220,21 +1383,14 @@ static void metadata_event_loop(void *arg) if (!uv_timer_stop(&wc->timer_req)) uv_close((uv_handle_t *)&wc->timer_req, NULL); - /* - * uv_async_send after uv_close does not seem to crash in linux at the moment, - * it is however undocumented behaviour we need to be aware if this becomes - * an issue in the future. - */ uv_close((uv_handle_t *)&wc->async, NULL); - uv_run(loop, UV_RUN_DEFAULT); - uv_cond_destroy(&wc->cmd_cond); int rc; - do { rc = uv_loop_close(loop); } while (rc != UV_EBUSY); + buffer_free(work_buffer); freez(loop); worker_unregister(); @@ -1272,6 +1428,9 @@ void metadata_sync_shutdown(void) void metadata_sync_shutdown_prepare(void) { + if (unlikely(!metasync_worker.loop)) + return; + struct metadata_cmd cmd; memset(&cmd, 0, sizeof(cmd)); @@ -1303,8 +1462,6 @@ void metadata_sync_init(void) { struct metadata_wc *wc = &metasync_worker; - fatal_assert(0 == uv_mutex_init(&metadata_async_lock)); - memset(wc, 0, sizeof(*wc)); metadata_init_cmd_queue(wc); completion_init(&wc->init_complete); @@ -1364,6 +1521,20 @@ void metaqueue_host_update_info(RRDHOST *host) queue_metadata_cmd(METADATA_ADD_HOST_INFO, host, NULL); } +void metaqueue_ml_load_models(RRDDIM *rd) +{ + if (unlikely(!metasync_worker.loop)) + return; + queue_metadata_cmd(METADATA_ML_LOAD_MODELS, rd, NULL); +} + +void metadata_queue_load_host_context(RRDHOST *host) +{ + if (unlikely(!metasync_worker.loop)) + return; + queue_metadata_cmd(METADATA_LOAD_HOST_CONTEXT, host, NULL); +} + // // unitests // @@ -1419,7 +1590,7 @@ static void *metadata_unittest_threads(void) unittest_queue_metadata, &tu); } - uv_async_send(&metasync_worker.async); + (void) uv_async_send(&metasync_worker.async); sleep_usec(seconds_to_run * USEC_PER_SEC); __atomic_store_n(&tu.join, 1, __ATOMIC_RELAXED); diff --git a/database/sqlite/sqlite_metadata.h b/database/sqlite/sqlite_metadata.h index d578b7a8..6b0676ee 100644 --- a/database/sqlite/sqlite_metadata.h +++ b/database/sqlite/sqlite_metadata.h @@ -14,7 +14,9 @@ void metadata_sync_shutdown_prepare(void); void metaqueue_delete_dimension_uuid(uuid_t *uuid); void metaqueue_store_claim_id(uuid_t *host_uuid, uuid_t *claim_uuid); void metaqueue_host_update_info(RRDHOST *host); +void metaqueue_ml_load_models(RRDDIM *rd); void migrate_localhost(uuid_t *host_uuid); +void metadata_queue_load_host_context(RRDHOST *host); // UNIT TEST int metadata_unittest(void); diff --git a/database/storage_engine.c b/database/storage_engine.c index c5ba8655..19982382 100644 --- a/database/storage_engine.c +++ b/database/storage_engine.c @@ -6,120 +6,78 @@ #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, \ - .change_collection_frequency = rrddim_store_metric_change_collection_frequency, \ - .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_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[] = { { .id = RRD_MEMORY_MODE_NONE, .name = RRD_MEMORY_MODE_NONE_NAME, + .backend = STORAGE_ENGINE_BACKEND_RRDDIM, .api = { .metric_get = rrddim_metric_get, .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, } }, { .id = RRD_MEMORY_MODE_RAM, .name = RRD_MEMORY_MODE_RAM_NAME, + .backend = STORAGE_ENGINE_BACKEND_RRDDIM, .api = { .metric_get = rrddim_metric_get, .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, } }, { .id = RRD_MEMORY_MODE_MAP, .name = RRD_MEMORY_MODE_MAP_NAME, + .backend = STORAGE_ENGINE_BACKEND_RRDDIM, .api = { .metric_get = rrddim_metric_get, .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, } }, { .id = RRD_MEMORY_MODE_SAVE, .name = RRD_MEMORY_MODE_SAVE_NAME, + .backend = STORAGE_ENGINE_BACKEND_RRDDIM, .api = { .metric_get = rrddim_metric_get, .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, } }, { .id = RRD_MEMORY_MODE_ALLOC, .name = RRD_MEMORY_MODE_ALLOC_NAME, + .backend = STORAGE_ENGINE_BACKEND_RRDDIM, .api = { .metric_get = rrddim_metric_get, .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, } }, #ifdef ENABLE_DBENGINE { .id = RRD_MEMORY_MODE_DBENGINE, .name = RRD_MEMORY_MODE_DBENGINE_NAME, + .backend = STORAGE_ENGINE_BACKEND_DBENGINE, .api = { .metric_get = rrdeng_metric_get, .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, - .flush = rrdeng_store_metric_flush_current_page, - .finalize = rrdeng_store_metric_finalize, - .change_collection_frequency = rrdeng_store_metric_change_collection_frequency, - .metrics_group_get = rrdeng_metrics_group_get, - .metrics_group_release = rrdeng_metrics_group_release, - }, - .query_ops = { - .init = rrdeng_load_metric_init, - .next_metric = rrdeng_load_metric_next, - .is_finished = rrdeng_load_metric_is_finished, - .finalize = rrdeng_load_metric_finalize, - .latest_time_s = rrdeng_metric_latest_time, - .oldest_time_s = rrdeng_metric_oldest_time, - .align_to_optimal_before = rrdeng_load_align_to_optimal_before, - } } }, #endif diff --git a/diagrams/ephemeral-nodes-two-parents.xml b/diagrams/ephemeral-nodes-two-parents.xml new file mode 100644 index 00000000..999efb4d --- /dev/null +++ b/diagrams/ephemeral-nodes-two-parents.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/diagrams/simple-parent-child-no-cloud.xml b/diagrams/simple-parent-child-no-cloud.xml new file mode 100644 index 00000000..69ad3112 --- /dev/null +++ b/diagrams/simple-parent-child-no-cloud.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/diagrams/simple-parent-child.xml b/diagrams/simple-parent-child.xml new file mode 100644 index 00000000..b6cc81f6 --- /dev/null +++ b/diagrams/simple-parent-child.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/diagrams/windows.xml b/diagrams/windows.xml new file mode 100644 index 00000000..0ba8de09 --- /dev/null +++ b/diagrams/windows.xml @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/Add-more-charts-to-netdata.md b/docs/Add-more-charts-to-netdata.md deleted file mode 100644 index 35a89fba..00000000 --- a/docs/Add-more-charts-to-netdata.md +++ /dev/null @@ -1,13 +0,0 @@ - - -# Add more charts to Netdata - -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](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) to see all the sources Netdata can collect metrics -from. diff --git a/docs/Demo-Sites.md b/docs/Demo-Sites.md index 5c4d1018..1fd0d419 100644 --- a/docs/Demo-Sites.md +++ b/docs/Demo-Sites.md @@ -1,12 +1,17 @@ -# Demo sites +# Live demos -You can also view live demos of Netdata at **https://app.netdata.cloud/spaces/netdata-demo** +See the live Netdata Cloud demo with rooms for specific use cases at **https://app.netdata.cloud/spaces/netdata-demo** | Location | Netdata demo URL | 60 mins reqs | VM donated by | | :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| :------------------------------------------------- | diff --git a/docs/Donations-netdata-has-received.md b/docs/Donations-netdata-has-received.md deleted file mode 100644 index a8623c5d..00000000 --- a/docs/Donations-netdata-has-received.md +++ /dev/null @@ -1,29 +0,0 @@ - - -# Donations - -This is a list of the donations we have received for Netdata (sorted alphabetically on their name): - -| what donated|related links|who donated|description of the donation| -|-----------:|:-----------:|:---------:|:--------------------------| -| Packages Distribution|-|**[PackageCloud.io](https://packagecloud.io/)**|**PackageCloud.io** donated to a free open-source subscription to their awesome Package Distribution services.| -| Cross Browser Testing|-|**[BrowserStack.com](https://www.browserstack.com/)**|**BrowserStack.com** donated a free subscription to their awesome Browser Testing services (all three of them: Live, Screenshots, Responsive).| -| Cloud VM|[cdn77.my-netdata.io](http://cdn77.my-netdata.io)|**[CDN77.com](https://www.cdn77.com/)**|**CDN77.com** donated a VM with 2 CPU cores, 4GB RAM and 20GB HD, on their excellent CDN network.| -| Localization Management|[Netdata localization project](https://crowdin.com/project/netdata) (check issue [#279](https://github.com/netdata/netdata/issues/279))|**[Crowdin.com](https://crowdin.com/)**|**Crowdin.com** donated an open source license to their Localization Management Platform.| -| Cloud VMs|[london.my-netdata.io](https://london.my-netdata.io) (Several VMs)|**[DigitalOcean.com](https://www.digitalocean.com/)**|**DigitalOcean.com** donated 1000 USD to be used in their excellent Cloud Computing services. Many thanks to [Justin Paine](https://github.com/xxdesmus) for making this happen.| -| Development IDE|-|**[JetBrains.com](https://www.jetbrains.com/)**|**JetBrains.com** donated an open source license for 4 developers for 1 year, to their excellent IDEs.| -| Cloud VM|[octopuscs.my-netdata.io](https://octopuscs.my-netdata.io)|**[OctopusCS.com](https://octopuscs.com/)**|**OctopusCS.com** donated a VM with 4 CPU cores, 16GB RAM and 50GB HD in their excellent Cloud Computing services.| -| Cloud VM|[stackscale.my-netdata.io](https://stackscale.my-netdata.io)|**[stackscale.com](https://www.stackscale.com/)**|**StackScale.com** donated a VM with 4 CPU cores, 16GB RAM and 100GB HD in their excellent Cloud Computing services.| - -Thank you! - ---- - -**Do you want to donate?** We are thirsty for on-line services that can help us make Netdata better. We also try to build a network of demo sites (VMs) that can help us show the full potential of Netdata. - -Please contact me at costa@tsaousis.gr. - - diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index e3b91561..00000000 --- a/docs/README.md +++ /dev/null @@ -1,17 +0,0 @@ - - -# Read documentation on - -Welcome to the Netdata documentation! While you can read Netdata documentation here, or throughout the Netdata -repository, our intention is that these pages are read on [learn.netdata.cloud](https://learn.netdata.cloud). - -Links between documentation pages will work fine here, but the formatting may not be perfect, as our documentation site -uses a few extra Markdown features that GitHub doesn't support natively. Other things might be missing or look less than -perfect. - -Now get out there and build an exceptional infrastructure. - - diff --git a/docs/Running-behind-apache.md b/docs/Running-behind-apache.md index d152306f..045bb676 100644 --- a/docs/Running-behind-apache.md +++ b/docs/Running-behind-apache.md @@ -1,13 +1,4 @@ - - -# Netdata via apache's mod_proxy +# Netdata via Apache's mod_proxy Below you can find instructions for configuring an apache server to: @@ -38,13 +29,11 @@ Also, enable the rewrite module: ```sh sudo a2enmod rewrite ``` - - ## Netdata on an existing virtual host On any **existing** and already **working** apache virtual host, you can redirect requests for URL `/netdata/` to one or more Netdata servers. -### proxy one Netdata, running on the same server apache runs +### Proxy one Netdata, running on the same server apache runs Add the following on top of any existing virtual host. It will allow you to access Netdata as `http://virtual.host/netdata/`. @@ -74,7 +63,7 @@ Add the following on top of any existing virtual host. It will allow you to acce ``` -### proxy multiple Netdata running on multiple servers +### Proxy multiple Netdata running on multiple servers Add the following on top of any existing virtual host. It will allow you to access multiple Netdata as `http://virtual.host/netdata/HOSTNAME/`, where `HOSTNAME` is the hostname of any other Netdata server you have (to access the `localhost` Netdata, use `http://virtual.host/netdata/localhost/`). @@ -355,7 +344,7 @@ If your apache server is not on localhost, you can set: `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 +## Prevent the double access.log apache logs accesses and Netdata logs them too. You can prevent Netdata from generating its access log, by setting this in `/etc/netdata/netdata.conf`: diff --git a/docs/Running-behind-caddy.md b/docs/Running-behind-caddy.md index d7d61375..b7608b30 100644 --- a/docs/Running-behind-caddy.md +++ b/docs/Running-behind-caddy.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/Running-be sidebar_label: "Netdata via Caddy" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Setup/Expose local dashboard through proxy" +learn_rel_path: "Configuration/Secure your nodes" --> # Netdata via Caddy diff --git a/docs/Running-behind-h2o.md b/docs/Running-behind-h2o.md index 8a1e22b2..deadc91c 100644 --- a/docs/Running-behind-h2o.md +++ b/docs/Running-behind-h2o.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/Running-be sidebar_label: "Running Netdata behind H2O" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Setup/Expose local dashboard through proxy" +learn_rel_path: "Configuration/Secure your nodes" --> # Running Netdata behind H2O diff --git a/docs/Running-behind-haproxy.md b/docs/Running-behind-haproxy.md index f87eaa1f..4c9c32cc 100644 --- a/docs/Running-behind-haproxy.md +++ b/docs/Running-behind-haproxy.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/Running-be sidebar_label: "Netdata via HAProxy" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Setup/Expose local dashboard through proxy" +learn_rel_path: "Configuration/Secure your nodes" --> # Netdata via HAProxy diff --git a/docs/Running-behind-lighttpd.md b/docs/Running-behind-lighttpd.md index 6350b474..d1d9acc3 100644 --- a/docs/Running-behind-lighttpd.md +++ b/docs/Running-behind-lighttpd.md @@ -4,7 +4,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/Running-be sidebar_label: "Netdata via lighttpd v1.4.x" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Setup/Expose local dashboard through proxy" +learn_rel_path: "Configuration/Secure your nodes" --> # Netdata via lighttpd v1.4.x diff --git a/docs/Running-behind-nginx.md b/docs/Running-behind-nginx.md index a94f4058..842a9c32 100644 --- a/docs/Running-behind-nginx.md +++ b/docs/Running-behind-nginx.md @@ -1,12 +1,3 @@ - - # Running Netdata behind Nginx ## Intro @@ -51,7 +42,7 @@ With this method instead of `SERVER_IP_ADDRESS:19999`, the Netdata dashboard can upstream backend { # the Netdata server server 127.0.0.1:19999; - keepalive 64; + keepalive 1024; } server { @@ -216,8 +207,6 @@ 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: ``` @@ -259,6 +248,26 @@ Nginx logs accesses and Netdata logs them too. You can prevent Netdata from gene access log = none ``` +## Use gzip compression + +By default, netdata compresses its responses. You can have nginx do that instead, with the following options in the `location /` block: + +```conf + location / { + ... + gzip on; + gzip_proxied any; + gzip_types *; + } +``` + +To disable Netdata's gzip compression, open `netdata.conf` and in the `[web]` section put: + +```conf +[web] + enable gzip compression = no +``` + ## SELinux If you get an 502 Bad Gateway error you might check your Nginx error log: diff --git a/docs/a-github-star-is-important.md b/docs/a-github-star-is-important.md deleted file mode 100644 index 22659ea6..00000000 --- a/docs/a-github-star-is-important.md +++ /dev/null @@ -1,24 +0,0 @@ - - -# A GitHub star is important - -**GitHub stars** allow Netdata to expand its reach, its community, especially attract people with skills willing to -contribute to it. - -Compared to its first release, Netdata is now **twice as fast**, has all its bugs settled and a lot more functionality. -This happened because a lot of people find it useful, use it daily at home and work, **rely on it** and **contribute to -it**. - -**GitHub stars** also **motivate** us. They state that you find our work **useful**. They give us strength to continue, -to work **harder** to make it even **better**. - -So, give Netdata a **GitHub star**, at the top right of this page. - -Thank you! - -Costa Tsaousis - - diff --git a/docs/agent-cloud.md b/docs/agent-cloud.md deleted file mode 100644 index b5b99661..00000000 --- a/docs/agent-cloud.md +++ /dev/null @@ -1,78 +0,0 @@ - - -# Use the Agent with Netdata Cloud - -While the Netdata Agent is an enormously powerful _distributed_ health monitoring and performance troubleshooting tool, -many of its users need to monitor dozens or hundreds of systems at the same time. That's why we built Netdata Cloud, a -hosted web interface that gives you real-time visibility into your entire infrastructure. - -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. - -## 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) - -[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://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 - -If you don't want to use Netdata Cloud's web interface, you can still connect multiple nodes through the **Visited -nodes** menu, which appears on the left-hand side of the dashboard. - -You can use the Visited nodes menu to navigate between the dashboards of many different Agent-monitored systems quickly. - -To add nodes to your Visited nodes menu, you first need to navigate to that node's dashboard, then click the **Sign in** -button at the top of the dashboard. On the screen that appears, which states your node is requesting access to your -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) - -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) - -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. - -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) - -## 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://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 13eb465c..512cd02d 100644 --- a/docs/anonymous-statistics.md +++ b/docs/anonymous-statistics.md @@ -1,9 +1,12 @@ -# Anonymous statistics +# Anonymous telemetry events By default, Netdata collects anonymous usage information from the open-source monitoring agent using the open-source product analytics platform [PostHog](https://github.com/PostHog/posthog). We use their [cloud enterprise platform](https://posthog.com/product). @@ -97,9 +100,4 @@ Each of these opt-out processes does the following: - Forces the anonymous statistics script to exit immediately. - Stops the PostHog JavaScript snippet, which remains on the dashboard, from firing and sending any data to the Netdata PostHog. -## Migration from Google Analytics and Google Tag Manager. - -Prior to v1.29.4 we used Google Analytics to capture this information. This led to discomfort with some of our users in sending any product usage data to a third party like Google. It was also not even that useful in terms of generating the insights we needed to help catch bugs early and find opportunities for product improvement as Google Analytics does not allow its users access to the raw underlying data without paying a significant amount of money which would be infeasible for a project like Netdata. - -While we migrate fully away from Google Analytics to PostHog there maybe be a small period of time where we run both in parallel before we remove all Google Analytics related code. This is to ensure we can fully test and validate the Netdata PostHog implementation before fully defaulting to it. diff --git a/docs/category-overview-pages/deployment-strategies.md b/docs/category-overview-pages/deployment-strategies.md new file mode 100644 index 00000000..a1d393f2 --- /dev/null +++ b/docs/category-overview-pages/deployment-strategies.md @@ -0,0 +1,66 @@ +# Deployment strategies + +Netdata can be used to monitor all kinds of infrastructure, from stand-alone tiny IoT devices to complex hybrid setups +combining on-premise and cloud infrastructure, mixing bare-metal servers, virtual machines and containers. + +There are 3 components to structure your Netdata ecosystem: + +1. **Netdata Agents** + To monitor the physical or virtual nodes of your infrastructure, including all applications and containers running on them. + + Netdata Agents are Open-Source, licensed under GPL v3+. + +2. **Netdata Parents** + To create data centralization points within your infrastructure, to offload Netdata Agents functions from your production + systems, to provide high-availability of your data, increased data retention and isolation of your nodes. + + Netdata Parents are implemented using the Netdata Agent software. Any Netdata Agent can be an Agent for a node and a Parent + for other Agents, at the same time. + + It is recommended to set up multiple Netdata Parents. They will all seamlessly be integrated by Netdata Cloud into one monitoring solution. + + +3. **Netdata Cloud** + Our SaaS, combining all your infrastructure, all your Netdata Agents and Parents, into one uniform, distributed, infinitely + scalable, monitoring database, offering advanced data slicing and dicing capabilities, custom dashboards, advanced troubleshooting + tools, user management, centralized management of alerts, and more. + + +The Netdata Agent is a highly modular software piece, providing data collection via numerous plugins, an in-house crafted time-series +database, a query engine, health monitoring and alerts, machine learning and anomaly detection, metrics exporting to third party systems. + + +To help our users have a complete experience of Netdata when they install it for the first time, a Netdata Agent with default configuration +is a complete monitoring solution out of the box, having all these features enabled and available. + +We strongly recommend the following configuration changes for production deployments: + +1. Understand Netdata's [security and privacy design](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md) and + [secure your nodes](https://github.com/netdata/netdata/blob/master/docs/category-overview-pages/secure-nodes.md) + + To safeguard your infrastructure and comply with your organization's security policies. + +2. Set up [streaming and replication](https://github.com/netdata/netdata/blob/master/streaming/README.md) to: + + - Offload Netdata Agents running on production systems and free system resources for the production applications running on them. + - Isolate production systems from the rest of the world and improve security. + - Increase data retention. + - Make your data highly available. + +3. [Optimize the Netdata Agents system utilization and performance](https://github.com/netdata/netdata/edit/master/docs/guides/configure/performance.md) + + To save valuable system resources, especially when running on weak IoT devices. + +We also suggest that you: + +1. [Use Netdata Cloud to access the dashboards](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md) + + For increased security, user management and access to our latest tools for advanced dashboarding and troubleshooting. + +2. [Change how long Netdata stores metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) + + To control Netdata's memory use, when you have a lot of ephemeral metrics. + +3. [Use host labels](https://github.com/netdata/netdata/blob/master/docs/guides/using-host-labels.md) + + To organize systems, metrics, and alarms. diff --git a/docs/category-overview-pages/installation-overview.md b/docs/category-overview-pages/installation-overview.md new file mode 100644 index 00000000..e60dd442 --- /dev/null +++ b/docs/category-overview-pages/installation-overview.md @@ -0,0 +1,10 @@ +# Installation + +In this category you can find instructions on all the possible ways you can install Netdata on the +[supported platforms](https://github.com/netdata/netdata/blob/master/packaging/PLATFORM_SUPPORT.md). + +If this is your first time using Netdata, we recommend that you first start with the +[quick installation guide](https://github.com/netdata/netdata/edit/master/packaging/installer/README.md) and then +go into the more advanced options available to you. + + diff --git a/docs/category-overview-pages/integrations-overview.md b/docs/category-overview-pages/integrations-overview.md new file mode 100644 index 00000000..6fa2f50a --- /dev/null +++ b/docs/category-overview-pages/integrations-overview.md @@ -0,0 +1,31 @@ + + +# Integrations + +Netdata's ability to monitor out of the box every potentially useful aspect of a node's operation is unparalleled. +But Netdata also provides out of the box, meaningful charts and alerts for hundreds of applications, with the ability +to be easily extended to monitor anything. See the full list of Netdata's capabilities and how you can extend them in the +[supported collectors list](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md). + +Our out of the box alerts were created by expert professionals and have been validated on the field, countless times. +Use them to trigger [alert notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) +either centrally, via the +[Cloud alert notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.md) +, or by configuring individual +[agent notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md). + +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](https://github.com/netdata/netdata/blob/master/database/README.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/edit/master/exporting/README.md). + + diff --git a/docs/category-overview-pages/misc-overview.md b/docs/category-overview-pages/misc-overview.md new file mode 100644 index 00000000..e0c1cc0d --- /dev/null +++ b/docs/category-overview-pages/misc-overview.md @@ -0,0 +1,19 @@ + + +# Miscellaneous material + +This section contains temporary material that no longer belongs in our official documentation, and will +be moved to other locations. We keep it here to make it accessible while we create the new articles. + + + + + diff --git a/docs/category-overview-pages/reverse-proxies.md b/docs/category-overview-pages/reverse-proxies.md new file mode 100644 index 00000000..07c8b9bd --- /dev/null +++ b/docs/category-overview-pages/reverse-proxies.md @@ -0,0 +1,34 @@ +# Running Netdata behind a reverse proxy + +If you need to access a Netdata agent's user interface or API in a production environment we recommend you put Netdata behind +another web server and secure access to the dashboard via SSL, user authentication and firewall rules. + +A dedicated web server also provides more robustness and capabilities than the Agent's [internal web server](https://github.com/netdata/netdata/blob/master/web/README.md). + +We have documented running behind +[nginx](https://github.com/netdata/netdata/blob/master/docs/Running-behind-nginx.md), +[Apache](https://github.com/netdata/netdata/blob/master/docs/Running-behind-apache.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), +[Caddy](https://github.com/netdata/netdata/blob/master/docs/Running-behind-caddy.md), +and [H2O](https://github.com/netdata/netdata/blob/master/docs/Running-behind-h2o.md). +If you prefer a different web server, we suggest you follow the documentation for nginx and tell us how you did it + by adding your own "Running behind webserverX" document. + +When you run Netdata behind a reverse proxy, we recommend you firewall protect all your Netdata servers, so that only the web server IP will be allowed to directly access Netdata. To do this, run this on each of your servers (or use your firewall manager): + +```sh +PROXY_IP="1.2.3.4" +iptables -t filter -I INPUT -p tcp --dport 19999 \! -s ${PROXY_IP} -m conntrack --ctstate NEW -j DROP +``` + +The above will prevent anyone except your web server to access a Netdata dashboard running on the host. + +You can also use `netdata.conf`: + +``` +[web] + allow connections from = localhost 1.2.3.4 +``` + +Of course, you can add more IPs. diff --git a/docs/category-overview-pages/secure-nodes.md b/docs/category-overview-pages/secure-nodes.md new file mode 100644 index 00000000..33e205f0 --- /dev/null +++ b/docs/category-overview-pages/secure-nodes.md @@ -0,0 +1,177 @@ +# Secure your nodes + +Netdata is a monitoring system. It should be protected, the same way you protect all your admin apps. We assume Netdata +will be installed privately, for your eyes only. + +Upon installation, the Netdata Agent serves the **local dashboard** at port `19999`. If the node is accessible to the +internet at large, anyone can access the dashboard and your node's metrics at `http://NODE:19999`. We made this decision +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. + +Viewers will be able to get some information about the system Netdata is running. This information is everything the dashboard +provides. The dashboard includes a list of the services each system runs (the legends of the charts under the `Systemd Services` +section), the applications running (the legends of the charts under the `Applications` section), the disks of the system and +their names, the user accounts of the system that are running processes (the `Users` and `User Groups` section of the dashboard), +the network interfaces and their names (not the IPs) and detailed information about the performance of the system and its applications. + +This information is not sensitive (meaning that it is not your business data), but **it is important for possible attackers**. +It will give them clues on what to check, what to try and in the case of DDoS against your applications, they will know if they +are doing it right or not. + +Also, viewers could use Netdata itself to stress your servers. Although the Netdata daemon runs unprivileged, with the minimum +process priority (scheduling priority `idle` - lower than nice 19) and adjusts its OutOfMemory (OOM) score to 1000 (so that it +will be first to be killed by the kernel if the system starves for memory), some pressure can be applied on your systems if +someone attempts a DDoS against Netdata. + +Instead of dictating how to secure your infrastructure, we give you many options to establish security best practices +that align with your goals and your organization's standards. + +- [Disable the local dashboard](#disable-the-local-dashboard): **Simplest and recommended method** for those who have + added nodes to Netdata Cloud and view dashboards and metrics there. + +- [Expose Netdata only in a private LAN](#expose-netdata-only-in-a-private-lan). Simplest and recommended method for those who do not use Netdata Cloud. + +- [Fine-grained access control](#fine-grained-access-control): Allow local dashboard access from + only certain IP addresses, such as a trusted static IP or connections from behind a management LAN. Full support for Netdata Cloud. + +- [Use a reverse proxy (authenticating web server in proxy mode)](#use-an-authenticating-web-server-in-proxy-mode): Password-protect + a local dashboard and enable TLS to secure it. Full support for Netdata Cloud. + +- [Use Netdata parents as Web Application Firewalls](#use-netdata-parents-as-web-application-firewalls) + +- [Other methods](#other-methods) list some less common methods of protecting Netdata. + +## Disable the local dashboard + +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 tab, and Cloud dashboards. + +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. + +Open `netdata.conf` with `./edit-config netdata.conf`. Scroll down to the `[web]` section, and find the `mode = +static-threaded` setting, and change it to `none`. + +```conf +[web] + mode = none +``` + +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](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) for details on how to find +`netdata.conf` and use +> `edit-config`. + +## Expose Netdata only in a private LAN + +If your organisation has a private administration and management LAN, you can bind Netdata on this network interface on all your servers. +This is done in `Netdata.conf` with these settings: + +``` +[web] + bind to = 10.1.1.1:19999 localhost:19999 +``` + +You can bind Netdata to multiple IPs and ports. If you use hostnames, Netdata will resolve them and use all the IPs +(in the above example `localhost` usually resolves to both `127.0.0.1` and `::1`). + +**This is the best and the suggested way to protect Netdata**. Your systems **should** have a private administration and management +LAN, so that all management tasks are performed without any possibility of them being exposed on the internet. + +For cloud based installations, if your cloud provider does not provide such a private LAN (or if you use multiple providers), +you can create a virtual management and administration LAN with tools like `tincd` or `gvpe`. These tools create a mesh VPN +allowing all servers to communicate securely and privately. Your administration stations join this mesh VPN to get access to +management and administration tasks on all your cloud servers. + +For `gvpe` we have developed a [simple provisioning tool](https://github.com/netdata/netdata-demo-site/tree/master/gvpe) you +may find handy (it includes statically compiled `gvpe` binaries for Linux and FreeBSD, and also a script to compile `gvpe` +on your macOS system). We use this to create a management and administration LAN for all Netdata demo sites (spread all over +the internet using multiple hosting providers). + +## Fine-grained access control + +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](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 +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](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md). + +```conf +[web] + # Allow only localhost connections + allow connections from = localhost + + # Allow only from management LAN running on `10.X.X.X` + allow connections from = 10.* + + # Allow connections only from a specific FQDN/hostname + allow connections from = example* +``` + +The `allow connections from` setting is global and restricts access to the dashboard, badges, streaming, API, and +`netdata.conf`, but you can also set each of those access lists more granularly if you choose: + +```conf +[web] + allow connections from = localhost * + allow dashboard from = localhost * + allow badges from = * + allow streaming from = * + allow netdata.conf from = localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.* 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.* 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.* 172.31.* + allow management from = localhost +``` + +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 an authenticating web server in proxy mode + +Use one web server to provide authentication in front of **all your Netdata servers**. So, you will be accessing all your Netdata with +URLs like `http://{HOST}/netdata/{NETDATA_HOSTNAME}/` and authentication will be shared among all of them (you will sign-in once for all your servers). +Instructions are provided on how to set the proxy configuration to have Netdata run behind +[nginx](https://github.com/netdata/netdata/blob/master/docs/Running-behind-nginx.md), +[HAproxy](https://github.com/netdata/netdata/blob/master/docs/Running-behind-haproxy.md), +[Apache](https://github.com/netdata/netdata/blob/master/docs/Running-behind-apache.md), +[lighthttpd](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), and +[H2O](https://github.com/netdata/netdata/blob/master/docs/Running-behind-h2o.md). + +## Use Netdata parents as Web Application Firewalls + +The Netdata Agents you install on your production systems do not need direct access to the Internet. Even when you use +Netdata Cloud, you can appoint one or more Netdata Parents to act as border gateways or application firewalls, isolating +your production systems from the rest of the world. Netdata +Parents receive metric data from Netdata Agents or other Netdata Parents on one side, and serve most queries using their own +copy of the data to satisfy dashboard requests on the other side. + +For more information see [Streaming and replication](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.md). + +## Other methods + +Of course, there are many more methods you could use to protect Netdata: + +- Bind Netdata to localhost and use `ssh -L 19998:127.0.0.1:19999 remote.netdata.ip` to forward connections of local port 19998 to remote port 19999. +This way you can ssh to a Netdata server and then use `http://127.0.0.1:19998/` on your computer to access the remote Netdata dashboard. + +- If you are always under a static IP, you can use the script given above to allow direct access to your Netdata servers without authentication, +from all your static IPs. + +- Install all your Netdata in **headless data collector** mode, forwarding all metrics in real-time to a parent + Netdata server, which will be protected with authentication using an nginx server running locally at the parent + Netdata server. This requires more resources (you will need a bigger parent Netdata server), but does not require + any firewall changes, since all the child Netdata servers will not be listening for incoming connections. diff --git a/docs/category-overview-pages/troubleshooting-overview.md b/docs/category-overview-pages/troubleshooting-overview.md new file mode 100644 index 00000000..60406edd --- /dev/null +++ b/docs/category-overview-pages/troubleshooting-overview.md @@ -0,0 +1,5 @@ +# Troubleshooting and machine learning + +In this section you can learn about Netdata's advanced tools that can assist you in troubleshooting issues with +your infrastructure, to facilitate the identification of a root cause. + diff --git a/docs/category-overview-pages/visualizations-overview.md b/docs/category-overview-pages/visualizations-overview.md new file mode 100644 index 00000000..d07af062 --- /dev/null +++ b/docs/category-overview-pages/visualizations-overview.md @@ -0,0 +1,4 @@ +# Visualizations, charts and dashboards + +In this section you can learn about the various ways Netdata visualizes the collected metrics at an infrastructure level with Netdata Cloud +and at a single node level, with the Netdata Agent Dashboard. diff --git a/docs/cloud/alerts-notifications/add-discord-notification.md b/docs/cloud/alerts-notifications/add-discord-notification.md index 386e6035..d1769f0e 100644 --- a/docs/cloud/alerts-notifications/add-discord-notification.md +++ b/docs/cloud/alerts-notifications/add-discord-notification.md @@ -1,17 +1,8 @@ - +# Add Discord notification configuration From the Netdata Cloud UI, you can manage your space's notification settings and enable the configuration to deliver notifications on Discord. -#### Prerequisites +## Prerequisites To enable Discord notifications you need: @@ -19,7 +10,7 @@ To enable Discord notifications you need: - 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 +## Steps 1. Click on the **Space settings** cog (located above your profile icon) 1. Click on the **Notification** tab @@ -35,9 +26,9 @@ To enable Discord notifications you need: - 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 +## Settings on Discord -#### Enable webhook integrations on Discord server +## Enable webhook integrations on Discord server To enable the webhook integrations on Discord you need: 1. Go to *Integrations** under your **Server Settings @@ -51,9 +42,3 @@ To enable the webhook integrations on Discord you need: ![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-opsgenie-notification-configuration.md b/docs/cloud/alerts-notifications/add-opsgenie-notification-configuration.md new file mode 100644 index 00000000..28e526c9 --- /dev/null +++ b/docs/cloud/alerts-notifications/add-opsgenie-notification-configuration.md @@ -0,0 +1,37 @@ +# Add Opsgenie notification configuration + +From the Cloud interface, you can manage your space's notification settings and from these you can add a specific configuration to get notifications delivered on Opsgenie. + +## Prerequisites + +To add Opsgenie notification configurations you need + +- A Netdata Cloud account +- Access to the space as an **administrator** +- Space on **Business** plan or higher +- Have a permission to add new integrations in Opsgenie. + +## 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 **Opsgenie** 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 Opsgenie: + - API Key - a key provided on Opsgenie for the channel you want to receive your notifications. For more details check [how to configure this on Opsgenie](#settings-on-opsgenie) + +## Settings on Opsgenie + +To enable the Netdata integration on Opsgenie you need: +1. Go to integrations tab of your team, click **Add integration**. + + ![image](https://user-images.githubusercontent.com/93676586/230361479-cb73919c-452d-47ec-8066-ed99be5f05e2.png) + +1. Pick **API** from available integrations. Copy your API Key and press **Save Integration**. + +1. Paste copied API key into the corresponding field in **Integration configuration** section of Opsgenie modal window in Netdata. \ 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 index 6e47cfd9..64880ebe 100644 --- a/docs/cloud/alerts-notifications/add-pagerduty-notification-configuration.md +++ b/docs/cloud/alerts-notifications/add-pagerduty-notification-configuration.md @@ -1,26 +1,17 @@ - +# Add PagerDuty notification configuration -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. +From the Cloud interface, you can manage your space's notification settings and from these you can add a specific configuration to get notifications delivered on PagerDuty. -#### Prerequisites +## 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 +- Space 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 +## Steps 1. Click on the **Space settings** cog (located above your profile icon) 1. Click on the **Notification** tab @@ -34,9 +25,9 @@ To add PagerDuty notification configurations you need 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 +## Settings on PagerDuty -#### Enable webhook integrations 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: @@ -49,12 +40,4 @@ To enable the webhook integrations on PagerDuty you need: 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 index d8d6185f..99bb2d5b 100644 --- a/docs/cloud/alerts-notifications/add-slack-notification-configuration.md +++ b/docs/cloud/alerts-notifications/add-slack-notification-configuration.md @@ -1,26 +1,17 @@ - +# Add Slack notification configuration -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. +From the Cloud interface, you can manage your space's notification settings and from these you can add a specific configuration to get notifications delivered on Slack. -#### Prerequisites +## 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 +- Space 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 +## Steps 1. Click on the **Space settings** cog (located above your profile icon) 1. Click on the **Notification** tab @@ -34,7 +25,7 @@ To add discord notification configurations you need 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 +## 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 @@ -54,10 +45,3 @@ To enable the webhook integrations on Slack you need: ![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 index e6d04233..0140c30f 100644 --- a/docs/cloud/alerts-notifications/add-webhook-notification-configuration.md +++ b/docs/cloud/alerts-notifications/add-webhook-notification-configuration.md @@ -1,17 +1,8 @@ - +# Add webhook notification configuration -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. +From the Cloud interface, you can manage your space's notification settings and from these you can add a specific configuration to get notifications delivered on a webhook using a predefined schema. -#### Prerequisites +## Prerequisites To add discord notification configurations you need @@ -20,7 +11,7 @@ To add discord notification configurations you need - 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 +## Steps 1. Click on the **Space settings** cog (located above your profile icon) 1. Click on the **Notification** tab @@ -34,16 +25,16 @@ To add discord notification configurations you need 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): + - Authentication Mechanism - Netdata webhook integration supports 3 different authentication mechanisms. For mode details check [Authentication mechanisms](#authentication-mechanisms): - 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 +## 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 +### 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. @@ -59,16 +50,17 @@ The notification content sent to the destination service will be a JSON object h | 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. | +| rooms | object[object(string,string)] | Object with list of rooms names and urls where the node belongs to. | | 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. | +| additional_active_critical_alerts | integer | Number of additional critical alerts currently existing on the same node. | +| additional_active_warning_alerts | integer | Number of additional warning alerts currently existing on the same node. | | alarm_url | string | Netdata Cloud URL for this alarm. | -##### Extra headers +### 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. @@ -78,28 +70,165 @@ By default, the following headers will be sent in the HTTP request |:-------------------------------:|-----------------------------| | Content-Type | application/json | -##### Authorization mechanism +### Authentication mechanisms -Netdata webhook integration supports 3 different authorization mechanisms: +Netdata webhook integration supports 3 different authentication mechanisms: -1. Mutual TLS (recommended) +#### Mutual TLS authentication (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 +In mutual Transport Layer Security (mTLS) authentication, 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. This is the default authentication mechanism used if no other method is selected. -2. Basic +To take advantage of mutual TLS, you can configure your server to verify Netdata's client certificate. In order to achieve this, the Netdata client sending the notification supports mutual TLS (mTLS) to identify itself with a client certificate that your server can validate. + +The steps to perform this validation are as follows: + +- Store Netdata CA certificate on a file in your disk. The content of this file should be: + +
+ Netdata CA certificate + +``` +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIUDV0rS5jXsyNX33evHEQOwn9fPo0wDQYJKoZIhvcNAQEN +BQAwgYAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH +Ew1TYW4gRnJhbmNpc2NvMRYwFAYDVQQKEw1OZXRkYXRhLCBJbmMuMRIwEAYDVQQL +EwlDbG91ZCBTUkUxGDAWBgNVBAMTD05ldGRhdGEgUm9vdCBDQTAeFw0yMzAyMjIx +MjQzMDBaFw0zMzAyMTkxMjQzMDBaMIGAMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +Q2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEWMBQGA1UEChMNTmV0 +ZGF0YSwgSW5jLjESMBAGA1UECxMJQ2xvdWQgU1JFMRgwFgYDVQQDEw9OZXRkYXRh +IFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwIg7z3R++ +ppQYYVVoMIDlhWO3qVTMsAQoJYEvVa6fqaImUBLW/k19LUaXgUJPohB7gBp1pkjs +QfY5dBo8iFr7MDHtyiAFjcQV181sITTMBEJwp77R4slOXCvrreizhTt1gvf4S1zL +qeHBYWEgH0RLrOAqD0jkOHwewVouO0k3Wf2lEbCq3qRk2HeDvkv0LR7sFC+dDms8 +fDHqb/htqhk+FAJELGRqLeaFq1Z5Eq1/9dk4SIeHgK5pdYqsjpBzOTmocgriw6he +s7F3dOec1ZZdcBEAxOjbYt4e58JwuR81cWAVMmyot5JNCzYVL9e5Vc5n22qt2dmc +Tzw2rLOPt9pT5bzbmyhcDuNg2Qj/5DySAQ+VQysx91BJRXyUimqE7DwQyLhpQU72 +jw29lf2RHdCPNmk8J1TNropmpz/aI7rkperPugdOmxzP55i48ECbvDF4Wtazi+l+ +4kx7ieeLfEQgixy4lRUUkrgJlIDOGbw+d2Ag6LtOgwBiBYnDgYpvLucnx5cFupPY +Cy3VlJ4EKUeQQSsz5kVmvotk9MED4sLx1As8V4e5ViwI5dCsRfKny7BeJ6XNPLnw +PtMh1hbiqCcDmB1urCqXcMle4sRhKccReYOwkLjLLZ80A+MuJuIEAUUuEPCwywzU +R7pagYsmvNgmwIIuJtB6mIJBShC7TpJG+wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC +AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU9IbvOsPSUrpr8H2zSafYVQ9e +Ft8wDQYJKoZIhvcNAQENBQADggIBABQ08aI31VKZs8jzg+y/QM5cvzXlVhcpkZsY +1VVBr0roSBw9Pld9SERrEHto8PVXbadRxeEs4sKivJBKubWAooQ6NTvEB9MHuGnZ +VCU+N035Gq/mhBZgtIs/Zz33jTB2ju3G4Gm9VTZbVqd0OUxFs41Iqvi0HStC3/Io +rKi7crubmp5f2cNW1HrS++ScbTM+VaKVgQ2Tg5jOjou8wtA+204iYXlFpw9Q0qnP +qq6ix7TfLLeRVp6mauwPsAJUgHZluz7yuv3r7TBdukU4ZKUmfAGIPSebtB3EzXfH +7Y326xzv0hEpjvDHLy6+yFfTdBSrKPsMHgc9bsf88dnypNYL8TUiEHlcTgCGU8ts +ud8sWN2M5FEWbHPNYRVfH3xgY2iOYZzn0i+PVyGryOPuzkRHTxDLPIGEWE5susM4 +X4bnNJyKH1AMkBCErR34CLXtAe2ngJlV/V3D4I8CQFJdQkn9tuznohUU/j80xvPH +FOcDGQYmh4m2aIJtlNVP6+/92Siugb5y7HfslyRK94+bZBg2D86TcCJWaaZOFUrR +Y3WniYXsqM5/JI4OOzu7dpjtkJUYvwtg7Qb5jmm8Ilf5rQZJhuvsygzX6+WM079y +nsjoQAm6OwpTN5362vE9SYu1twz7KdzBlUkDhePEOgQkWfLHBJWwB+PvB1j/cUA3 +5zrbwvQf +-----END CERTIFICATE----- +``` +
+ +- Enable client certificate validation on the web server that is doing the TLS termination. Below we show you how to perform this configuration in `NGINX` and `Apache` + + **NGINX** + +```bash +server { + listen 443 ssl default_server; + + # ... existing SSL configuration for server authentication ... + ssl_verify_client on; + ssl_client_certificate /path/to/Netdata_CA.pem; + + location / { + if ($ssl_client_s_dn !~ "CN=api.netdata.cloud") { + return 403; + } + # ... existing location configuration ... + } +} +``` + +**Apache** + +```bash +Listen 443 + + # ... existing SSL configuration for server authentication ... + SSLVerifyClient require + SSLCACertificateFile "/path/to/Netdata_CA.pem" + + + Require expr "%{SSL_CLIENT_S_DN_CN} == 'api.netdata.cloud'" + # ... existing directory configuration ... + +``` + +#### Basic authentication 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 +#### Bearer token authentication + +In bearer token authentication, 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. + +##### Challenge secret + +To validate that you has ownership of the web application that will receive the webhook events, we are using a challenge response check mechanism. + +This mechanism works as follows: + +- The challenge secret parameter that you provide is a shared secret between you and Netdata only. +- On your request for creating a new Webhook integration, we will make a GET request to the url of the webhook, adding a query parameter `crc_token`, consisting of a random string. +- You will receive this request on your application and it must construct an encrypted response, consisting of a base64-encoded HMAC SHA-256 hash created from the crc_token and the shared secret. The response will be in the format: + +```json +{ + "response_token": "sha256=9GKoHJYmcHIkhD+C182QWN79YBd+D+Vkj4snmZrfNi4=" +} +``` + +- We will compare your application's response with the hash that we will generate using the challenge secret, and if they are the same, the integration creation will succeed. + +We will do this validation everytime you update your integration configuration. + +- Response requirements: + - A base64 encoded HMAC SHA-256 hash created from the crc_token and the shared secret. + - Valid response_token and JSON format. + - Latency less than 5 seconds. + - 200 HTTP response code. + +**Example response token generation in Python:** + +Here you can see how to define a handler for a Flask application in python 3: + +```python +import base64 +import hashlib +import hmac +import json + +key ='YOUR_CHALLENGE_SECRET' + +@app.route('/webhooks/netdata') +def webhook_challenge(): + token = request.args.get('crc_token').encode('ascii') + + # creates HMAC SHA-256 hash from incomming token and your consumer secret + sha256_hash_digest = hmac.new(key.encode(), + msg=token, + digestmod=hashlib.sha256).digest() + + # construct response data with base64 encoded hash + response = { + 'response_token': 'sha256=' + base64.b64encode(sha256_hash_digest).decode('ascii') + } -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. + # returns properly formatted json response + return json.dumps(response) +``` #### 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) +- [Alert Notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.md) - [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 index 115aaae7..17c7f879 100644 --- a/docs/cloud/alerts-notifications/manage-notification-methods.md +++ b/docs/cloud/alerts-notifications/manage-notification-methods.md @@ -1,25 +1,17 @@ - +# 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 +## Manage space notification settings -#### Prerequisites +### 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 +### Available actions per notification methods based on service level | **Action** | **Personal service level** | **System service level** | | :- | :-: | :-: | @@ -30,9 +22,9 @@ To manage space notification settings, you will need the following: 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)) +* 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.md#service-classification)) -#### Steps +### Steps 1. Click on the **Space settings** cog (located above your profile icon) 1. Click on the **Notification** tab @@ -53,9 +45,9 @@ Notes: 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 +## Manage user notification settings -#### Prerequisites +### Prerequisites To manage user specific notification settings, you will need the following: @@ -64,7 +56,7 @@ To manage user specific notification settings, you will need the following: 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 +### Steps 1. Click on the **User notification settings** shortcut on top of the help button 1. You are presented with: @@ -78,11 +70,3 @@ Note: If an administrator has disabled a Personal [service level](https://github 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.md b/docs/cloud/alerts-notifications/notifications.md new file mode 100644 index 00000000..94cd2dc3 --- /dev/null +++ b/docs/cloud/alerts-notifications/notifications.md @@ -0,0 +1,121 @@ +# Cloud alert notifications + +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 alerts in one place. +* 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/metric-correlations.md) +or [Anomaly Advisor](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.md) + +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). + + + +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/health/REFERENCE.md) each node's alert watchdog. + + + +### 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, Opsgenie + +## 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) diff --git a/docs/cloud/alerts-notifications/notifications.mdx b/docs/cloud/alerts-notifications/notifications.mdx deleted file mode 100644 index e594606e..00000000 --- a/docs/cloud/alerts-notifications/notifications.mdx +++ /dev/null @@ -1,155 +0,0 @@ ---- -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). - - - -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. - - - -### 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 deleted file mode 100644 index b9240ce4..00000000 --- a/docs/cloud/alerts-notifications/smartboard.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -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 deleted file mode 100644 index 1035b682..00000000 --- a/docs/cloud/alerts-notifications/view-active-alerts.mdx +++ /dev/null @@ -1,76 +0,0 @@ ---- -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 deleted file mode 100644 index c51f08fb..00000000 --- a/docs/cloud/beta-architecture/new-architecture.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -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.md b/docs/cloud/cheatsheet.md new file mode 100644 index 00000000..35a6a2c9 --- /dev/null +++ b/docs/cloud/cheatsheet.md @@ -0,0 +1,215 @@ +# Useful management and configuration actions + +Below you will find some of the most common actions that one can take while using Netdata. You can use this page as a quick reference for installing Netdata, connecting a node to the Cloud, properly editing the configuration, accessing Netdata's API, and more! + +### Install Netdata + +```bash +wget -O /tmp/netdata-kickstart.sh https://my-netdata.io/kickstart.sh && sh /tmp/netdata-kickstart.sh + +# Or, if you have cURL but not wget (such as on macOS): +curl https://my-netdata.io/kickstart.sh > /tmp/netdata-kickstart.sh && sh /tmp/netdata-kickstart.sh +``` + +#### Connect a node to Netdata Cloud + +To do so, sign in to Netdata Cloud, on your Space under the Nodes tab, click `Add Nodes` and paste the provided command into your node’s terminal and run it. +You can also copy the Claim token and pass it to the installation script with `--claim-token` and re-run it. + +### Configuration + +**Netdata's config directory** is `/etc/netdata/` but in some operating systems it might be `/opt/netdata/etc/netdata/`. +Look for the `# config directory =` line over at `http://NODE_IP:19999/netdata.conf` to find your config directory. + +From within that directory you can run `sudo ./edit-config netdata.conf` **to edit Netdata's configuration.** +You can edit other config files too, by specifying their filename after `./edit-config`. +You are expected to use this method in all following configuration changes. + + + +--- + +#### Enable/disable plugins (groups of collectors) + +```bash +sudo ./edit-config netdata.conf +``` + +```conf +[plugins] + go.d = yes # enabled + node.d = no # disabled +``` + +#### Enable/disable specific collectors + +```bash +sudo ./edit-config go.d.conf # edit a plugin's config +``` + +```yaml +modules: + activemq: no # disabled + cockroachdb: yes # enabled +``` + +#### Edit a collector's config + +```bash +sudo ./edit-config go.d/mysql.conf +``` + +### Alarms & notifications + + +After any change, reload the Netdata health configuration: + +```bash +netdatacli reload-health +#or if that command doesn't work on your installation, use: +killall -USR2 netdata +``` + +#### Configure a specific alarm + +```bash +sudo ./edit-config health.d/example-alarm.conf +``` + +#### Silence a specific alarm + +```bash +sudo ./edit-config health.d/example-alarm.conf +``` + +``` + to: silent +``` + + + +--- + +### Manage the daemon + +| Intent | Action | +| :-------------------------- | --------------------------------------------------------------------: | +| Start Netdata | `$ sudo service netdata start` | +| Stop Netdata | `$ sudo service netdata stop` | +| Restart Netdata | `$ sudo service netdata restart` | +| Reload health configuration | `$ sudo netdatacli reload-health` `$ killall -USR2 netdata` | +| View error logs | `less /var/log/netdata/error.log` | +| View collectors logs | `less /var/log/netdata/collector.log` | + +#### Change the port Netdata listens to (example, set it to port 39999) + +```conf +[web] +default port = 39999 +``` + +### 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 + +You can access the API like this: `http://NODE:19999/api/VERSION/REQUEST`. +If you want to take a look at all the API requests, check our API page at + + + + + + + diff --git a/docs/cloud/cheatsheet.mdx b/docs/cloud/cheatsheet.mdx deleted file mode 100644 index c1d0a471..00000000 --- a/docs/cloud/cheatsheet.mdx +++ /dev/null @@ -1,231 +0,0 @@ ---- -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 & configuration cheatsheet to simplify your interactions with Netdata, including configuration, -using charts, managing the daemon, and more. - -## Install Netdata - -#### Install Netdata - - - -Or, if you have cURL but not wget (such as on macOS): - - - -#### 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`

`$ 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**
use the `zoom in` and `zoom out` buttons on any chart (upper right corner)

**Agent**
`SHIFT` or `ALT` + `mouse scrollwheel`
`SHIFT` or `ALT` + `two-finger pinch` (touchscreen)
`SHIFT` or `ALT` + `two-finger scroll` (touchscreen) | -| Zoom to a specific timeframe | **Cloud**
use the `select and zoom` button on any chart and then do a `mouse selection`

**Agent**
`SHIFT` + `mouse selection` | -| Pan forward or back in time | `click` & `drag`
`touch` & `drag` (touchpad/touchscreen) | -| Select a certain timeframe | `ALT` + `mouse selection`
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 deleted file mode 100644 index 764ba0e8..00000000 --- a/docs/cloud/cloud.mdx +++ /dev/null @@ -1,74 +0,0 @@ ---- -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 ](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 ](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 ](https://community.netdata.cloud/tags/c/general/29/faq) for answers to common questions. - - - - 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. - - - -## Learn about Netdata Cloud's features - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/cloud/data-privacy.mdx b/docs/cloud/data-privacy.mdx deleted file mode 100644 index c99cff94..00000000 --- a/docs/cloud/data-privacy.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -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 deleted file mode 100644 index b9f83af8..00000000 --- a/docs/cloud/get-started.mdx +++ /dev/null @@ -1,133 +0,0 @@ ---- -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. - - - - - -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. - - - -**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. - - - -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.md b/docs/cloud/insights/anomaly-advisor.md new file mode 100644 index 00000000..4804dbc1 --- /dev/null +++ b/docs/cloud/insights/anomaly-advisor.md @@ -0,0 +1,87 @@ + + +# Anomaly Advisor + +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. + + + +## 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://github.com/netdata/netdata/blob/master/ml/README.md#configuration). + +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/anomaly-advisor.mdx b/docs/cloud/insights/anomaly-advisor.mdx deleted file mode 100644 index 98a28d92..00000000 --- a/docs/cloud/insights/anomaly-advisor.mdx +++ /dev/null @@ -1,86 +0,0 @@ ---- -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. - - - -## 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/events-feed.md b/docs/cloud/insights/events-feed.md new file mode 100644 index 00000000..0e297ba8 --- /dev/null +++ b/docs/cloud/insights/events-feed.md @@ -0,0 +1,79 @@ + + +# Events feed + +Netdata Cloud provides the Events feed which is a powerful feature that tracks events that happen on your infrastructure, or in your Space. The feed lets you investigate events that occurred in the past, which is invaluable for troubleshooting. Common use cases are ones like when a node goes offline, and you want to understand what events happened before that. A detailed event history can also assist in attributing sudden pattern changes in a time series to specific changes in your environment. + +## What are the available events? + +At a high-level view, these are the domains from which the Events feed will provide visibility into. + +> ⚠️ Based on your space's plan, different allowances are defined to query past data. + +| **Domains of events** | **Community** | **Pro** | **Business** | +| :-- | :-- | :-- | :-- | +| **Auditing events** - COMING SOON
Events related to actions done on your Space, e.g. invite user, change user role or change plan.| 4 hours | 7 days | 90 days | +| **[Topology events](#topology-events)**
Node state transition events, e.g. live or offline.| 4 hours | 7 days | 14 days | +| **[Alert events](#alert-events)**
Alert state transition events, can be seen as an alert history log.| 4 hours | 7 days | 90 days | + +### Topology events + +| **Event name** | **Description** | **Example** | +| :-- | :-- | :-- | +| Node Became Live | The node is collecting and streaming metrics to Cloud.| Node `netdata-k8s-state-xyz` was **live** | +| Node Became Stale | The node is offline and not streaming metrics to Cloud. It can show historical data from a parent node. | Node `ip-xyz.ec2.internal` was **stale** | +| Node Became Offline | The node is offline, not streaming metrics to Cloud and not available in any parent node.| Node `ip-xyz.ec2.internal` was **offline** | +| Node Created | The node is created but it is still `Unseen` on Cloud, didn't establish a successful connection yet.| Node `ip-xyz.ec2.internal` was **created** | +| Node Removed |The node was removed from the Space, for example by using the `Delete` action on the node. This is a soft delete in that the node gets marked as deleted, but retains the association with this space. If it becomes live again, it will be restored (see `Node Restored` below) and reappear in this space as before. | Node `ip-xyz.ec2.internal` was **deleted (soft)** | +| Node Restored | The node was restored. See `Node Removed` above. | Node `ip-xyz.ec2.internal` was **restored** | +| Node Deleted | The node was deleted from the Space. This is a hard delete and no information on the node is retained. | Node `ip-xyz.ec2.internal` was **deleted (hard)** | +| Agent Connected | The agent connected to the Cloud MQTT server (Agent-Cloud Link established).
These events can only be seen on _All nodes_ War Room. | Agent with claim ID `7d87bqs9-cv42-4823-8sd4-3614548850c7` has connected to Cloud. | +| Agent Disconnected | The agent disconnected from the Cloud MQTT server (Agent-Cloud Link severed).
These events can only be seen on _All nodes_ War Room. | Agent with claim ID `7d87bqs9-cv42-4823-8sd4-3614548850c7` has disconnected from Cloud: **Connection Timeout**. | +| Space Statistics | Daily snapshot of space node statistics.
These events can only be seen on _All nodes_ War Room. | Space statistics. Nodes: **22 live**, **21 stale**, **18 removed**, **61 total**. | + + +### Alert events + +| **Event name** | **Description** | **Example** | +| :-- | :-- | :-- | +| Node Alert State Changed | These are node alert state transition events and can be seen as an alert history log. You will be able to see transitions to or from any of these states: Cleared, Warning, Critical, Removed, Error or Unknown | Transition to Cleared:
`httpcheck_web_service_bad_status` for `httpcheck_netdata_cloud.request_status` on `netdata-parent-xyz` recovered with value **8.33%**

Transition from Cleared to Warning or Critical:
`httpcheck_web_service_bad_status` for `httpcheck_netdata_cloud.request_status` on `netdata-parent-xyz` was raised to **WARNING** with value **10%**

Transition from Warning to Critical:
`httpcheck_web_service_bad_status` for `httpcheck_netdata_cloud.request_status` on `netdata-parent-xyz` escalated to **CRITICAL** with value **25%**

Transition from Critical to Warning:
`httpcheck_web_service_bad_status` for `httpcheck_netdata_cloud.request_status` on `netdata-parent-xyz` was demoted to **WARNING** with value **10%**

Transition to Removed:
Alert `httpcheck_web_service_bad_status` for `httpcheck_netdata_cloud.request_status` on `netdata-parent-xyz` is no longer available, state can't be assessed.

Transition to Error:
For this alert `httpcheck_web_service_bad_status` related to `httpcheck_netdata_cloud.request_status` on `netdata-parent-xyz` we couldn't calculate the current value ⓘ| + +## Who can access the events? + +All users will be able to see events from the Topology and Alerts domain but Auditing events, once these are added, only be accessible to administrators. For more details checkout [Netdata Role-Based Access model](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/role-based-access.md). + +## How to use the events feed + +1. Click on the **Events** tab (located near the top of your screen) +1. You will be presented with a table listing the events that occurred from the timeframe defined on the date time picker +1. You can use the filtering capabilities available on right-hand bar to slice through the results provided. See more details on event types and filters + +Note: When you try to query a longer period than what your space allows you will see an error message highlighting that you are querying data outside of your plan. + +### Event types and filters + +| Event type | Tags | Nodes | Alert Status | Alert Names | Chart Names | +| :-- | :-- | :-- | :-- | :-- | :-- | +| Node Became Live | node, lifecycle | Node name | - | - | - | +| Node Became Stale | node, lifecycle | Node name | - | - | - | +| Node Became Offline | node, lifecycle | Node name | - | - | - | +| Node Created | node, lifecycle | Node name | - | - | - | +| Node Removed | node, lifecycle | Node name | - | - | - | +| Node Restored | node, lifecycle | Node name | - | - | - | +| Node Deleted | node, lifecycle | Node name | - | - | - | +| Agent Claimed | agent | - | - | - | - | +| Agent Connected | agent | - | - | - | - | +| Agent Disconnected | agent | - | - | - | - | +| Agent Authenticated | agent | - | - | - | - | +| Agent Authentication Failed | agent | - | - | - | - | +| Space Statistics | space, node, statistics | Node name | - | - | - | +| Node Alert State Changed | alert, node | Node name | Cleared, Warning, Critical, Removed, Error or Unknown | Alert name | Chart name | diff --git a/docs/cloud/insights/metric-correlations.md b/docs/cloud/insights/metric-correlations.md index ce8835d3..c8ead9be 100644 --- a/docs/cloud/insights/metric-correlations.md +++ b/docs/cloud/insights/metric-correlations.md @@ -1,4 +1,4 @@ ---- + + +# Metric Correlations 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. @@ -51,9 +53,9 @@ Behind the scenes, Netdata will aggregate the raw data as needed such that arbit ### 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. +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](https://github.com/netdata/netdata/tree/master/ml#anomaly-bit---100--anomalous-0--normal)" 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](https://github.com/netdata/netdata/tree/master/ml#anomaly-rate---averageanomaly-bit)" 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. +**Note**: Read more [here](https://github.com/netdata/netdata/blob/master/ml/README.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. @@ -72,7 +74,7 @@ Should you still want to, disabling nodes for Metric Correlation on the agent is ## 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: +- When running Metric Correlations from the [Overview tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#overview-and-single-node-view) 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. @@ -81,7 +83,3 @@ Should you still want to, disabling nodes for Metric Correlation on the agent is - `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 index f294a627..da2d51f7 100644 --- a/docs/cloud/manage/invite-your-team.md +++ b/docs/cloud/manage/invite-your-team.md @@ -1,37 +1,24 @@ ---- -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 your team + +Invite your entire SRE, DevOPs, or ITOps team to Netdata Cloud, to give everyone insights into your infrastructure from a single pane of glass. 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) +![image](https://user-images.githubusercontent.com/70198089/227887469-e46bad55-ef5d-441a-83a5-dcc2af038678.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. +You will be prompted to 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. +Next, pick a role for the invited user. You can read more about [which roles are available](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/role-based-access.md#what-roles-are-available) based on your [subscription plan](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/plans.md). + 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. +to [sign up](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/sign-in.md) and join your Space. -![The invitation panel in Netdata Cloud](https://user-images.githubusercontent.com/1153921/97762959-53b33680-1ac7-11eb-8e9d-f3f4a14c0028.png) +![image](https://user-images.githubusercontent.com/70198089/227888899-8511081b-0157-4e22-81d9-898cc464dcb0.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/plans.md b/docs/cloud/manage/plans.md new file mode 100644 index 00000000..9180ab5a --- /dev/null +++ b/docs/cloud/manage/plans.md @@ -0,0 +1,120 @@ +# Netdata Plans + +This page will guide you through the differences between the Community, Pro, Business and Enterprise plans. + +At Netdata, we believe in providing free and unrestricted access to high-quality monitoring solutions, and our commitment to this principle will not change. We offer our free SaaS offering - what we call **Community plan** - and Open Source Agent, which features unlimited nodes and users, unlimited metrics, and retention, providing real-time, high-fidelity, out-of-the-box infrastructure monitoring for packaged applications, containers, and operating systems. + +We also provide paid subscriptions that designed to provide additional features and capabilities for businesses that need tighter and customizable integration of the free monitoring solution to their processes. These are divided into three different plans: **Pro**, **Business**, and **Enterprise**. Each plan will offers a different set of features and capabilities to meet the needs of businesses of different sizes and with different monitoring requirements. + +> ### Note +> To not disrupt the existing space user's access rights we will keep them in the **Early Bird** plan. The reason for this is to allow users to +> keep using the legacy **Member** role with the exact same permissions as it has currently. +> +> If you move from the **Early Bird** plan to a paid plan, you will not be able to return to the **Early Bird** plan again. The **Community** free plan will always be available to you, but it does not allow +> you to invite or change users using the Member role. See more details on our [roles and plans](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/role-based-access.md#what-roles-are-available) documentation. + +### Plans + +The plan is an attribute that is directly attached to your space(s) and that dictates what capabilities and customizations you have on your space. If you have different spaces you can have different Netdata plans on them. This gives you flexibility to chose what is more adequate for your needs on each of your spaces. + +Netdata Cloud plans, with the exception of Community, work as subscriptions and overall consist of two pricing components: + +* A flat fee component, that is a price per space, and +* An on-demand metered component, that is related to your usage of Netdata which directly links to the [number of nodes you have running](#running-nodes-and-billing) + +Netdata provides two billing frequency options: + +* Monthly - Pay as you go, where we charge both the flat fee and the on-demand component every month +* Yearly - Annual prepayment, where we charge upfront the flat fee and committed amount related to your estimated usage of Netdata (more details [here](#committed-nodes)) + +For more details on the plans and subscription conditions please check . + +#### Running nodes and billing + +The only dynamic variable we consider for billing is the number of concurrently running nodes or agents. We only charge you for your active running nodes, so we don't count: + +* offline nodes +* stale nodes, nodes that are available to query through a Netdata parent agent but are not actively connecting metrics at the moment + +To ensure we don't overcharge you due to sporadic spikes throughout a month or even at a certain point in a day we are: + +* Calculate a daily P90 figure for your running nodes. To achieve that, we take a daily snapshot of your running nodes, and using the node state change events (live, offline) we guarantee that a daily P90 figure is calculated to remove any daily spikes +* On top of the above, we do a running P90 calculation from the start to the end of your billing cycle. Even if you have an yearly billing frequency we keep a monthly subscription linked to that to identify any potential overage over your [committed nodes](#committed-nodes). + +#### Committed nodes + +When you subscribe to an Yearly plan you will need to specify the number of nodes that you will commit to. On these nodes, a discounted price of less 25% than the original cost per node of the plan is applied. This amount will be part of your annual prepayment. + +``` +Node plan discounted price x committed nodes x 12 months +``` + +If, for a given month, your usage is over these committed nodes we will charge the original cost per node for the nodes above the committed number. + +#### Plan changes and credit balance + +It is ok to change your mind. We allow to change your plan, billing frequency or adjust the committed nodes, on yearly plans, at any time. + +To achieve this you will need to: + +* Move to the Community plan, where we will cancel the current subscription and: + * Issue a credit to you for the unused period, in case you are on a **yearly plan** + * Charge you only for the current used period and issue a credit for the unused period related to the flat fee, in case you are on a **monthly plan** +* Select the new subscription with the change that you want + +> ⚠️ On a move to Community (cancellation of an active subscription), please note that you will have all your notification methods configurations active **for a period of 24 hours**. +> After that, any notification methods unavailable in your new plan at that time will be automatically disabled. You can always re-enable them once you move to a paid plan that includes them. + +> ⚠️ Any credit given to you will be available to use on future paid subscriptions with us. It will be available until the the **end of the following year**. + +### Areas impacted by plans + +##### Role-Based Access model + +Depending on the plan associated to your space you will have different roles available: + +| **Role** | **Community** | **Pro** | **Business** | **Early Bird** | +| :-- | :--: | :--: | :--: | :--: | +| **Administrators**

Users with this role can control Spaces, War Rooms, Nodes, Users and Billing.

They can also access any War Room in the Space.

| :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| **Managers**

Users with this role can manage War Rooms and Users.

They can access any War Room in the Space.

| - | - | :heavy_check_mark: | - | +| **Troubleshooters**

Users with this role can use Netdata to troubleshoot, not manage entities.

They can access any War Room in the Space.

| - | :heavy_check_mark: | :heavy_check_mark: | - | +| **Observers**

Users with this role can only view data in specific War Rooms.

💡 Ideal for restricting your customer's access to their own dedicated rooms.

| - | - | :heavy_check_mark: | - | +| **Billing**

Users with this role can handle billing options and invoices.

| - | - | :heavy_check_mark: | - | +| **Member** ⚠️ Legacy role

Users with this role can create War Rooms and invite other Members.

They can only see the War Rooms they belong to and all Nodes in the All Nodes room.

| - | - | - | :heavy_check_mark: | + +For more details check the documentation under [Role-Based Access model](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/role-based-access.md). + +##### Events feed + +The plan you have subscribed on your space will determine the amount of historical data you will be able to query: + +| **Type of events** | **Community** | **Pro** | **Business** | +| :-- | :-- | :-- | :-- | +| **Auditing events** - COMING SOON

Events related to actions done on your Space, e.g. invite user, change user role or create room.

| 4 hours | 7 days | 90 days | +| **Topology events**

Node state transition events, e.g. live or offline.

| 4 hours | 7 days | 14 days | +| **Alert events**

Alert state transition events, can be seen as an alert history log.

| 4 hours | 7 days | 90 days | + +For more details check the documentation under [Events feed](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/events-feed.md). + +##### Notification integrations + +The plan on your space will determine what type of notifications methods will be available to you: + +* **Community** - Email and Discord +* **Pro** - Email, Discord and webhook +* **Business** - Unlimited, this includes Slack, PagerDuty, Opsgenie etc. + +For mode details check the documentation under [Alert Notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.md). + +### Related Topics + +#### **Related Concepts** + +* [Spaces](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md) +* [Alert Notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.md) +* [Events feed](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/events-feed.md) +* [Role-Based Access model](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/role-based-access.md) + +#### Related Tasks + +* [View Plan & Billing](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/view-plan-billing.md) diff --git a/docs/cloud/manage/role-based-access.md b/docs/cloud/manage/role-based-access.md new file mode 100644 index 00000000..1696e096 --- /dev/null +++ b/docs/cloud/manage/role-based-access.md @@ -0,0 +1,136 @@ +# Role-Based Access model + +Netdata Cloud's role-based-access mechanism allows you to control what functionalities in the app users can access. Each user can be assigned only one role, which fully specifies all the capabilities they are afforded. + +## What roles are available? + +With the advent of the paid plans we revamped the roles to cover needs expressed by Netdata users, like providing more limited access to their customers, or +being able to join any room. We also aligned the offered roles to the target audience of each plan. The end result is the following: + +| **Role** | **Community** | **Pro** | **Business** | **Early Bird** | +| :-- | :--: | :--: | :--: | :--: | +| **Administrators**

Users with this role can control Spaces, War Rooms, Nodes, Users and Billing.

They can also access any War Room in the Space.

| :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| **Managers**

Users with this role can manage War Rooms and Users.

They can access any War Room in the Space.

| - | - | :heavy_check_mark: | - | +| **Troubleshooters**

Users with this role can use Netdata to troubleshoot, not manage entities.

They can access any War Room in the Space.

| - | :heavy_check_mark: | :heavy_check_mark: | - | +| **Observers**

Users with this role can only view data in specific War Rooms.

💡 Ideal for restricting your customer's access to their own dedicated rooms.

| - | - | :heavy_check_mark: | - | +| **Billing**

Users with this role can handle billing options and invoices.

| - | - | :heavy_check_mark: | - | +| **Member** ⚠️ Legacy role

Users with this role can create War Rooms and invite other Members.

They can only see the War Rooms they belong to and all Nodes in the All Nodes room.

| - | - | - | :heavy_check_mark: | + +## What happens to the previous Member role? + +We will maintain a Early Bird plan for existing users, which will continue to provide access to the Member role. + +## Which functionalities are available for each role? + +In more detail, you can find on the following tables which functionalities are available for each role on each domain. + +### Space Management + +| **Functionality** | **Administrator** | **Manager** | **Troubleshooter** | **Observer** | **Billing** | **Member** | +| :-- | :--: | :--: | :--: | :--: | :--: | :--: | +| See Space | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Leave Space | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Delete Space | :heavy_check_mark: | - | - | - | - | - | +| Change name | :heavy_check_mark: | - | - | - | - | - | +| Change description | :heavy_check_mark: | - | - | - | - | - | + +### Node Management + +| **Functionality** | **Administrator** | **Manager** | **Troubleshooter** | **Observer** | **Billing** | **Member** | Notes | +| :-- | :--: | :--: | :--: | :--: | :--: | :--: | :-- | +| See all Nodes in Space (_All Nodes_ room) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | - | :heavy_check_mark: | Members are always on the _All Nodes_ room | +| Connect Node to Space | :heavy_check_mark: | - | - | - | - | - | - | +| Delete Node from Space | :heavy_check_mark: | - | - | - | - | - | - | + +### User Management + +| **Functionality** | **Administrator** | **Manager** | **Troubleshooter** | **Observer** | **Billing** | **Member** | Notes | +| :-- | :--: | :--: | :--: | :--: | :--: | :--: | :-- | +| See all Users in Space | :heavy_check_mark: | :heavy_check_mark: | - | - | - | :heavy_check_mark: | | +| Invite new User to Space | :heavy_check_mark: | :heavy_check_mark: | - | - | - | :heavy_check_mark: | You can't invite a user with a role you don't have permissions to appoint to (see below) | +| Delete Pending Invitation to Space | :heavy_check_mark: | :heavy_check_mark: | - | - | - | :heavy_check_mark: | | +| Delete User from Space | :heavy_check_mark: | :heavy_check_mark: | - | - | - | - | You can't delete a user if he has a role you don't have permissions to appoint to (see below) | +| Appoint Administrators | :heavy_check_mark: | - | - | - | - | - | | +| Appoint Billing user | :heavy_check_mark: | - | - | - | - | - | | +| Appoint Managers | :heavy_check_mark: | :heavy_check_mark: | - | - | - | - | | +| Appoint Troubleshooters | :heavy_check_mark: | :heavy_check_mark: | - | - | - | - | | +| Appoint Observer | :heavy_check_mark: | :heavy_check_mark: | - | - | - | - | | +| Appoint Member | :heavy_check_mark: | - | - | - | - | :heavy_check_mark: | Only available on Early Bird plans | +| See all Users in a Room | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | | +| Invite existing user to Room | :heavy_check_mark: | :heavy_check_mark: | - | - | - | :heavy_check_mark: | User already invited to the Space | +| Remove user from Room | :heavy_check_mark: | :heavy_check_mark: | - | - | - | - | | + +### Room Management + +| **Functionality** | **Administrator** | **Manager** | **Troubleshooter** | **Observer** | **Billing** | **Member** | Notes | +| :-- | :--: | :--: | :--: | :--: | :--: | :--: | :-- | +| See all Rooms in a Space | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | - | - | | +| Join any Room in a Space | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | - | - | By joining a room you will be enabled to get notifications from nodes on that room | +| Leave Room | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | | +| Create a new Room in a Space | :heavy_check_mark: | :heavy_check_mark: | - | - | - | :heavy_check_mark: | | +| Delete Room | :heavy_check_mark: | :heavy_check_mark: | - | - | - | - | | +| Change Room name | :heavy_check_mark: | :heavy_check_mark: | - | - | - | :heavy_check_mark: | If not the _All Nodes_ room | +| Change Room description | :heavy_check_mark: | :heavy_check_mark: | - | - | - | :heavy_check_mark: | | +| Add existing Nodes to Room | :heavy_check_mark: | :heavy_check_mark: | - | - | - | :heavy_check_mark: | Node already connected to the Space | +| Remove Nodes from Room | :heavy_check_mark: | :heavy_check_mark: | - | - | - | :heavy_check_mark: | | + +### Notifications Management + +| **Functionality** | **Administrator** | **Manager** | **Troubleshooter** | **Observer** | **Billing** | **Member** | Notes | +| :-- | :--: | :--: | :--: | :--: | :--: | :--: | :-- | +| See all configured notifications on a Space | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | | +| Add new configuration | :heavy_check_mark: | - | - | - | - | - | | +| Enable/Disable configuration | :heavy_check_mark: | - | - | - | - | - | | +| Edit configuration | :heavy_check_mark: | - | - | - | - | - | Some exceptions apply depending on [service level](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/manage-notification-methods.md#available-actions-per-notification-methods-based-on-service-level) | +| Delete configuration | :heavy_check_mark: | - | - | - | - | - | | +| Edit personal level notification settings | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | [Manage user notification settings](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/manage-notification-methods.md#manage-user-notification-settings) | + +Notes: +* 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.md#service-classification)) + +### Dashboards + +| **Functionality** | **Administrator** | **Manager** | **Troubleshooter** | **Observer** | **Billing** | **Member** | +| :-- | :--: | :--: | :--: | :--: | :--: | :--: | +| See all dashboards in Room | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | +| Add new dashboard to Room | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | +| Edit any dashboard in Room | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | - | :heavy_check_mark: | +| Edit own dashboard in Room | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | +| Delete any dashboard in Room | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | - | :heavy_check_mark: | +| Delete own dashboard in Room | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | + +### Functions + +| **Functionality** | **Administrator** | **Manager** | **Troubleshooter** | **Observer** | **Billing** | **Member** | Notes | +| :-- | :--: | :--: | :--: | :--: | :--: | :--: | :-- | +| See all functions in Room | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | +| Run any function in Room | :heavy_check_mark: | :heavy_check_mark: | - | - | - | - | +| Run read-only function in Room | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | | +| Run sensitive function in Room | :heavy_check_mark: | :heavy_check_mark: | - | - | - | - | There isn't any function on this category yet, so subject to change. | + +### Events feed + +| **Functionality** | **Administrator** | **Manager** | **Troubleshooter** | **Observer** | **Billing** | **Member** | Notes | +| :-- | :--: | :--: | :--: | :--: | :--: | :--: | :-- | +| See Alert or Topology events | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | | +| See Auditing events | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | - | These are coming soon, not currently available | + +### Billing + +| **Functionality** | **Administrator** | **Manager** | **Troubleshooter** | **Observer** | **Billing** | **Member** | Notes | +| :-- | :--: | :--: | :--: | :--: | :--: | :--: | :-- | +| See Plan & Billing details | :heavy_check_mark: | - | - | - | :heavy_check_mark: | - | Current plan and usage figures | +| Update plans | :heavy_check_mark: | - | - | - | - | - | This includes cancelling current plan (going to Community plan) | +| See invoices | :heavy_check_mark: | - | - | - | :heavy_check_mark: | - | | +| Manage payment methods | :heavy_check_mark: | - | - | - | :heavy_check_mark: | - | | +| Update billing email | :heavy_check_mark: | - | - | - | :heavy_check_mark: | - | | + +### Other permissions + +| **Functionality** | **Administrator** | **Manager** | **Troubleshooter** | **Observer** | **Billing** | **Member** | +| :-- | :--: | :--: | :--: | :--: | :--: | :--: | +| See Bookmarks in Space | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | +| Add Bookmark to Space | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | - | :heavy_check_mark: | +| Delete Bookmark from Space | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | - | :heavy_check_mark: | +| See Visited Nodes | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | +| Update Visited Nodes | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | - | :heavy_check_mark: | diff --git a/docs/cloud/manage/sign-in.md b/docs/cloud/manage/sign-in.md new file mode 100644 index 00000000..96275f57 --- /dev/null +++ b/docs/cloud/manage/sign-in.md @@ -0,0 +1,81 @@ +# Sign in to Netdata + +This page explains how to sign in to Netdata with your email, Google account, or GitHub account, and provides some tips if you're having trouble signing in. + +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/packaging/installer/README.md). + +### Troubleshooting + +You should receive your sign in email in less than a minute. The subject is **Verify your email!** for new sign-ups, **Sign in to Netdata** for sign ins. +The sender is `no-reply@netdata.cloud` via `sendgrid.net`. + +If you don't see the email, try the following: + +- Check your spam folder. +- In Gmail, check the **Updates** category. +- 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). + +You may also want to add `no-reply@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 +`netdata.cloud` domain. + +In some cases, temporary issues with your mail server or email account may result in your email address being added to a Bounce list by Sendgrid. +If you are added to that list, no Netdata cloud email can reach you, including alarm notifications. Let us know in Discord that you have trouble receiving +any email from us and someone will ask you to provide your email address privately, so we can check if you are on the Bounce list. + +## 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`. diff --git a/docs/cloud/manage/sign-in.mdx b/docs/cloud/manage/sign-in.mdx deleted file mode 100644 index 32fcb22e..00000000 --- a/docs/cloud/manage/sign-in.mdx +++ /dev/null @@ -1,88 +0,0 @@ ---- -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 index 11d5cb32..aaf193a8 100644 --- a/docs/cloud/manage/themes.md +++ b/docs/cloud/manage/themes.md @@ -1,12 +1,4 @@ ---- -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" ---- +# Choose your Netdata Cloud theme The Dark theme is the default for all new Netdata Cloud accounts. diff --git a/docs/cloud/manage/view-plan-billing.md b/docs/cloud/manage/view-plan-billing.md new file mode 100644 index 00000000..d29f93f9 --- /dev/null +++ b/docs/cloud/manage/view-plan-billing.md @@ -0,0 +1,92 @@ +# View Plan & Billing + +From the Cloud interface, you can view and manage your space's plan and billing settings, and see the space's usage in terms of running nodes. + +To view and manage some specific settings, related to billing options and invoices, you'll be redirected to our billing provider Customer Portal. + +## Prerequisites + +To see your plan and billing setting you need: + +- A Cloud account +- Access to the space as an Administrator or Billing user + +## Steps + +1. Click on the **Space settings** cog (located above your profile icon) +1. Click on the **Plan & Billing** tab +1. On this page you will be presented with information on your current plan, billing settings, and usage information: + 1. At the top of the page you will see: + - **Credit** amount which refers to any amount you have available to use on future invoices or subscription changes () - this is displayed once you have had an active paid subscription with us + - **Billing email** the email that was specified to be linked to tha plan subscription. This is where invoices, payment, and subscription-related notifications will be sent. + - **Billing options and Invoices** is the link to our billing provider Customer Portal where you will be able to: + - See the current subscription. There will always be 2 subscriptions active for the two pricing components mentioned on [Netdata Plans documentation page](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/plans.md#plans) + - Change directly the payment method associated to current subscriptions + - View, add, delete or change your default payment methods + - View or change or Billing information: + - Billing email + - Address + - Phone number + - Tax ID + - View your invoice history + 1. At the middle, you'll see details on your current plan as well as means to: + - Upgrade or cancel your plan + - View full plan details page + 1. At the bottom, you will find your Usage chart that displays: + - Daily count - The weighted 90th percentile of the live node count during the day, taking time as the weight. If you have 30 live nodes throughout the day + except for a two hour peak of 44 live nodes, the daily value is 31. + - Period count: The 90th percentile of the daily counts for this period up to the date. The last value for the period is used as the number of nodes for the bill for that period. See more details in [running nodes and billing](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/plans.md#running-nodes-and-billing) (only applicable if you are on a paid plan subscription) + - Committed nodes: The number of nodes committed to in the yearly plan. In case the period count is higher than the number of committed nodes, the difference is billed as overage. + +> ⚠️ At the moment, any changes to an active paid plan, upgrades, change billing frequency or committed nodes, will be a manual two-setup flow: +> +> 1. cancel your current subscription - move you to the Community plan +> 2. chose the plan with the intended changes +> +> This is a temporary process that we aim to sort out soon so that it will effortless for you to do any of these actions. + +## FAQ + +### 1. What Payment Methods are accepted? + +You can easily pay online via most major Credit/Debit Cards. More payment options are expected to become available in the near future. + +### 2. What happens if a renewal payment fails? + +After an initial failed payment, we will attempt to process your payment every week for the next 15 days. After three failed attempts your Space will be moved to the **Community** plan (free forever). + +For the next 24 hours, you will be able to use all your current notification method configurations. After 24 hours, any of the notification method configurations that aren't available on your space's plan will be automatically disabled. + +Cancellation might affect users in your Space. Please check what roles are available on the [Community plan](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/plans.md#areas-impacted-by-plans). Users with unavailable roles on the Community plan will immediately have restricted access to the Space. + +### 3. Which currencies do you support? + +We currently accept payments only in US Dollars (USD). We currently have plans to also accept payments in Euros (EUR), but do not currently have an estimate for when such support will be available. + +### 4. Can I get a refund? How? + +Payments for Netdata subscriptions are refundable **only** if you cancel your subscription within 14 days of purchase. The refund will be credited to the Credit/Debit Card used for making the purchase. To request a refund, please email us at [billing@netdata.cloud](mailto:billing@netdata.cloud). + +### 5. How do I cancel my paid Plan? + +Your annual or monthly Netdata Subscription plan will automatically renew until you cancel it. You can cancel your paid plan at any time by clicking ‘Cancel Plan’ from the **Plan & Billing** section under settings. You can also cancel your paid Plan by clicking the _Select_ button under **Community** plan in the **Plan & Billing** Section under Settings. + +### 6. How can I access my Invoices/Receipts after I paid for a Plan? + +You can visit the _Billing Options & Invoices_ in the **Plan & Billing** section under settings in your Netdata Space where you can find all your Invoicing history. + +### 7. Why do I see two separate Invoices? + +Every time you purchase or renew a Plan, two separate Invoices are generated: + +- One Invoice includes the recurring fees of the Plan you have chosen + +- The other Invoice includes your monthly “On Demand - Usage”. + + Right after the activation of your subscription, you will receive a zero value Invoice since you had no usage when you subscribed. + + On the following month you will receive an Invoice based on your monthly usage. + +You can find some further details on the [Netdata Plans page](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/plans.md#plans). + +> ⚠️ We expect this to change to a single invoice in the future, but currently do not have a concrete timeline for when this change will happen. diff --git a/docs/cloud/netdata-functions.md b/docs/cloud/netdata-functions.md index e1b9dd0b..9fcf732c 100644 --- a/docs/cloud/netdata-functions.md +++ b/docs/cloud/netdata-functions.md @@ -9,6 +9,8 @@ learn_rel_path: "Concepts" learn_docs_purpose: "Present the Netdata Functions what these are and why they should be used." --> +# Netdata Functions + 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. diff --git a/docs/cloud/runtime-troubleshooting-with-functions.md b/docs/cloud/runtime-troubleshooting-with-functions.md index 3800ea20..839b8c9e 100644 --- a/docs/cloud/runtime-troubleshooting-with-functions.md +++ b/docs/cloud/runtime-troubleshooting-with-functions.md @@ -1,13 +1,4 @@ - +# Run-time troubleshooting with 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. @@ -19,14 +10,14 @@ 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) +### Execute a function (from the Functions tab) 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) +### Execute a function (from the Nodes tab) 1. Click on the functions icon for a node that has this active 2. You are directed to the **Functions** tab diff --git a/docs/cloud/spaces.md b/docs/cloud/spaces.md index 31d8a47a..2a275c14 100644 --- a/docs/cloud/spaces.md +++ b/docs/cloud/spaces.md @@ -1,14 +1,6 @@ ---- -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" ---- +# Netdata Cloud Spaces + +Organize your multi-organization infrastructure monitoring on Netdata Cloud by creating Spaces to completely isolate access to your Agent-monitored nodes. 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. @@ -70,8 +62,9 @@ will open a side tab in which you can: 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 -::: +> ### Note +> +> \* This action requires admin rights for this space ## Obsoleting offline nodes from a Space @@ -84,8 +77,3 @@ Netdata admin users now have the ability to remove obsolete nodes from a 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 index 3c6d7ffd..a9376db1 100644 --- a/docs/cloud/visualize/dashboards.md +++ b/docs/cloud/visualize/dashboards.md @@ -1,14 +1,4 @@ ---- -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" ---- +# Build new dashboards 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. @@ -25,7 +15,7 @@ dashboards](https://user-images.githubusercontent.com/1153921/108529360-a2145d00 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 +node. If you select **All Nodes**, you will add a [composite chart](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md) 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 @@ -44,12 +34,11 @@ 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 +node. If you select **All Nodes**, you will add a [composite chart](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md) 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. +dashboard in Cloud. Zoom in and out, highlight timeframes, and more. Charts also synchronize as you interact with them, even across contexts _or_ nodes. @@ -81,7 +70,7 @@ 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 +can now navigate through Netdata Cloud freely, individual Cloud dashboards, the Nodes tab, 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 diff --git a/docs/cloud/visualize/interact-new-charts.md b/docs/cloud/visualize/interact-new-charts.md index 4b33fe85..4c6c2ebf 100644 --- a/docs/cloud/visualize/interact-new-charts.md +++ b/docs/cloud/visualize/interact-new-charts.md @@ -1,20 +1,6 @@ ---- -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. +# Interact with charts + +Learn how to use Netdata's powerful charts to troubleshoot with real-time, per-second metric data. 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. @@ -33,39 +19,40 @@ These charts provide a lot of useful information, so that you can: - 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 +These charts are available on Netdata Cloud's +[Overview tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md), Single Node tab and on your [Custom Dashboards](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md). +Some of the features listed below are also available on the simpler charts that are available on each agent's user interface. + ## 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: +from the Single Node tab: -![NRve6zr325.gif](https://images.zenhubusercontent.com/60b4ebb03f4163193ec31819/5ecaf5ec-1229-480e-b122-62f63e9df227) +image With a quick glance you have immediate information available at your disposal: - Chart title and units +- Definition bar - 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). +Your charts are controlled using the available +[Time controls](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.md#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 +- 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 +- 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) +- Double clicking to release a previously locked chart - move the time control back to Play | Interaction | Keyboard/mouse | Touchpad/touchscreen | Time control | |:------------------|:---------------|:---------------------|:----------------------| @@ -84,7 +71,7 @@ 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) + if [Time controls](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.md#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 @@ -92,9 +79,147 @@ The elements that you can find on this top bar are: - 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) +![image](https://user-images.githubusercontent.com/70198089/222689197-f9506ca7-a869-40a9-871f-8c4e1fa4b927.png) + + +## 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—`-2.1`, `-5.5`, `-10.2`, `-15`, `-0.1`—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. -### Chart action bar +### 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](https://github.com/netdata/netdata/blob/master/web/api/queries/README.md#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 + 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**. + +## Chart action bar On this bar you have access to immediate actions over the chart, the available actions are: @@ -104,7 +229,8 @@ On this bar you have access to immediate actions over the chart, the available a - 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. - + + ## Exploration action bar @@ -116,7 +242,7 @@ available actions that you can see are: - Horizontal and Vertical zooms - In-context zoom in and out - + ### Pan @@ -129,24 +255,13 @@ it like pushing the current timeframe off the screen to see what came before or ### 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. +Selecting timeframes is useful when you see an interesting spike or change in a chart and want to investigate further by: - +- Looking at the same period of time on other charts/sections +- Running [metric correlations](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md) + to filter metrics that also show something different in the selected period, vs the previous one -

+image | Interaction | Keyboard/mouse | Touchpad/touchscreen | |:-----------------------------------|:---------------------------------------------------------|:---------------------| @@ -160,10 +275,11 @@ week, which is useful in understanding what "normal" looks like, or to identify 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. +a specific Y-axis area to further investigate a spike or dive on your charts. + +![f8722ee8-e69b-426c-8bcb-6cb79897c177](https://user-images.githubusercontent.com/70198089/222689676-ad16a2a0-3c3d-48fa-87af-c40ae142dd79.gif) + -![Y5IESOjD3s.gif](https://images.zenhubusercontent.com/60b4ebb03f4163193ec31819/f8722ee8-e69b-426c-8bcb-6cb79897c177) | Interaction | Keyboard/mouse | Touchpad/touchscreen | |:-------------------------------------------|:-------------------------------------|:-----------------------------------------------------| @@ -182,7 +298,7 @@ The bottom legend of the chart where you can see the dimensions of the chart can - Dimension name (Ascending or Descending) - Dimension value (Ascending or Descending) - + ### Show and hide dimensions @@ -200,23 +316,4 @@ To resize the chart, click-and-drag the icon on the bottom-right corner of any c 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) +![1bcc6a0a-a58e-457b-8a0c-e5d361a3083c](https://user-images.githubusercontent.com/70198089/222689845-51a9c054-a57d-49dc-925d-39b924dae2f8.gif) diff --git a/docs/cloud/visualize/kubernetes.md b/docs/cloud/visualize/kubernetes.md index 0ff83970..46e46bc1 100644 --- a/docs/cloud/visualize/kubernetes.md +++ b/docs/cloud/visualize/kubernetes.md @@ -1,4 +1,4 @@ ---- + + +# Kubernetes visualizations Netdata Cloud features enhanced visualizations for the resource utilization of Kubernetes (k8s) clusters, embedded in -the default [Overview](/docs/cloud/visualize/overview/) dashboard. +the default [Overview](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md) 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 +See our [Kubernetes deployment instructions](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kubernetes.md) for details on installation and connecting to Netdata Cloud. ## Available Kubernetes metrics @@ -87,7 +79,7 @@ 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 +Filtering behaves identically to the [node filter in War Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md#node-filter), with the ability to filter pods/containers by `container_id` and `namespace`. ### Detailed information @@ -120,7 +112,7 @@ problematic behavior to investigate further, troubleshoot, and remediate with `k 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 +See the [Overview](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#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: @@ -148,7 +140,3 @@ There are some caveats and known issues with Kubernetes monitoring with Netdata [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/node-filter.md b/docs/cloud/visualize/node-filter.md new file mode 100644 index 00000000..889caaf8 --- /dev/null +++ b/docs/cloud/visualize/node-filter.md @@ -0,0 +1,21 @@ +# Node filter + +The node filter allows you to quickly filter the nodes visualized in a War Room's views. It appears on all views, except on single-node dashboards. + +Inside the filter, the nodes get categorized into three groups: + +- Live nodes + Nodes that are currently online, collecting and streaming metrics to Cloud. + - Live nodes display raised [Alert](https://github.com/netdata/netdata/blob/master/docs/monitor/view-active-alarms.md) counters, [Machine Learning](https://github.com/netdata/netdata/blob/master/ml/README.md) availability, and [Functions](https://github.com/netdata/netdata/blob/master/docs/cloud/netdata-functions.md) availability +- Stale nodes + Nodes that are offline and not streaming metrics to Cloud. Only historical data can be presented from a parent node. + - For these nodes you can only see their ML status, as they are not online to provide more information +- Offline nodes + Nodes that are offline, not streaming metrics to Cloud and not available in any parent node. + Offline nodes are automatically deleted after 30 days and can also be deleted manually. + +By using the search bar, you can narrow down to specific nodes based on their name. + +When you select one or more nodes, the total selected number will appear in the **Nodes** bar on the **Selected** field. + +![The node filter](https://user-images.githubusercontent.com/70198089/225249850-60ce4fcc-4398-4412-a6b5-6082308f4e60.png) diff --git a/docs/cloud/visualize/nodes.md b/docs/cloud/visualize/nodes.md index 9878b6b1..4160166f 100644 --- a/docs/cloud/visualize/nodes.md +++ b/docs/cloud/visualize/nodes.md @@ -1,20 +1,12 @@ ---- -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 +# Nodes tab + +The Nodes tab 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 +![The Nodes tab 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 +Each War Room's Nodes tab 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. @@ -39,15 +31,9 @@ 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) +need to configure those nodes to collect from additional data sources. See our [collectors configuration reference](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.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) +If you want to see up to 30 days of historical metrics in Cloud (and more on individual node dashboards), read about [changing how long Netdata stores metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md). Also, see our +[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) 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 index 35c07656..84638f05 100644 --- a/docs/cloud/visualize/overview.md +++ b/docs/cloud/visualize/overview.md @@ -1,250 +1,48 @@ ---- -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, overview and single node tabs + +Learn how to use the Home, Overview, and Single Node tabs in Netdata Cloud, to explore your infrastructure and troubleshoot issues. ## 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 +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 +## Overview and single node tab 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**. +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—`-2.1`, `-5.5`, `-10.2`, `-15`, `-0.1`—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. +When you [interact with composite charts](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md) +you can see your infrastructure from a single pane of glass, discover trends or anomalies, and perform root cause analysis. -### Instances +The Single Node tab dashboard is exactly the same as the Overview, but with a hard-coded filter to only show a single node. -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. +### Chart navigation Menu -### 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 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 +Netdata Cloud uses a similar menu to local Agent dashboards, 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) +![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 +One difference between the Netdata Cloud menu and those found in 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 +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) +The Cloud also condenses 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. +The aggregation applies to disks, network devices, and other metric types that involve multiple instances of a piece of hardware or software. ## 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). +Of course you can [change the filtering or grouping](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md) of metrics in the composite charts that aggregate all these instances, to see only the information you are interested in, and save that tab in a custom dashboard. -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). +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. diff --git a/docs/cloud/war-rooms.md b/docs/cloud/war-rooms.md index 99f9e368..c599fd5b 100644 --- a/docs/cloud/war-rooms.md +++ b/docs/cloud/war-rooms.md @@ -1,162 +1,60 @@ ---- -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 +# Netdata Cloud War rooms -### Non static tabs +Netdata Cloud uses War Rooms to organize your connected nodes and provide infrastructure-wide dashboards using real-time metrics and visualizations. -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. +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. -![The node filter](https://user-images.githubusercontent.com/12612986/172674440-df224058-2b2c-41da-bb45-f4eb82e342e5.png) +![An example War Room](https://user-images.githubusercontent.com/43294513/225355998-f16730ba-06d4-4953-8fd3-f1c2751e102d.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. +- **Service, purpose, location, etc.** + You can group War Rooms by a service (Nginx, MySQL, Pulsar, and so on), their purpose (webserver, database, application), their physical location, whether they're "bare metal" 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. +- **End-to-end apps/services** + If you have a user-facing SaaS product, or an internal service that this 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. +- **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. +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. +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. +All the users and nodes involved in a particular Space can 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: +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. +1. To **change a War Room's name, description, or public/private status**, click on **War Room** tab. -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. +2. To **include an existing node** to a War Room or **connect a new node\*** click on **Nodes** tab. 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. +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 -::: +> ### 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 +To **view or remove nodes** in a War Room, click on the **Nodes tab**. 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). +> ### Info +> +> Removing a node from a War Room does not remove it from your Space. diff --git a/docs/collect/application-metrics.md b/docs/collect/application-metrics.md index 454ed95a..ec73cefe 100644 --- a/docs/collect/application-metrics.md +++ b/docs/collect/application-metrics.md @@ -51,11 +51,10 @@ 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](https://github.com/netdata/netdata/blob/master/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/packaging/installer/README.md) 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](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 +Once you have Netdata running on that separate system, you can follow the [collectors configuration reference](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md) documentation 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 diff --git a/docs/collect/container-metrics.md b/docs/collect/container-metrics.md index b6b6a432..cde54183 100644 --- a/docs/collect/container-metrics.md +++ b/docs/collect/container-metrics.md @@ -55,8 +55,7 @@ metrics](https://github.com/netdata/go.d.plugin/blob/master/modules/mysql/README ### 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](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) procedures apply whether an application runs on the host system or inside +You could use this technique to monitor an entire infrastructure of Docker containers. The same [enable and configure](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.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](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md) itself, and then collect metrics about the diff --git a/docs/collect/enable-configure.md b/docs/collect/enable-configure.md deleted file mode 100644 index cd8960ac..00000000 --- a/docs/collect/enable-configure.md +++ /dev/null @@ -1,72 +0,0 @@ - - -# 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://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. - -However, not every node or infrastructure uses standard ports, paths, files, or naming conventions. You may need to -enable or configure a collector to gather all available metrics from your systems, containers, or applications. - -## Enable a collector or its orchestrator - -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](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) to open -the orchestrator primary configuration file: - -```bash -cd /etc/netdata -sudo ./edit-config go.d.conf -``` - -Within this file, you can either disable the orchestrator entirely (`enabled: yes`), or find a specific collector and -enable/disable it with `yes` and `no` settings. Uncomment any line you change to ensure the Netdata daemon reads it on -start. - -After you make your changes, restart the 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. - -## Configure a collector - -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](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 -./edit-config go.d/nginx.conf -``` - -Each configuration file describes every available option and offers examples to help you tweak Netdata's settings -according to your needs. In addition, every collector's documentation shows the exact command you need to run to -configure that collector. Uncomment any line you change to ensure the collector's orchestrator or the Netdata daemon -read it on start. - -After you make your changes, restart the 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. - -## What's next? - -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](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md) -using Netdata Cloud, or learn how to [interact with dashboards and -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 deleted file mode 100644 index 382d4ccc..00000000 --- a/docs/collect/how-collectors-work.md +++ /dev/null @@ -1,82 +0,0 @@ - - -# How Netdata's metrics collectors work - -When Netdata starts, and with zero configuration, it auto-detects thousands of data sources and immediately collects -per-second metrics. - -Netdata can immediately collect metrics from these endpoints thanks to 300+ **collectors**, which all come pre-installed -when you [install Netdata](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx). - -Every collector has two primary jobs: - -- Look for exposed metrics at a pre- or user-defined endpoint. -- Gather exposed metrics and use additional logic to build meaningful, interactive visualizations. - -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](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://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://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. - -The endpoint is user-configurable, as are many other specifics of what a given collector does. - -## What can Netdata collect? - -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](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](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](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 - -**Collector** is a catch-all term for any Netdata process that gathers metrics from an endpoint. - -While we use _collector_ most often in documentation, release notes, and educational content, you may encounter other -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://github.com/netdata/go.d.plugin/blob/master/README.md): An orchestrator for data - collection modules written in `go`. - - [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+. -- **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. -- **Internal plugins** gather metrics from `/proc`, `/sys`, and other Linux kernel sources. They are written in `C`, - and run as threads within the Netdata daemon. - -## What's next? - -[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](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 442b1382..daaf61d7 100644 --- a/docs/collect/system-metrics.md +++ b/docs/collect/system-metrics.md @@ -37,15 +37,14 @@ can find all system collectors in our [supported collectors list](https://github ## Collect Windows system metrics -Netdata is also capable of monitoring Windows systems. The [WMI -collector](https://github.com/netdata/go.d.plugin/blob/master/modules/wmi/README.md) integrates with +Netdata is also capable of monitoring Windows systems. The [Windows +collector](https://github.com/netdata/go.d.plugin/blob/master/modules/windows/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://github.com/netdata/go.d.plugin/blob/master/modules/wmi/README.md#requirements). +on Windows systems. The Windows collector then gathers metrics from an endpoint created by windows_exporter, for more +details see [the requirements](https://github.com/netdata/go.d.plugin/blob/master/modules/windows/README.md#requirements). -Next, [configure the WMI -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 +Next, [configure](https://github.com/netdata/go.d.plugin/blob/master/modules/windows/README.md#configuration) the Windows +collector to point to the URL and port of your exposed endpoint. 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. You'll start seeing Windows system metrics, such as CPU utilization, memory, bandwidth per NIC, number of processes, and much more. diff --git a/docs/configure/common-changes.md b/docs/configure/common-changes.md index e1dccfce..f171e49e 100644 --- a/docs/configure/common-changes.md +++ b/docs/configure/common-changes.md @@ -5,7 +5,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/configure/ sidebar_label: "Common configuration changes" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Setup" +learn_rel_path: "Configuration" --> # Common configuration changes @@ -29,16 +29,6 @@ changes reflected in those visualizations due to the way Netdata Cloud proxies m ### 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](https://github.com/netdata/netdata/blob/master/daemon/config/README.md#global-section-options) -of `netdata.conf`. - -```conf -[global] - page cache size = 128 # 128 MiB of memory for metrics storage - dbengine multihost disk space = 4096 # 4GiB of disk space for metrics storage -``` - 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 @@ -61,7 +51,7 @@ 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](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) +the [enable or configure a collector](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md#enable-and-disable-a-specific-collection-module) doc for details. ### Disable a collector or plugin @@ -93,7 +83,7 @@ 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](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) to +Read more about [configuring alarms](https://github.com/netdata/netdata/blob/master/health/REFERENCE.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. @@ -142,56 +132,7 @@ 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](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](https://github.com/netdata/netdata/blob/master/web/server/README.md#configuration) -of `netdata.conf`. - -```conf -[web] - mode = none -``` - -### 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](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](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 - -Create a file called `.opt-out-from-anonymous-statistics` inside of your Netdata config directory to immediately stop -the statistics script. - -```bash -sudo touch .opt-out-from-anonymous-statistics -``` - -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 - -Change the `default port` setting in the `[web]` section to a port other than `19999`. - -```conf -[web] - default port = 39999 -``` - -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. +Learn more about the available options in the [security design documentation](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md). ## Reduce resource usage @@ -217,36 +158,3 @@ The following restrictions apply to host label names: 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 templates. - -## What's next? - -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 - -You can also take what you've learned about node configuration to tweak the Agent's behavior or enable new features: - -- [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](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) -using the database engine. - -### Related reference documentation - -- [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 8f54b1bf..0f31715a 100644 --- a/docs/configure/nodes.md +++ b/docs/configure/nodes.md @@ -1,13 +1,3 @@ - - # Configure the Netdata Agent Netdata's zero-configuration collection, storage, and visualization features work for many users, infrastructures, and @@ -23,7 +13,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](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx#install-on-linux-with-one-line-installer), the **Netdata config +installation](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#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`. @@ -44,14 +34,14 @@ exist. 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](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md#enable-a-collector-or-its-orchestrator) behave. Examples: `go.d.conf`, + collectors](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md) 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](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md#configure-a-collector). + specific collectors](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md). - `apps_groups.conf` is a configuration file for changing how applications/processes are grouped when viewing the **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.d/` is a directory that contains [health configuration files](https://github.com/netdata/netdata/blob/master/health/REFERENCE.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. @@ -73,31 +63,32 @@ See [configure agent containers](https://github.com/netdata/netdata/blob/master/ The **recommended way to easily and safely edit Netdata's configuration** is with the `edit-config` script. This script opens existing Netdata configuration files using your system's `$EDITOR`. If the file doesn't yet exist in your config -directory, the script copies the stock version from `/usr/lib/netdata/conf.d` and opens it for editing. +directory, the script copies the stock version from `/usr/lib/netdata/conf.d` (or wherever the symlink `orig` under the config directory leads to) +to the proper place in the config directory and opens the copy for editing. + +If you have trouble running the script, you can manually copy the file and edit the copy. -Run `edit-config` without any options to see details on its usage and a list of all the configuration files you can -edit. +e.g. `cp /usr/lib/netdata/conf.d/go.d/bind.conf /etc/netdata/go.d/bind.conf; vi /etc/netdata/go.d/bind.conf` + +Run `edit-config` without options, to see details on its usage, or `edit-config --list` to see a list of all the configuration +files you can edit. ```bash -./edit-config USAGE: - ./edit-config FILENAME + ./edit-config [options] 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: '/usr/lib/netdata/conf.d' + Stock config files at: '/etc/netdata/../../usr/lib/netdata/conf.d' User config files at: '/etc/netdata' - Available files in '/usr/lib/netdata/conf.d' to copy and edit: + The editor to use can be specified either by setting the EDITOR + environment variable, or by using the --editor option. -./apps_groups.conf ./health.d/phpfpm.conf -./aws_kinesis.conf ./health.d/pihole.conf -./charts.d/ap.conf ./health.d/portcheck.conf -./charts.d/apcupsd.conf ./health.d/postgres.conf -... + The file to edit can also be specified using the --file option. + + For a list of known config files, run './edit-config --list' ``` To edit `netdata.conf`, run `./edit-config netdata.conf`. You may need to elevate your privileges with `sudo` or another @@ -146,29 +137,3 @@ wget -O /etc/netdata/netdata.conf http://localhost:19999/netdata.conf # or curl -o /etc/netdata/netdata.conf http://NODE:19999/netdata.conf ``` - -## What's next? - -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](https://github.com/netdata/netdata/blob/master/docs/configure/common-changes.md) to quickly tweak the Agent's behavior. - -[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](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](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) using the database engine. - -### Related reference documentation - -- [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 deleted file mode 100644 index 75bf6fd3..00000000 --- a/docs/configure/secure-nodes.md +++ /dev/null @@ -1,127 +0,0 @@ - - -# Secure your nodes - -Upon installation, the Netdata Agent serves the **local dashboard** at port `19999`. If the node is accessible to the -internet at large, anyone can access the dashboard and your node's metrics at `http://NODE:19999`. We made this decision -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](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](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. - -Instead of dictating how to secure your infrastructure, we give you many options to establish security best practices -that align with your goals and your organization's standards. - -- [Disable the local dashboard](#disable-the-local-dashboard): **Simplest and recommended method** for those who have - added nodes to Netdata Cloud and view dashboards and metrics there. -- [Restrict access to the local dashboard](#restrict-access-to-the-local-dashboard): Allow local dashboard access from - only certain IP addresses, such as a trusted static IP or connections from behind a management LAN. Full support for - Netdata Cloud. -- [Use a reverse proxy](#use-a-reverse-proxy): Password-protect a local dashboard and enable TLS to secure it. Full - support for Netdata Cloud. - -## Disable the local dashboard - -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](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. - -Open `netdata.conf` with `./edit-config netdata.conf`. Scroll down to the `[web]` section, and find the `mode = -static-threaded` setting, and change it to `none`. - -```conf -[web] - mode = none -``` - -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](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](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 -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](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md). - -```conf -[web] - # Allow only localhost connections - allow connections from = localhost - - # Allow only from management LAN running on `10.X.X.X` - allow connections from = 10.* - - # Allow connections only from a specific FQDN/hostname - allow connections from = example* -``` - -The `allow connections from` setting is global and restricts access to the dashboard, badges, streaming, API, and -`netdata.conf`, but you can also set each of those access lists more granularly if you choose: - -```conf -[web] - allow connections from = localhost * - allow dashboard from = localhost * - allow badges from = * - allow streaming from = * - allow netdata.conf from = localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.* 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.* 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.* 172.31.* - allow management from = localhost -``` - -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 - -You can also put Netdata behind a reverse proxy for additional security while retaining the functionality of both the -local dashboard and Netdata Cloud dashboards. You can use a reverse proxy to password-protect the local dashboard and -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](https://github.com/netdata/netdata/blob/master/docs/Running-behind-nginx.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](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](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 3c04777d..45691bc9 100644 --- a/docs/configure/start-stop-restart.md +++ b/docs/configure/start-stop-restart.md @@ -1,20 +1,10 @@ - - # Start, stop, or restart the Netdata Agent -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. +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 -configuration](#reload-health-configuration) files are the only exception, as they can be reloaded without restarting +You will most often need to _restart_ the Agent to load new or editing configuration files. +[Health configuration](#reload-health-configuration) files are the only exception, as they can be reloaded without restarting the entire Agent. Stopping or restarting the Netdata Agent will cause gaps in stored metrics until the `netdata` process initiates @@ -51,6 +41,14 @@ using your preferred method listed above. sudo netdatacli shutdown-agent ``` +## Netdata MSI installations + +Netdata provides an installer for Windows using WSL, on those installations by using a Windows terminal (e.g. the Command prompt or Windows Powershell) you can: + +- Start Netdata, by running `start-netdata` +- Stop Netdata, by running `stop-netdata` +- Restart Netdata, by running `restart-netdata` + ## Reload health configuration You do not need to restart the Netdata Agent between changes to health configuration files, such as specific health @@ -82,21 +80,75 @@ ps aux| grep netdata The output of `ps aux` should show no `netdata` or associated processes running. You can now start the Netdata Agent again with `service netdata start`, or the appropriate method for your system. -## What's next? +## Starting Netdata at boot + +In the `system` directory you can find scripts and configurations for the +various distros. + +### systemd + +The installer already installs `netdata.service` if it detects a systemd system. + +To install `netdata.service` by hand, run: + +```sh +# stop Netdata +killall netdata + +# copy netdata.service to systemd +cp system/netdata.service /etc/systemd/system/ + +# let systemd know there is a new service +systemctl daemon-reload + +# enable Netdata at boot +systemctl enable netdata + +# start Netdata +systemctl start netdata +``` + +### init.d + +In the system directory you can find `netdata-lsb`. Copy it to the proper place according to your distribution +documentation. For Ubuntu, this can be done via running the following commands as root. + +```sh +# copy the Netdata startup file to /etc/init.d +cp system/netdata-lsb /etc/init.d/netdata + +# make sure it is executable +chmod +x /etc/init.d/netdata + +# enable it +update-rc.d netdata defaults +``` -Learn more about [securing the Netdata Agent](https://github.com/netdata/netdata/blob/master/docs/configure/secure-nodes.md). +### openrc (gentoo) -You can also use the restart/reload methods described above to enable new features: +In the `system` directory you can find `netdata-openrc`. Copy it to the proper +place according to your distribution documentation. + +### CentOS / Red Hat Enterprise Linux + +For older versions of RHEL/CentOS that don't have systemd, an init script is included in the system directory. This can +be installed by running the following commands as root. + +```sh +# copy the Netdata startup file to /etc/init.d +cp system/netdata-init-d /etc/init.d/netdata + +# make sure it is executable +chmod +x /etc/init.d/netdata + +# enable it +chkconfig --add netdata +``` -- [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](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) using the database engine. +_There have been some recent work on the init script, see PR +_ -### Related reference documentation +### other systems -- [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) +You can start Netdata by running it from `/etc/rc.local` or equivalent. -[![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 deleted file mode 100644 index da28272b..00000000 --- a/docs/contributing/contributing-documentation.md +++ /dev/null @@ -1,109 +0,0 @@ - - -# Contributing to documentation - -We welcome contributions to Netdata's already extensive documentation. - -We store documentation related to the open-source Netdata Agent inside of the [`netdata/netdata` -repository](https://github.com/netdata/netdata) on GitHub. Documentation related to Netdata Cloud is stored in a private -repository and is not currently open to community contributions. - -The Netdata team aggregates and publishes all documentation at [learn.netdata.cloud](https://learn.netdata.cloud/) using -[Docusaurus](https://v2.docusaurus.io/) in a private GitHub repository. - -## Before you get started - -Anyone interested in contributing to documentation should first read the [Netdata style -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 -paragraphs, styled text, lists, tables, and more. - -### Netdata's documentation structure - -Netdata's documentation is separated into four sections. - -- **Netdata**: Documents based on the actions users want to take, and solutions to their problems, such both the Netdata - Agent and Netdata Cloud. - - Stored in various subfolders of the [`/docs` folder](https://github.com/netdata/netdata/tree/master/docs) within the - `netdata/netdata` repository: `/docs/collect`, `/docs/configure`, `/docs/export`, `/docs/get`, `/docs/monitor`, - `/docs/overview`, `/docs/quickstart`, `/docs/store`, and `/docs/visualize`. - - Published at [`https://learn.netdata.cloud/docs`](https://learn.netdata.cloud/docs). -- **Netdata Agent reference**: Reference documentation for the open-source Netdata Agent. - - Stored in various `.md` files within the `netdata/netdata` repository alongside the code responsible for that - feature. For example, the database engine's reference documentation is at `/database/engine/README.md`. - - 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://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 - `netdata/netdata` repository. Organized into subfolders that roughly correlate with the core Netdata documentation. - - Published at [`https://learn.netdata.cloud/guides`](https://learn.netdata.cloud/guides). - -Generally speaking, if you want to contribute to the reference documentation for a specific Netdata Agent feature, find -the appropriate `.md` file co-located with that feature. If you want to contribute documentation that spans features or -products, or has no direct correlation with the existing directory structure, place it in the `/docs` folder within -`netdata/netdata`. - -## 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 [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** -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. - -![Screenshot of forking the Netdata -repository](https://user-images.githubusercontent.com/1153921/59873572-25f5a380-9351-11e9-92a4-a681fe4a2ed9.png) - -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](https://learn.netdata.cloud/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. - - diff --git a/docs/contributing/style-guide.md b/docs/contributing/style-guide.md index 7d1b8647..997bc61a 100644 --- a/docs/contributing/style-guide.md +++ b/docs/contributing/style-guide.md @@ -1,19 +1,14 @@ - - # Netdata style 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. +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. + +> ### Note +> This document is meant to be accompanied by the [Documentation Guidelines](https://github.com/netdata/netdata/blob/master/docs/guidelines.md). If you want to contribute to Netdata's documentation, please read it too. + +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: +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, @@ -353,56 +348,6 @@ The Netdata team uses [`remark-lint`](https://github.com/remarkjs/remark-lint) f 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. -### 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 - - -# Deploy Netdata with Ansible - -... -``` - -Questions about frontmatter in -documentation? [Ask on our community forum](https://community.netdata.cloud/c/blog-posts-and-articles/6). - -### Linking between documentation - -Documentation should link to relevant pages whenever it's relevant and provides valuable context to the reader. - -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 `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 When referencing a user interface (UI) element in Netdata, reference the label text of the link/button with Markdown's @@ -464,6 +409,58 @@ 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. +### Adding Notes + +Notes inside files should render properly both in GitHub and in Learn, to do that, it is best to use the format listed below: + +``` +> ### Note +> This is an info or a note block. + +> ### Tip, Best Practice +> This is a tip or a best practice block. + +> ### Warning, Caution +> This is a warning or a caution block. +``` + +Which renders into: + + +> ### Note +> This is an info or a note block. + +> ### Tip, Best Practice +> This is a tip or a best practice block. + +> ### Warning, Caution +> This is a warning or a caution block. + +### Tabs + +Docusaurus allows for Tabs to be used, but we have to ensure that a user accessing the file from GitHub doesn't notice any weird artifacts while reading. So, we use tabs only when necessary in this format: + +``` + + + + +

Header for tab1

+ +text for tab1, both visible in GitHub and Docusaurus + + + + + +

Header for tab2

+ +text for tab2, both visible in GitHub and Docusaurus + +
+ +``` + ## Word list The following tables describe the standard spelling, capitalization, and usage of words found in Netdata's writing. @@ -476,7 +473,6 @@ The following tables describe the standard spelling, capitalization, and usage o | **Netdata** | The company behind the open-source Netdata Agent and the Netdata Cloud web application. Never use _netdata_ or _NetData_.

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.

Never use _Cloud_ without the preceding _Netdata_ to avoid ambiguity. | -| **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.

_Space_ is always capitalized. | @@ -489,7 +485,5 @@ The following tables describe the standard spelling, capitalization, and usage o | 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). | +| **preconfigured** | The concept that many of Netdata's features come with sane defaults that users don't need to configure to find immediate value. | | **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.md b/docs/dashboard/customize.md new file mode 100644 index 00000000..d9538e62 --- /dev/null +++ b/docs/dashboard/customize.md @@ -0,0 +1,72 @@ +# Customize the standard dashboard + +While the [Netdata dashboard](https://github.com/netdata/netdata/blob/master/web/gui/README.md) 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 + +To change dashboard settings, click the on the **settings** icon +![Import icon](https://raw.githubusercontent.com/netdata/netdata-ui/98e31799c1ec0983f433537ff16d2ac2b0d994aa/src/components/icon/assets/gear.svg) +in the top panel. + +These settings only affect how the dashboard behaves in your browser. They take effect immediately and are permanently +saved to browser local storage (except the refresh on focus / always option). Some settings are applied immediately, and +others are only reflected after the dashboard is refreshed, which happens automatically. + +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](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.md#dimension) _below_ charts. +Click this toggle to move the legend to the _right_ of charts. + + +### Change theme + +Find this setting under the **Visual** tab. Choose between Dark (the default) and White. + +## Customize the standard dashboard info + +Netdata stores information about individual charts in the `dashboard_info.js` file. This file includes section and +subsection headings, descriptions, colors, titles, tooltips, and other information for Netdata to render on the +dashboard. + +One common use case for customizing the standard dashboard is adding internal "documentation" a section or specific +chart that can then be read by anyone with access to that dashboard. + +For example, here is how `dashboard_info.js` defines the **System Overview** section. + +```javascript +netdataDashboard.menu = { + 'system': { + title: 'System Overview', + icon: '', + info: 'Overview of the key system metrics.' + }, +``` + +If you want to customize this information, use the example `dashboard_info_custom_example.js` as a starting point. +First, navigate to the web server's directory. If you're on a Linux system, this should be at `/usr/share/netdata/web/`. +Copy the example file, then ensure that its permissions match the rest of the web server, which is `netdata:netdata` by +default. + +```bash +cd /usr/share/netdata/web/ +sudo cp dashboard_info_custom_example.js your_dashboard_info_file.js +sudo chown netdata:netdata your_dashboard_info_file.js +``` + +Edit the file with customizations to the `title`, `icon`, and `info` fields. Replace the string after `fas fa-` with any +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](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 +[web] + custom dashboard_info.js = your_dashboard_info_file.js +``` + +Reload your browser tab to see your custom configuration. diff --git a/docs/dashboard/customize.mdx b/docs/dashboard/customize.mdx deleted file mode 100644 index 3c30ee23..00000000 --- a/docs/dashboard/customize.mdx +++ /dev/null @@ -1,99 +0,0 @@ ---- -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/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](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 - -To change dashboard settings, click the on the **settings** icon ![Import -icon](https://raw.githubusercontent.com/netdata/netdata-ui/98e31799c1ec0983f433537ff16d2ac2b0d994aa/src/components/icon/assets/gear.svg) -in the top panel. - -These settings only affect how the dashboard behaves in your browser. They take effect immediately and are permanently -saved to browser local storage (except the refresh on focus / always option). Some settings are applied immediately, and -others are only reflected after the dashboard is refreshed, which happens automatically. - -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](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. - - -### Change theme - -Find this setting under the **Visual** tab. Choose between Dark (the default) and White. - -## Customize the standard dashboard info - -Netdata stores information about individual charts in the `dashboard_info.js` file. This file includes section and -subsection headings, descriptions, colors, titles, tooltips, and other information for Netdata to render on the -dashboard. - -One common use case for customizing the standard dashboard is adding internal "documentation" a section or specific -chart that can then be read by anyone with access to that dashboard. - -For example, here is how `dashboard_info.js` defines the **System Overview** section. - -```javascript -netdataDashboard.menu = { - 'system': { - title: 'System Overview', - icon: '', - info: 'Overview of the key system metrics.' - }, -``` - -If you want to customize this information, use the example `dashboard_info_custom_example.js` as a starting point. -First, navigate to the web server's directory. If you're on a Linux system, this should be at `/usr/share/netdata/web/`. -Copy the example file, then ensure that its permissions match the rest of the web server, which is `netdata:netdata` by -default. - -```bash -cd /usr/share/netdata/web/ -sudo cp dashboard_info_custom_example.js your_dashboard_info_file.js -sudo chown netdata:netdata your_dashboard_info_file.js -``` - -Edit the file with customizations to the `title`, `icon`, and `info` fields. Replace the string after `fas fa-` with any -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](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 -[web] - custom dashboard_info.js = your_dashboard_info_file.js -``` - -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](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](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.md b/docs/dashboard/dimensions-contexts-families.md new file mode 100644 index 00000000..41e839c8 --- /dev/null +++ b/docs/dashboard/dimensions-contexts-families.md @@ -0,0 +1,69 @@ +# Chart dimensions, contexts, and families + +While Netdata's charts require no configuration and are [easy to interact with](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md), +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](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md), or play around +with the [API](https://github.com/netdata/netdata/blob/master/web/api/README.md). + +## Dimension + +A **dimension** is a value that gets shown on a chart. The value can be raw data or calculated values, such as the +average (the default), minimum, or maximum. These values can then be given any type of unit. For example, CPU +utilization is represented as a percentage, disk I/O as `MiB/s`, and available RAM as an absolute value in `MiB` or +`GiB`. + +Beneath every chart (or on the right-side if you configure the dashboard) is a legend of dimensions. When there are +multiple dimensions, you'll see a different entry in the legend for each dimension. + +The **Apps CPU Time** chart (with the [context](#context) `apps.cpu`), which visualizes CPU utilization of +different types of processes/services/applications on your node, always provides a vibrant example of a chart with +multiple dimensions. + +![An example apps.cpu chart with many +dimensions](https://user-images.githubusercontent.com/1153921/114207816-a5cb7400-9911-11eb-8800-06f60b745f9c.png) + +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](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md#show-and-hide-dimensions) to help you focus your +attention. + +## Context + +A **context** is a way of grouping charts by the types of metrics collected and dimensions displayed. It's kind of like +a machine-readable naming and organization scheme. + +For example, the **Apps CPU Time** has the context `apps.cpu`. A little further down on the dashboard is a similar +chart, **Apps Real Memory (w/o shared)** with the context `apps.mem`. The `apps` portion of the context is the **type**, +whereas anything after the `.` is specified either by the chart's developer or by the [**family**](#family). + +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](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md) (the [`on` +line](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#alarm-line-on)). + +## Family + +**Families** are a _single instance_ of a hardware or software resource that needs to be displayed separately from +similar instances. + +For example, let's look at the **Disks** section, which contains a number of charts with contexts like `disk.io`, +`disk.ops`, `disk.backlog`, and `disk.util`. If your node has multiple disk drives at `sda` and `sdb`, Netdata creates +a separate family for each. + +Netdata now merges the contexts and families to create charts that are grouped by family, following a +`[context].[family]` naming scheme, so that you can see the `disk.io` and `disk.ops` charts for `sda` right next to each +other. + +Given the four example contexts, and two families of `sda` and `sdb`, Netdata will create the following charts and their +names: + +| Context | `sda` family | `sdb` family | +| :------------- | ------------------ | ------------------ | +| `disk.io` | `disk_io.sda` | `disk_io.sdb` | +| `disk.ops` | `disk_ops.sda` | `disk_ops.sdb` | +| `disk.backlog` | `disk_backlog.sda` | `disk_backlog.sdb` | +| `disk.util` | `disk_util.sda` | `disk_util.sdb` | diff --git a/docs/dashboard/dimensions-contexts-families.mdx b/docs/dashboard/dimensions-contexts-families.mdx deleted file mode 100644 index ee9636d1..00000000 --- a/docs/dashboard/dimensions-contexts-families.mdx +++ /dev/null @@ -1,102 +0,0 @@ ---- -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/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](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](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](https://github.com/netdata/netdata/blob/master/docs/dashboard/how-dashboard-works.mdx). - -## Dimension - -A **dimension** is a value that gets shown on a chart. The value can be raw data or calculated values, such as the -average (the default), minimum, or maximum. These values can then be given any type of unit. For example, CPU -utilization is represented as a percentage, disk I/O as `MiB/s`, and available RAM as an absolute value in `MiB` or -`GiB`. - -Beneath every chart (or on the right-side if you configure the dashboard) is a legend of dimensions. When there are -multiple dimensions, you'll see a different entry in the legend for each dimension. - -The **Apps CPU Time** chart (with the [context](#context) `apps.cpu`), which visualizes CPU utilization of -different types of processes/services/applications on your node, always provides a vibrant example of a chart with -multiple dimensions. - -![An example apps.cpu chart with many -dimensions](https://user-images.githubusercontent.com/1153921/114207816-a5cb7400-9911-11eb-8800-06f60b745f9c.png) - -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](https://github.com/netdata/netdata/blob/master/docs/dashboard/interact-charts.mdx#show-and-hide-dimensions) to help you focus your -attention. - -## Context - -A **context** is a way of grouping charts by the types of metrics collected and dimensions displayed. It's kind of like -a machine-readable naming and organization scheme. - -For example, the **Apps CPU Time** has the context `apps.cpu`. A little further down on the dashboard is a similar -chart, **Apps Real Memory (w/o shared)** with the context `apps.mem`. The `apps` portion of the context is the **type**, -whereas anything after the `.` is specified either by the chart's developer or by the [**family**](#family). - -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](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 - -**Families** are a _single instance_ of a hardware or software resource that needs to be displayed separately from -similar instances. - -For example, let's look at the **Disks** section, which contains a number of charts with contexts like `disk.io`, -`disk.ops`, `disk.backlog`, and `disk.util`. If your node has multiple disk drives at `sda` and `sdb`, Netdata creates -a separate family for each. - -Netdata now merges the contexts and families to create charts that are grouped by family, following a -`[context].[family]` naming scheme, so that you can see the `disk.io` and `disk.ops` charts for `sda` right next to each -other. - -Given the four example contexts, and two families of `sda` and `sdb`, Netdata will create the following charts and their -names: - -| Context | `sda` family | `sdb` family | -| :------------- | ------------------ | ------------------ | -| `disk.io` | `disk_io.sda` | `disk_io.sdb` | -| `disk.ops` | `disk_ops.sda` | `disk_ops.sdb` | -| `disk.backlog` | `disk_backlog.sda` | `disk_backlog.sdb` | -| `disk.util` | `disk_util.sda` | `disk_util.sdb` | - -## 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](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.mdx). - -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](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 deleted file mode 100644 index f1440270..00000000 --- a/docs/dashboard/how-dashboard-works.mdx +++ /dev/null @@ -1,118 +0,0 @@ ---- -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/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 - -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](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 -visualized in different charts. - -It's essential to understand the core concepts and features of Netdata's dashboard if you want to maximize your Netdata -experience right after installation. - -## Open the dashboard - -Access Netdata's dashboard by navigating to `http://NODE:19999` in your browser, replacing `NODE` with either -`localhost` or the hostname/IP address of a remote node. - -![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](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 - -As mentioned in the introduction, Netdata automatically organizes all the metrics it collects from your node, and places -them into **sections** of closely related charts. - -The first section on any dashboard is the **System Overview**, followed by **CPUs**, **Memory**, and so on. - -These sections populate the **menu**, which is on the right-hand side of the dashboard. Instead of manually scrolling up -and down to explore the dashboard, it's generally faster to click on the relevant menu item to jump to that position on -the dashboard. - -Many menu items also contain a **submenu**, with links to additional categories. For example, the **Disks** section is often separated into multiple groups based on the number of disk drives/partitions on your node, which are also known as a family. - -![Animated GIF of using Netdata's menus and -submenus](https://user-images.githubusercontent.com/1153921/80832425-7c528600-8ba1-11ea-8140-d0a17a62009b.gif) - -## Charts - -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](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) - -Hover over any chart to temporarily pause it and see the exact metrics values presented as different dimensions. Click -or tap to stop the chart from automatically updating with new metrics, thereby locking it to a single timeframe. -Double-click it to resume auto-updating. - -Let's cover two of the most important ways to interact with charts: panning through time and zooming. - -To pan through time, **click and hold** (or touch and hold) on any chart, then **drag your mouse** (or finger) to the -left or right. Drag 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. - -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](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 - -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](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) -in the top panel to open the alarms panel, which shows you all the active alarms. The other **All** tab shows every -active alarm, and the **Log** tab shows a historical record of exactly when alarms triggered and to which state. - -![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](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](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](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](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.md b/docs/dashboard/import-export-print-snapshot.md new file mode 100644 index 00000000..35c3b9db --- /dev/null +++ b/docs/dashboard/import-export-print-snapshot.md @@ -0,0 +1,74 @@ + + +# Import, export, and print a snapshot + +Netdata can export snapshots of the contents of your dashboard at a given time, which you can then import into any other +node running Netdata. Or, you can create a print-ready version of your dashboard to save to PDF or actually print to +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](https://github.com/netdata/netdata/blob/master/docs/dashboard/visualization-date-and-time-controls.md) when the alarm triggered, export a snapshot, and send it to a + +colleague for further analysis. + +Or, send the Netdata team a snapshot of your dashboard when [filing a bug +report](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml) on +GitHub. + +![The export, import, and print +buttons](https://user-images.githubusercontent.com/1153921/114218399-360fb600-991e-11eb-8dea-fabd2bffc5b3.gif) + +## Import a snapshot + +To import a snapshot, click on the **import** icon ![Import +icon](https://raw.githubusercontent.com/netdata/netdata-ui/98e31799c1ec0983f433537ff16d2ac2b0d994aa/src/components/icon/assets/upload.svg) +in the top panel. + +Select the Netdata snapshot file to import. Once the file is loaded, the modal updates with information about the +snapshot and the system from which it was taken. Click **Import** to begin to process. + +Netdata takes the data embedded inside the snapshot and re-creates a static replica on your dashboard. When the import +finishes, you're free to move around and examine the charts. + +Some caveats and tips to keep in mind: + +- Only metrics in the export timeframe are available to you. If you zoom out or pan through time, you'll see the + beginning and end of the snapshot. +- Charts won't update with new information, as you're looking at a static replica, not the live dashboard. +- The import is only temporary. Reload your browser tab to return to your node's real-time dashboard. + +## Export a snapshot + +To export a snapshot, first pan/zoom any chart to an appropriate _visible timeframe_. The export snapshot will only +contain the metrics you see in charts, so choose the most relevant timeframe. + +Next, click on the **export** icon ![Export +icon](https://raw.githubusercontent.com/netdata/netdata-ui/98e31799c1ec0983f433537ff16d2ac2b0d994aa/src/components/icon/assets/download.svg) +in the top panel. + +Select the metrics resolution to export. The default is 1-second, equal to how often Netdata collects and stores +metrics. Lowering the resolution will reduce the number of data points, and thus the snapshot's overall size. + +Edit the snapshot file name and select your desired compression method. Click on **Export**. When the export is +complete, your browser will prompt you to save the `.snapshot` file to your machine. + +## Print a snapshot + +To print a snapshot, click on the **print** icon ![Import +icon](https://raw.githubusercontent.com/netdata/netdata-ui/98e31799c1ec0983f433537ff16d2ac2b0d994aa/src/components/icon/assets/print.svg) +in the top panel. + +When you click **Print**, Netdata opens a new window to render every chart. This might take some time. When finished, +Netdata opens a browser print dialog for you to save to PDF or print. diff --git a/docs/dashboard/import-export-print-snapshot.mdx b/docs/dashboard/import-export-print-snapshot.mdx deleted file mode 100644 index 23430a56..00000000 --- a/docs/dashboard/import-export-print-snapshot.mdx +++ /dev/null @@ -1,90 +0,0 @@ ---- -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/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 - -Netdata can export snapshots of the contents of your dashboard at a given time, which you can then import into any other -node running Netdata. Or, you can create a print-ready version of your dashboard to save to PDF or actually print to -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](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. - -Or, send the Netdata team a snapshot of your dashboard when [filing a bug -report](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml) on -GitHub. - -![The export, import, and print -buttons](https://user-images.githubusercontent.com/1153921/114218399-360fb600-991e-11eb-8dea-fabd2bffc5b3.gif) - -## Import a snapshot - -To import a snapshot, click on the **import** icon ![Import -icon](https://raw.githubusercontent.com/netdata/netdata-ui/98e31799c1ec0983f433537ff16d2ac2b0d994aa/src/components/icon/assets/upload.svg) -in the top panel. - -Select the Netdata snapshot file to import. Once the file is loaded, the modal updates with information about the -snapshot and the system from which it was taken. Click **Import** to begin to process. - -Netdata takes the data embedded inside the snapshot and re-creates a static replica on your dashboard. When the import -finishes, you're free to move around and examine the charts. - -Some caveats and tips to keep in mind: - -- Only metrics in the export timeframe are available to you. If you zoom out or pan through time, you'll see the - beginning and end of the snapshot. -- Charts won't update with new information, as you're looking at a static replica, not the live dashboard. -- The import is only temporary. Reload your browser tab to return to your node's real-time dashboard. - -## Export a snapshot - -To export a snapshot, first pan/zoom any chart to an appropriate _visible timeframe_. The export snapshot will only -contain the metrics you see in charts, so choose the most relevant timeframe. - -Next, click on the **export** icon ![Export -icon](https://raw.githubusercontent.com/netdata/netdata-ui/98e31799c1ec0983f433537ff16d2ac2b0d994aa/src/components/icon/assets/download.svg) -in the top panel. - -Select the metrics resolution to export. The default is 1-second, equal to how often Netdata collects and stores -metrics. Lowering the resolution will reduce the number of data points, and thus the snapshot's overall size. - -Edit the snapshot file name and select your desired compression method. Click on **Export**. When the export is -complete, your browser will prompt you to save the `.snapshot` file to your machine. - -## Print a snapshot - -To print a snapshot, click on the **print** icon ![Import -icon](https://raw.githubusercontent.com/netdata/netdata-ui/98e31799c1ec0983f433537ff16d2ac2b0d994aa/src/components/icon/assets/print.svg) -in the top panel. - -When you click **Print**, Netdata opens a new window to render every chart. This might take some time. When finished, -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](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](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 deleted file mode 100644 index a733bc9e..00000000 --- a/docs/dashboard/interact-charts.mdx +++ /dev/null @@ -1,201 +0,0 @@ ---- -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" -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://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 -troubleshooting and root cause analysis begins by manipulating the default charts. To help you troubleshoot, Netdata -synchronizes every chart every time you interact with one of them. - -Here's what synchronization looks like: - -![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) - -Once you understand all the interactions available to you, you'll be able to quickly move around the dashboard, search -for anomalies, and find root causes using per-second metrics. - -## Pause or stop - -| Interaction | Keyboard/mouse | Touchpad/touchscreen | -| :---------------- | :------------- | :------------------- | -| **Pause** a chart | `hover` | `n/a` | -| **Stop** a chart | `click` | `tap` | - -By hovering over any chart, you temporarily pause it so that you can hover over a specific timeframe and see the exact -values presented as dimensions. Click on the chart to lock it to this timeframe, which is useful if you want to jump to -a different chart to look for possible correlations. - -![Animated GIF of hovering over a chart to see -values](https://user-images.githubusercontent.com/1153921/62968279-9227dd00-bdbf-11e9-9112-1d21444d0f31.gif) - -## Pan - -| Interaction | Keyboard/mouse | Touchpad/touchscreen | -| :---------- | :------------- | :------------------- | -| **Pan** | `click + drag` | `swipe` | - -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. - -## Zoom - -| Interaction | Keyboard/mouse | Touchpad/touchscreen | -| :------------------------------- | :-------------------------- | :--------------------------------------------------- | -| **Zoom** in or out | `Shift + mouse scrollwheel` | `two-finger pinch`
`Shift + two-finger scroll` | -| **Zoom** to a specific timeframe | `Shift + mouse selection` | `n/a` | - -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. - -## Select - -| Interaction | Keyboard/mouse | Touchpad/touchscreen | -| :------------------------------ | :-------------------------------------------------------- | :------------------- | -| **Select** a specific timeframe | `Alt + mouse selection` or `⌘ + mouse selection` (macOS) | `n/a` | - -Selecting timeframes is useful when you see an interesting spike or change in a chart and want to investigate further. - -Select a timeframe, then move to different charts/sections of the dashboard. Each chart shows the same selection to help -you immediately identify the timeframe and look for correlations. - -## Reset a chart to its default state - -| Interaction | Keyboard/mouse | Touchpad/touchscreen | -| :---------------- | :------------- | :------------------- | -| **Reset** a chart | `double-click` | `n/a` | - -Double-check on a chart to restore it to the default auto-updating state, with a timeframe based on your browser -viewport. - -## Resize - -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. - -![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) - -## Show and hide dimensions - -| Interaction | Keyboard/mouse | Touchpad/touchscreen | -| :------------------------------------- | :-------------- | :------------------- | -| **Show one** dimension and hide others | `click` | `tap` | -| **Toggle (show/hide)** one dimension | `Shift + click` | `n/a` | - -Hiding dimensions simplifies the chart and can help you better discover exactly which aspect of your system might be -behaving strangely. - -## See the context - -Hover your mouse over the date that appears just beneath the chart itself. A tooltip will tell you the context for that -chart. Below, the context is `apps.cpu`. - -![See a chart's -context](https://user-images.githubusercontent.com/1153921/114212924-39ec0a00-9917-11eb-9a9e-7e171057b3fd.gif) - -## See the resolution and update frequency - -Hover your mouse over the timestamp just to the right of the date. `resolution` is the number of seconds between each -"tick" in the chart. `collection every` is how often Netdata collects and stores that metric. - -If the `resolution` value is higher than `collection every`, such as `resolution 5 secs, collected every 1 sec`, this -means that each tick is calculating represents the average values across a 5-second period. You can zoom in to increase -the resolution to `resolution 1 sec` to see the exact values. - -## Chart controls - -Many of the above interactions can also be triggered using the icons on the bottom-right corner of every chart. They -are, respectively, `Pan Left`, `Reset`, `Pan Right`, `Zoom In`, and `Zoom Out`. - -## Chart label filtering - -The chart label filtering feature supports grouping by and filtering each chart based on labels (key/value pairs) applicable to the context and provides fine-grain capability on slicing the data and metrics. - -All metrics collected get "tagged" with labels and values, thus providing a powerful way of slicing and visualizing all metrics related to the infrastructure. - -The chart label filtering is currently enabled on: - -- All charts on the **Overview** tab -- Custom dashboards - -![Chart filtering on Overview tab chart](https://user-images.githubusercontent.com/88642300/193084084-01074495-c826-4519-a09f-d210f7e3e6be.png) -![Chart filtering on Custom dashboard](https://user-images.githubusercontent.com/88642300/193084172-358dfded-c318-4d9f-b6e2-46a8fc33030b.png) - -The top panel on each chart displays the various filters and grouping options selected on the specific chart. These filters are specific for each chart and need to be manually configured on each chart. - -Additionally, the charts can be saved to a custom dashboard, new or existing, with the selected filters from the overview screen. - -![Chart filtering saved on custom dashboard](https://user-images.githubusercontent.com/88642300/193084225-1b65984e-566c-4815-8bc1-a2781d3564bd.png) - -## Custom labels for Collectors - -In addition to the default labels associated with a collector and metrics context (you can identify them by seeing which ones have an underscore as a prefix), there is now a new feature enabled to create custom labels. These custom labels may be needed to group your jobs or instances into various categories. - -These custom labels can be configured within your go.d plugins by simply associating a label key/value pair, as in the following eaxmple. - -```conf -jobs: - - name: example_1 - someOption: someValue - labels: - label1: value1 - label2: value2 - - name: example_2 - someOption: someValue - labels: - label3: value3 - label4: value4 -``` - -For instance, you may be running multiple Postgres database instances within an infrastructure. Some of these may be associated with testing environments, some with staging and some with production environments. You can now associate each Postgres job / instance with a custom label. The “group by” and filtering options will then allow you to associate individual jobs by specific labels. - -```conf -jobs: - - name: local - dsn: 'postgres://postgres:postgres@127.0.0.1:5432/postgres' - collect_databases_matching: '*' - labels: - instance_type: production - ``` - ![Group by individual job labels one](https://user-images.githubusercontent.com/88642300/193084580-49df500a-ddfb-45bb-a209-3c7a904ee9e0.png) - ![group by individual job labels two](https://user-images.githubusercontent.com/88642300/193084624-6d9848d0-9400-4e34-9cd4-78e50c784cc0.png) - -### Future Work - -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](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](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](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](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 deleted file mode 100644 index f90e6f87..00000000 --- a/docs/dashboard/reference-web-server.mdx +++ /dev/null @@ -1,278 +0,0 @@ ---- -title: "Web server reference" -description: "The Netdata Agent's local static-threaded web server serves dashboards and real-time visualizations with security and DDoS protection." -type: reference -custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/dashboard/reference-web-server.mdx ---- - -# Web server reference - -The Netdata web server is `static-threaded`, with a fixed, configurable number of threads. - -All the threads are concurrently listening for web requests on the same sockets, and the kernel distributes the incoming -requests to them. Each thread uses non-blocking I/O so it can serve any number of web requests in parallel. - -This web server respects the `keep-alive` HTTP header to serve multiple HTTP requests via the same connection. - -## Configuration - -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`. - -``` -sudo ./edit-config netdata.conf -``` - -Scroll down to the `[web]` section to find the following settings. - -## Settings - -| Setting | Default | Description | -|:-------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `ssl key` | `/etc/netdata/ssl/key.pem` | Declare the location of an SSL key to [enable HTTPS](#enable-httpstls-support). | -| `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](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. | -| `web files owner` | `netdata` | The user that owns the web static files. Netdata will refuse to serve a file that is not owned by this user, even if it has read access to that file. If the user given is not found, Netdata will only serve files owned by user given in `run as user`. | -| `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](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](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` | | -| `allow badges from` | `*` | | -| `allow badges by dns` | `heuristic` | | -| `allow streaming from` | `*` | | -| `allow streaming by dns` | `heuristic` | | -| `allow netdata.conf` | `localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.* 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.* 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.* 172.31.* UNKNOWN` | | -| `allow netdata.conf by dns` | `no` | | -| `allow management from` | `localhost` | | -| `allow management by dns` | `heuristic` | | -| `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 settings are `default`, `filtered`, `huffman only`, `rle` and `fixed`. | -| `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](https://github.com/netdata/netdata/blob/master/docs/dashboard/customize.mdx#customize-the-standard-dashboard) for details. | - -## Examples - -### Disable the web server - -Disable the web server by editing `netdata.conf` and setting: - -``` -[web] - mode = none -``` - -### Change the number of threads - -Control the number of threads and sockets with the following settings: - -``` -[web] - web server threads = 4 - web server max sockets = 512 -``` - -### Binding Netdata to multiple ports - -Netdata can bind to multiple IPs and ports, offering access to different services on each. Up to 100 sockets can be used (increase it at compile time with `CFLAGS="-DMAX_LISTEN_FDS=200" ./netdata-installer.sh ...`). - -The ports to bind are controlled via `[web].bind to`, like this: - -``` -[web] - default port = 19999 - bind to = 127.0.0.1=dashboard^SSL=optional 10.1.1.1:19998=management|netdata.conf hostname:19997=badges [::]:19996=streaming^SSL=force localhost:19995=registry *:http=dashboard unix:/run/netdata/netdata.sock -``` - -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](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. - -The option `[web].default port` is used when an entries in `[web].bind to` do not specify a port. - -Note that the access permissions specified with the `=request type|request type|...` format are available from version 1.12 onwards. -As shown in the example above, these permissions are optional, with the default being to permit all request types on the specified port. -The request types are strings identical to the `allow X from` directives of the access lists, i.e. `dashboard`, `streaming`, `registry`, `netdata.conf`, `badges` and `management`. -The access lists themselves and the general setting `allow connections from` in the next section are applied regardless of the ports that are configured to provide these services. -The API requests are serviced as follows: - -- `dashboard` gives access to the UI, the read API and badges API calls. -- `badges` gives access only to the badges API calls. -- `management` gives access only to the management API calls. - -### Enable HTTPS/TLS support - -Since v1.16.0, Netdata supports encrypted HTTP connections to the web server, plus encryption of streaming data to a -parent from its child nodes, via the TLS protocol. - -Inbound unix socket connections are unaffected, regardless of the TLS settings. - -> While Netdata uses Transport Layer Security (TLS) 1.2 to encrypt communications rather than the obsolete SSL protocol, -> it's still common practice to refer to encrypted web connections as `SSL`. Many vendors, like Nginx and even Netdata -> itself, use `SSL` in configuration files, whereas documentation will always refer to encrypted communications as `TLS` -> or `TLS/SSL`. - -To enable TLS, provide the path to your certificate and private key in the `[web]` section of `netdata.conf`: - -```conf -[web] - ssl key = /etc/netdata/ssl/key.pem - ssl certificate = /etc/netdata/ssl/cert.pem -``` - -Both files must be readable by the `netdata` user. If either of these files do not exist or are unreadable, Netdata will fall back to HTTP. For a parent-child connection, only the parent needs these settings. - -For test purposes, generate self-signed certificates with the following command: - -```bash -openssl req -newkey rsa:2048 -nodes -sha512 -x509 -days 365 -keyout key.pem -out cert.pem -``` - -> If you use 4096 bits for your key and the certificate, Netdata will need more CPU to process the communication. -> `rsa4096` can be up to 4 times slower than `rsa2048`, so we recommend using 2048 bits. Verify the difference -> by running: -> -> ```sh -> openssl speed rsa2048 rsa4096 -> ``` - -### Select TLS version - -Beginning with version `v1.21.0`, specify the TLS version and the ciphers that you want to use: - -```conf -[web] - tls version = 1.3 - tls ciphers = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 -``` - -If you do not specify these options, Netdata will use the highest available protocol version on your system and the default cipher list for that protocol provided by your TLS implementation. - -#### TLS/SSL enforcement - -When the certificates are defined and unless any other options are provided, a Netdata server will: - -- Redirect all incoming HTTP web server requests to HTTPS. Applies to the dashboard, the API, `netdata.conf` and badges. -- Allow incoming child connections to use both unencrypted and encrypted communications for streaming. - -To change this behavior, you need to modify the `bind to` setting in the `[web]` section of `netdata.conf`. At the end of each port definition, append `^SSL=force` or `^SSL=optional`. What happens with these settings differs, depending on whether the port is used for HTTP/S requests, or for streaming. - -| SSL setting | HTTP requests|HTTPS requests|Unencrypted Streams|Encrypted Streams| -|:---------:|:-----------:|:------------:|:-----------------:|:----------------| -| none | Redirected to HTTPS|Accepted|Accepted|Accepted| -| `force`| Redirected to HTTPS|Accepted|Denied|Accepted| -| `optional`| Accepted|Accepted|Accepted|Accepted| - -Example: - -``` -[web] - 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](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: - -``` -[web] - bind to = *=dashboard|registry|badges|management|streaming|netdata.conf^SSL=force *:20000=netdata.conf^SSL=optional *:20001=dashboard|registry -``` - -Netdata will: - -- Force all HTTP requests to the default port to be redirected to HTTPS (same port). -- Refuse unencrypted streaming connections from child nodes on the default port. -- Allow both HTTP and HTTPS requests to port 20000 for `netdata.conf` -- Force HTTP requests to port 20001 to be redirected to HTTPS (same port). Only allow requests for the dashboard, the read API and the registry on port 20001. - -#### TLS/SSL errors - -When you start using Netdata with TLS, you may find errors in the Netdata log, which is stored at `/var/log/netdata/error.log` by default. - -Most of the time, these errors are due to incompatibilities between your browser's options related to TLS/SSL protocols and Netdata's internal configuration. The most common error is `error:00000006:lib(0):func(0):EVP lib`. - -In the near future, Netdata will allow our users to change the internal configuration to avoid similar errors. Until then, we're recommending only the most common and safe encryption protocols listed above. - -### Access lists - -Netdata supports access lists in `netdata.conf`: - -``` -[web] - allow connections from = localhost * - allow dashboard from = localhost * - allow badges from = * - allow streaming from = * - allow netdata.conf from = localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.* 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.* 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.* 172.31.* - allow management from = localhost -``` - -`*` does string matches on the IPs or FQDNs of the clients. - -- `allow connections from` matches anyone that connects on the Netdata port(s). - So, if someone is not allowed, it will be connected and disconnected immediately, without reading even - a single byte from its connection. This is a global setting with higher priority to any of the ones below. - -- `allow dashboard from` receives the request and examines if it is a static dashboard file or an API call the - dashboards do. - -- `allow badges from` checks if the API request is for a badge. Badges are not matched by `allow dashboard from`. - -- `allow streaming from` checks if the child willing to stream metrics to this Netdata is allowed. - This can be controlled per API KEY and MACHINE GUID in `stream.conf`. - The setting in `netdata.conf` is checked before the ones in `stream.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](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 -a forward DNS resolution is made to validate the IP address against the name-pattern. - -Please note that this process can be expensive on a machine that is serving many connections. Each access list has an -associated configuration option to turn off DNS-based patterns completely to avoid incurring this cost at run-time: - -``` - allow connections by dns = heuristic - allow dashboard by dns = heuristic - allow badges by dns = heuristic - allow streaming by dns = heuristic - allow netdata.conf by dns = no - allow management by dns = heuristic -``` - -The three possible values for each of these options are `yes`, `no` and `heuristic`. The `heuristic` option disables -the check when the pattern only contains IPv4/IPv6 addresses or `localhost`, and enables it when wildcards are -present that may match DNS FQDNs. - -## DDoS protection - -If you publish your Netdata web server to the internet, you may want to apply some protection against DDoS: - -1. Use the `static-threaded` web server (it is the default) -2. Use reasonable `[web].web server max sockets` (the default is) -3. Don't use all your CPU cores for Netdata (lower `[web].web server threads`) -4. Run the `netdata` process with a low process scheduling priority (the default is the lowest) -5. If possible, proxy Netdata via a full featured web server (Nginx, Apache, etc) diff --git a/docs/dashboard/visualization-date-and-time-controls.md b/docs/dashboard/visualization-date-and-time-controls.md new file mode 100644 index 00000000..99e4c308 --- /dev/null +++ b/docs/dashboard/visualization-date-and-time-controls.md @@ -0,0 +1,92 @@ +# Visualization date and time controls + +Netdata's dashboard features powerful date visualization controls that include a time control, a timezone selector and a rich date and timeframe selector. + +The controls come with useful defaults and rich customization, to help you narrow your focus when troubleshooting issues or anomalies. + +## Time controls + +The time control provides you the following options: **Play**, **Pause** and **Force Play**. + +- **Play** - the content of the page will be automatically refreshed while this is in the foreground +- **Pause** - the content of the page isn't refreshed due to a manual request to pause it or, for example, when your investigating data on a chart (cursor is on top of a chart) +- **Force Play** - the content of the page will be automatically refreshed even if this is in the background + +With this, we aim to bring more clarity and allow you to distinguish if the content you are looking at is live or historical and also allow you to always refresh the content of the page when the tabs are in the background. + +Main use cases for **Force Play**: + +- You use a terminal or deployment tools to do changes in your infra and want to see the effect immediately, Netdata is in the background, displaying the impact of these changes +- You want to have Netdata on the background, example displayed on a TV, to constantly see metrics through dashboards or to watch the alert status + +![The time control with Play, Pause and Force Play](https://user-images.githubusercontent.com/70198089/225850250-1fe12477-23f8-4b4d-b497-79b416963e10.png) + +## Date and time selector + +The date and time selector allows you to change the visible timeframe and change the timezone used in the interface. + +### Pick timeframes to visualize + +While [panning through time and zooming in/out](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md) 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? + +Netdata's dashboard features a **timeframe selector** to help you visualize specific timeframes in a few helpful ways. +By default, it shows a certain number of minutes of historical metrics based on the your browser's viewport to ensure it's always showing per-second granularity. + +#### Open the timeframe selector + +To visualize a new timeframe, you need to open the picker, which appears just above the menu, near the top-right bar of the dashboard. + +![Timeframe Selector](https://user-images.githubusercontent.com/70198089/225850611-728936d9-7ca4-49fa-8d37-1ce73dd6f76c.png) + +The **Clear** button resets the dashboard back to its default state based on your browser viewport, and **Apply** closes +the picker and shifts all charts to the selected timeframe. + +#### Use the pre-defined timeframes + +Click any of the following options in the predefined timeframe column to choose between: + +- Last 5 minutes +- Last 15 minutes +- Last 30 minutes +- Last hour +- Last 2 hours +- Last 6 hours +- Last 12 hours +- Last day +- Last 2 days +- Last 7 days + +Click **Apply** to see metrics from your selected timeframe. + +#### Choose a specific interval + +Beneath the predefined timeframe columns is an input field and dropdown you use in combination to select a specific timeframe of +minutes, hours, days, or months. Enter a number and choose the appropriate unit of time, then click **Apply**. + +#### Choose multiple days via the calendar + +Use the calendar to select multiple days. Click on a date to begin the timeframe selection, then an ending date. The +timeframe begins at noon on the beginning and end dates. Click **Apply** to see your selected multi-day timeframe. + +#### Caveats and considerations + +**Longer timeframes will decrease metrics granularity**. At the default timeframe, based on your browser viewport, each +"tick" on charts represents one second. If you select a timeframe of 6 hours, each tick represents the _average_ value +across a larger period of time. + +**You can only see metrics as far back in history as your metrics retention policy allows**. Netdata uses an internal +time-series database (TSDB) to store as many metrics as it can within a specific amount of disk space. The default +storage is 256 MiB, which should be enough for 1-3 days of historical metrics. If you navigate back to a timeframe +beyond stored historical metrics, you'll see this message: + +![image](https://user-images.githubusercontent.com/70198089/225851033-43b95164-a651-48f2-8915-6aac9739ed93.png) + +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. + +### Timezone selector + +The default timezone used in all date and time fields in Netdata Cloud comes from your browser. To change it, open the +date and time selector and use the control displayed here: + +![Timezone selector](https://user-images.githubusercontent.com/43294513/216628390-c3bd1cd2-349d-4523-b8d3-c7e68395f670.png) diff --git a/docs/dashboard/visualization-date-and-time-controls.mdx b/docs/dashboard/visualization-date-and-time-controls.mdx deleted file mode 100644 index a59a1f06..00000000 --- a/docs/dashboard/visualization-date-and-time-controls.mdx +++ /dev/null @@ -1,125 +0,0 @@ - - -# Visualization date and time controls - -## Date and time selector - -### Pick timeframes to visualize - -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? - -Netdata's dashboard features a **timeframe selector** to help you visualize specific timeframes in a few helpful ways. -By default, it shows a certain number of minutes of historical metrics based on the your browser's viewport to ensure -it's always showing per-second granularity. - -#### Open the timeframe selector - -To visualize a new timeframe, you need to open the picker, which appears just above the menu, near the top-right cover -of the dashboard. - -![The timeframe selector in the local Agent -dashboard](https://user-images.githubusercontent.com/1153921/101507784-2c585080-3934-11eb-9d6e-eff30b8553e4.png) - -The **Clear** button resets the dashboard back to its default state based on your browser viewport, and **Apply** closes -the picker and shifts all charts to the selected timeframe. - -#### Use the Quick Selector - -Click any of the following options in the **Quick Selector** to choose a commonly-used timeframe. - -- Last 5 minutes -- Last 15 minutes -- Last 2 hours -- Last 6 hours -- Last 12 hours - -Click **Apply** to see metrics from your selected timeframe. - -#### Choose a specific interval - -Beneath the Quick Selector is an input field and dropdown you use in combination to select a specific timeframe of -minutes, hours, days, or months. Enter a number and choose the appropriate unit of time, then click **Apply**. - -#### Choose multiple days - -Use the calendar to select multiple days. Click on a date to begin the timeframe selection, then an ending date. The -timeframe begins at noon on the beginning and end dates. Click **Apply** to see your selected multi-day timeframe. - -## Time controls - -The time control provides you the following options: **Play**, **Pause** and **Force Play**. -* **Play** - the content of the page will be automatically refreshed while this is in the foreground -* **Pause** - the content of the page isn't refreshed due to a manual request to pause it or, for example, when your investigating data on a -chart (cursor is on top of a chart) -* **Force Play** - the content of the page will be automatically refreshed even if this is in the background - -With this, we aim to bring more clarity and allow you to distinguish if the content you are looking at is live or historical and also allow you - to always refresh the content of the page when the tabs are in the background. - -Main use cases for **Force Play**: -* You use a terminal or deployment tools to do changes in your infra and want to see immediately, Netdata is in the background, displaying the impact -of these changes -* You want to have Netdata on the background, example displayed on a TV, to constantly see metrics through dashboards or to watch the alert -status - -![The time control with Play, Pause and -Force Play](https://user-images.githubusercontent.com/82235632/129206460-03c47d0d-1a5b-428a-b972-473718b74bdb.png) - -## Timezone selector - -With the timezone selector, you have the ability to change the timezone on Netdata Cloud. More often than not teams are -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. - -Untitled1 - -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. - -## Caveats and considerations - -**Longer timeframes will decrease metrics granularity**. At the default timeframe, based on your browser viewport, each -"tick" on charts represents one second. If you select a timeframe of 6 hours, each tick represents the _average_ value -across a larger period of time. - -**You can only see metrics as far back in history as your metrics retention policy allows**. Netdata uses an internal -time-series database (TSDB) to store as many metrics as it can within a specific amount of disk space. The default -storage is 256 MiB, which should be enough for 1-3 days of historical metrics. If you navigate back to a timeframe -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](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](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](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](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 28208e2f..02e380e1 100644 --- a/docs/export/enable-connector.md +++ b/docs/export/enable-connector.md @@ -5,7 +5,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/docs/export/ena sidebar_label: "Enable an exporting connector" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Setup" +learn_rel_path: "Configuration" --> # Enable an exporting connector @@ -92,8 +92,8 @@ details. 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_](https://github.com/netdata/netdata/blob/master/docs/guides/export/export-netdata-metrics-graphite.md). Or, start +For a comprehensive example of using the Graphite connector, read our documentation on +[exporting metrics to Graphite providers](https://github.com/netdata/netdata/blob/master/exporting/graphite/README.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 diff --git a/docs/export/external-databases.md b/docs/export/external-databases.md index 00ca7410..715e8660 100644 --- a/docs/export/external-databases.md +++ b/docs/export/external-databases.md @@ -22,7 +22,7 @@ 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](https://github.com/netdata/netdata/blob/master/docs/overview/netdata-monitoring-stack.md) +Exporting is an important part of Netdata's effort to be interoperable 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. @@ -75,19 +75,3 @@ documentation and the [enabling a connector](https://github.com/netdata/netdata/ Can't find your preferred external time-series database? Ask our [community](https://community.netdata.cloud/) for solutions, or file an [issue on GitHub](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml). - -## What's next? - -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_](https://github.com/netdata/netdata/blob/master/docs/guides/export/export-netdata-metrics-graphite.md). - -### Related reference documentation - -- [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 deleted file mode 100644 index aa82e811..00000000 --- a/docs/get-started.mdx +++ /dev/null @@ -1,129 +0,0 @@ - - -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://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](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md). -This script automatically installs dependencies and builds Netdata from its source code. - -To install, copy the script, paste it into your node's terminal, and hit `Enter` to begin the installation process. - - - wget> - - - - - curl> - - - - - - -:::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 - - - - - - - - - - - -## What's next? - -To start using Netdata, open a browser and navigate to `http://NODE:19999`, replacing `NODE` with either `localhost` or -the hostname/IP address of a remote node. - -Where you go from here is based on your use case, immediate needs, and experience with monitoring and troubleshooting. - -### Dashboard - -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](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](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](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](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. - -### Make your deployment production ready - -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 deleted file mode 100644 index 9f38a67d..00000000 --- a/docs/getting-started/integrations.md +++ /dev/null @@ -1,12 +0,0 @@ - - -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 index 1ace5e3a..b164074b 100644 --- a/docs/getting-started/introduction.md +++ b/docs/getting-started/introduction.md @@ -1,13 +1,6 @@ - +# Getting started with Netdata + +Learn how Netdata can get you monitoring your infrastructure in minutes. ## What is Netdata ? @@ -21,34 +14,34 @@ 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 +- **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. +- **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 +- **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 +- **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 +- **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 +- **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 +- **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. -- +- **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 @@ -66,21 +59,19 @@ open-source tools. | **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 +- **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 +- **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 +- **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 +## 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. @@ -93,9 +84,9 @@ 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. +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: +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](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md). But here's a good breakdown: ### Netdata Agent @@ -104,12 +95,56 @@ Netdata's distributed monitoring Agent collects thousands of metrics from system 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. +## Use Netdata standalone or as part of your monitoring stack + +Netdata is an extremely powerful monitoring, visualization, and troubleshooting platform. While you can use it as an +effective standalone tool, we also designed it to be open and interoperable with other tools you might already be using. + +Netdata helps you collect everything and scales to infrastructure of any size, but it doesn't lock-in data or force you +to use specific tools or methodologies. Each feature is extensible and interoperable so they can work in parallel with +other tools. For example, you can use Netdata to collect metrics, visualize metrics with a second open-source program, +and centralize your metrics in a cloud-based time-series database solution for long-term storage or further analysis. + +You can build a new monitoring stack, including Netdata, or integrate Netdata's metrics with your existing monitoring +stack. No matter which route you take, Netdata helps you monitor infrastructure of any size. + +Here are a few ways to enrich your existing monitoring and troubleshooting stack with Netdata: + +### Collect metrics from Prometheus endpoints + +Netdata automatically detects 600 popular endpoints and collects per-second metrics from them via the [generic +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 +configuring Netdata. Netdata will detect these Prometheus metrics endpoints and collect even more granular metrics than +your existing solutions. You can now use all of Netdata's meaningfully-visualized charts to diagnose issues and +troubleshoot anomalies. + +### Export metrics to external time-series databases + +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. + +Once you have Netdata's metrics in a secondary time-series database, you can use them however you'd like, such as +additional visualization/dashboarding tools or aggregation of data from multiple sources. + +### Visualize metrics with Grafana + +One popular monitoring stack is Netdata, Prometheus, and Grafana. Netdata acts as the stack's metrics collection +powerhouse, Prometheus as the time-series database, and Grafana as the visualization platform. You can also use Grafite instead of Prometheus, +or directly use the [Netdata source plugin for Grafana](https://blog.netdata.cloud/introducing-netdata-source-plugin-for-grafana/) + +Of course, just because you export or visualize metrics elsewhere, it doesn't mean Netdata's equivalent features +disappear. You can always build new dashboards in Netdata Cloud, drill down into per-second metrics using Netdata's +charts, or use Netdata's health watchdog to send notifications whenever an anomaly strikes. ## Community @@ -120,14 +155,14 @@ ask questions, find resources, and engage with passionate professionals. The tea 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/) +- [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 diff --git a/docs/glossary.md b/docs/glossary.md new file mode 100644 index 00000000..fe61cc11 --- /dev/null +++ b/docs/glossary.md @@ -0,0 +1,180 @@ +# Glossary + +The Netdata community welcomes engineers, SREs, admins, etc. of all levels of expertise with engineering and the Netdata tool. And just as a journey of a thousand miles starts with one step, sometimes, the journey to mastery begins with understanding a single term. + +As such, we want to provide a little Glossary as a reference starting point for new users who might be confused about the Netdata vernacular that more familiar users might take for granted. + +If you're here looking for the definition of a term you heard elsewhere in our community or products, or if you just want to learn Netdata from the ground up, you've come to the right page. + +Use the alphabatized list below to find the answer to your single-term questions, and click the bolded list items to explore more on the topics! We'll be sure to keep constantly updating this list, so if you hear a word that you would like for us to cover, just let us know or submit a request! + +[A](#a) | [B](#b) | [C](#c) | [D](#d)| [E](#e) | [F](#f) | [G](#g) | [H](#h) | [I](#i) | [J](#j) | [K](#k) | [L](#l) | [M](#m) | [N](#n) | [O](#o) | [P](#p) +| [Q](#q) | [R](#r) | [S](#s) | [T](#t) | [U](#u) | [V](#v) | [W](#w) | [X](#x) | [Y](#y) | [Z](#z) + +## A + +- [**Agent** or **Netdata Agent**](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md): 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. + +- [**Agent-cloud link** or **ACLK**](https://github.com/netdata/netdata/blob/master/aclk/README.md): The Agent-Cloud link (ACLK) is the mechanism responsible for securely connecting a Netdata Agent to your web browser through Netdata Cloud. + +- [**Aggregate Function**](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md#aggregate-functions-over-time): A function applied When the granularity of the data collected is higher than the plotted points on the chart. + +- [**Alerts** (formerly **Alarms**)](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.md): With the information that appears on Netdata Cloud and the local dashboard about active alerts, you can configure alerts to match your infrastructure's needs or your team's goals. + +- [**Alarm Entity Type**](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#health-entity-reference): Entity types that are attached to specific charts and use the `alarm` label. + +- [**Anomaly Advisor**](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.md): A Netdata feature that lets you quickly surface potentially anomalous metrics and charts related to a particular highlight window of interest. + +## B + +- [**Bookmarks**](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md#manage-spaces): Netdata Cloud's bookmarks put your 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. + +## C + +- [**Child**](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.md#streaming-basics): A node, running Netdata, that streams metric data to one or more parent. + +- [**Cloud** or **Netdata Cloud**](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md): 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. + +- [**Collector**](https://github.com/netdata/netdata/blob/master/collectors/README.md#collector-architecture-and-terminology): A catch-all term for any Netdata process that gathers metrics from an endpoint. + +- [**Community**](https://community.netdata.cloud/): As a company with a passion and genesis in open-source, we are not just very proud of our community, but we consider our users, fans, and chatters to be an imperative part of the Netdata experience and culture. + +- [**Composite Charts**](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#overview-and-single-node-view): Charts used by the **Overview** tab which aggregate metrics from all the nodes (or a filtered selection) in a given War Room. + +- [**Context**](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.md#context): A way of grouping charts by the types of metrics collected and dimensions displayed. It's kind of like a machine-readable naming and organization scheme. + +- [**Custom dashboards**](https://github.com/netdata/netdata/blob/master/web/gui/custom/README.md) A dashboard that you can create using simple HTML (no javascript is required for basic dashboards). + +## D + +- [**Dashboards**](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md): Out-of-the box visual presentation of metrics that allows you to make sense of your infrastructure and its health and performance. + +- [**Definition Bar**](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md): Bar within a composite chart that provides important information and options about the metrics within the chart. + +- [**Dimension**](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.md#dimension): A dimension is a value that gets shown on a chart. + +- [**Distributed Architecture**](https://github.com/netdata/netdata/blob/master/docs/store/distributed-data-architecture.md): The data architecture mindset with which Netdata was built, where all data are collected and stored on the edge, whenever it's possible, creating countless benefits. + +## E + +- [**External Plugins**](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md): These gather metrics from external processes, such as a webserver or database, and run as independent processes that communicate with the Netdata daemon via pipes. + +## F + +- [**Family**](https://github.com/netdata/netdata/blob/master/docs/dashboard/dimensions-contexts-families.md#family): 1. What we consider our Netdata community of users and engineers. 2. A single instance of a hardware or software resource that needs to be displayed separately from similar instances. + +- [**Flood Protection**](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.md#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 + +- [**Functions** or **Netdata Functions**](https://github.com/netdata/netdata/blob/master/docs/cloud/netdata-functions.md): Routines exposed by a collector on the Netdata Agent that can bring additional information to support troubleshooting or trigger some action to happen on the node itself. + +## G + +- [**Guided Troubleshooting**](https://github.com/netdata/netdata/blob/master/docs/category-overview-pages/troubleshooting-overview.md): Troubleshooting with our Machine-Learning-powered tools designed to give you a cutting edge advantage in your troubleshooting battles. + +- [**Group by**](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md#group-by-dimension-node-or-chart): The drop-down on the dimension bar of a composite chart that allows you to group metrics by dimension, node, or chart. + +## H + +- [**Headless Collector Streaming**](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.md#supported-streaming-configurations): Streaming configuration where child `A`, _without_ a database or web dashboard, streams metrics to parent `B`. + +- [**Health Configuration Files**](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#edit-health-configuration-files): Files that you can edit to configure your Agent's health watchdog service. + +- [**Health Entity Reference**](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#health-entity-reference): + +- [**Home** tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#home): Tab in Netdata Cloud that provides a predefined dashboard of relevant information about entities in the War Room. + +## I + +- [**Internal plugins**](https://github.com/netdata/netdata/blob/master/collectors/README.md#collector-architecture-and-terminology): These gather metrics from `/proc`, `/sys`, and other Linux kernel sources. They are written in `C` and run as threads within the Netdata daemon. + +## K + +- [**Kickstart** or **Kickstart Script**](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md): An automatic one-line installation script named 'kickstart.sh' that works on all Linux distributions and macOS. + +- [**Kubernetes Dashboard** or **Kubernetes Tab**](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/kubernetes.md): Netdata Cloud features enhanced visualizations for the resource utilization of Kubernetes (k8s) clusters, embedded in the default Overview dashboard. + +## M + +- [**Metrics Collection**](https://github.com/netdata/netdata/blob/master/collectors/README.md): With zero configuration, Netdata auto-detects thousands of data sources upon starting and immediately collects per-second metrics. Netdata can immediately collect metrics from these endpoints thanks to 300+ collectors, which all come pre-installed when you install Netdata. + +- [**Metric Correlations**](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/metric-correlations.md): A Netdata feature that lets you quickly find metrics and charts related to a particular window of interest that you want to explore further. + +- [**Metrics Exporting**](https://github.com/netdata/netdata/blob/master/docs/export/external-databases.md): Netdata allows you to export metrics to external time-series databases with the exporting engine. This system uses a number of connectors to initiate connections to more than thirty supported databases, including InfluxDB, Prometheus, Graphite, ElasticSearch, and much more. + +- [**Metrics Storage**](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md): Upon collection the collected metrics need to be either forwarded, exported or just stored for further treatment. The Agent is capable to store metrics both short and long-term, with or without the usage of non-volatile storage. + +- [**Metrics Streaming Replication**](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.md): Each node running Netdata can stream the metrics it collects, in real time, to another node. Metric streaming allows you to replicate metrics data across multiple nodes, or centralize all your metrics data into a single time-series database (TSDB). + +- [**Module**](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md#enable-and-disable-a-specific-collection-module): A type of collector. + +## N + +- [**Netdata**](https://github.com/netdata/netdata/blob/master/docs/getting-started/introduction.md): Netdata is a monitoring tool 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. + +- [**Netdata Agent** or **Agent**](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md): 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. + +- [**Netdata Cloud** or **Cloud**](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md): 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. + +- [**Netdata Functions** or **Functions**](https://github.com/netdata/netdata/blob/master/docs/cloud/netdata-functions.md): Routines exposed by a collector on the Netdata Agent that can bring additional information to support troubleshooting or trigger some action to happen on the node itself. + + + + + +## O + +- [**Obsoletion**(of nodes)](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md#obsoleting-offline-nodes-from-a-space): Removing nodes from a space. + +- [**Orchestrators**](https://github.com/netdata/netdata/blob/master/collectors/README.md#collector-architecture-and-terminology): External plugins that run and manage one or more modules. They run as independent processes. + +- [**Overview** tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#overview-and-single-node-view): Tab in Netdata Cloud that uses composite charts. These charts display real-time aggregated metrics from all the nodes (or a filtered selection) in a given War Room. + +## P + +- [**Parent**](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.md#streaming-basics): A node, running Netdata, that receives streamed metric data. + +- [**Proxy**](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.md#streaming-basics): A node, running Netdata, that receives metric data from a child and "forwards" them on to a separate parent node. + +- [**Proxy Streaming**](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.md#supported-streaming-configurations): Streaming configuration where child `A`, _with or without_ a database, sends metrics to proxy `C`, also _with or without_ a database. `C` sends metrics to parent `B` + +## R + +- [**Registry**](https://github.com/netdata/netdata/blob/master/registry/README.md): Registry that allows Netdata to provide unified cross-server dashboards. + +- [**Replication Streaming**](https://github.com/netdata/netdata/blob/master/streaming/README.md): Streaming configuration where child `A`, _with_ a database and web dashboard, streams metrics to parent `B`. + +- [**Room** or **War Room**](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md): War Rooms organize your connected nodes and provide infrastructure-wide dashboards using real-time metrics and visualizations. + +## S + +- [**Single Node Dashboard**](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#overview-and-single-node-view): A dashboard pre-configured with every installation of the Netdata agent, with thousand of metrics and hundreds of interactive charts that requires no set up. + + + +- [**Space**](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md): A high-level container and virtual collaboration area where you can organize team members, access levels,and the nodes you want to monitor. + +## T + +- [**Template Entity Type**](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#entity-types): Entity type that defines rules that apply to all charts of a specific context, and use the template label. Templates help you apply one entity to all disks, all network interfaces, all MySQL databases, and so on. + +- [**Tiers**](https://github.com/netdata/netdata/blob/master/database/engine/README.md#tiers): Tiering is a mechanism of providing multiple tiers of data with different granularity of metrics (the frequency they are collected and stored, i.e. their resolution). + +## U + +- [**Unlimited Scalability**](https://www.netdata.cloud/#:~:text=love%20community%20contributions!-,Infinite%20Scalability,-By%20storing%20data): With Netdata's distributed architecture, you can seamless observe a couple, hundreds or +even thousands of nodes. There are no actual bottlenecks especially if you retain metrics locally in the Agents. + +## V + +- [**Visualizations**](https://github.com/netdata/netdata/blob/master/docs/category-overview-pages/visualizations-overview.md): Netdata uses dimensions, contexts, and families to sort your metric data into graphs, charts, and alerts that maximize your understand of your infrastructure and your ability to troubleshoot it, along or on a team. + +## W + +- [**War Room** or **Room**](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md): War Rooms organize your connected nodes and provide infrastructure-wide dashboards using real-time metrics and visualizations. + +## Z + +- [**Zero Configuration**](https://github.com/netdata/netdata/blob/master/docs/getting-started/introduction.md#simple-to-deploy): Netdata is preconfigured and capable to autodetect and monitor any well known application that runs on your system. You just deploy and claim Netdata Agents in your Netdata space, and monitor them in seconds. diff --git a/docs/guidelines.md b/docs/guidelines.md index 6c1c3ba7..e8ff98e4 100644 --- a/docs/guidelines.md +++ b/docs/guidelines.md @@ -1,772 +1,71 @@ - - -import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +# Contribute to the documentation Welcome to our docs developer guidelines! -This document will guide you to the process of contributing to our -docs (**learn.netdata.cloud**) +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: - - - - - - ```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? - - ``` - - - - - ```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. - ``` - - - +Our documentation in is generated by markdown documents in the public Github repositories of the "netdata" organization. - ```markdown - Small intro, give some context to the user of what you will cover on this document +The structure of the documentation is handled by a [map](https://github.com/netdata/learn/blob/master/map.tsv) file, that contains metadata for every markdown files in the repos we ingest from. - ### Reference name (omit if the document describes only one reference) +Then the ingest script parses that map and organizes the markdown files accordingly. - #### Requirements - - Document any dependencies needed to run this module +### Improve existing documentation - #### Requirements on the monitored component +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. - Document any steps user must take to sucessful monitor application, - for instance (create a user) +Each published document on [Netdata Learn](https://learn.netdata.cloud) includes at the bottom a link to **Edit this page**. +Clicking on that link is the recommended way to improve our documentation, as it leads you directly to GitHub's code editor. +Make your suggested changes, and use the ***Preview changes*** button to ensure your Markdown syntax works as expected. - #### 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 ` - - #### 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) - - -``` - - - - -### 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,
`Unpublished`: Document archived in learn,
`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
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). +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. +### Create a new document -- 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. +You can create a pull request to add a completely new markdown document in any of our public repositories. +After the Github pull request is merged, our documentation team will decide where in the documentation hierarchy to publish that document. -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. +If you wish to contribute documentation that is tailored to your specific infrastructure monitoring/troubleshooting experience, please consider submitting a blog post about your experience. -#### MDX and markdown +Check out our [blog](https://github.com/netdata/blog#readme) repo! Any blog submissions that have widespread or universal application will be integrated into our permanent documentation. -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. +#### Before you get started -In detail: +Anyone interested in contributing significantly to documentation should first read the [Netdata style 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). -Due to a bug with docusaurus, we prefer to use `

heading

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). +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. -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. +#### Edit locally -#### Admonitions +Editing documentation locally is the preferred method for completely new documents, or complex changes that span multiple documents. Clone the repository where you wish to make your changes, work on a new branch and create a pull request with that branch. -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. +### Links to other documents -You can find the supported admonitions in the docusaurus's [documentation](https://docusaurus.io/docs/markdown-features/admonitions). +Please ensure that any links to a different documentation resource are fully expanded URLs to the relevant markdown document, not links to "learn.netdata.cloud". -#### Images +e.g. -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); -} +[Correct link to this document](https://github.com/netdata/netdata/blob/master/docs/guidelines.md) +vs +[Incorrect link to this document](https://learn.netdata.cloud/XYZ) ``` -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: +This permalink ensures that the link will not be broken by any future restructuring in learn.netdata.cloud. -- 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). +You can see the URL to the source of any published documentation page in the **Edit this page** link at the bottom. +If you just replace `edit` with `blob` in that URL, you have the permalink to the original markdown document. -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
Service Discovery and Auto-Detection:
Install netdata with docker | -| ** -Recommended** | Getting started guide
Service discovery and auto-detection
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.
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.
Run `systemctl restart netdata` to restart the Netdata Agent. | -| ** -Recommended** | Run `sudo edit-config netdata.conf` to configure the Netdata Agent.
Run `sudo systemctl restart netdata` to restart the Netdata Agent. | - -## Deploy and test docs - - +### Making a pull request -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). +Pull requests (PRs) should be concise and informative. +See our [PR guidelines](https://github.com/netdata/.github/blob/main/CONTRIBUTING.md#pr-guidelines) for specifics. -## Netdata-specific terms +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. -Consult the [Netdata Glossary](https://github.com/netdata/netdata/blob/master/docs/glossary.md) Netdata specific terms \ No newline at end of file +After merging your PR, the Netdata team rebuilds the [documentation site](https://learn.netdata.cloud) to publish the changed documentation. diff --git a/docs/guides/collect-apache-nginx-web-logs.md b/docs/guides/collect-apache-nginx-web-logs.md index b4a52547..e9b38c27 100644 --- a/docs/guides/collect-apache-nginx-web-logs.md +++ b/docs/guides/collect-apache-nginx-web-logs.md @@ -1,16 +1,8 @@ - +# Monitor Nginx or Apache web server log files -# Monitor Nginx or Apache web server log files with Netdata +Parsing web server log files with Netdata, revealing the volume of redirects, requests and other metrics, can give you a better overview of your infrastructure. -Log files have been a critical resource for developers and system administrators who want to understand the health and -performance of their web servers, and Netdata is taking important steps to make them even more valuable. - -By parsing web server log files with Netdata, and seeing the volume of redirects, requests, or server errors over time, -you can better understand what's happening on your infrastructure. Too many bad requests? Maybe a recent deploy missed a -few small SVG icons. Too many requests? Time to batten down the hatches—it's a DDoS. +Too many bad requests? Maybe a recent deploy missed a few small SVG icons. Too many requests? Time to batten down the hatches—it's a DDoS. You can use the [LTSV log format](http://ltsv.org/), track TLS and cipher usage, and the whole parser is faster than ever. In one test on a system with SSD storage, the collector consistently parsed the logs for 200,000 requests in @@ -116,12 +108,5 @@ You can also edit this file directly with `edit-config`: ./edit-config health.d/weblog.conf ``` -For more information about editing the defaults or writing new alarm entities, see our [health monitoring -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://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. +For more information about editing the defaults or writing new alarm entities, see our +[health monitoring documentation](https://github.com/netdata/netdata/blob/master/health/README.md). diff --git a/docs/guides/collect-unbound-metrics.md b/docs/guides/collect-unbound-metrics.md index 5400fd83..c5f4deb5 100644 --- a/docs/guides/collect-unbound-metrics.md +++ b/docs/guides/collect-unbound-metrics.md @@ -1,7 +1,11 @@ # Monitor Unbound DNS servers with Netdata diff --git a/docs/guides/configure/performance.md b/docs/guides/configure/performance.md index 256d6e85..2e5e105f 100644 --- a/docs/guides/configure/performance.md +++ b/docs/guides/configure/performance.md @@ -1,110 +1,101 @@ - - # How to optimize the Netdata Agent's performance We designed the Netdata Agent to be incredibly lightweight, even when it's collecting a few thousand dimensions every -second and visualizing that data into hundreds of charts. When properly configured for a production node, the Agent -itself should never use more than 1% of a single CPU core, roughly 50-100 MiB of RAM, and minimal disk I/O to collect, -store, and visualize all this data. - -We take this scalability seriously. We have one user [running -Netdata](https://github.com/netdata/netdata/issues/1323#issuecomment-266427841) on a system with 144 cores and 288 -threads. Despite collecting 100,000 metrics every second, the Agent still only uses 9% CPU utilization on a -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](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. +second and visualizing that data into hundreds of charts. However, the default settings of the Netdata Agent are not +optimized for performance, but for a simple, standalone setup. We want the first install to give you something you can +run without any configuration. Most of the settings and options are enabled, since we want you to experience the full thing. -The default settings of the Netdata Agent are not optimized for performance, but for a simple standalone setup. We want -the first install to give you something you can run without any configuration. Most of the settings and options are -enabled, since we want you to experience the full thing. +By default, Netdata will automatically detect applications running on the node it is installed to start collecting metrics in +real-time, has health monitoring enabled to evaluate alerts and trains Machine Learning (ML) models for each metric, to detect anomalies. +This document describes the resources required for the various default capabilities and the strategies to optimize Netdata for production use. -## Prerequisites +## Summary of performance optimizations -- A node running the Netdata Agent. -- Familiarity with configuring the Netdata Agent with `edit-config`. +The following table summarizes the effect of each optimization on the CPU, RAM and Disk IO utilization in production. -If you're not familiar with how to configure the Netdata Agent, read our [node configuration -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. +Optimization | CPU | RAM | Disk IO +-- | -- | -- |-- +[Use streaming and replication](#use-streaming-and-replication) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: +[Disable unneeded plugins or collectors](#disable-unneeded-plugins-or-collectors) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: +[Reduce data collection frequency](#reduce-collection-frequency) | :heavy_check_mark: | | :heavy_check_mark: +[Change how long Netdata stores metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) | | :heavy_check_mark: | :heavy_check_mark: +[Use a different metric storage database](https://github.com/netdata/netdata/blob/master/database/README.md) | | :heavy_check_mark: | :heavy_check_mark: +[Disable machine learning](#disable-machine-learning) | :heavy_check_mark: | | +[Use a reverse proxy](#run-netdata-behind-a-proxy) | :heavy_check_mark: | | +[Disable/lower gzip compression for the agent dashboard](#disablelower-gzip-compression-for-the-dashboard) | :heavy_check_mark: | | -## What affects Netdata's performance? +## Resources required by a default Netdata installation Netdata's performance is primarily affected by **data collection/retention** and **clients accessing data**. -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](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). +You can configure almost all aspects of data collection/retention, and certain aspects of clients accessing data. + +### CPU consumption + +Expect about: + - 1-3% of a single core for the netdata core + - 1-3% of a single core for the various collectors (e.g. go.d.plugin, apps.plugin) + - 5-10% of a single core, when ML training runs -The Netdata Agent runs with the lowest possible [process scheduling -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. +Your experience may vary depending on the number of metrics collected, the collectors enabled and the specific environment they +run on, i.e. the work they have to do to collect these metrics. + +As a general rule, for modern hardware and VMs, the total CPU consumption of a standalone Netdata installation, including all its components, +should be below 5 - 15% of a single core. For example, on 8 core server it will use only 0.6% - 1.8% of a total CPU capacity, depending on +the CPU characteristics. + +The Netdata Agent runs with the lowest possible [process scheduling 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. -Let's walk through the best ways to improve the Netdata Agent's performance. +To reduce CPU usage you can [disable machine learning](#disable-machine-learning), +[use streaming and replication](#use-streaming-and-replication), +[reduce the data collection frequency](#reduce-collection-frequency), [disable unneeded plugins or collectors](#disable-unneeded-plugins-or-collectors), [use a reverse proxy](#run-netdata-behind-a-proxy), and [disable/lower gzip compression for the agent dashboard](#disablelower-gzip-compression-for-the-dashboard). -## Reduce collection frequency +### Memory consumption -The fastest way to improve the Agent's resource utilization is to reduce how often it collects metrics. +The memory footprint of Netdata is mainly influenced by the number of metrics concurrently being collected. Expect about 150MB of RAM for a typical 64-bit server collecting about 2000 to 3000 metrics. -### Global +To estimate and control memory consumption, you can [disable unneeded plugins or collectors](#disable-unneeded-plugins-or-collectors), [change how long Netdata stores metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md), or [use a different metric storage database](https://github.com/netdata/netdata/blob/master/database/README.md). -If you don't need per-second metrics, or if the Netdata Agent uses a lot of CPU even when no one is viewing that node's -dashboard, configure the Agent to collect metrics less often. -Open `netdata.conf` and edit the `update every` setting. The default is `1`, meaning that the Agent collects metrics -every second. +### Disk footprint and I/O -If you change this to `2`, Netdata enforces a minimum `update every` setting of 2 seconds, and collects metrics every -other second, which will effectively halve CPU utilization. Set this to `5` or `10` to collect metrics every 5 or 10 -seconds, respectively. +By default, Netdata should not use more than 1GB of disk space, most of which is dedicated for storing metric data and metadata. For typical installations collecting 2000 - 3000 metrics, this storage should provide a few days of high-resolution retention (per second), about a month of mid-resolution retention (per minute) and more than a year of low-resolution retention (per hour). -```conf -[global] - update every = 5 -``` +Netdata spreads I/O operations across time. For typical standalone installations there should be a few write operations every 5-10 seconds of a few kilobytes each, occasionally up to 1MB. In addition, under heavy load, collectors that require disk I/O may stop and show gaps in charts. -### Specific plugin or collector +To configure retention, you can [change how long Netdata stores metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md). +To control disk I/O [use a different metric storage database](https://github.com/netdata/netdata/blob/master/database/README.md), avoid querying the +production system [using streaming and replication](#use-streaming-and-replication), [reduce the data collection frequency](#reduce-collection-frequency), and [disable unneeded plugins or collectors](#disable-unneeded-plugins-or-collectors). -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](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) doc for details. +## Use streaming and replication -To reduce the frequency of an [internal -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: +For all production environments, parent Netdata nodes outside the production infrastructure should be receiving all +collected data from children Netdata nodes running on the production infrastructure, using [streaming and replication](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.md). -```conf -[plugin:apps] - update every = 5 -``` +### Disable health checks on the child nodes -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`: +When you set up streaming, we recommend you run your health checks on the parent. This saves resources on the children +and makes it easier to configure or disable alerts and agent notifications. + +The parents by default run health checks for each child, as long as the child is connected (the details are in `stream.conf`). +On the child nodes you should add to `netdata.conf` the following: ```conf -# [ GLOBAL ] -update_every: 10 +[health] + enabled = no ``` +### Use memory mode ram or save for the child nodes + +See [using a different metric storage database](https://github.com/netdata/netdata/blob/master/database/README.md). + ## Disable unneeded plugins or collectors If you know that you don't need an [entire plugin or a specific -collector](https://github.com/netdata/netdata/blob/master/docs/collect/how-collectors-work.md#collector-architecture-and-terminology), you can disable any of them. +collector](https://github.com/netdata/netdata/blob/master/collectors/README.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. @@ -137,42 +128,60 @@ modules: fail2ban: no ``` -## Lower memory usage for metrics retention +## Reduce collection frequency + +The fastest way to improve the Agent's resource utilization is to reduce how often it collects metrics. + +### Global + +If you don't need per-second metrics, or if the Netdata Agent uses a lot of CPU even when no one is viewing that node's +dashboard, [configure the Agent](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) to collect metrics less often. + +Open `netdata.conf` and edit the `update every` setting. The default is `1`, meaning that the Agent collects metrics +every second. + +If you change this to `2`, Netdata enforces a minimum `update every` setting of 2 seconds, and collects metrics every +other second, which will effectively halve CPU utilization. Set this to `5` or `10` to collect metrics every 5 or 10 +seconds, respectively. -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. +```conf +[global] + update every = 5 +``` -The `page cache size` option also directly impacts Netdata's memory usage, but has a minimum value of `32`. +### Specific plugin or collector -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](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md#calculate-the-system-resources-ram-disk-space-needed-to-store-metrics). +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 [collectors configuration reference](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md) for details. -All the settings are found in the `[global]` section of `netdata.conf`: +To reduce the frequency of an [internal +plugin/collector](https://github.com/netdata/netdata/blob/master/collectors/README.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: ```conf -[db] - memory mode = dbengine - page cache size = 32 - dbengine multihost disk space = 256 +[plugin:apps] + update every = 5 ``` -To save even more memory, you can disable the dbengine and reduce retention to just 30 minutes, as shown below: +To [configure an individual collector](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md#configure-a-collector), 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`: ```conf -[db] - storage tiers = 1 - mode = alloc - retention = 1800 +# [ GLOBAL ] +update_every: 10 ``` -Metric retention is not important in certain use cases, such as: - - Data collection nodes stream collected metrics collected to a centralization point. - - Data collection nodes export their metrics to another time series DB, or are scraped by Prometheus - - Netdata installed only during incidents, to get richer information. -In such cases, you may not want to use the dbengine at all and instead opt for memory mode -`memory mode = alloc` or `memory mode = none`. +## Lower memory usage for metrics retention + +See how to [change how long Netdata stores metrics](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md). + +## Use a different metric storage database + +Consider [using a different metric storage database](https://github.com/netdata/netdata/blob/master/database/README.md) when running Netdata on IoT devices, +and for children in a parent-child set up based on [streaming and replication](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.md). ## Disable machine learning @@ -185,34 +194,12 @@ with the following: enabled = no ``` -## Run Netdata behind Nginx +## Run Netdata behind a proxy -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). +A dedicated web server like nginx provides 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](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: - -```conf - location / { - ... - gzip on; - gzip_proxied any; - gzip_types *; - } -``` - -Finally, edit `netdata.conf` with the following settings: - -```conf -[global] - bind socket to IP = 127.0.0.1 - disconnect idle web clients after seconds = 3600 - enable web responses gzip compression = no -``` +For details on installing another web server as a proxy for the local Agent dashboard, see [reverse proxies](https://github.com/netdata/netdata/blob/master/docs/category-overview-pages/reverse-proxies.md). ## Disable/lower gzip compression for the dashboard @@ -235,43 +222,3 @@ Or to lower the default compression level: gzip compression level = 1 ``` -## Disable logs - -If you installation is working correctly, and you're not actively auditing Netdata's logs, disable them in -`netdata.conf`. - -```conf -[logs] - debug log = none - error log = none - access log = none -``` - -## Disable health checks - -If you are streaming metrics to parent nodes, we recommend you run your health checks on the parent, for all the metrics collected -by the children nodes. This saves resources on the children and makes it easier to configure or disable alerts and agent notifications. - -The parents by default run health checks for each child, as long as it is connected (the details are in `stream.conf`). -On the child nodes you should add to `netdata.conf` the following: - -```conf -[health] - enabled = no -``` - -## What's next? - -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](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](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 deleted file mode 100644 index 0472bdc6..00000000 --- a/docs/guides/deploy/ansible.md +++ /dev/null @@ -1,180 +0,0 @@ - - -# Deploy Netdata with Ansible - -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. - -But what if you're trying to bootstrap an infrastructure monitoring solution as quickly as possible? What if you need to -deploy Netdata across an entire infrastructure with many nodes? What if you want to make this deployment reliable, -repeatable, and idempotent? What if you want to write and deploy your infrastructure or cloud monitoring system like -code? - -Enter [Ansible](https://ansible.com), a popular system provisioning, configuration management, and infrastructure as -code (IaC) tool. Ansible uses **playbooks** to glue many standardized operations together with a simple syntax, then run -those operations over standard and secure SSH connections. There's no agent to install on the remote system, so all you -have to worry about is your application and your monitoring software. - -Ansible has some competition from the likes of [Puppet](https://puppet.com/) or [Chef](https://www.chef.io/), but the -most valuable feature about Ansible is **idempotent**. From the [Ansible -glossary](https://docs.ansible.com/ansible/latest/reference_appendices/glossary.html) - -> An operation is idempotent if the result of performing it once is exactly the same as the result of performing it -> repeatedly without any intervening actions. - -Idempotency means you can run an Ansible playbook against your nodes any number of times without affecting how they -operate. When you deploy Netdata with Ansible, you're also deploying _monitoring as code_. - -In this guide, we'll walk through the process of using an [Ansible -playbook](https://github.com/netdata/community/tree/main/netdata-agent-deployment/ansible-quickstart) to automatically -deploy the Netdata Agent to any number of distributed nodes, manage the configuration of each node, and connect them to -your Netdata Cloud account. You'll go from some unmonitored nodes to a infrastructure monitoring solution in a matter of -minutes. - -## Prerequisites - -- A Netdata Cloud account. [Sign in and create one](https://app.netdata.cloud) if you don't have one already. -- An administration system with [Ansible](https://www.ansible.com/) installed. -- One or more nodes that your administration system can access via [SSH public - keys](https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key) (preferably password-less). - -## Download and configure the playbook - -First, download the -[playbook](https://github.com/netdata/community/tree/main/netdata-agent-deployment/ansible-quickstart), move it to the -current directory, and remove the rest of the cloned repository, as it's not required for using the Ansible playbook. - -```bash -git clone https://github.com/netdata/community.git -mv community/netdata-agent-deployment/ansible-quickstart . -rm -rf community -``` - -Or if you don't want to clone the entire repository, use the [gitzip browser extension](https://gitzip.org/) to get the netdata-agent-deployment directory as a zip file. - -Next, `cd` into the Ansible directory. - -```bash -cd ansible-quickstart -``` - -### Edit the `hosts` file - -The `hosts` file contains a list of IP addresses or hostnames that Ansible will try to run the playbook against. The -`hosts` file that comes with the repository contains two example IP addresses, which you should replace according to the -IP address/hostname of your nodes. - -```conf -203.0.113.0 hostname=node-01 -203.0.113.1 hostname=node-02 -``` - -You can also set the `hostname` variable, which appears both on the local Agent dashboard and Netdata Cloud, or you can -omit the `hostname=` string entirely to use the system's default hostname. - -#### Set the login user (optional) - -If you SSH into your nodes as a user other than `root`, you need to configure `hosts` according to those user names. Use -the `ansible_user` variable to set the login user. For example: - -```conf -203.0.113.0 hostname=ansible-01 ansible_user=example -``` - -#### Set your SSH key (optional) - -If you use an SSH key other than `~/.ssh/id_rsa` for logging into your nodes, you can set that on a per-node basis in -the `hosts` file with the `ansible_ssh_private_key_file` variable. For example, to log into a Lightsail instance using -two different SSH keys supplied by AWS. - -```conf -203.0.113.0 hostname=ansible-01 ansible_ssh_private_key_file=~/.ssh/LightsailDefaultKey-us-west-2.pem -203.0.113.1 hostname=ansible-02 ansible_ssh_private_key_file=~/.ssh/LightsailDefaultKey-us-east-1.pem -``` - -### 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](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, -then click on **Manage your Space**. Click on the **Nodes** tab in the panel that appears, which displays a script with -`token` and `room` strings. - -![Animated GIF of finding the claiming script and the token and room -strings](https://user-images.githubusercontent.com/1153921/98740235-f4c3ac00-2367-11eb-8ffd-e9ab0f04c463.gif) - -Copy those strings into the `claim_token` and `claim_rooms` variables. - -```yml -claim_token: XXXXX -claim_rooms: XXXXX -``` - -Change the `dbengine_multihost_disk_space` if you want to change the metrics retention policy by allocating more or less -disk space for storing metrics. The default is 2048 Mib, or 2 GiB. - -Because we're connecting this node to Netdata Cloud, and will view its dashboards there instead of via the IP address or -hostname of the node, the playbook disables that local dashboard by setting `web_mode` to `none`. This gives a small -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://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. - -## Run the playbook - -Time to run the playbook from your administration system: - -```bash -ansible-playbook -i hosts tasks/main.yml -``` - -Ansible first connects to your node(s) via SSH, then [collects -facts](https://docs.ansible.com/ansible/latest/user_guide/playbooks_vars_facts.html#ansible-facts) about the system. -This playbook doesn't use these facts, but you could expand it to provision specific types of systems based on the -makeup of your infrastructure. - -Next, Ansible makes changes to each node according to the `tasks` defined in the playbook, and -[returns](https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#changed) whether each -task results in a changed, failure, or was skipped entirely. - -The task to install Netdata will take a few minutes per node, so be patient! Once the playbook reaches the connect to Cloud -task, your nodes start populating your Space in Netdata Cloud. - -## What's next? - -Go use Netdata! - -If you need a bit more guidance for how you can use Netdata for health monitoring and performance troubleshooting, see -our [documentation](https://learn.netdata.cloud/docs). It's designed like a comprehensive guide, based on what you might -want to do with Netdata, so use those categories to dive in. - -Some of the best places to start: - -- [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 -repo](https://github.com/netdata/community). Anyone is able to fork the repo and submit a PR, either to improve this -playbook, extend it, or create an entirely new experience for deploying Netdata across entire infrastructure. - - diff --git a/docs/guides/export/export-netdata-metrics-graphite.md b/docs/guides/export/export-netdata-metrics-graphite.md deleted file mode 100644 index 985ba224..00000000 --- a/docs/guides/export/export-netdata-metrics-graphite.md +++ /dev/null @@ -1,181 +0,0 @@ - -import { OneLineInstallWget } from '@site/src/components/OneLineInstall/' - -# Export and visualize Netdata metrics in Graphite - -Collecting metrics is an essential part of monitoring any application, service, or infrastructure, but it's not the -final step for any developer, sysadmin, SRE, or DevOps engineer who's keeping an eye on things. To take meaningful -action on these metrics, you may need to develop a stack of monitoring tools that work in parallel to help you diagnose -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](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 -time-series data, such as all the metrics collected by the Netdata Agent itself. Using Netdata and Graphite together, -you get more visibility into the health and performance of your entire infrastructure. - -![A custom dashboard in Grafana with Netdata -metrics](https://user-images.githubusercontent.com/1153921/83903855-b8828480-a713-11ea-8edb-927ba521599b.png) - -Let's get started. - -## Install the Netdata Agent - -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: - - - -Once installation finishes, open your browser and navigate to `http://NODE:19999`, replacing `NODE` with the IP address -or hostname of your system, to find the Agent dashboard. - -## Install Graphite via Docker - -For this guide, we'll install Graphite using Docker. See the [Docker documentation](https://docs.docker.com/get-docker/) -for details if you don't yet have it installed on your system. - -> If you already have Graphite installed, skip this step. If you want to install via a different method, see the -> [Graphite installation docs](https://graphite.readthedocs.io/en/latest/install.html), with the caveat that some -> configuration settings may be different. - -Start up the Graphite image with `docker run`. - -```bash -docker run -d \ - --name graphite \ - --restart=always \ - -p 80:80 \ - -p 2003-2004:2003-2004 \ - -p 2023-2024:2023-2024 \ - -p 8125:8125/udp \ - -p 8126:8126 \ - graphiteapp/graphite-statsd -``` - -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) - -## Enable the Graphite exporting connector - -You're now ready to begin exporting Netdata metrics to Graphite. - -Begin by using `edit-config` to open the `exporting.conf` file. - -```bash -cd /etc/netdata # Replace this path with your Netdata config directory -sudo ./edit-config exporting.conf -``` - -If you haven't already, enable the exporting engine by setting `enabled` to `yes` in the `[exporting:global]` section. - -```conf -[exporting:global] - enabled = yes -``` - -Next, configure the connector. Find the `[graphite:my_graphite_instance]` example section and uncomment the line. -Replace `my_graphite_instance` with a name of your choice. Let's go with `[graphite:netdata]`. Set `enabled` to `yes` -and uncomment the line. Your configuration should now look like this: - -```conf -[graphite:netdata] - enabled = yes - # destination = localhost - # data source = average - # prefix = netdata - # hostname = my_hostname - # update every = 10 - # buffer on failures = 10 - # timeout ms = 20000 - # send names instead of ids = yes - # send charts matching = * - # send hosts matching = localhost * -``` - -Set the `destination` setting to `localhost:2003`. By default, the Docker image for Graphite listens on port `2003` for -incoming metrics. If you installed Graphite a different way, or tweaked the `docker run` command, you may need to change -the port accordingly. - -```conf -[graphite:netdata] - enabled = yes - destination = localhost:2003 - ... -``` - -We'll not worry about the rest of the settings for now. Restart the Agent using `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 spin up the exporting engine. - -## See and organize Netdata metrics in Graphite - -Head back to the Graphite interface again, then click on the **Dashboard** link to get started with Netdata's exported -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) - -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`. - -Let's add some system CPU charts so you can monitor the long-term health of your system. Click through the tree to find -**hostname → system → cpu** metrics, then click on the **user** context. A chart with metrics from that context appears -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) - -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). - -## 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) - -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://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) - -## 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. - -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/longer-metrics-storage.md b/docs/guides/longer-metrics-storage.md deleted file mode 100644 index 8ccd9585..00000000 --- a/docs/guides/longer-metrics-storage.md +++ /dev/null @@ -1,158 +0,0 @@ - - -# Netdata Longer Metrics Retention - -Metrics retention affects 3 parameters on the operation of a Netdata Agent: - -1. The disk space required to store the metrics. -2. The memory the Netdata Agent will require to have that retention available for queries. -3. The CPU resources that will be required to query longer time-frames. - -As retention increases, the resources required to support that retention increase too. - -Since Netdata Agents usually run at the edge, inside production systems, Netdata Agent **parents** should be considered. When having a **parent - child** setup, the child (the Netdata Agent running on a production system) delegates all 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). - - -## Ephemerality of metrics - -The ephemerality of metrics plays an important role in retention. In environments where metrics stop being collected and new metrics are constantly being generated, we are interested about 2 parameters: - -1. The **expected concurrent number of metrics** as an average for the lifetime of the database. - This affects mainly the storage requirements. - -2. The **expected total number of unique metrics** for the lifetime of the database. - This affects mainly the memory requirements for having all these metrics indexed and available to be queried. - -## Granularity of metrics - -The granularity of metrics (the frequency they are collected and stored, i.e. their resolution) is significantly affecting retention. - -Lowering the granularity from per second to every two seconds, will double their retention and half the CPU requirements of the Netdata Agent, without affecting disk space or memory requirements. - -## Which database mode to use - -Netdata Agents support multiple database modes. - -The default mode `[db].mode = dbengine` has been designed to scale for longer retentions. - -The other available database modes are designed to minimize resource utilization and should usually be considered on **parent - child** setups at the children side. - -So, - -* On a single node setup, use `[db].mode = dbengine` to increase retention. -* On a **parent - child** setup, use `[db].mode = dbengine` on the parent to increase retention and a more resource efficient mode (like `save`, `ram` or `none`) for the child to minimize resources utilization. - -To use `dbengine`, set this in `netdata.conf` (it is the default): - -``` -[db] - mode = dbengine -``` - -## Tiering - -`dbengine` supports tiering. Tiering allows having up to 3 versions of the data: - -1. Tier 0 is the high resolution data. -2. Tier 1 is the first tier that samples data every 60 data collections of Tier 0. -3. Tier 2 is the second tier that samples data every 3600 data collections of Tier 0 (60 of Tier 1). - -To enable tiering set `[db].storage tiers` in `netdata.conf` (the default is 1, to enable only Tier 0): - -``` -[db] - mode = dbengine - storage tiers = 3 -``` - -## Disk space requirements - -Netdata Agents require about 1 bytes on disk per database point on Tier 0 and 4 times more on higher tiers (Tier 1 and 2). They require 4 times more storage per point compared to Tier 0, because for every point higher tiers store `min`, `max`, `sum`, `count` and `anomaly rate` (the values are 5, but they require 4 times the storage because `count` and `anomaly rate` are 16-bit integers). The `average` is calculated on the fly at query time using `sum / count`. - -### Tier 0 - per second for a week - -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. - -The setting to control this is in `netdata.conf`: - -``` -[db] - mode = dbengine - - # per second data collection - update every = 1 - - # enable only Tier 0 - storage tiers = 1 - - # Tier 0, per second data for a week - dbengine multihost disk space MB = 1100 -``` - -By setting it to `1100` and restarting the Netdata Agent, 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. - -### Tier 1 - per minute for a month - -Tier 1 is by default sampling the data every 60 points of Tier 0. If Tier 0 is per second, then Tier 1 is per minute. - -Tier 1 needs 4 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. - -Do this in `netdata.conf`: - -``` -[db] - mode = dbengine - - # per second data collection - update every = 1 - - # enable only Tier 0 and Tier 1 - storage tiers = 2 - - # Tier 0, per second data for a week - dbengine multihost disk space MB = 1100 - - # Tier 1, per minute data for a month - dbengine tier 1 multihost disk space MB = 330 -``` - -Once `netdata.conf` is edited, the Netdata Agent needs to be restarted for the changes to take effect. - -### Tier 2 - per hour for a year - -Tier 2 is by default sampling data every 3600 points of Tier 0 (60 of Tier 1). If Tier 0 is per second, then Tier 2 is per hour. - -The storage requirements are the same to Tier 1. - -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. - -Do this in `netdata.conf`: - -``` -[db] - mode = dbengine - - # per second data collection - update every = 1 - - # enable only Tier 0 and Tier 1 - storage tiers = 3 - - # Tier 0, per second data for a week - dbengine multihost disk space MB = 1100 - - # Tier 1, per minute data for a month - dbengine tier 1 multihost disk space MB = 330 - - # Tier 2, per hour data for a year - dbengine tier 2 multihost disk space MB = 67 -``` - -Once `netdata.conf` is edited, the Netdata Agent needs to be restarted for the changes to take effect. - - - diff --git a/docs/guides/monitor-cockroachdb.md b/docs/guides/monitor-cockroachdb.md index 3c6e1b2c..ea94d7a0 100644 --- a/docs/guides/monitor-cockroachdb.md +++ b/docs/guides/monitor-cockroachdb.md @@ -1,6 +1,10 @@ # Monitor CockroachDB metrics with Netdata @@ -20,9 +24,11 @@ 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) +- [Monitor CockroachDB metrics with Netdata](#monitor-cockroachdb-metrics-with-netdata) + - [What's in this guide](#whats-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) + - [Tweak CockroachDB alarms](#tweak-cockroachdb-alarms) ## Configure the CockroachDB collector @@ -109,25 +115,4 @@ cd /etc/netdata/ # Replace with your Netdata configuration directory, if not /et ./edit-config health.d/cockroachdb.conf # You may need to use `sudo` for write privileges ``` -For more information about editing the defaults or writing new alarm entities, see our health monitoring [quickstart -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 -thoughts. - -Also, be sure to check out these useful resources: - -- [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) - - +For more information about editing the defaults or writing new alarm entities, see our documentation on [configuring health alarms](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md). diff --git a/docs/guides/monitor-hadoop-cluster.md b/docs/guides/monitor-hadoop-cluster.md index cce261fe..91282b95 100644 --- a/docs/guides/monitor-hadoop-cluster.md +++ b/docs/guides/monitor-hadoop-cluster.md @@ -1,6 +1,10 @@ # Monitor a Hadoop cluster with Netdata @@ -184,20 +188,5 @@ sudo /etc/netdata/edit-config health.d/hdfs.conf 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](https://github.com/netdata/netdata/blob/master/health/README.md). - -## What's next? - -If you're having issues with Netdata auto-detecting your HDFS/Zookeeper servers, or want to help improve how Netdata -collects or presents metrics from these services, feel free to [file an -issue](https://github.com/netdata/netdata/issues/new?assignees=&labels=bug%2Cneeds+triage&template=BUG_REPORT.yml). - -- Read up on the [HDFS configuration - file](https://github.com/netdata/go.d.plugin/blob/master/config/go.d/hdfs.conf) to understand how to configure - global options or per-job options, such as username/password, TLS certificates, timeouts, and more. -- Read up on the [Zookeeper configuration - file](https://github.com/netdata/go.d.plugin/blob/master/config/go.d/zookeeper.conf) to understand how to configure - global options or per-job options, timeouts, TLS certificates, and more. - - +For more information about editing the defaults or writing new alarm entities, see our +[health monitoring documentation](https://github.com/netdata/netdata/blob/master/health/README.md). diff --git a/docs/guides/monitor/anomaly-detection-python.md b/docs/guides/monitor/anomaly-detection-python.md deleted file mode 100644 index d6d27f4e..00000000 --- a/docs/guides/monitor/anomaly-detection-python.md +++ /dev/null @@ -1,189 +0,0 @@ - - -# Detect anomalies in systems and applications - -Beginning with v1.27, the [open-source Netdata Agent](https://github.com/netdata/netdata) is capable of unsupervised -[anomaly detection](https://en.wikipedia.org/wiki/Anomaly_detection) with machine learning (ML). As with all things -Netdata, the anomalies collector comes with preconfigured alarms and instant visualizations that require no query -languages or organizing metrics. You configure the collector to look at specific charts, and it handles the rest. - -Netdata's implementation uses a handful of functions in the [Python Outlier Detection (PyOD) -library](https://github.com/yzhao062/pyod/tree/master), which periodically runs a `train` function that learns what -"normal" looks like on your node and creates an ML model for each chart, then utilizes the -[`predict_proba()`](https://pyod.readthedocs.io/en/latest/api_cc.html#pyod.models.base.BaseDetector.predict_proba) and -[`predict()`](https://pyod.readthedocs.io/en/latest/api_cc.html#pyod.models.base.BaseDetector.predict) PyOD functions to -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](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 -and/or the node that hosts it, which will give you the tools to configure parallel unsupervised monitors for any -application in your infrastructure. Let's get started. - -![Example anomaly detection with an Nginx web -server](https://user-images.githubusercontent.com/1153921/103586700-da5b0a00-4ea2-11eb-944e-46edd3f83e3a.png) - -## Prerequisites - -- 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`](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 - -The anomalies collector uses a few Python packages, available with `pip3`, to run ML training. It requires -[`numba`](http://numba.pydata.org/), [`scikit-learn`](https://scikit-learn.org/stable/), -[`pyod`](https://pyod.readthedocs.io/en/latest/), in addition to -[`netdata-pandas`](https://github.com/netdata/netdata-pandas), which is a package built by the Netdata team to pull data -from a Netdata Agent's API into a [Pandas](https://pandas.pydata.org/). Read more about `netdata-pandas` on its [package -repo](https://github.com/netdata/netdata-pandas) or in Netdata's [community -repo](https://github.com/netdata/community/tree/main/netdata-agent-api/netdata-pandas). - -```bash -# Become the netdata user -sudo su -s /bin/bash netdata - -# Install required packages for the netdata user -pip3 install --user netdata-pandas==0.0.38 numba==0.50.1 scikit-learn==0.23.2 pyod==0.8.3 -``` - -> If the `pip3` command fails, you need to install it. For example, on an Ubuntu system, use `sudo apt install -> python3-pip`. - -Use `exit` to become your normal user again. - -## Enable the anomalies collector - -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 -sudo ./edit-config python.d.conf -``` - -In `python.d.conf` file, search for the `anomalies` line. If the line exists, set the value to `yes`. Add the line -yourself if it doesn't already exist. Either way, the final result should look like: - -```conf -anomalies: yes -``` - -[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. - -> 💡 The anomaly collector may need 30-60 seconds to finish its initial training and have enough data to start -> generating anomaly scores. You may need to refresh your browser tab for the **Anomalies** section to appear in menus -> on both the local Agent dashboard or Netdata Cloud. - -## Configure the anomalies collector - -Open `python.d/anomalies.conf` with `edit-conf`. - -```bash -sudo ./edit-config python.d/anomalies.conf -``` - -The file contains many user-configurable settings with sane defaults. Here are some important settings that don't -involve tweaking the behavior of the ML training itself. - -- `charts_regex`: Which charts to train models for and run anomaly detection on, with each chart getting a separate - model. -- `charts_to_exclude`: Specific charts, selected by the regex in `charts_regex`, to exclude. -- `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](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. - -> ⚠️ Setting `charts_regex` with many charts or `train_n_secs` to a very large number will have an impact on the -> resources and time required to train a model for every chart. The actual performance implications depend on the -> resources available on your node. If you plan on changing these settings beyond the default, or what's mentioned in -> this guide, make incremental changes to observe the performance impact. Considering `train_max_n` to cap the number of -> observations actually used to train on. - -### Run anomaly detection on Nginx and log file metrics - -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://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 -characters (`.*`). Note the escape character (`\`) around the first period to capture a period character exactly, and -not any character. - -Change `charts_regex` in `anomalies.conf` to the following: - -```conf - charts_regex: 'system\..*|nginx_local\..*|web_log_nginx\..*|apps.cpu|apps.mem' -``` - -This value tells the anomaly collector to train against every `system.` chart, every `nginx_local` chart, every -`web_log_nginx` chart, and specifically the `apps.cpu` and `apps.mem` charts. - -![The anomalies collector chart with many -dimensions](https://user-images.githubusercontent.com/1153921/102813877-db5e4880-4386-11eb-8040-d7a1d7a476bb.png) - -### Remove some metrics from anomaly detection - -As you can see in the above screenshot, this node is now looking for anomalies in many places. The result is a single -`anomalies_local.probability` chart with more than twenty dimensions, some of which the dashboard hides at the bottom of -a scrollable area. In addition, training and analyzing the anomaly collector on many charts might require more CPU -utilization that you're willing to give. - -First, explicitly declare which `system.` charts to monitor rather than of all of them using regex (`system\..*`). - -```conf - charts_regex: 'system\.cpu|system\.load|system\.io|system\.net|system\.ram|nginx_local\..*|web_log_nginx\..*|apps.cpu|apps.mem' -``` - -Next, remove some charts with the `charts_to_exclude` setting. For this example, using an Nginx web server, focus on the -volume of requests/responses, not, for example, which type of 4xx response a user might receive. - -```conf - charts_to_exclude: 'web_log_nginx.excluded_requests,web_log_nginx.responses_by_status_code_class,web_log_nginx.status_code_class_2xx_responses,web_log_nginx.status_code_class_4xx_responses,web_log_nginx.current_poll_uniq_clients,web_log_nginx.requests_by_http_method,web_log_nginx.requests_by_http_version,web_log_nginx.requests_by_ip_proto' -``` - -![The anomalies collector with less -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](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](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](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://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](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 ce819d93..4552e7a7 100644 --- a/docs/guides/monitor/anomaly-detection.md +++ b/docs/guides/monitor/anomaly-detection.md @@ -1,13 +1,14 @@ +# Machine learning (ML) powered anomaly detection ## Overview @@ -34,7 +35,7 @@ This guide will explain how to get started using these ML based anomaly detectio ## Anomaly Advisor -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. +The [Anomaly Advisor](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.md) 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) @@ -52,13 +53,13 @@ Pressing the anomalies icon (next to the information icon in the chart header) w ## Anomaly Rate Based Alerts -It is possible to use the `anomaly-bit` when defining traditional Alerts within netdata. The `anomaly-bit` is just another `options` parameter that can be passed as part of an [alarm line lookup](https://learn.netdata.cloud/docs/agent/health/reference#alarm-line-lookup). +It is possible to use the `anomaly-bit` when defining traditional Alerts within netdata. The `anomaly-bit` is just another `options` parameter that can be passed as part of an [alarm line lookup](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md#alarm-line-lookup). You can see some example ML based alert configurations below: -- [Anomaly rate based CPU dimensions alarm](https://learn.netdata.cloud/docs/agent/health/reference#example-8---anomaly-rate-based-cpu-dimensions-alarm) -- [Anomaly rate based CPU chart alarm](https://learn.netdata.cloud/docs/agent/health/reference#example-9---anomaly-rate-based-cpu-chart-alarm) -- [Anomaly rate based node level alarm](https://learn.netdata.cloud/docs/agent/health/reference#example-10---anomaly-rate-based-node-level-alarm) +- [Anomaly rate based CPU dimensions alarm](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md#example-8---anomaly-rate-based-cpu-dimensions-alarm) +- [Anomaly rate based CPU chart alarm](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md#example-9---anomaly-rate-based-cpu-chart-alarm) +- [Anomaly rate based node level alarm](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md#example-10---anomaly-rate-based-node-level-alarm) - More examples in the [`/health/health.d/ml.conf`](https://github.com/netdata/netdata/blob/master/health/health.d/ml.conf) file that ships with the agent. ## Learn More @@ -66,7 +67,7 @@ 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://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). +- [Anomaly Advisor documentation](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.md). - [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/). diff --git a/docs/guides/monitor/dimension-templates.md b/docs/guides/monitor/dimension-templates.md deleted file mode 100644 index d2795a9c..00000000 --- a/docs/guides/monitor/dimension-templates.md +++ /dev/null @@ -1,181 +0,0 @@ - - -# Use dimension templates to create dynamic alarms - -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](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](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](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](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. - -Let's get into some examples so you can see how the new parameter works. - -> ⚠️ The following entities are examples to showcase the functionality and syntax of dimension templates. They are not -> meant to be run as-is on production systems. - -## Condensing entities with `foreach` - -Let's say you want to monitor the `system`, `user`, and `nice` dimensions in your system's overall CPU utilization. -Before dimension templates, you would need the following three entities: - -```yaml - alarm: cpu_system - on: system.cpu -lookup: average -10m percentage of system - every: 1m - warn: $this > 50 - crit: $this > 80 - - alarm: cpu_user - on: system.cpu -lookup: average -10m percentage of user - every: 1m - warn: $this > 50 - crit: $this > 80 - - alarm: cpu_nice - on: system.cpu -lookup: average -10m percentage of nice - every: 1m - warn: $this > 50 - crit: $this > 80 -``` - -With dimension templates, you can condense these into a single alarm. Take note of the `alarm` and `lookup` lines. - -```yaml - alarm: cpu_template - on: system.cpu -lookup: average -10m percentage foreach system,user,nice - every: 1m - warn: $this > 50 - crit: $this > 80 -``` - -The `alarm` line specifies the naming scheme Netdata will use. You can use whatever naming scheme you'd like, with `.` -and `_` being the only allowed symbols. - -The `lookup` line has changed from `of` to `foreach`, and we're now passing three dimensions. - -In this example, Netdata will create three alarms with the names `cpu_template_system`, `cpu_template_user`, and -`cpu_template_nice`. Every minute, each alarm will use the same database query to calculate the average CPU usage for -the `system`, `user`, and `nice` dimensions over the last 10 minutes and send out alarms if necessary. - -You can find these three alarms active by clicking on the **Alarms** button in the top navigation, and then clicking on -the **All** tab and scrolling to the **system - cpu** collapsible section. - -![Three new alarms created from the dimension template](https://user-images.githubusercontent.com/1153921/66218994-29523800-e67f-11e9-9bcb-9bca23e2c554.png) - -Let's look at some other examples of how `foreach` works so you can best apply it in your configurations. - -### Using a Netdata simple pattern in `foreach` - -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](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 -wildcard as the simple pattern tells Netdata to create a separate alarm for _every_ process on your system: - -```yaml - alarm: app_cpu - on: apps.cpu -lookup: average -10m percentage foreach * - every: 1m - warn: $this > 50 - crit: $this > 80 -``` - -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](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md). - -## Using `foreach` with alarm templates - -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 -words, every CPU core. - -```yaml -template: cpu_template - on: cpu.cpu - lookup: average -10m percentage foreach system,user,nice - every: 1m - warn: $this > 50 - crit: $this > 80 -``` - -On a system with a 6-core, 12-thread Ryzen 5 1600 CPU, this one entity creates alarms on the following charts and -dimensions: - -- `cpu.cpu0` - - `cpu_template_user` - - `cpu_template_system` - - `cpu_template_nice` -- `cpu.cpu1` - - `cpu_template_user` - - `cpu_template_system` - - `cpu_template_nice` -- `cpu.cpu2` - - `cpu_template_user` - - `cpu_template_system` - - `cpu_template_nice` -- ... -- `cpu.cpu11` - - `cpu_template_user` - - `cpu_template_system` - - `cpu_template_nice` - -And how just a few of those dimension template-generated alarms look like in the Netdata dashboard. - -![A few of the created alarms in the Netdata dashboard](https://user-images.githubusercontent.com/1153921/66219669-708cf880-e680-11e9-8b3a-7bfe178fa28b.png) - -All in all, this single entity creates 36 individual alarms. Much easier than writing 36 separate entities in your -health configuration files! - -## What's next? - -We hope you're excited about the possibilities of using dimension templates! Maybe they'll inspire you to build new -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](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 5732fc96..96d79935 100644 --- a/docs/guides/monitor/kubernetes-k8s-netdata.md +++ b/docs/guides/monitor/kubernetes-k8s-netdata.md @@ -1,14 +1,6 @@ - - -# Kubernetes monitoring with Netdata: Overview and visualizations +# Kubernetes monitoring with Netdata + +This document gives an overview of what visualizations Netdata provides on Kubernetes deployments. At Netdata, we've built Kubernetes monitoring tools that add visibility without complexity while also helping you actively troubleshoot anomalies or outages. This guide walks you through each of the visualizations and offers best @@ -140,7 +132,7 @@ visualizations](https://user-images.githubusercontent.com/1153921/109049195-349f ### Health map -The first visualization is the [health map](https://learn.netdata.cloud/docs/cloud/visualize/kubernetes#health-map), +The first visualization is the [health map](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/kubernetes.md#health-map), which places each container into its own box, then varies the intensity of their color to visualize the resource utilization. By default, the health map shows the **average CPU utilization as a percentage of the configured limit** for every container in your cluster. diff --git a/docs/guides/monitor/lamp-stack.md b/docs/guides/monitor/lamp-stack.md index 165888c4..190ea87e 100644 --- a/docs/guides/monitor/lamp-stack.md +++ b/docs/guides/monitor/lamp-stack.md @@ -1,15 +1,8 @@ - import { OneLineInstallWget } from '@site/src/components/OneLineInstall/' -# LAMP stack monitoring (Linux, Apache, MySQL, PHP) with Netdata +# LAMP stack monitoring with Netdata + +Set up robust LAMP stack monitoring (Linux, Apache, MySQL, PHP) in a few minutes using Netdata. The LAMP stack is the "hello world" for deploying dynamic web applications. It's fast, flexible, and reliable, which means a developer or sysadmin won't go far in their career without interacting with the stack and its services. @@ -58,7 +51,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](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx): +kickstart command](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md): @@ -171,10 +164,9 @@ If the Netdata Agent isn't already open in your browser, open a new tab and navi 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](https://github.com/netdata/netdata/blob/master/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/cloud/visualize/interact-new-charts.md) for all the details. -![The Netdata -dashboard](https://user-images.githubusercontent.com/1153921/109520555-98e17800-7a69-11eb-86ec-16f689da4527.png) +![The Netdata dashboard](https://user-images.githubusercontent.com/1153921/109520555-98e17800-7a69-11eb-86ec-16f689da4527.png) The **System Overview** section, which you can also see in the right-hand menu, contains key hardware monitoring charts, including CPU utilization, memory page faults, network monitoring, and much more. The **Applications** section shows you @@ -211,7 +203,7 @@ shows any alarms currently triggered, while the **All** tab displays a list of _ ![An example of LAMP stack alarms](https://user-images.githubusercontent.com/1153921/109524120-5883f900-7a6d-11eb-830e-0e7baaa28163.png) -[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 +[Tweak alarms](https://github.com/netdata/netdata/blob/master/health/REFERENCE.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](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md). @@ -238,7 +230,7 @@ source of issues faster with [Metric Correlations](https://github.com/netdata/ne ### Related reference documentation -- [Netdata Agent · Get started](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx) +- [Netdata Agent · Get started](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md) - [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) diff --git a/docs/guides/monitor/pi-hole-raspberry-pi.md b/docs/guides/monitor/pi-hole-raspberry-pi.md index 5099d12b..4f0ff4cd 100644 --- a/docs/guides/monitor/pi-hole-raspberry-pi.md +++ b/docs/guides/monitor/pi-hole-raspberry-pi.md @@ -1,13 +1,17 @@ -import { OneLineInstallWget } from '@site/src/components/OneLineInstall/' # Monitor Pi-hole (and a Raspberry Pi) with Netdata +import { OneLineInstallWget } from '@site/src/components/OneLineInstall/' + Between intrusive ads, invasive trackers, and vicious malware, many techies and homelab enthusiasts are advancing their networks' security and speed with a tiny computer and a powerful piece of software: [Pi-hole](https://pi-hole.net/). @@ -61,9 +65,7 @@ populates its dashboard with more than 250 charts. Open your browser of choice and navigate to `http://NODE:19999/`, replacing `NODE` with the IP address of your Raspberry Pi. Not sure what that IP is? Try running `hostname -I | awk '{print $1}'` from the Pi itself. -You'll see Netdata's dashboard and a few hundred real-time, -[interactive](https://learn.netdata.cloud/guides/step-by-step/step-02#interact-with-charts) charts. Feel free to -explore, but let's turn our attention to installing Pi-hole. +You'll see Netdata's dashboard and a few hundred real-time, interactive charts. Feel free to explore, but let's turn our attention to installing Pi-hole. ## Install Pi-Hole @@ -98,8 +100,7 @@ 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](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). +If you're completely new to Netdata, look at the [Introduction](https://github.com/netdata/netdata/blob/master/docs/getting-started/introduction.md) section for a walkthrough of all its features. For a more expedited tour, see the [get started documentation](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md). ### Enable temperature sensor monitoring @@ -137,26 +138,5 @@ more than 256. Use our [database sizing 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 +and the [Database configuration documentation](https://github.com/netdata/netdata/blob/master/database/README.md) to help you determine the right setting for your Raspberry Pi. - -## What's next? - -Now that you're monitoring Pi-hole and your Raspberry Pi with Netdata, you can extend its capabilities even further, or -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](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md). - -- [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. - -If you have any questions about using Netdata to monitor your Raspberry Pi, Pi-hole, or any other applications, head on -over to our [community forum](https://community.netdata.cloud/). - - diff --git a/docs/guides/monitor/process.md b/docs/guides/monitor/process.md index 7cc327a0..9aa6911f 100644 --- a/docs/guides/monitor/process.md +++ b/docs/guides/monitor/process.md @@ -1,8 +1,11 @@ # Monitor any process in real-time with Netdata @@ -34,11 +37,7 @@ With Netdata's process monitoring, you can: ## Prerequisites -- 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. +- One or more Linux nodes running [Netdata](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md) - A general understanding of how to [configure the Netdata Agent](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) using `edit-config`. @@ -268,45 +267,4 @@ relevant data. `ebpf.plugin` visualizes additional eBPF metrics, which are system-wide and not per-process, under the **eBPF** section. -## What's next? - -Now that you have `apps_groups.conf` configured correctly, and know where to find per-process visualizations throughout -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](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](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](https://github.com/netdata/netdata/blob/master/docs/collect/enable-configure.md) doc for details. - -[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://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`](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 00b652bf..935d0f6c 100644 --- a/docs/guides/monitor/raspberry-pi-anomaly-detection.md +++ b/docs/guides/monitor/raspberry-pi-anomaly-detection.md @@ -1,12 +1,6 @@ ---- -title: "Unsupervised anomaly detection for Raspberry Pi monitoring" -description: "Use a low-overhead machine learning algorithm and an open-source monitoring tool to detect anomalous metrics on a Raspberry Pi." -image: /img/seo/guides/monitor/raspberry-pi-anomaly-detection.png -author: "Andy Maguire" -author_title: "Senior Machine Learning Engineer" -author_img: "/img/authors/andy-maguire.jpg" -custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/monitor/raspberry-pi-anomaly-detection.md ---- +# Anomaly detection for RPi monitoring + +Learn how to use a low-overhead machine learning algorithm alongside Netdata to detect anomalous metrics on a Raspberry Pi. We love IoT and edge at Netdata, we also love machine learning. Even better if we can combine the two to ease the pain of monitoring increasingly complex systems. @@ -23,7 +17,7 @@ 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](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx). + node yet, [get started now](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md). ## Install dependencies @@ -63,7 +57,6 @@ Now you're ready to enable the collector and [restart Netdata](https://github.co ```bash sudo ./edit-config python.d.conf -# set `anomalies: no` to `anomalies: yes` # restart netdata sudo systemctl restart netdata @@ -100,26 +93,4 @@ during training. By default, the anomalies collector, along with all other runni ![RAM utilization of anomaly detection on the Raspberry Pi](https://user-images.githubusercontent.com/1153921/110149720-9e0d3280-7d9b-11eb-883d-b1d4d9b9b5e1.png) -## What's next? - -So, all in all, with a small little bit of extra set up and a small overhead on the Pi itself, the anomalies collector -looks like a potentially useful addition to enable unsupervised anomaly detection on your Pi. - -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_](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_](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](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 deleted file mode 100644 index 848e2649..00000000 --- a/docs/guides/monitor/statsd.md +++ /dev/null @@ -1,298 +0,0 @@ - - -# StatsD Guide - -StatsD is a protocol and server implementation, first introduced at Etsy, to aggregate and summarize application metrics. With StatsD, applications are instrumented by developers using the libraries that already exist for the language, without caring about managing the data. The StatsD server is in charge of receiving the metrics, performing some simple processing on them, and then pushing them to the time-series database (TSDB) for long-term storage and visualization. - -Netdata is a fully-functional StatsD server and TSDB implementation, so you can instantly visualize metrics by simply sending them to Netdata using the built-in StatsD server. - -In this guide, we'll go through a scenario of visualizing our data in Netdata in a matter of seconds using [k6](https://k6.io), an open-source tool for automating load testing that outputs metrics to the StatsD format. - -Although we'll use k6 as the use-case, the same principles can be applied to every application that supports the StatsD protocol. Simply enable the StatsD output and point it to the node that runs Netdata, which is `localhost` in this case. - -In general, the process for creating a StatsD collector can be summarized in 2 steps: - -- 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](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. - -Now, let's see the above process in detail. - -## Prerequisites - -- 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 - -The real in instrumenting an application with StatsD for you is to decide what metrics you want to visualize and how you want them grouped. In other words, you need decide which metrics will be grouped in the same charts and how the charts will be grouped on Netdata's dashboard. - -Start with documentation for the particular application that you want to monitor (or the technological stack that you are using). In our case, the [k6 documentation](https://k6.io/docs/using-k6/metrics/) has a whole page dedicated to the metrics output by k6, along with descriptions. - -If you are using StatsD to monitor an existing application, you don't have much control over these metrics. For example, k6 has a type called `trend`, which is identical to timers and histograms. Thus, _k6 is clearly dictating_ which metrics can be used as histograms and simple gauges. - -On the other hand, if you are instrumenting your own code, you will need to not only decide what are the "things" that you want to measure, but also decide which StatsD metric type is the appropriate for each. - -## Use private charts to see all available metrics - -In Netdata, every metric will receive its own chart, called a `private chart`. Although in the final implementation this is something that we will disable, since it can create considerable noise (imagine having 100s of metrics), it’s very handy while building the configuration file. - -You can get a quick visual representation of the metrics and their type (e.g it’s a gauge, a timer, etc.). - -An important thing to notice is that StatsD has different types of metrics, as illustrated in the [Netdata documentation](https://learn.netdata.cloud/docs/agent/collectors/statsd.plugin#metrics-supported-by-netdata). Histograms and timers support mathematical operations to be performed on top of the baseline metric, like reporting the `average` of the value. - -Here are some examples of default private charts. You can see that the histogram private charts will visualize all the available operations. - -**Gauge private chart** - -![Gauge metric example](https://i.imgur.com/Sr5nJEV.png) - -**Histogram private chart** - -![Timer metric example](https://i.imgur.com/P4p0hvq.png) - -## Create a new StatsD configuration file - -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 -``` - -Copy the following configuration into your file as a starting point. - -```conf -[app] - name = k6 - metrics = k6* - private charts = yes - gaps when not collected = no - memory mode = dbengine -``` - -Next, you need is to understand how to organize metrics in Netdata’s StatsD. - -### Synthetic charts - -Netdata lets you group the metrics exposed by your instrumented application with _synthetic charts_. - -First, create a `[dictionary]` section to transform the names of the metrics into human-readable equivalents. `http_req_blocked`, `http_req_connecting`, `http_req_receiving`, and `http_reqs` are all metrics exposed by k6. - -``` -[dictionary] - http_req_blocked = Blocked HTTP Requests - http_req_connecting = Connecting HTTP Requests - http_req_receiving = Receiving HTTP Requests - http_reqs = Total HTTP requests -``` - -Continue this dictionary process with any other metrics you want to collect with Netdata. - -### Families and context - -Families and context are additional ways to group metrics. Families control the submenu at right-hand menu and it's a subcategory of the section. Given the metrics given by K6, we are organizing them in 2 major groups, or `families`: `k6 native metrics` and `http metrics`. - -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](https://github.com/netdata/netdata/blob/master/web/README.md#families). - -### Dimension - -Now, having decided on how we are going to group the charts, we need to define how we are going to group metrics into different charts. This is particularly important, since we decide: - -- What metrics **not** to show, since they are not useful for our use-case. -- What metrics to consolidate into the same charts, so as to reduce noise and increase visual correlation. - -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](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**: - - For all charts: - - `events`: The number of events (data points) received by the StatsD server - - `last`: The last value that the server received - - For histograms and timers: - - `min`, `max`, `sum`, `average`, `percentile`, `median`, `stddev`: This is helpful if you want to see different representations of the same value. You can find an example at the `[iteration_duration]` above. Note that the baseline `metric` is the same, but the `name` of the dimension is different, since we use the baseline, but we perform a computation on it, creating a different final metric for visualization(dimension). -- **MULTIPLIER DIVIDER**: Handy if you want to convert Kilobytes to Megabytes or you want to give negative value. The second is handy for better visualization of send/receive. You can find an example at the **packets** submenu of the **IPv4 Networking Section**. - -> ❕ If you define a chart, run Netdata to visualize metrics, and then add or remove a dimension from that chart, this will result in a new chart with the same name, confusing Netdata. If you change the dimensions of the chart, please make sure to also change the `name` of that chart, since it serves as the `id` of that chart in Netdata's storage. (e.g http_req --> http_req_1). - -### Finalize your StatsD configuration file - -It's time to assemble all the pieces together and create the synthetic charts that will consist our application dashboard in Netdata. We can do it in a few simple steps: - -- Decide which metrics we want to use (we have viewed all of them as private charts). For example, we want to use `k6.http_requests`, `k6.vus`, etc. -- Decide how we want organize them in different synthetic charts. For example, we want `k6.http_requests`, `k6.vus` on their own, but `k6.http_req_blocked` and `k6.http_req_connecting` on the same chart. -- For each synthetic chart, we define a **unique** name and a human readable title. -- We decide at which `family` (submenu section) we want each synthetic chart to belong to. For example, here we have defined 2 families: `http requests`, `k6_metrics`. -- If we have multiple instances of the same metric, we can define different contexts, (Optional). -- We define a dimension according to the syntax we highlighted above. -- We define a type for each synthetic chart (line, area, stacked) -- We define the units for each synthetic chart. - -Following the above steps, we append to the `k6.conf` that we defined above, the following configuration: - -``` -[http_req_total] - name = http_req_total - title = Total HTTP Requests - family = http requests - context = k6.http_requests - dimension = k6.http_reqs http_reqs last 1 1 sum - type = line - units = requests/s - -[vus] - name = vus - title = Virtual Active Users - family = k6_metrics - dimension = k6.vus vus last 1 1 - dimension = k6.vus_max vus_max last 1 1 - type = line - unit = vus - -[iteration_duration] - name = iteration_duration_2 - title = Iteration duration - family = k6_metrics - dimension = k6.iteration_duration iteration_duration last 1 1 - dimension = k6.iteration_duration iteration_duration_max max 1 1 - dimension = k6.iteration_duration iteration_duration_min min 1 1 - dimension = k6.iteration_duration iteration_duration_avg avg 1 1 - type = line - unit = s - -[dropped_iterations] - name = dropped_iterations - title = Dropped Iterations - family = k6_metrics - dimension = k6.dropped_iterations dropped_iterations last 1 1 - units = iterations - type = line - -[data] - name = data - title = K6 Data - family = k6_metrics - dimension = k6.data_received data_received last 1 1 - dimension = k6.data_sent data_sent last -1 1 - units = kb/s - type = area - -[http_req_status] - name = http_req_status - title = HTTP Requests Status - family = http requests - dimension = k6.http_req_blocked http_req_blocked last 1 1 - dimension = k6.http_req_connecting http_req_connecting last 1 1 - units = ms - type = line - -[http_req_duration] - name = http_req_duration - title = HTTP requests duration - family = http requests - dimension = k6.http_req_sending http_req_sending last 1 1 - dimension = k6.http_req_waiting http_req_waiting last 1 1 - dimension = k6.http_req_receiving http_req_receiving last 1 1 - units = ms - type = stacked -``` - -> 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](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md). - -## Final touches - -At this point, you have used StatsD to gather metrics for k6, creating a whole new section in your Netdata dashboard in the process. Moreover, you can further customize the icon of the particular section, as well as the description for each chart. - -To edit the section, please follow the Netdata [documentation](https://learn.netdata.cloud/docs/agent/web/gui#customizing-the-local-dashboard). - -While the following configuration will be placed in a new file, as the documentation suggests, it is instructing to use `dashboard_info.js` as a template. Open the file and see how the rest of sections and collectors have been defined. - -```javascript= -netdataDashboard.menu = { - 'k6': { - title: 'K6 Load Testing', - icon: '', - info: 'k6 is an open-source load testing tool and cloud service providing the best developer experience for API performance testing.' - }, - . - . - . -``` - -We can then add a description for each chart. Simply find the following section in `dashboard_info.js` to understand how a chart definitions are used: - -```javascript= -netdataDashboard.context = { - 'system.cpu': { - info: function (os) { - void (os); - return 'Total CPU utilization (all cores). 100% here means there is no CPU idle time at all. You can get per core usage at the CPUs section and per application usage at the Applications Monitoring section.' - + netdataDashboard.sparkline('
Keep an eye on iowait ', 'system.cpu', 'iowait', '%', '. If it is constantly high, your disks are a bottleneck and they slow your system down.') - + netdataDashboard.sparkline('
An important metric worth monitoring, is softirq ', 'system.cpu', 'softirq', '%', '. A constantly high percentage of softirq may indicate network driver issues.'); - }, - valueRange: "[0, 100]" - }, -``` - -Afterwards, you can open your `custom_dashboard_info.js`, as suggested in the documentation linked above, and add something like the following example: - -```javascript= -netdataDashboard.context = { - 'k6.http_req_duration': { - info: "Total time for the request. It's equal to http_req_sending + http_req_waiting + http_req_receiving (i.e. how long did the remote server take to process the request and respond, without the initial DNS lookup/connection times)" - }, - -``` -The chart is identified as ``.``. - -These descriptions can greatly help the Netdata user who is monitoring your application in the midst of an incident. - -The `info` field supports `html`, embedding useful links and instructions in the description. - -## Vendoring a new collector - -While we learned how to visualize any data source in Netdata using the StatsD protocol, we have also created a new collector. - -As long as you use the same underlying collector, every new `myapp.conf` file will create a new data source and dashboard section for Netdata. Netdata loads all the configuration files by default, but it will **not** create dashboard sections or charts, unless it starts receiving data for that particular data source. This means that we can now share our collector with the rest of the Netdata community. - -If you want to contribute or you need any help in developing your collector, we have a whole [Forum Category](https://community.netdata.cloud/c/agent-development/9) dedicated to contributing to the Netdata Agent. - -### Making a PR to the netdata/netdata repository - -- Make sure you follow the contributing guide and read our Code of Conduct -- Fork the netdata/netdata repository -- Place the configuration file inside `netdata/collectors/statsd.plugin` -- Add a reference in `netdata/collectors/statsd.plugin/Makefile.am`. For example, if we contribute the `k6.conf` file: -```Makefile -dist_statsdconfig_DATA = \ - example.conf \ - k6.conf \ - $(NULL) -``` - -## What's next? - -In this tutorial, you learned how to monitor an application using Netdata's StatsD implementation. - -Netdata allows you easily visualize any StatsD metric without any configuration, since it creates a private metric per chart by default. But to make your implementation more robust, you also learned how to group metrics by family and context, and create multiple dimensions. With these tools, you can quickly instrument any application with StatsD to monitor its performance and availability with per-second metrics. - -### Related reference documentation - -- [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 deleted file mode 100644 index 3c026a89..00000000 --- a/docs/guides/monitor/stop-notifications-alarms.md +++ /dev/null @@ -1,92 +0,0 @@ - - -# Stop notifications for individual alarms - -In this short tutorial, you'll learn how to stop notifications for individual alarms in Netdata's health -monitoring system. We also refer to this process as _silencing_ the alarm. - -Why silence alarms? We designed Netdata's pre-configured alarms for production systems, so they might not be -relevant if you run Netdata on your laptop or a small virtual server. If they're not helpful, they can be a distraction -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](https://github.com/netdata/netdata/blob/master/health/notifications/README.md) entirely. - -## Find the alarm configuration file - -To silence an alarm, you need to know where to find its configuration file. - -Let's use the `system.cpu` chart as an example. It's the first chart you'll see on most Netdata dashboards. - -To figure out which file you need to edit, open up Netdata's dashboard and, click the **Alarms** button at the top -of the dashboard, followed by clicking on the **All** tab. - -In this example, we're looking for the `system - cpu` entity, which, when opened, looks like this: - -![The system - cpu alarm -entity](https://user-images.githubusercontent.com/1153921/67034648-ebb4cc80-f0cc-11e9-9d49-1023629924f5.png) - -In the `source` row, you see that this chart is getting its configuration from -`4@/usr/lib/netdata/conf.d/health.d/cpu.conf`. The relevant part of begins at `health.d`: `health.d/cpu.conf`. That's -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](https://github.com/netdata/netdata/blob/master/health/QUICKSTART.md#edit-health-configuration-files). - -## Edit the file to enable silencing - -To edit `health.d/cpu.conf`, use `edit-config` from inside of your Netdata configuration directory. - -```bash -cd /etc/netdata/ # Replace with your Netdata configuration directory, if not /etc/netdata/ -./edit-config health.d/cpu.conf -``` - -> You may need to use `sudo` or another method of elevating your privileges. - -The beginning of the file looks like this: - -```yaml -template: 10min_cpu_usage - on: system.cpu - os: linux - hosts: * - lookup: average -10m unaligned of user,system,softirq,irq,guest - units: % - every: 1m - warn: $this > (($status >= $WARNING) ? (75) : (85)) - crit: $this > (($status == $CRITICAL) ? (85) : (95)) - delay: down 15m multiplier 1.5 max 1h - info: average cpu utilization for the last 10 minutes (excluding iowait, nice and steal) - to: sysadmin -``` - -To silence this alarm, change `sysadmin` to `silent`. - -```yaml - to: silent -``` - -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. - -## What's next? - -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](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](https://github.com/netdata/netdata/blob/master/health/notifications/README.md). - -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 deleted file mode 100644 index 90ce20a4..00000000 --- a/docs/guides/monitor/visualize-monitor-anomalies.md +++ /dev/null @@ -1,142 +0,0 @@ ---- -title: "Monitor and visualize anomalies with Netdata (part 2)" -description: "Using unsupervised anomaly detection and machine learning, get notified " -image: /img/seo/guides/monitor/visualize-monitor-anomalies.png -author: "Joel Hans" -author_title: "Editorial Director, Technical & Educational Resources" -author_img: "/img/authors/joel-hans.jpg" -custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/guides/monitor/visualize-monitor-anomalies.md ---- - -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](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 -anomalous data, when, and where to look next. - -> 💡 In certain cases, the anomalies collector doesn't start immediately after restarting the Netdata Agent. If this -> happens, you won't see the dashboard section or the relevant [charts](#visualize-anomalies-in-charts) right away. Wait -> a minute or two, refresh, and look again. If the anomalies charts and alarms are still not present, investigate the -> error log with `less /var/log/netdata/error.log | grep anomalies`. - -## Test anomaly detection - -Time to see the Netdata Agent's unsupervised anomaly detection in action. To trigger anomalies on the Nginx web server, -use `ab`, otherwise known as [Apache Bench](https://httpd.apache.org/docs/2.4/programs/ab.html). Despite its name, it -works just as well with Nginx web servers. Install it on Ubuntu/Debian systems with `sudo apt install apache2-utils`. - -> 💡 If you haven't followed the guide's example of using Nginx, an easy way to test anomaly detection on your node is -> to use the `stress-ng` command, which is available on most Linux distributions. Run `stress-ng --cpu 0` to create CPU -> stress or `stress-ng --vm 0` for RAM stress. Each test will cause some "collateral damage," in that you may see CPU -> utilization rise when running the RAM test, and vice versa. - -The following test creates a minimum of 10,000,000 requests for Nginx to handle, with a maximum of 10 at any given time, -with a run time of 60 seconds. If your system can handle those 10,000,000 in less than 60 seconds, `ab` will keep -sending requests until the timer runs out. - -```bash -ab -k -c 10 -t 60 -n 10000000 http://127.0.0.1/ -``` - -Let's see how Netdata detects this anomalous behavior and propagates information to you through preconfigured alarms and -dashboards that automatically organize anomaly detection metrics into meaningful charts to help you begin root cause -analysis (RCA). - -## Monitor anomalies with alarms - -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`](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 -at least the last two minutes. - -![An example anomaly probability -alarm](https://user-images.githubusercontent.com/1153921/104225767-0a0a9480-5404-11eb-9bfd-e29592397203.png) - -The second class triggers warning alarms when the number of anomalies in the last two minutes hits 10 or higher. - -![An example anomaly count -alarm](https://user-images.githubusercontent.com/1153921/104225769-0aa32b00-5404-11eb-95f3-7309f9429fe1.png) - -If you see either of these alarms in Netdata Cloud, the local Agent dashboard, or on your preferred notification -platform, it's a safe bet that the node's current metrics have deviated from normal. That doesn't necessarily mean -there's a full-blown incident, depending on what application/service you're using anomaly detection on, but it's worth -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](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](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](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`](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 -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→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](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. - -![Two charts created by the anomalies -collector](https://user-images.githubusercontent.com/1153921/104226380-ef84eb00-5404-11eb-9faf-9e64c43b95ff.png) - -Before `08:32:00`, both charts show little in the way of verified anomalies. Based on the metrics the anomalies -collector has trained on, a certain percentage of anomaly probability score is normal, as seen in the -`web_log_nginx_requests_prob` dimension and a few others. What you're looking for is large deviations from the "noise" -in the `anomalies.probability` chart, or any increments to the `anomalies.anomaly` chart. - -Unsurprisingly, the stress test that began at `08:32:00` caused significant changes to these charts. The three -dimensions that immediately shot to 100% anomaly probability, and remained there during the test, were -`web_log_nginx.requests_prob`, `nginx_local.connections_accepted_handled_prob`, and `system.cpu_pressure_prob`. - -## Build an anomaly detection dashboard - -[Netdata Cloud](https://app.netdata.cloud) features a drag-and-drop [dashboard -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 -documentation about why the dashboard exists and where to look next based on what you're seeing: - -![An example anomaly detection -dashboard](https://user-images.githubusercontent.com/1153921/104226915-c6188f00-5405-11eb-9bb4-559a18016fa7.png) - -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://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](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. - -We'd love to hear your feedback on the anomalies collector. Hop over to the [community -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](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md). - -### Related reference documentation - -- [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 e0e7a604..f7769949 100644 --- a/docs/guides/python-collector.md +++ b/docs/guides/python-collector.md @@ -1,35 +1,57 @@ - - # Develop a custom data collector in Python -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](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 - -You have a custom application or infrastructure that you need to monitor, but no open-source monitoring tool offers a -prebuilt method for collecting your required metric data. - -## Solution +The Netdata Agent uses [data collectors](https://github.com/netdata/netdata/blob/master/collectors/README.md) to +fetch metrics from hundreds of system, container, and service endpoints. While the Netdata team and community has built +[powerful collectors](https://github.com/netdata/netdata/blob/master/collectors/COLLECTORS.md) for most system, container, +and service/application endpoints, some custom applications can't be monitored by default. In this tutorial, you'll learn how to leverage the [Python programming language](https://www.python.org/) to build a custom data collector for the Netdata Agent. Follow along with your own dataset, using the techniques and best practices covered here, or use the included examples for collecting and organizing either random or weather data. +## Disclaimer + +If you're comfortable with Golang, consider instead writing a module for the [go.d.plugin](https://github.com/netdata/go.d.plugin). +Golang is more performant, easier to maintain, and simpler for users since it doesn't require a particular runtime on the node to +execute. Python plugins require Python on the machine to be executed. Netdata uses Go as the platform of choice for +production-grade collectors. + +We generally do not accept contributions of Python modules to the Github project netdata/netdata. If you write a Python collector and +want to make it available for other users, you should create the pull request in https://github.com/netdata/community. + ## 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](https://github.com/netdata/netdata/blob/master/docs/get-started.mdx) monitoring agent. + - A physical or virtual Linux system, which we'll call a _node_. + - A working [installation of Netdata](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md) monitoring agent. + +### Quick start + +For a quick start, you can look at the +[example plugin](https://raw.githubusercontent.com/netdata/netdata/master/collectors/python.d.plugin/example/example.chart.py). + +**Note**: If you are working 'locally' on a new collector and would like to run it in an already installed and running +Netdata (as opposed to having to install Netdata from source again with your new changes) you 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://github.com/netdata/netdata/blob/master/packaging/installer/methods/manual.md) you +have made to do your development on). + +```bash +# clone your fork (done once at the start but shown here for clarity) +#git clone --branch my-example-collector https://github.com/mygithubusername/netdata.git --depth=100 --recursive +# go into your netdata source folder +cd netdata +# git pull your latest changes (assuming you built from a fork you are using to develop on) +git pull +# instead of running the installer we can just copy over the updated collector files +#sudo ./netdata-installer.sh --dont-wait +# copy over the file you have updated locally (pretending we are working on the 'example' collector) +sudo cp collectors/python.d.plugin/example/example.chart.py /usr/libexec/netdata/python.d/ +# become user netdata +sudo su -s /bin/bash netdata +# run your updated collector in debug mode to see if it works without having to reinstall netdata +/usr/libexec/netdata/plugins.d/python.d.plugin example debug trace nolock +``` ## Jobs and elements of a Python collector @@ -50,6 +72,11 @@ The basic elements of a Netdata collector are: - `data{}`: A dictionary containing the values to be displayed. - `get_data()`: The basic function of the plugin which will return to Netdata the correct values. +**Note**: All names are better explained in the +[External Plugins Documentation](https://github.com/netdata/netdata/blob/master/collectors/plugins.d/README.md). +Parameters like `priority` and `update_every` mentioned in that documentation are handled by the `python.d.plugin`, +not by each collection module. + Let's walk through these jobs and elements as independent elements first, then apply them to example Python code. ### Determine how to gather metrics data @@ -135,11 +162,18 @@ correct values. ## Framework classes -The `python.d` plugin has a number of framework classes that can be used to speed up the development of your python -collector. Your class can inherit one of these framework classes, which have preconfigured methods. +Every module needs to implement its own `Service` class. This class should inherit from one of the framework classes: + +- `SimpleService` +- `UrlService` +- `SocketService` +- `LogService` +- `ExecutableService` -For example, the snippet below is from the [RabbitMQ -collector](https://github.com/netdata/netdata/blob/91f3268e9615edd393bd43de4ad8068111024cc9/collectors/python.d.plugin/rabbitmq/rabbitmq.chart.py#L273). +Also it needs to invoke the parent class constructor in a specific way as well as assign global variables to class variables. + +For example, the snippet below is from the +[RabbitMQ collector](https://github.com/netdata/netdata/blob/91f3268e9615edd393bd43de4ad8068111024cc9/collectors/python.d.plugin/rabbitmq/rabbitmq.chart.py#L273). This collector uses an HTTP endpoint and uses the `UrlService` framework class, which only needs to define an HTTP endpoint for data collection. @@ -166,8 +200,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](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/README.md#how-to-write-a-new-module) from -the Netdata documentation. +You can find below the [framework class reference](#framework-class-reference). ## An example collector using weather station data @@ -196,6 +229,35 @@ CHARTS = { ## Parse the data to extract or create the actual data to be represented +Every collector must implement `_get_data`. This method should grab raw data from `_get_raw_data`, +parse it, and return a dictionary where keys are unique dimension names, or `None` if no data is collected. + +For example: +```py +def _get_data(self): + try: + raw = self._get_raw_data().split(" ") + return {'active': int(raw[2])} + except (ValueError, AttributeError): + return None +``` + +In our weather data collector we declare `_get_data` as follows: + +```python + def get_data(self): + #The data dict is basically all the values to be represented + # The entries are in the format: { "dimension": value} + #And each "dimension" should belong to a chart. + data = dict() + + self.populate_data() + + data['current_temperature'] = self.weather_data["temp"] + + return data +``` + A standard practice would be to either get the data on JSON format or transform them to JSON format. We use a dictionary to give this format and issue random values to simulate received data. @@ -461,26 +523,104 @@ variables and inform the user about the defaults. For example, take a look at th You can read more about the configuration file on the [`python.d.plugin` documentation](https://github.com/netdata/netdata/blob/master/collectors/python.d.plugin/README.md). -## What's next? +You can find the source code for the above examples on [GitHub](https://github.com/papajohn-uop/netdata). + +## Pull Request Checklist for Python Plugins + +Pull requests should be created in https://github.com/netdata/community. + +This is a generic checklist for submitting a new Python plugin for Netdata. It is by no means comprehensive. + +At minimum, to be buildable and testable, the PR needs to include: + +- The module itself, following proper naming conventions: `collectors/python.d.plugin//.chart.py` +- A README.md file for the plugin under `collectors/python.d.plugin/`. +- The configuration file for the module: `collectors/python.d.plugin//.conf`. Python config files are in YAML format, and should include comments describing what options are present. The instructions are also needed in the configuration section of the README.md +- A basic configuration for the plugin in the appropriate global config file: `collectors/python.d.plugin/python.d.conf`, which is also in YAML format. Either add a line that reads `# : yes` if the module is to be enabled by default, or one that reads `: no` if it is to be disabled by default. +- A makefile for the plugin at `collectors/python.d.plugin//Makefile.inc`. Check an existing plugin for what this should look like. +- A line in `collectors/python.d.plugin/Makefile.am` including the above-mentioned makefile. Place it with the other plugin includes (please keep the includes sorted alphabetically). +- Optionally, chart information in `web/gui/dashboard_info.js`. This generally involves specifying a name and icon for the section, and may include descriptions for the section or individual charts. +- Optionally, some default alarm configurations for your collector in `health/health.d/.conf` and a line adding `.conf` in `health/Makefile.am`. + +## Framework class reference + +Every framework class has some user-configurable variables which are specific to this particular class. Those variables should have default values initialized in the child class constructor. + +If module needs some additional user-configurable variable, it can be accessed from the `self.configuration` list and assigned in constructor or custom `check` method. Example: + +```py +def __init__(self, configuration=None, name=None): + UrlService.__init__(self, configuration=configuration, name=name) + try: + self.baseurl = str(self.configuration['baseurl']) + except (KeyError, TypeError): + self.baseurl = "http://localhost:5001" +``` + +Classes implement `_get_raw_data` which should be used to grab raw data. This method usually returns a list of strings. + +### `SimpleService` + +This is last resort class, if a new module cannot be written by using other framework class this one can be used. + +Example: `ceph`, `sensors` + +It is the lowest-level class which implements most of module logic, like: + +- threading +- handling run times +- chart formatting +- logging +- chart creation and updating + +### `LogService` + +Examples: `apache_cache`, `nginx_log`_ + +Variable from config file: `log_path`. + +Object created from this class reads new lines from file specified in `log_path` variable. It will check if file exists and is readable. Also `_get_raw_data` returns list of strings where each string is one line from file specified in `log_path`. + +### `ExecutableService` + +Examples: `exim`, `postfix`_ + +Variable from config file: `command`. + +This allows to execute a shell command in a secure way. It will check for invalid characters in `command` variable and won't proceed if there is one of: + +- '&' +- '|' +- ';' +- '>' +- '\<' + +For additional security it uses python `subprocess.Popen` (without `shell=True` option) to execute command. Command can be specified with absolute or relative name. When using relative name, it will try to find `command` in `PATH` environment variable as well as in `/sbin` and `/usr/sbin`. + +`_get_raw_data` returns list of decoded lines returned by `command`. + +### UrlService + +Examples: `apache`, `nginx`, `tomcat`_ + +Variables from config file: `url`, `user`, `pass`. + +If data is grabbed by accessing service via HTTP protocol, this class can be used. It can handle HTTP Basic Auth when specified with `user` and `pass` credentials. + +Please note that the config file can use different variables according to the specification of each module. + +`_get_raw_data` returns list of utf-8 decoded strings (lines). + +### SocketService + +Examples: `dovecot`, `redis` -Find the source code for the above examples on [GitHub](https://github.com/papajohn-uop/netdata). +Variables from config file: `unix_socket`, `host`, `port`, `request`. -Now you are ready to start developing our Netdata python Collector and share it with the rest of the Netdata community. +Object will try execute `request` using either `unix_socket` or TCP/IP socket with combination of `host` and `port`. This can access unix sockets with SOCK_STREAM or SOCK_DGRAM protocols and TCP/IP sockets in version 4 and 6 with SOCK_STREAM setting. -- 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://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, - then browse other [existing collectors](https://github.com/netdata/netdata/tree/master/collectors/python.d.plugin) - that might have similarities to what you want to do. -- If you're developing a proof of concept (PoC), consider migrating the collector in Golang - ([go.d.plugin](https://github.com/netdata/go.d.plugin)) once you validate its value in production. Golang is more - performant, easier to maintain, and simpler for users since it doesn't require a particular runtime on the node to - execute (Python plugins require Python on the machine to be executed). Netdata uses Go as the platform of choice for - production-grade collectors. -- Celebrate! You have contributed to an open-source project with hundreds of thousands of users! +Sockets are accessed in non-blocking mode with 15 second timeout. +After every execution of `_get_raw_data` socket is closed, to prevent this module needs to set `_keep_alive` variable to `True` and implement custom `_check_raw_data` method. +`_check_raw_data` should take raw data and return `True` if all data is received otherwise it should return `False`. Also it should do it in fast and efficient way. diff --git a/docs/guides/step-by-step/step-00.md b/docs/guides/step-by-step/step-00.md deleted file mode 100644 index 2f83ee9b..00000000 --- a/docs/guides/step-by-step/step-00.md +++ /dev/null @@ -1,120 +0,0 @@ - -import { OneLineInstallWget, OneLineInstallCurl } from '@site/src/components/OneLineInstall/' - -# The step-by-step Netdata guide - -Welcome to Netdata! We're glad you're interested in our health monitoring and performance troubleshooting system. - -Because Netdata is entirely open-source software, you can use it free of charge, whether you want to monitor one or ten -thousand systems! All our code is hosted on [GitHub](https://github.com/netdata/netdata). - -This guide is designed to help you understand what Netdata is, what it's capable of, and how it'll help you make -faster and more informed decisions about the health and performance of your systems and applications. If you're -completely new to Netdata, or have never tried health monitoring/performance troubleshooting systems before, this -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](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. - -## Where to go if you need help - -No matter where you are in this Netdata guide, if you need help, head over to our [GitHub -repository](https://github.com/netdata/netdata/). That's where we collect questions from users, help fix their bugs, and -point people toward documentation that explains what they're having trouble with. - -Click on the **issues** tab to see all the conversations we're having with Netdata users. Use the search bar to find -previously-written advice for your specific problem, and if you don't see any results, hit the **New issue** button to -send us a question. - - -## Before we get started - -Let's make sure you have Netdata installed on your system! - -> If you already installed Netdata, feel free to skip to [Step 1: Netdata's building blocks](step-01.md). - -The easiest way to install Netdata on a Linux system is our `kickstart.sh` one-line installer. Run this on your system -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](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: - - - -Or, if you have cURL but not wget (such as on macOS): - - - - -Once finished, you'll have Netdata installed, and you'll be set up to get _nightly updates_ to get the latest features, -improvements, and bugfixes. - -If this method doesn't work for you, or you want to use a different process, visit our [installation -documentation](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md) for details. - -## Netdata fundamentals - -[Step 1. Netdata's building blocks](step-01.md) - -In this introductory step, we'll talk about the fundamental ideas, philosophies, and UX decisions behind Netdata. - -[Step 2. Get to know Netdata's dashboard](step-02.md) - -Visit Netdata's dashboard to explore, manipulate charts, and check out alarms. Get your first taste of visual anomaly -detection. - -[Step 3. Monitor more than one system with Netdata](step-03.md) - -While the dashboard lets you quickly move from one agent to another, Netdata Cloud is our SaaS solution for monitoring -the health of many systems. We'll cover its features and the benefits of using Netdata Cloud on top of the dashboard. - -[Step 4. The basics of configuring Netdata](step-04.md) - -While Netdata can monitor thousands of metrics in real-time without any configuration, you may _want_ to tweak some -settings based on your system's resources. - -## Intermediate steps - -[Step 5. Health monitoring alarms and notifications](step-05.md) - -Learn how to tune, silence, and write custom alarms. Then enable notifications so you never miss a change in health -status or performance anomaly. - -[Step 6. Collect metrics from more services and apps](step-06.md) - -Learn how to enable/disable collection plugins and configure a collection plugin job to add more charts to your Netdata -dashboard and begin monitoring more apps and services, like MySQL, Nginx, MongoDB, and hundreds more. - -[Step 7. Netdata's dashboard in depth](step-07.md) - -Now that you configured your Netdata monitoring agent to your exact needs, you'll dive back into metrics snapshots, -updates, and the dashboard's settings. - -## Advanced steps - -[Step 8. Building your first custom dashboard](step-08.md) - -Using simple HTML, CSS, and JavaScript, we'll build a custom dashboard that displays essential information in any format -you choose. You can even monitor many systems from a single HTML file. - -[Step 9. Long-term metrics storage](step-09.md) - -By default, Netdata can store lots of real-time metrics, but you can also tweak our custom database engine to your -heart's content. Want to take your Netdata metrics elsewhere? We're happy to help you archive data to Prometheus, -MongoDB, TimescaleDB, and others. - -[Step 10. Set up a proxy](step-10.md) - -Run Netdata behind an Nginx proxy to improve performance, and enable TLS/HTTPS for better security. - - diff --git a/docs/guides/step-by-step/step-01.md b/docs/guides/step-by-step/step-01.md deleted file mode 100644 index e60bb076..00000000 --- a/docs/guides/step-by-step/step-01.md +++ /dev/null @@ -1,156 +0,0 @@ - - -# Step 1. Netdata's building blocks - -Netdata is a distributed and real-time _health monitoring and performance troubleshooting toolkit_ for monitoring your -systems and applications. - -Because the monitoring agent is highly-optimized, you can install it all your physical systems, containers, IoT devices, -and edge devices without disrupting their core function. - -By default, and without configuration, Netdata delivers real-time insights into everything happening on the system, from -CPU utilization to packet loss on every network device. Netdata can also auto-detect metrics from hundreds of your -favorite services and applications, like MySQL/MariaDB, Docker, Nginx, Apache, MongoDB, and more. - -All metrics are automatically-updated, providing interactive dashboards that allow you to dive in, discover anomalies, -and figure out the root cause analysis of any issue. - -Best of all, Netdata is entirely free, open-source software! Solo developers and enterprises with thousands of systems -can both use it free of charge. We're hosted on [GitHub](https://github.com/netdata/netdata). - -Want to learn about the history of Netdata, and what inspired our CEO to build it in the first place, and where we're -headed? Read Costa's comprehensive blog post: _[Redefining monitoring with Netdata (and how it came to -be)](https://blog.netdata.cloud/posts/redefining-monitoring-netdata/)_. - -## What you'll learn in this step - -In the first step of the Netdata guide, you'll learn about: - -- [Netdata's core features](#netdatas-core-features) -- [Why you should use Netdata](#why-you-should-use-netdata) -- [How Netdata has complementary systems, not competitors](#how-netdata-has-complementary-systems-not-competitors) - -Let's get started! - -## Netdata's core features - -Netdata has only been around for a few years, but it's a complex piece of software. Here are just some of the features -we'll cover throughout this guide. - -- A sophisticated **dashboard**, which we'll cover in [step 2](step-02.md). The real-time, highly-granular dashboard, - with hundreds of charts, is your main source of information about the health and performance of your systems/ - applications. We designed the dashboard with anomaly detection and quick analysis in mind. We'll return to - dashboard-related topics in both [step 7](step-07.md) and [step 8](step-08.md). -- **Long-term metrics storage** by default. With our new database engine, you can store days, weeks, or months of - per-second historical metrics. Or you can archive metrics to another database, like MongoDB or Prometheus. We'll - cover all these options in [step 9](step-09.md). -- **No configuration necessary**. Without any configuration, you'll get thousands of real-time metrics and hundreds of - alarms designed by our community of sysadmin experts. But you _can_ configure Netdata in a lot of ways, some of - which we'll cover in [step 4](step-04.md). -- **Distributed, per-system installation**. Instead of centralizing metrics in one location, you install Netdata on - _every_ system, and each system is responsible for its metrics. Having distributed agents reduces cost and lets - Netdata run on devices with little available resources, such as IoT and edge devices, without affecting their core - purpose. -- **Sophisticated health monitoring** to ensure you always know when an anomaly hits. In [step 5](step-05.md), we dive - into how you can tune alarms, write your own alarm, and enable two types of notifications. -- **High-speed, low-resource collectors** that allow you to collect thousands of metrics every second while using only - a fraction of your system's CPU resources and a few MiB of RAM. -- **Netdata Cloud** is our SaaS toolkit that helps Netdata users monitor the health and performance of entire - infrastructures, whether they are two or two thousand (or more!) systems. We'll cover Netdata Cloud in [step - 3](step-03.md). - -## Why you should use Netdata - -Because you care about the health and performance of your systems and applications, and all of the awesome features we -just mentioned. And it's free! - -All these may be valid reasons, but let's step back and talk about Netdata's _principles_ for health monitoring and -performance troubleshooting. We have a lot of [complementary -systems](#how-netdata-has-complementary-systems-not-competitors), and we think there's a good reason why Netdata should -always be your first choice when troubleshooting an anomaly. - -We built Netdata on four principles. - -### Per-second data collection - -Our first principle is per-second data collection for all metrics. - -That matters because you can't monitor a 2-second service-level agreement (SLA) with 10-second metrics. You can't detect -quick anomalies if your metrics don't show them. - -How do we solve this? By decentralizing monitoring. Each node is responsible for collecting metrics, triggering alarms, -and building dashboards locally, and we work hard to ensure it does each step (and others) with remarkable efficiency. -For example, Netdata can [collect 100,000 metrics](https://github.com/netdata/netdata/issues/1323) every second while -using only 9% of a single server-grade CPU core! - -By decentralizing monitoring and emphasizing speed at every turn, Netdata helps you scale your health monitoring and -performance troubleshooting to an infrastructure of every size. _And_ you get to keep per-second metrics in long-term -storage thanks to the database engine. - -### Unlimited metrics - -We believe all metrics are fundamentally important, and all metrics should be available to the user. - -If you don't collect _all_ the metrics a system creates, you're only seeing part of the story. It's like saying you've -read a book after skipping all but the last ten pages. You only know the ending, not everything that leads to it. - -Most monitoring solutions exist to poke you when there's a problem, and then tell you to use a dozen different console -tools to find the root cause. Netdata prefers to give you every piece of information you might need to understand why an -anomaly happened. - -### Meaningful presentation - -We want every piece of Netdata's dashboard not only to look good and update every second, but also provide context as to -what you're looking at and why it matters. - -The principle of meaningful presentation is fundamental to our dashboard's user experience (UX). We could have put -charts in a grid or hidden some behind tabs or buttons. We instead chose to stack them vertically, on a single page, so -you can visually see how, for example, a jump in disk usage can also increase system load. - -Here's an example of a system undergoing a disk stress test: - -![Screen Shot 2019-10-23 at 15 38 -32](https://user-images.githubusercontent.com/1153921/67439589-7f920700-f5ab-11e9-930d-fb0014900d90.png) - -> For the curious, here's the command: `stress-ng --fallocate 4 --fallocate-bytes 4g --timeout 1m --metrics --verify -> --times`! - -### Immediate results - -Finally, Netdata should be usable from the moment you install it. - -As we've talked about, and as you'll learn in the following nine steps, Netdata comes installed with: - -- Auto-detected metrics -- Human-readable units -- Metrics that are structured into charts, families, and contexts -- Automatically generated dashboards -- Charts designed for visual anomaly detection -- Hundreds of pre-configured alarms - -By standardizing your monitoring infrastructure, Netdata tries to make at least one part of your administrative tasks -easy! - -## How Netdata has complementary systems, not competitors - -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](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 -between. We just want to give you all the metrics every second, and what you do with them next is your business, not -ours. Our mission is helping people create more extraordinary infrastructures! - -## What's next? - -We think it's imperative you understand why we built Netdata the way we did. But now that we have that behind us, let's -get right into that dashboard you've heard so much about. - -[Next: Get to know Netdata's dashboard →](step-02.md) - - diff --git a/docs/guides/step-by-step/step-02.md b/docs/guides/step-by-step/step-02.md deleted file mode 100644 index 535f3cfa..00000000 --- a/docs/guides/step-by-step/step-02.md +++ /dev/null @@ -1,208 +0,0 @@ - - -# Step 2. Get to know Netdata's dashboard - -Welcome to Netdata proper! Now that you understand how Netdata works, how it's built, and why we built it, you can start -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](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 - -In this step of the Netdata guide, you'll learn how to: - -- [Visit and explore the dashboard](#visit-and-explore-the-dashboard) -- [Explore available charts using menus](#explore-available-charts-using-menus) -- [Read the descriptions accompanying charts](#read-the-descriptions-accompanying-charts) -- [Interact with charts](#interact-with-charts) -- [See raised alarms and the alarm log](#see-raised-alarms-and-the-alarm-log) - -Let's get started! - -## Visit and explore the dashboard - -Netdata's dashboard is where you interact with your system's metrics. Time to open it up and start exploring. Open up -your browser of choice. - -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. Hit **Enter**. Welcome to Netdata! - -![Animated GIF of navigating to the -dashboard](https://user-images.githubusercontent.com/1153921/80825153-abaec600-8b94-11ea-8b17-1b770a2abaa9.gif) - -> From here on out in this guide, we'll refer to the address you use to view your dashboard as `NODE`. Be sure to -> replace it with either `localhost`, the IP address, or the hostname of your system. - -## Explore available charts using menus - -**Menus** are located on the right-hand side of the Netdata dashboard. You can use these to navigate to the -charts you're interested in. - -![Animated GIF of using the menus and -submenus](https://user-images.githubusercontent.com/1153921/80832425-7c528600-8ba1-11ea-8140-d0a17a62009b.gif) - -Netdata shows all its charts on a single page, so you can also scroll up and down using the mouse wheel, your -touchscreen/touchpad, or the scrollbar. - -Both menus and the items displayed beneath them, called **submenus**, are populated automatically by Netdata based on -what it's collecting. If you run Netdata on many different systems using different OS types or versions, the -menus and submenus may look a little different for each one. - -To learn more about menus, see our documentation about [navigating the standard -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 -> to show?** setting to **All**. In addition, if you start Netdata and immediately load the dashboard, not all -> charts/menus/submenus may be displayed, as some collectors can take a while to initialize. - -## Read the descriptions accompanying charts - -Many charts come with a short description of what dimensions the chart is displaying and why they matter. - -For example, here's the description that accompanies the **swap** chart. - -![Screenshot of the swap -description](https://user-images.githubusercontent.com/1153921/63452078-477b1600-c3fa-11e9-836b-2fc90fba8b4b.png) - -If you're new to health monitoring and performance troubleshooting, we recommend you spend some time reading these -descriptions and learning more at the pages linked above. - -## Understand charts, dimensions, families, and contexts - -A **chart** is an interactive visualization of one or more collected/calculated metrics. You can see the name (also -known as its unique ID) of a chart by looking at the top-left corner of a chart and finding the parenthesized text. On a -Linux system, one of the first charts on the dashboard will be the system CPU chart, with the name `system.cpu`: - -![Screenshot of the system CPU chart in the Netdata -dashboard](https://user-images.githubusercontent.com/1153921/67443082-43b16e80-f5b8-11e9-8d33-d6ee052c6678.png) - -A **dimension** is any value that gets shown on a chart. The value can be raw data or calculated values, such as -percentages, aggregates, and more. Most charts will have more than one dimension, in which case it will display each in -a different color. Here, a `system.cpu` chart is showing many dimensions, such as `user`, `system`, `softirq`, `irq`, -and more. - -![Screenshot of the dimensions shown in the system CPU chart in the Netdata -dashboard](https://user-images.githubusercontent.com/1153921/62721031-2bba4d80-b9c0-11e9-9dca-32403617ce72.png) - -A **family** is _one_ instance of a monitored hardware or software resource that needs to be monitored and displayed -separately from similar instances. For example, if your system has multiple partitions, Netdata will create different -families for `/`, `/boot`, `/home`, and so on. Same goes for entire disks, network devices, and more. - -![A number of families created for disk partitions](https://user-images.githubusercontent.com/1153921/67896952-a788e980-fb1a-11e9-880b-2dfb3945c8d6.png) - -A **context** groups several charts based on the types of metrics being collected and displayed. For example, the -**Disk** section often has many contexts: `disk.io`, `disk.ops`, `disk.backlog`, `disk.util`, and so on. Netdata uses -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. - -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](https://github.com/netdata/netdata/blob/master/web/README.md#charts-contexts-families). - -## Interact with charts - -We built Netdata to be a big sandbox for learning more about your systems and applications. Time to play! - -Netdata's charts are fully interactive. You can pan through historical metrics, zoom in and out, select specific -timeframes for further analysis, resize charts, and more. - -Best of all, Whenever you use a chart in this way, Netdata synchronizes all the other charts to match it. - -![Animated GIF of the standard Netdata dashboard being manipulated and synchronizing -charts](https://user-images.githubusercontent.com/1153921/81867875-3d6beb00-9526-11ea-94b8-388951e2e03d.gif) - -### Pan, zoom, highlight, and reset charts - -You can change how charts show their metrics in a few different ways, each of which have a few methods: - -| Change | Method #1 | Method #2 | Method #3 | -| ------------------------------------------------- | ----------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------- | -| **Reset** charts to default auto-refreshing state | `double click` | `double tap` (touchpad/touchscreen) | | -| **Select** a certain timeframe | `ALT` + `mouse selection` | `⌘` + `mouse selection` (macOS) | | -| **Pan** forward or back in time | `click and drag` | `touch and drag` (touchpad/touchscreen) | | -| **Zoom** to a specific timeframe | `SHIFT` + `mouse selection` | | | -| **Zoom** in/out | `SHIFT`/`ALT` + `mouse scrollwheel` | `SHIFT`/`ALT` + `two-finger pinch` (touchpad/touchscreen) | `SHIFT`/`ALT` + `two-finger scroll` (touchpad/touchscreen) | - -These interactions can also be triggered using the icons on the bottom-right corner of every chart. They are, -respectively, `Pan Left`, `Reset`, `Pan Right`, `Zoom In`, and `Zoom Out`. - -### Show and hide dimensions - -Each dimension can be hidden by clicking on it. Hiding dimensions simplifies the chart and can help you better discover -exactly which aspect of your system is behaving strangely. - -### Resize charts - -Additionally, resize charts by clicking-and-dragging the icon on the bottom-right corner of any chart. To restore the -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](https://github.com/netdata/netdata/blob/master/web/README.md). - -## See raised alarms and the alarm log - -Aside from performance troubleshooting, the Agent helps you monitor the health of your systems and applications. That's -why every Netdata installation comes with dozens of pre-configured alarms that trigger alerts when your system starts -acting strangely. - -Find the **Alarms** button in the top navigation bring up a modal that shows currently raised alarms, all running -alarms, and the alarms log. - -Here is an example of a raised `system.cpu` alarm, followed by the full list and alarm log: - -![Animated GIF of looking at raised alarms and the alarm -log](https://user-images.githubusercontent.com/1153921/80842482-8c289500-8bb6-11ea-9791-600cfdbe82ce.gif) - -And a static screenshot of the raised CPU alarm: - -![Screenshot of a raised system CPU alarm](https://user-images.githubusercontent.com/1153921/80842330-2dfbb200-8bb6-11ea-8147-3cd366eb0f37.png) - -The alarm itself is named *system - cpu**, and its context is `system.cpu`. Beneath that is an auto-updating badge that -shows the latest value the chart that triggered the alarm. - -With the three icons beneath that and the **role** designation, you can: - -1. Scroll to the chart associated with this raised alarm. -2. Copy a link to the badge to your clipboard. -3. Copy the code to embed the badge onto another web page using an `` element. - -The table on the right-hand side displays information about the alarm's configuration. In above example, Netdata -triggers a warning alarm when CPU usage is between 75 and 85%, and a critical alarm when above 85%. It's a _little_ more -complicated than that, but we'll get into more complex health entity configurations in a later step. - -The `calculation` field is the equation used to calculate those percentages, and the `check every` field specifies how -often Netdata should be calculating these metrics to see if the alarm should remain triggered. - -The `execute` field tells Netdata how to notify you about this alarm, and the `source` field lets you know where you can -find the configuration file, if you'd like to edit its configuration. - -We'll cover alarm configuration in more detail later in the guide, so don't worry about it too much for now! Right -now, it's most important that you understand how to see alarms, and parse their details, if and when they appear on your -system. - -## What's next? - -In this step of the Netdata guide, you learned how to: - -- Visit the dashboard -- Explore available charts (using the right-side menu) -- Read the descriptions accompanying charts -- Interact with charts -- See raised alarms and the alarm log - -Next, you'll learn how to monitor multiple nodes through the dashboard. - -[Next: Monitor more than one system with Netdata →](step-03.md) - - diff --git a/docs/guides/step-by-step/step-03.md b/docs/guides/step-by-step/step-03.md deleted file mode 100644 index 3204765b..00000000 --- a/docs/guides/step-by-step/step-03.md +++ /dev/null @@ -1,94 +0,0 @@ - - -# Step 3. Monitor more than one system with Netdata - -The Netdata agent is _distributed_ by design. That means each agent operates independently from any other, collecting -and creating charts only for the system you installed it on. We made this decision a long time ago to [improve security -and performance](step-01.md). - -You might be thinking, "So, now I have to remember all these IP addresses, and type them into my browser -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://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 -Cloud](https://user-images.githubusercontent.com/1153921/80828986-1ebb3b00-8b9b-11ea-957f-2c8d0d009e44.gif) - -## What you'll learn in this step - -In this step of the Netdata guide, we'll talk about the following: - -- [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://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 -> distributed Agents in headless mode _and_ access the real-time metrics and insightful charts from their dashboards. -> View key metrics and active alarms at-a-glance, and then seamlessly dive into any of your distributed dashboards -> without leaving Cloud's centralized interface. - -You can add as many nodes and team members as you need, and as our free and open source Agent gets better with more -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://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. - -

Sign in to Cloud

- -When you finish that guide, circle back to this step in the guide to learn how to use the Visited Nodes feature on -top of Cloud's centralized web interface. - -## Navigate between dashboards with Visited Nodes - -To add nodes to your visited nodes, you first need to navigate to that node's dashboard, then click the **Sign in** -button at the top of the dashboard. On the screen that appears, which states your node is requesting access to your -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 menu populated by a single visited node. - -![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 -alongside your visited nodes. 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) - -To add other visited nodes, navigate to their dashboard and sign in to Cloud by clicking on the **Sign in** button. This -process connects that node to your 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) - -## What's next? - -Now that you have a Netdata Cloud account with a connected node (or a few!) and can navigate between your dashboards with -Visited nodes, it's time to learn more about how you can configure Netdata to your liking. From there, you'll be able to -customize your Netdata experience to your exact infrastructure and the information you need. - -[Next: The basics of configuring Netdata →](step-04.md) - - diff --git a/docs/guides/step-by-step/step-04.md b/docs/guides/step-by-step/step-04.md deleted file mode 100644 index fcd84ce6..00000000 --- a/docs/guides/step-by-step/step-04.md +++ /dev/null @@ -1,144 +0,0 @@ - - -# Step 4. The basics of configuring Netdata - -Welcome to the fourth step of the Netdata guide. - -Since the beginning, we've covered the building blocks of Netdata, dashboard basics, and how you can monitor many -individual systems using many distributed Netdata agents. - -Next up: configuration. - -## What you'll learn in this step - -We'll talk about Netdata's default configuration, and then you'll learn how to do the following: - -- [Find your `netdata.conf` file](#find-your-netdataconf-file) -- [Use edit-config to open `netdata.conf`](#use-edit-config-to-open-netdataconf) -- [Navigate the structure of `netdata.conf`](#the-structure-of-netdataconf) -- [Edit your `netdata.conf` file](#edit-your-netdataconf-file) - -## Find your `netdata.conf` file - -Netdata primarily uses the `netdata.conf` file to configure its core functionality. `netdata.conf` resides within your -**Netdata config directory**. - -The location of that directory and `netdata.conf` depends on your operating system and the method you used to install -Netdata. - -The most reliable method of finding your Netdata config directory is loading your `netdata.conf` on your browser. Open a -tab and navigate to `http://HOST:19999/netdata.conf`. Your browser will load a text document that looks like this: - -![A netdata.conf file opened in the -browser](https://user-images.githubusercontent.com/1153921/68346763-344f1c80-00b2-11ea-9d1d-0ccac74d5558.png) - -Look for the line that begins with `# config directory = `. The text after that will be the path to your Netdata config -directory. - -In the system represented by the screenshot, the line reads: `config directory = /etc/netdata`. That means -`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](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**. - -## Use edit-config to open `netdata.conf` - -Inside your Netdata config directory, there is a helper scripted called `edit-config`. This script will open existing -Netdata configuration files using a text editor. Or, if the configuration file doesn't yet exist, the script will copy -an example file to your Netdata config directory and then allow you to edit it before saving it. - -> `edit-config` will use the `EDITOR` environment variable on your system to edit the file. On many systems, that is -> defaulted to `vim` or `nano`. We highly recommend `nano` for beginners. To change this variable for the current -> session (it will revert to the default when you reboot), export a new value: `export EDITOR=nano`. Or, [make the -> change permanent](https://stackoverflow.com/questions/13046624/how-to-permanently-export-a-variable-in-linux). - -Let's give it a shot. Navigate to your Netdata config directory. To use `edit-config` on `netdata.conf`, you need to -have permissions to edit the file. On Linux/macOS systems, you can usually use `sudo` to elevate your permissions. - -```bash -cd /etc/netdata # Replace this path with your Netdata config directory, if different as found in the steps above -sudo ./edit-config netdata.conf -``` - -You should now see `netdata.conf` your editor! Let's walk through how the file is structured. - -## The structure of `netdata.conf` - -There are two main parts of the file to note: **sections** and **options**. - -The `netdata.conf` file is broken up into various **sections**, such as `[global]`, `[web]`, and `[registry]`. Each -section contains the configuration options for some core component of Netdata. - -Each section also contains many **options**. Options have a name and a value. With the option `config directory = -/etc/netdata`, `config directory` is the name, and `/etc/netdata` is the value. - -Most lines are **commented**, in that they start with a hash symbol (`#`), and the value is set to a sane default. To -tell Netdata that you'd like to change any option from its default value, you must **uncomment** it by removing that -hash. - -### Edit your `netdata.conf` file - -Let's try editing the options in `netdata.conf` to see how the process works. - -First, add a fake option to show you how Netdata loads its configuration files. Add a `test` option under the `[global]` -section and give it the value of `1`. - -```conf -[global] - test = 1 -``` - -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. - -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. - -Here's the process in GIF form! - -![Animated GIF of creating a fake option in -netdata.conf](https://user-images.githubusercontent.com/1153921/65470254-4422e200-de1f-11e9-9597-a97c89ee59b8.gif) - -Now, let's make a slightly more substantial edit to `netdata.conf`: change the Agent's name. - -If you edit the value of the `hostname` option, you can change the name of your Netdata Agent on the dashboard and a -handful of other places, like the Visited nodes menu _and_ Netdata Cloud. - -Use `edit-config` to change the `hostname` option to a name like `hello-world`. Be sure to uncomment it! - -```conf -[global] - hostname = hello-world -``` - -Once you're done, restart Netdata and refresh the dashboard. Say hello to your renamed agent! - -![Animated GIF of editing the hostname option in -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](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? - -At this point, you should be comfortable with getting to your Netdata directory, opening and editing `netdata.conf`, and -seeing your changes reflected in the dashboard. - -Netdata has many more configuration files that you might want to change, but we'll cover those in the following steps of -this guide. - -In the next step, we're going to cover one of Netdata's core functions: monitoring the health of your systems via alarms -and notifications. You'll learn how to disable alarms, create new ones, and push notifications to the system of your -choosing. - -[Next: Health monitoring alarms and notifications →](step-05.md) - - diff --git a/docs/guides/step-by-step/step-05.md b/docs/guides/step-by-step/step-05.md deleted file mode 100644 index 3ef498d4..00000000 --- a/docs/guides/step-by-step/step-05.md +++ /dev/null @@ -1,349 +0,0 @@ - - -# Step 5. Health monitoring alarms and notifications - -In the fifth step of the Netdata guide, we're introducing you to one of our core features: **health monitoring**. - -To accurately monitor the health of your systems and applications, you need to know _immediately_ when there's something -strange going on. Netdata's alarm and notification systems are essential to keeping you informed. - -Netdata comes with hundreds of pre-configured alarms that don't require configuration. They were designed by our -community of system administrators to cover the most important parts of production systems, so, in many cases, you won't -need to edit them. - -Luckily, Netdata's alarm and notification system are incredibly adaptable to your infrastructure's unique needs. - -## What you'll learn in this step - -We'll talk about Netdata's default configuration, and then you'll learn how to do the following: - -- [Tune Netdata's pre-configured alarms](#tune-netdatas-pre-configured-alarms) -- [Write your first health entity](#write-your-first-health-entity) -- [Enable Netdata's notification systems](#enable-netdatas-notification-systems) - -## Tune Netdata's pre-configured alarms - -First, let's tune an alarm that came pre-configured with your Netdata installation. - -The first chart you see on any Netdata dashboard is the `system.cpu` chart, which shows the system's CPU utilization -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) - -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 -`health.d/cpu.conf`. Go to your [Netdata config directory](step-04.md#find-your-netdataconf-file) and use the -`edit-config` script. - -```bash -sudo ./edit-config health.d/cpu.conf -``` - -The first **health entity** in that file looks like this: - -```yaml -template: 10min_cpu_usage - on: system.cpu - os: linux - hosts: * - lookup: average -10m unaligned of user,system,softirq,irq,guest - units: % - every: 1m - warn: $this > (($status >= $WARNING) ? (75) : (85)) - crit: $this > (($status == $CRITICAL) ? (85) : (95)) - delay: down 15m multiplier 1.5 max 1h - info: average cpu utilization for the last 10 minutes (excluding iowait, nice and steal) - to: sysadmin -``` - -Let's say you want to tune this alarm to trigger warning and critical alarms at a lower CPU utilization. You can change -the `warn` and `crit` lines to the values of your choosing. For example: - -```yaml - warn: $this > (($status >= $WARNING) ? (60) : (75)) - crit: $this > (($status == $CRITICAL) ? (75) : (85)) -``` - -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](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](https://github.com/netdata/netdata/blob/master/health/README.md). - -### Silence an individual alarm - -Many Netdata users don't need all the default alarms enabled. Instead of disabling any given alarm, or even _all_ -alarms, you can silence individual alarms by changing one line in a given health entity. Let's look at that -`health/cpu.conf` file again. - -```yaml -template: 10min_cpu_usage - on: system.cpu - os: linux - hosts: * - lookup: average -10m unaligned of user,system,softirq,irq,guest - units: % - every: 1m - warn: $this > (($status >= $WARNING) ? (75) : (85)) - crit: $this > (($status == $CRITICAL) ? (85) : (95)) - delay: down 15m multiplier 1.5 max 1h - info: average cpu utilization for the last 10 minutes (excluding iowait, nice and steal) - to: sysadmin -``` - -To silence this alarm, change `sysadmin` to `silent`. - -```yaml - to: silent -``` - -Use `netdatacli reload-health` to reload your health configuration. You can add `to: silent` to any alarm you'd rather not -bother you with notifications. - -## Write your first health entity - -The best way to understand how health entities work is building your own and experimenting with the options. To start, -let's build a health entity that triggers an alarm when system RAM usage goes above 80%. - -We will first 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 -``` - -The first line in a health entity will be `alarm:`. This is how you name your entity. You can give it any name you -choose, but the only symbols allowed are `.` and `_`. Let's call the alarm `ram_usage`. - -```yaml - alarm: ram_usage -``` - -> You'll see some funky indentation in the lines coming up. Don't worry about it too much! Indentation is not important -> to how Netdata processes entities, and it will make sense when you're done. - -Next, you need to specify which chart this entity listens via the `on:` line. You're declaring that you want this alarm -to check metrics on the `system.ram` chart. - -```yaml - on: system.ram -``` - -Now comes the `lookup`. This line specifies what metrics the alarm is looking for, what duration of time it's looking -at, and how to process the metrics into a more usable format. - -```yaml -lookup: average -1m percentage of used -``` - -Let's take a moment to break this line down. - -- `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 you want to calculate a percentage of RAM usage. -- `of used`: Specify which dimension (`used`) on the `system.ram` chart you want to monitor with this entity. - -In other words, you're taking 1 minute's worth of metrics from the `used` dimension on the `system.ram` chart, -calculating their average, and returning it as a percentage. - -You can move on to the `units` line, which lets Netdata know that we're working with a percentage and not an absolute -unit. - -```yaml - units: % -``` - -Next, the `every` line tells Netdata how often to perform the calculation you specified in the `lookup` line. For -certain alarms, you might want to use a shorter duration, which you can specify using values like `10s`. - -```yaml - every: 1m -``` - -We'll put the next two lines—`warn` and `crit`—together. In these lines, you declare at which percentage you want to -trigger a warning or critical alarm. Notice the variable `$this`, which is the value calculated by the `lookup` line. -These lines will trigger a warning if that average RAM usage goes above 80%, and a critical alert if it's above 90%. - -```yaml - warn: $this > 80 - crit: $this > 90 -``` - -> ❗ 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](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. - -Finish off with the `info` line, which creates a description of the alarm that will then appear in any -[notification](#enable-netdatas-notification-systems) you set up. This line is optional, but it has value—think of it as -documentation for a health entity! - -```yaml - info: The percentage of RAM being used by the system. -``` - -Here's what the entity looks like in full. Now you can see why we indented the lines, too. - -```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 being used by the system. -``` - -What about what it looks like on the Netdata dashboard? - -![An active alert for the ram_usage alarm](https://user-images.githubusercontent.com/1153921/67056219-f89ee380-f0ff-11e9-8842-7dc210dd2908.png) - -If you'd like to try this alarm on your system, you can install a small program called -[stress](http://manpages.ubuntu.com/manpages/disco/en/man1/stress.1.html) to create a synthetic load. Use the command -below, and change the `8G` value to a number that's appropriate for the amount of RAM on your system. - -```bash -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](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 - -Health alarms, while great on their own, are pretty useless without some way of you knowing they've been triggered. -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](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. - -### Email notifications - -To use email notifications, you need `sendmail` or an equivalent installed on your system. Linux systems use `sendmail` -or similar programs to, unsurprisingly, send emails to any inbox. - -> Learn more about `sendmail` via its [documentation](http://www.postfix.org/sendmail.1.html). - -Edit the `health_alarm_notify.conf` file, which resides in your Netdata directory. - -```bash -sudo ./edit-config health_alarm_notify.conf -``` - -Look for the following lines: - -```conf -# if a role recipient is not configured, an email will be send to: -DEFAULT_RECIPIENT_EMAIL="root" -# to receive only critical alarms, set it to "root|critical" -``` - -Change the value of `DEFAULT_RECIPIENT_EMAIL` to the email address at which you'd like to receive notifications. - -```conf -# if a role recipient is not configured, an email will be sent to: -DEFAULT_RECIPIENT_EMAIL="me@example.com" -# to receive only critical alarms, set it to "root|critical" -``` - -Test email notifications system by first becoming the Netdata user and then asking Netdata to send a test alarm: - -```bash -sudo su -s /bin/bash netdata -/usr/libexec/netdata/plugins.d/alarm-notify.sh test -``` - -You should see output similar to this: - -```bash -# SENDING TEST WARNING ALARM TO ROLE: sysadmin -2019-10-17 18:23:38: alarm-notify.sh: INFO: sent email notification for: hostname test.chart.test_alarm is WARNING to 'me@example.com' -# OK - -# SENDING TEST CRITICAL ALARM TO ROLE: sysadmin -2019-10-17 18:23:38: alarm-notify.sh: INFO: sent email notification for: hostname test.chart.test_alarm is CRITICAL to 'me@example.com' -# OK - -# SENDING TEST CLEAR ALARM TO ROLE: sysadmin -2019-10-17 18:23:39: alarm-notify.sh: INFO: sent email notification for: hostname test.chart.test_alarm is CLEAR to 'me@example.com' -# OK -``` - -... and you should get three separate emails, one for each test alarm, in your inbox! (Be sure to check your spam -folder.) - -## Enable Slack notifications - -If you're one of the many who spend their workday getting pinged with GIFs by your colleagues, why not add Netdata -notifications to the mix? It's a great way to immediately see, collaborate around, and respond to anomalies in your -infrastructure. - -To get Slack notifications working, you first need to add an [incoming -webhook](https://slack.com/apps/A0F7XDUAZ-incoming-webhooks) to the channel of your choice. Click the green **Add to -Slack** button, choose the channel, and click the **Add Incoming WebHooks Integration** button. - -On the following page, you'll receive a **Webhook URL**. That's what you'll need to configure Netdata, so keep it handy. - -Time to dive back into your `health_alarm_notify.conf` file: - -```bash -sudo ./edit-config health_alarm_notify.conf -``` - -Look for the `SLACK_WEBHOOK_URL=" "` line and add the incoming webhook URL you got from Slack: - -```conf -SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXX" -``` - -A few lines down, edit the `DEFAULT_RECIPIENT_SLACK` line to contain a single hash `#` character. This instructs Netdata -to send a notification to the channel you configured with the incoming webhook. - -```conf -DEFAULT_RECIPIENT_SLACK="#" -``` - -Time to test the notifications again! - -```bash -sudo su -s /bin/bash netdata -/usr/libexec/netdata/plugins.d/alarm-notify.sh test -``` - -You should receive three notifications in your Slack channel. - -Congratulations! You're set up with two awesome ways to get notified about any change in the health of your systems or -applications. - -To further configure your email or Slack notification setup, or to enable other notification systems, check out the -following documentation: - -- [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? - -In this step, you learned the fundamentals of Netdata's health monitoring tools: alarms and notifications. You should be -able to tune default alarms, silence them, and understand some of the basics of writing health entities. And, if you so -chose, you'll now have both email and Slack notifications enabled. - -You're coming along quick! - -Next up, we're going to cover how Netdata collects its metrics, and how you can get Netdata to collect real-time metrics -from hundreds of services with almost no configuration on your part. Onward! - -[Next: Collect metrics from more services and apps →](step-06.md) - - diff --git a/docs/guides/step-by-step/step-06.md b/docs/guides/step-by-step/step-06.md deleted file mode 100644 index b951a76b..00000000 --- a/docs/guides/step-by-step/step-06.md +++ /dev/null @@ -1,122 +0,0 @@ - - -# Step 6. Collect metrics from more services and apps - -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](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](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 - -We'll begin with an overview on Netdata's collector architecture, and then dive into the following: - -- [Netdata's collector architecture](#netdatas-collector-architecture) -- [Enable and disable plugins](#enable-and-disable-plugins) -- [Enable the Nginx collector as an example](#example-enable-the-nginx-collector) - -## Netdata's collector architecture - -Many Netdata users never have to configure collector or worry about which plugin orchestrator they want to use. - -But, if you want to configure collector or write a collector for your custom source, it's important to understand the -underlying architecture. - -By default, Netdata collects a lot of metrics every second using any number of discrete collector. Collectors, in turn, -are organized and manged by plugins. **Internal** plugins collect system metrics, **external** plugins collect -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://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 - -You don't need to explicitly enable plugins to auto-detect properly configured sources, but it's useful to know how to -enable or disable them. - -One reason you might want to _disable_ plugins is to improve Netdata's performance on low-resource systems, like -ephemeral nodes or edge devices. Disabling orchestrator plugins like `python.d` can save significant resources if you're -not using any of its data collector modules. - -You can enable or disable plugins in the `[plugin]` section of `netdata.conf`. This section features a list of all the -plugins with a boolean setting (`yes` or `no`) to enable or disable them. Be sure to uncomment the line by removing the -hash (`#`)! - -Enabled: - -```conf -[plugins] - # python.d = yes -``` - -Disabled: - -```conf -[plugins] - python.d = no -``` - -When you explicitly disable a plugin this way, it won't auto-collect metrics using its collectors. - -## Example: Enable the Nginx collector - -To help explain how the auto-detection process works, let's use an Nginx web server as an example. - -Even if you don't have Nginx installed on your system, we recommend you read through the following section so you can -apply the process to other data sources, such as Apache, Redis, Memcached, and more. - -The Nginx collector, which helps Netdata collect metrics from a running Nginx web server, is part of the -`python.d.plugin` external plugin _orchestrator_. - -In order for Netdata to auto-detect an Nginx web server, you need to enable `ngx_http_stub_status_module` and pass the -`stub_status` directive in the `location` block of your Nginx configuration file. - -You can confirm if the `stub_status` Nginx module is already enabled or not by using following command: - -```sh -nginx -V 2>&1 | grep -o with-http_stub_status_module -``` - -If this command returns nothing, you'll need to [enable this module](https://www.nginx.com/blog/monitoring-nginx/). - -Next, edit your `/etc/nginx/sites-enabled/default` file to include a `location` block with the following: - -```conf - location /stub_status { - stub_status; - } -``` - -Restart Netdata using `sudo systemctl restart netdata`, or the [appropriate -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 -by editing its configuration file: - -```sh -./edit-config python.d/nginx.conf -``` - -After configuring any source, or changing the configuration files for their respective modules, always restart Netdata. - -## What's next? - -Now that you've learned the fundamentals behind configuring data sources for auto-detection, it's time to move back to -the dashboard to learn more about some of its more advanced features. - -[Next: Netdata's dashboard in depth →](step-07.md) - - diff --git a/docs/guides/step-by-step/step-07.md b/docs/guides/step-by-step/step-07.md deleted file mode 100644 index 8c5c21be..00000000 --- a/docs/guides/step-by-step/step-07.md +++ /dev/null @@ -1,114 +0,0 @@ - - -# Step 7. Netdata's dashboard in depth - -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](https://github.com/netdata/netdata/blob/master/docs/guides/step-by-step/step-02.md). - -## What you'll learn in this step - -In this step of the Netdata guide, you'll learn how to: - -- [Change the dashboard's settings](#change-the-dashboards-settings) -- [Check if there's an update to Netdata](#check-if-theres-an-update-to-netdata) -- [Export and import a snapshot](#export-and-import-a-snapshot) - -Let's get started! - -## Change the dashboard's settings - -The settings area at the top of your Netdata dashboard houses browser settings. These settings do not affect the -operation of your Netdata server/daemon. They take effect immediately and are permanently saved to browser local storage -(except the refresh on focus / always option). - -You can see the **Performance**, **Synchronization**, **Visual**, and **Locale** tabs on the dashboard settings modal. - -![Animated GIF of opening the settings -modal](https://user-images.githubusercontent.com/1153921/80841197-c93f5800-8bb3-11ea-907d-85bfe23565e1.gif) - -To change any setting, click on the toggle button. We recommend you spend some time reading the descriptions for each setting to understand them before making changes. - -Pay particular attention to the following settings, as they have dramatic impacts on the performance and appearance of -your Netdata dashboard: - -- When to refresh the charts? -- How to handle hidden charts? -- Which chart refresh policy to use? -- Which theme to use? -- Do you need help? - -Some settings are applied immediately, and others are only reflected after you refresh the page. - -## Check if there's an update to Netdata - -You can always check if there is an update available from the **Update** area of your Netdata dashboard. - -![Opening the Agent's Update modal](https://user-images.githubusercontent.com/1153921/80829493-1adbe880-8b9c-11ea-9770-cc3b23a89414.gif) - -If an update is available, you'll see a modal similar to the one above. - -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 - -Netdata can export and import snapshots of the contents of your dashboard at a given time. Any Netdata agent can import -a snapshot created by any other Netdata agent. - -Snapshot files include all the information of the dashboard, including the URL of the origin server, its unique ID, and -chart data queries for the visible timeframe. While snapshots are not in real-time, and thus won't update with new -metrics, you can still pan, zoom, and highlight charts as you see fit. - -Snapshots can be incredibly useful for diagnosing anomalies after they've already happened. Let's say Netdata triggered -an alarm while you were sleeping. In the morning, you can look up the exact moment the alarm was raised, export a -snapshot, and send it to a colleague for further analysis. - -> ❗ Know how you shouldn't go around downloading software from suspicious-looking websites? Same policy goes for loading -> snapshots from untrusted or anonymous sources. Importing a snapshot loads quite a bit of data into your web browser, -> and so you should always err on the side of protecting your system. - -To export a snapshot, click on the **export** icon. - -![Animated GIF of opening the export -modal](https://user-images.githubusercontent.com/1153921/80993197-82d63d00-8def-11ea-88fa-98827814e930.gif) - -Edit the snapshot file name and select your desired compression method. Click on **Export**. - -When the export is complete, your browser will prompt you to save the `.snapshot` file to your machine. You can now -share this file with any other Netdata user via email, Slack, or even to help describe your Netdata experience when -[filing an issue](https://github.com/netdata/netdata/issues/new/choose) on GitHub. - -To import a snapshot, click on the **import** icon. - -![Animated GIF of opening the import -modal](https://user-images.githubusercontent.com/12263278/64901503-ee696f80-d691-11e9-9678-8d0e2a162402.gif) - -Select the Netdata snapshot file to import. Once the file is loaded, the dashboard will update with critical information -about the snapshot and the system from which it was taken. Click **import** to render it. - -Your Netdata dashboard will load data contained in the snapshot into charts. Because the snapshot only covers a certain -period, it won't update with new metrics. - -An imported snapshot is also temporary. If you reload your browser tab, Netdata will remove the snapshot data and -restore your real-time dashboard for your machine. - -## What's next? - -In this step of the Netdata guide, you learned how to: - -- Change the dashboard's settings -- Check if there's an update to Netdata -- Export or import a snapshot - -Next, you'll learn how to build your first custom dashboard! - -[Next: Build your first custom dashboard →](step-08.md) - - diff --git a/docs/guides/step-by-step/step-08.md b/docs/guides/step-by-step/step-08.md deleted file mode 100644 index 7a8d417f..00000000 --- a/docs/guides/step-by-step/step-08.md +++ /dev/null @@ -1,395 +0,0 @@ - - -# Step 8. Build your first custom dashboard - -In previous steps of the guide, you have learned how several sections of the Netdata dashboard worked. - -This step will show you how to set up a custom dashboard to fit your unique needs. If nothing else, Netdata is really, -really flexible. 🤸 - -## What you'll learn in this step - -In this step of the Netdata guide, you'll learn: - -- [Why you might want a custom dashboard](#why-should-i-create-a-custom-dashboard) -- [How to create and prepare your `custom-dashboard.html` file](#create-and-prepare-your-custom-dashboardhtml-file) -- [Where to add `dashboard.js` to your custom dashboard file](#add-dashboardjs-to-your-custom-dashboard-file) -- [How to add basic styling](#add-some-basic-styling) -- [How to add charts of different types, shapes, and sizes](#creating-your-dashboards-charts) - -Let's get on with it! - -## Why should I create a custom dashboard? - -Because it's cool! - -But there are way more reasons than that, most of which will prove more valuable to you. - -You could use custom dashboards to aggregate real-time data from multiple Netdata agents in one place. Or, you could put -all the charts with metrics collected from your custom application via `statsd` and perform application performance -monitoring from a single dashboard. You could even use a custom dashboard and a standalone web server to create an -enriched public status page for your service, and give your users something fun to look at while they're waiting for the -503 errors to clear up! - -Netdata's custom dashboarding capability is meant to be as flexible as your ideas. We hope you can take these -fundamental ideas and turn them into something amazing. - -## Create and prepare your `custom-dashboard.html` file - -By default, Netdata stores its web server files at `/usr/share/netdata/web`. As with finding the location of your -`netdata.conf` file, you can double-check this location by loading up `http://HOST:19999/netdata.conf` in your browser -and finding the value of the `web files directory` option. - -To create your custom dashboard, create a file at `/usr/share/netdata/web/custom-dashboard.html` and copy in the -following: - -```html - - - - My custom dashboard - - - - - - - - - - - - - -
- -

My custom dashboard

- - - -
- - - -``` - -Try visiting `http://HOST:19999/custom-dashboard.html` in your browser. - -If you get a blank page with this text: `Access to file is not permitted: /usr/share/netdata/web/custom-dashboard.html`. -You can fix this error by changing the dashboard file's permissions to make it owned by the `netdata` user. - -```bash -sudo chown netdata:netdata /usr/share/netdata/web/custom-dashboard.html -``` - -Reload your browser, and you should see a blank page with the title: **Your custom dashboard**! - -## Add `dashboard.js` to your custom dashboard file - -You need to include the `dashboard.js` file of a Netdata agent to add Netdata charts. Add the following to the `` -of your custom dashboard page and change `HOST` according to your setup. - -```html - - -``` - -When you add `dashboard.js` to any web page, it loads several JavaScript and CSS files to create and style charts. It -also scans the page for elements that define charts, builds them, and refreshes with new metrics. - -> If you enabled SSL on your Netdata dashboard already, you'll need to use `https://` to grab the `dashboard.js` file. - -## Add some basic styling - -While not necessary, let's add some basic styling to make our dashboard look a little nicer. We're putting some -basic CSS into a ` - - -``` - -## Creating your dashboard's charts - -Time to create a chart! - -You need to create a `
` for each new chart. Each `
` element accepts a few `data-` attributes, some of which -are required and some of which are optional. - -Let's cover a few important ones. And while we do it, we'll create a custom dashboard that shows a few CPU-related -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](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 `
` element with the `data-netdata` attribute. Put this in the `` of -your custom dashboard file beneath the helpful comment. - -```html - - -
- -

My custom dashboard

- -
- - -
- -
- -
- - -``` - -Reload the page, and you should see a real-time `system.cpu` chart! - -... and a whole lot of white space. Let's fix that by adding a few more charts. - -```html - -
-
-
-
-``` - -![Custom dashboard with four charts -added](https://user-images.githubusercontent.com/1153921/67526566-e675f580-f669-11e9-8ff5-d1f21a84fb2b.png) - -### Set chart duration - -By default, these charts visualize 10 minutes of Netdata metrics. Let's get a little more granular on this dashboard. To -do so, add a new `data-after=""` attribute to each chart. - -`data-after` takes a _relative_ number of seconds from _now_. So, by putting `-300` as the value, you're asking the -custom dashboard to display the _last 5 minutes_ (`5m * 60s = 300s`) of data. - -```html - -
-
-
-
-
-
-
-
-``` - -### Set chart size - -You can set the size of any chart using the `data-height=""` and `data-width=""` attributes. These attributes can be -anything CSS accepts for width and height (e.g. percentages, pixels, em/rem, calc, and so on). - -Let's make the charts a little taller and allow them to fit side-by-side for a more compact view. Add -`data-height="200px"` and `data-width="50%"` to each chart. - -```html -
-
-
-
-``` - -Now we're getting somewhere! - -![A custom dashboard with four charts -side-by-side](https://user-images.githubusercontent.com/1153921/67526620-ff7ea680-f669-11e9-92d3-575665fc3a8e.png) - -## Final touches - -While we already have a perfectly workable dashboard, let's add some final touches to make it a little more pleasant on -the eyes. - -First, add some extra CSS to create some vertical whitespace between the top and bottom row of charts. - -```html - -``` - -To create horizontal whitespace, change the value of `data-width="50%"` to `data-width="calc(50% - 2rem)"`. - -```html -
-
-
-
-``` - -Told you the `data-width` and `data-height` attributes can take any CSS values! - -Prefer a dark theme? Add this to your `` _above_ where you added `dashboard.js`: - -```html - - - - -``` - -Refresh the dashboard to give your eyes a break from all that blue light! - -![A finished custom -dashboard](https://user-images.githubusercontent.com/1153921/67531221-a23d2200-f676-11e9-91fe-c2cf1c426bf9.png) - -## The final `custom-dashboard.html` - -In case you got lost along the way, here's the final version of the `custom-dashboard.html` file: - -```html - - - - My custom dashboard - - - - - - - - - - - - - - - - - - -
- -

My custom dashboard

- -
- - -
-
-
-
- -
- -
- - - -``` - -## What's next? - -In this guide, you learned the fundamentals of building a custom Netdata dashboard. You should now be able to add more -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](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 →](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 deleted file mode 100644 index 839115a2..00000000 --- a/docs/guides/step-by-step/step-09.md +++ /dev/null @@ -1,162 +0,0 @@ - - -# Step 9. Long-term metrics storage - -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. - -On a system that's collecting 2,000 metrics every second, the database engine's default configuration will store about -two day's worth of metrics in RAM and on disk. - -That's a lot of metrics. We're talking 345,600,000 individual data points. And the database engine does it with a tiny -a portion of the RAM available on most systems. - -To store _even more_ metrics, you have two options. First, you can tweak the database engine's options to expand the RAM -or disk it uses. Second, you can archive metrics to an external database. For that, we'll use MongoDB as examples. - -## What you'll learn in this step - -In this step of the Netdata guide, you'll learn how to: - -- [Tweak the database engine's settings](#tweak-the-database-engines-settings) -- [Archive metrics to an external database](#archive-metrics-to-an-external-database) - - [Use the MongoDB database](#archive-metrics-via-the-mongodb-exporting-connector) - -Let's get started! - -## Tweak the database engine's settings - -If you're using Netdata v1.18.0 or higher, and you haven't changed your `memory mode` settings before following this -guide, your Netdata agent is already using the database engine. - -Let's look at your `netdata.conf` file again. Under the `[global]` section, you'll find three connected options. - -```conf -[db] - # mode = dbengine - # dbengine page cache size MB = 32 - # dbengine disk space MB = 256 -``` - -The `memory mode` option is set, by default, to `dbengine`. `page cache size` determines the amount of RAM, in MiB, that -the database engine dedicates to caching the metrics it's collecting. `dbengine disk space` determines the amount of -disk space, in MiB, that the database engine will use to store these metrics once they've been "spilled" to disk.. - -You can uncomment and change either `page cache size` or `dbengine disk space` based on how much RAM and disk you want -the database engine to use. The higher those values, the more metrics Netdata will store. If you change them to 64 and -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**](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. - -```conf -[db] - mode = dbengine - dbengine page cache size MB = 64 - dbengine disk space MB = 512 -``` - -After you've made your changes, restart Netdata using `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 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`. - -![Image of the database engine reflected in the Netdata -Dashboard](https://user-images.githubusercontent.com/12263278/64781383-9c71fe00-d55a-11e9-962b-efd5558efbae.png) - -## Archive metrics to an external database - -You can archive all the metrics collected by Netdata to **external databases**. The supported databases and services -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](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 -Netdata collects so many metrics every second, they can quickly overload small devices or even big servers that are -aggregating metrics streaming in from other Netdata agents. - -We even support resampling metrics during archiving. With resampling enabled, Netdata will archive only the average or -sum of every X seconds of metrics. This reduces the sheer amount of data, albeit with a little less accuracy. - -How you archive metrics, or if you archive metrics at all, is entirely up to you! But let's cover two easy archiving -methods, MongoDB and Prometheus remote write, to get you started. - -### Archive metrics via the MongoDB exporting connector - -Begin by installing MongoDB its dependencies via the correct package manager for your system. - -```bash -sudo apt-get install mongodb # Debian/Ubuntu -sudo dnf install mongodb # Fedora -sudo yum install mongodb # CentOS -``` - -Next, install the one essential dependency: v1.7.0 or higher of -[libmongoc](http://mongoc.org/libmongoc/current/installing.html). - -```bash -sudo apt-get install libmongoc-1.0-0 libmongoc-dev # Debian/Ubuntu -sudo dnf install mongo-c-driver mongo-c-driver-devel # Fedora -sudo yum install mongo-c-driver mongo-c-driver-devel # CentOS -``` - -Next, create a new MongoDB database and collection to store all these archived metrics. Use the `mongo` command to start -the MongoDB shell, and then execute the following command: - -```mongodb -use netdata -db.createCollection("netdata_metrics") -``` - -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! - -Now, from your Netdata config directory, initialize and edit a `exporting.conf` file to tell Netdata where to find the -database you just created. - -```sh -./edit-config exporting.conf -``` - -Add the following section to the file: - -```conf -[mongodb:my_mongo_instance] - enabled = yes - destination = mongodb://localhost - database = netdata - collection = netdata_metrics -``` - -Restart Netdata using `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 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](https://github.com/netdata/netdata/blob/master/exporting/README.md). - -## What's next? - -You're getting close to the end! In this step, you learned how to make the most of the database engine, or archive -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 →](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 deleted file mode 100644 index a24e803f..00000000 --- a/docs/guides/step-by-step/step-10.md +++ /dev/null @@ -1,232 +0,0 @@ - - -# Step 10. Set up a proxy - -You're almost through! At this point, you should be pretty familiar with now Netdata works and how to configure it to -your liking. - -In this step of the guide, we're going to add a proxy in front of Netdata. We're doing this for both improved -performance and security, so we highly recommend following these steps. Doubly so if you installed Netdata on a -publicly-accessible remote server. - -> ❗ If you installed Netdata on the machine you're currently using (e.g. on `localhost`), and have been accessing -> Netdata at `http://localhost:19999`, you can skip this step of the guide. In most cases, there is no benefit to -> setting up a proxy for a service running locally. - -> ❗❗ This guide requires more advanced administration skills than previous parts. If you're still working on your -> Linux administration skills, and would rather get back to Netdata, you might want to [skip this -> step](step-99.md) for now and return to it later. - -## What you'll learn in this step - -In this step of the Netdata guide, you'll learn: - -- [What a proxy is and the benefits of using one](#wait-whats-a-proxy) -- [How to connect Netdata to Nginx](#connect-netdata-to-nginx) -- [How to enable HTTPS in Nginx](#enable-https-in-nginx) -- [How to secure your Netdata dashboard with a password](#secure-your-netdata-dashboard-with-a-password) - -Let's dive in! - -## Wait. What's a proxy? - -A proxy is a middleman between the internet and a service you're running on your system. Traffic from the internet at -large enters your system through the proxy, which then routes it to the service. - -A proxy is often used to enable encrypted HTTPS connections with your browser, but they're also useful for load -balancing, performance, and password-protection. - -We'll use [Nginx](https://nginx.org/en/) for this step of the guide, but you can also use -[Caddy](https://caddyserver.com/) as a simple proxy if you prefer. - -## Required before you start - -You need three things to run a proxy using Nginx: - -- Nginx and Certbot installed on your system -- A fully qualified domain name -- A subdomain for Netdata that points to your system - -### Nginx and Certbot - -This step of the guide assumes you can install Nginx on your system. Here are the easiest methods to do so on Debian, -Ubuntu, Fedora, and CentOS systems. - -```bash -sudo apt-get install nginx # Debian/Ubuntu -sudo dnf install nginx # Fedora -sudo yum install nginx # CentOS -``` - -Check out [Nginx's installation -instructions](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/) for details on -other Linux distributions. - -Certbot is a tool to help you create and renew certificate+key pairs for your domain. Visit their -[instructions](https://certbot.eff.org/instructions) to get a detailed installation process for your operating system. - -### Fully qualified domain name - -The only other true prerequisite of using a proxy is a **fully qualified domain name** (FQDN). In other words, a domain -name like `example.com`, `netdata.cloud`, or `github.com`. - -If you don't have a domain name, you won't be able to use a proxy the way we'll describe here. - -Because we strongly recommend running Netdata behind a proxy, the cost of a domain name is worth the benefit. If you -don't have a preferred domain registrar, try [Google Domains](https://domains.google/), -[Cloudflare](https://www.cloudflare.com/products/registrar/), or [Namecheap](https://www.namecheap.com/). - -### Subdomain for Netdata - -Any of the three domain registrars mentioned above, and most registrars in general, will allow you to create new DNS -entries for your domain. - -To create a subdomain for Netdata, use your registrar's DNS settings to create an A record for a `netdata` subdomain. -Point the A record to the IP address of your system. - -Once finished with the steps below, you'll be able to access your dashboard at `http://netdata.example.com`. - -## Connect Netdata to Nginx - -The first part of enabling the proxy is to create a new server for Nginx. - -Use your favorite text editor to create a file at `/etc/nginx/sites-available/netdata`, copy in the following -configuration, and change the `server_name` line to match your domain. - -```nginx -upstream backend { - server 127.0.0.1:19999; - keepalive 64; -} - -server { - listen 80; - # uncomment the line if you want nginx to listen on IPv6 address - #listen [::]:80; - - # Change `example.com` to match your domain name. - server_name netdata.example.com; - - location / { - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Server $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://backend; - proxy_http_version 1.1; - proxy_pass_request_headers on; - proxy_set_header Connection "keep-alive"; - proxy_store off; - } -} -``` - -Save and close the file. - -Test your configuration file by running `sudo nginx -t`. - -If that returns no errors, it's time to make your server available. Run the command to create a symbolic link in the -`sites-enabled` directory. - -```bash -sudo ln -s /etc/nginx/sites-available/netdata /etc/nginx/sites-enabled/netdata -``` - -Finally, restart Nginx to make your changes live. Open your browser and head to `http://netdata.example.com`. You should -see your proxied Netdata dashboard! - -## Enable HTTPS in Nginx - -All this proxying doesn't mean much if we can't take advantage of one of the biggest benefits: encrypted HTTPS -connections! Let's fix that. - -Certbot will automatically get a certificate, edit your Nginx configuration, and get HTTPS running in a single step. Run -the following: - -```bash -sudo certbot --nginx -``` - -> See this error after running `sudo certbot --nginx`? -> -> ``` -> Saving debug log to /var/log/letsencrypt/letsencrypt.log -> The requested nginx plugin does not appear to be installed` -> ``` -> -> You must install `python-certbot-nginx`. On Ubuntu or Debian systems, you can run `sudo apt-get install -> python-certbot-nginx` to download and install this package. - -You'll be prompted with a few questions. At the `Which names would you like to activate HTTPS for?` question, hit -`Enter`. Next comes this question: - -```bash -Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access. -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -1: No redirect - Make no further changes to the webserver configuration. -2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for -new sites, or if you're confident your site works on HTTPS. You can undo this -change by editing your web server's configuration. -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -You _do_ want to force HTTPS, so hit `2` and then `Enter`. Nginx will now ensure all attempts to access -`netdata.example.com` use HTTPS. - -Certbot will automatically renew your certificate whenever it's needed, so you're done configuring your proxy. Open your -browser again and navigate to `https://netdata.example.com`, and you'll land on an encrypted, proxied Netdata dashboard! - -## Secure your Netdata dashboard with a password - -Finally, let's take a moment to put your Netdata dashboard behind a password. This step is optional, but you might not -want _anyone_ to access the metrics in your proxied dashboard. - -Run the below command after changing `user` to the username you want to use to log in to your dashboard. - -```bash -sudo sh -c "echo -n 'user:' >> /etc/nginx/.htpasswd" -``` - -Then run this command to create a password: - -```bash -sudo sh -c "openssl passwd -apr1 >> /etc/nginx/.htpasswd" -``` - -You'll be prompted to create a password. Next, open your Nginx configuration file at -`/etc/nginx/sites-available/netdata` and add these two lines under `location / {`: - -```nginx - location / { - auth_basic "Restricted Content"; - auth_basic_user_file /etc/nginx/.htpasswd; - ... -``` - -Save, exit, and restart Nginx. Then try visiting your dashboard one last time. You'll see a prompt for the username and -password you just created. - -![Username/password -prompt](https://user-images.githubusercontent.com/1153921/67431031-5320bf80-f598-11e9-9573-f9f9912f1ef6.png) - -Your Netdata dashboard is now a touch more secure. - -## What's next? - -You're a real sysadmin now! - -If you want to configure your Nginx proxy further, check out the following: - -- [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. - -For some celebratory emoji and a clap on the back, head on over to our final step. - -[Next: The end. →](step-99.md) - - diff --git a/docs/guides/step-by-step/step-99.md b/docs/guides/step-by-step/step-99.md deleted file mode 100644 index 58902fee..00000000 --- a/docs/guides/step-by-step/step-99.md +++ /dev/null @@ -1,51 +0,0 @@ - - -# Step ∞. You're finished! - -Congratulations. 🎉 - -You've completed the step-by-step Netdata guide. That means you're well on your way to becoming an expert in using -our toolkit for health monitoring and performance troubleshooting. - -But, perhaps more importantly, also that much closer to being an expert in the _fundamental skills behind health -monitoring and performance troubleshooting_, which you can take with you to any job or project. - -And that is the entire point of this guide, and Netdata's [documentation](https://learn.netdata.cloud) as a -whole—give you every resource possible to help you build faster, more resilient systems, services, and applications. - -Along the way, you learned how to: - -- Navigate Netdata's dashboard and visually detect anomalies using its charts. -- Monitor multiple systems using Netdata agents connected together with your browser and Netdata Cloud. -- Edit your `netdata.conf` file to tweak Netdata to your liking. -- Tune existing alarms and create entirely new ones, plus get notifications about alarms on your favorite services. -- Take advantage of Netdata's auto-detection capabilities to ensure your applications/services are monitored with - little to no configuration. -- Use advanced features within Netdata's dashboard. -- Build a custom dashboard using `dashboard.js`. -- Save more historical metrics with the database engine or archive metrics to MongoDB. -- Put Netdata behind a proxy to enable HTTPS and improve performance. - -Seems like a lot, right? Well, we hope it felt manageable and, yes, even _fun_. - -## What's next? - -Now that you're at the end of our step-by-step Netdata guide, the next steps are entirely up to you. In fact, you're -just at the beginning of your journey into health monitoring and performance troubleshooting. - -Our documentation exists to put every Netdata resource in front of you as easily and coherently as we possibly can. -Click around, search, and find new mountains to climb. - -If that feels like too much possibility to you, why not one of these options: - -- Share your experience with Netdata and this guide. Be sure to [@mention](https://twitter.com/linuxnetdata) us on - Twitter! -- Contribute to what we do. Browse our [open issues](https://github.com/netdata/netdata/issues) and check out out - [contributions doc](https://learn.netdata.cloud/contribute/) for ideas of how you can pitch in. - -We can't wait to see what you monitor next! Bon voyage! ⛵ - - diff --git a/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md b/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md index c79a038c..856985ec 100644 --- a/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md +++ b/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md @@ -1,8 +1,11 @@ # Monitor, troubleshoot, and debug applications with eBPF metrics @@ -83,7 +86,7 @@ to show other charts that will help you debug and troubleshoot how it interacts ## Configure the eBPF collector to monitor errors -The eBPF collector has [two possible modes](/collectors/ebpf.plugin#ebpf-load-mode): `entry` and `return`. The default +The eBPF collector has [two possible modes](https://github.com/netdata/netdata/blob/master/collectors/ebpf.plugin/README.md#ebpf-load-mode): `entry` and `return`. The default is `entry`, and only monitors calls to kernel functions, but the `return` also monitors and charts _whether these calls return in error_. @@ -236,35 +239,16 @@ 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://github.com/netdata/netdata/blob/master/docs/cloud/get-started.mdx) for a walkthrough of -connecting nodes to and other fundamentals. +You can also read how to [monitor your infrastructure with Netdata Cloud](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md) to understand the key features that it has to offer. Once you've added one or more nodes to a Space in Netdata Cloud, you can see aggregated eBPF metrics in the [Overview 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 +find on the local Agent dashboard. Or, [create new dashboards](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/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://github.com/netdata/netdata/blob/master/docs/cloud/manage/invite-your-team.md) and share your findings with others. -## What's next? - -Debugging and troubleshooting an application takes a special combination of practice, experience, and sheer luck. With -Netdata's eBPF metrics to back you up, you can rest assured that you see every minute detail of how your application -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](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 -to explore others and see what our community dreams up. If you have other use cases, whether simulated or real-world, -we'd love to hear them: [info@netdata.cloud](mailto:info@netdata.cloud). - -Happy troubleshooting! diff --git a/docs/guides/troubleshoot/troubleshooting-agent-with-cloud-connection.md b/docs/guides/troubleshoot/troubleshooting-agent-with-cloud-connection.md index 138182e0..a0e8973f 100644 --- a/docs/guides/troubleshoot/troubleshooting-agent-with-cloud-connection.md +++ b/docs/guides/troubleshoot/troubleshooting-agent-with-cloud-connection.md @@ -1,11 +1,7 @@ - - # Troubleshoot Agent-Cloud connectivity issues +Learn how to troubleshoot the Netdata Agent showing as offline after claiming, so you can connect the Agent to Netdata Cloud. + When you are claiming a node, you might not be able to immediately see it online in Netdata Cloud. This could be due to an error in the claiming process or a temporary outage of some services. @@ -13,9 +9,13 @@ We identified some scenarios that might cause this delay and possible actions yo The most common explanation for the delay usually falls into one of the following three categories: -- [The claiming process of the kickstart script was unsuccessful](#the-claiming-process-of-the-kickstart-script-was-unsuccessful) -- [Claiming on an older, deprecated version of the Agent](#claiming-on-an-older-deprecated-version-of-the-agent) -- [Network issues while connecting to the Cloud](#network-issues-while-connecting-to-the-cloud) +- [Troubleshoot Agent-Cloud connectivity issues](#troubleshoot-agent-cloud-connectivity-issues) + - [The claiming process of the kickstart script was unsuccessful](#the-claiming-process-of-the-kickstart-script-was-unsuccessful) + - [The kickstart script auto-claimed the Agent but there was no error message displayed](#the-kickstart-script-auto-claimed-the-agent-but-there-was-no-error-message-displayed) + - [Claiming on an older, deprecated version of the Agent](#claiming-on-an-older-deprecated-version-of-the-agent) + - [Network issues while connecting to the Cloud](#network-issues-while-connecting-to-the-cloud) + - [Verify that your IP is whitelisted from Netdata Cloud](#verify-that-your-ip-is-whitelisted-from-netdata-cloud) + - [Make sure that your node has internet connectivity and can resolve network domains](#make-sure-that-your-node-has-internet-connectivity-and-can-resolve-network-domains) ## The claiming process of the kickstart script was unsuccessful @@ -48,16 +48,14 @@ and you must do it manually, using the following steps: 3. Retry the kickstart claiming process. -:::note - -In some cases a simple restart of the Agent can fix the issue. -Read more about [Starting, Stopping and Restarting the Agent](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md). - -::: +> ### Note +> +> In some cases a simple restart of the Agent can fix the issue. +> Read more about [Starting, Stopping and Restarting the Agent](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md). ## Claiming on an older, deprecated version of the Agent -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). +Make sure that you are using the latest version of Netdata if you are using the [Claiming script](https://github.com/netdata/netdata/blob/master/claim/README.md#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://github.com/netdata/netdata/blob/master/packaging/installer/UPDATE.md) to the latest stable version. @@ -109,9 +107,7 @@ To verify this: main-ingress-545609a41fcaf5d6.elb.us-east-1.amazonaws.com has address 44.196.50.41 ``` - :::info - - There will be cases in which the firewall restricts network access. In those cases, you need to whitelist `api.netdata.cloud` and `mqtt.netdata.cloud` domains to be able to see your nodes in Netdata Cloud. - If you can't whitelist domains in your firewall, you can whitelist the IPs that the above command will produce, but keep in mind that they can change without any notice. - - ::: + > ### Info + > + > There will be cases in which the firewall restricts network access. In those cases, you need to whitelist `api.netdata.cloud` and `mqtt.netdata.cloud` domains to be able to see your nodes in Netdata Cloud. + > If you can't whitelist domains in your firewall, you can whitelist the IPs that the above command will produce, but keep in mind that they can change without any notice. diff --git a/docs/guides/using-host-labels.md b/docs/guides/using-host-labels.md index 7937d589..b9b15611 100644 --- a/docs/guides/using-host-labels.md +++ b/docs/guides/using-host-labels.md @@ -1,23 +1,81 @@ - +# Organize systems, metrics, and alerts -# Use host labels to organize systems, metrics, and alarms +When you use Netdata to monitor and troubleshoot an entire infrastructure, you need sophisticated ways of keeping everything organized. +Netdata allows to organize your observability infrastructure with spaces, war rooms, virtual nodes, host labels, and metric labels. -When you use Netdata to monitor and troubleshoot an entire infrastructure, whether that's dozens or hundreds of systems, -you need sophisticated ways of keeping everything organized. You need alarms that adapt to the system's purpose, or -whether the parent or child in a streaming setup. You need properly-labeled metrics archiving so you can sort, -correlate, and mash-up your data to your heart's content. You need to keep tabs on ephemeral Docker containers in a -Kubernetes cluster. +## Spaces and war rooms -You need **host labels**: a powerful new way of organizing your Netdata-monitored systems. We introduced host labels in -[v1.20 of Netdata](https://blog.netdata.cloud/posts/release-1.20/), and they come pre-configured out of the box. +[Spaces](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md) are used for organization-level or infrastructure-level +grouping of nodes and people. A node can only appear in a single space, while people can have access to multiple spaces. + +The [war rooms](https://github.com/netdata/netdata/edit/master/docs/cloud/war-rooms.md) in a space bring together nodes and people in +collaboration areas. War rooms can also be used for fine-tuned +[role based access control](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/role-based-access.md). + +## Virtual nodes + +Netdata’s virtual nodes functionality allows you to define nodes in configuration files and have them be treated as regular nodes +in all of the UI, dashboards, tabs, filters etc. For example, you can create a virtual node each for all your Windows machines +and monitor them as discrete entities. Virtual nodes can help you simplify your infrastructure monitoring and focus on the +individual node that matters. + +To define your windows server as a virtual node you need to: + + * Define virtual nodes in `/etc/netdata/vnodes/vnodes.conf` + + ```yaml + - hostname: win_server1 + guid: + ``` + Just remember to use a valid guid (On Linux you can use `uuidgen` command to generate one, on Windows just use the `[guid]::NewGuid()` command in PowerShell) + + * Add the vnode config to the data collection job. e.g. in `go.d/windows.conf`: + ```yaml + jobs: + - name: win_server1 + vnode: win_server1 + url: http://203.0.113.10:9182/metrics + ``` + +## Host labels + +Host labels can be extremely useful when: + +- You need alarms that adapt to the system's purpose +- You need properly-labeled metrics archiving so you can sort, correlate, and mash-up your data to your heart's content. +- You need to keep tabs on ephemeral Docker containers in a Kubernetes cluster. Let's take a peek into how to create host labels and apply them across a few of Netdata's features to give you more organization power over your infrastructure. -## Create unique host labels +### Default labels + +When Netdata starts, it captures relevant information about the system and converts them into automatically generated +host labels. You can use these to logically organize your systems via health entities, exporting metrics, +parent-child status, and more. + +They capture the following: + +- Kernel version +- Operating system name and version +- CPU architecture, system cores, CPU frequency, RAM, and disk space +- Whether Netdata is running inside of a container, and if so, the OS and hardware details about the container's host +- Whether Netdata is running inside K8s node +- What virtualization layer the system runs on top of, if any +- Whether the system is a streaming parent or child + +If you want to organize your systems without manually creating host labels, try the automatic labels in some of the +features below. You can see them under `http://HOST-IP:19999/api/v1/info`, beginning with an underscore `_`. +```json +{ + ... + "host_labels": { + "_is_k8s_node": "false", + "_is_parent": "false", + ... +``` + +### Custom labels Host labels are defined in `netdata.conf`. To create host labels, open that file using `edit-config`. @@ -68,28 +126,8 @@ read the status of your agent. For example, from a VPS system running Debian 10: } ``` -You may have noticed a handful of labels that begin with an underscore (`_`). These are automatic labels. - -### Automatic labels - -When Netdata starts, it captures relevant information about the system and converts them into automatically-generated -host labels. You can use these to logically organize your systems via health entities, exporting metrics, -parent-child status, and more. - -They capture the following: - -- Kernel version -- Operating system name and version -- CPU architecture, system cores, CPU frequency, RAM, and disk space -- Whether Netdata is running inside of a container, and if so, the OS and hardware details about the container's host -- Whether Netdata is running inside K8s node -- What virtualization layer the system runs on top of, if any -- Whether the system is a streaming parent or child - -If you want to organize your systems without manually creating host labels, try the automatic labels in some of the -features below. -## Host labels in streaming +### Host labels in streaming You may have noticed the `_is_parent` and `_is_child` automatic labels from above. Host labels are also now streamed from a child to its parent node, which concentrates an entire infrastructure's OS, hardware, container, @@ -108,7 +146,7 @@ child system. It's a vastly simplified way of accessing critical information abo You can also use `_is_parent`, `_is_child`, and any other host labels in both health entities and metrics exporting. Speaking of which... -## Host labels in health entities +### Host labels in alerts You can use host labels to logically organize your systems by their type, purpose, or location, and then apply specific alarms to them. @@ -156,7 +194,7 @@ 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](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 +### Host labels in metrics exporting 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 @@ -185,28 +223,31 @@ 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](https://github.com/netdata/netdata/blob/master/exporting/README.md). -## What's next? +## Metric labels -Host labels are a brand-new feature to Netdata, and yet they've already propagated deeply into some of its core -functionality. We're just getting started with labels, and will keep the community apprised of additional functionality -as it's made available. You can also track [issue #6503](https://github.com/netdata/netdata/issues/6503), which is where -the Netdata team first kicked off this work. +The Netdata aggregate charts allow you to filter and group metrics based on label name-value pairs. -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 +All go.d plugin collectors support the specification of labels at the "collection job" level. Some collectors come with out of the box +labels (e.g. generic Prometheus collector, Kubernetes, Docker and more). But you can also add your own custom labels, by configuring +the data collection jobs. -- [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](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) +For example, suppose we have a single Netdata agent, collecting data from two remote Apache web servers, located in different data centers. +The web servers are load balanced and provide access to the service "Payments". -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 -excited to make host labels even more valuable to our users, which we can only do with your input. +You can define the following in `go.d.conf`, to be able to group the web requests by service or location: +``` +jobs: + - name: mywebserver1 + url: http://host1/server-status?auto + labels: + service: "Payments" + location: "Atlanta" + - name: mywebserver2 + url: http://host2/server-status?auto + labels: + service: "Payments" + location: "New York" +``` +Of course you may define as many custom label/value pairs as you like, in as many data collection jobs you need. diff --git a/docs/metrics-storage-management/enable-streaming.md b/docs/metrics-storage-management/enable-streaming.md new file mode 100644 index 00000000..f54ffaeb --- /dev/null +++ b/docs/metrics-storage-management/enable-streaming.md @@ -0,0 +1,228 @@ +# How metrics streaming works + +Each node running Netdata can stream the metrics it collects, in real time, to another node. Streaming allows you to +replicate metrics data across multiple nodes, or centralize all your metrics data into a single time-series database +(TSDB). + +When one node streams metrics to another, the node receiving metrics can visualize them on the dashboard, 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](https://github.com/netdata/netdata/blob/master/docs/store/distributed-data-architecture.md) is +ideal for speed and scale, streaming provides centralization options and high data availability. + +This document will get you started quickly with streaming. More advanced concepts and suggested production deployments +can be found in the [streaming and replication reference](https://github.com/netdata/netdata/blob/master/streaming/README.md). + +## Streaming basics + +There are three types of nodes in Netdata's streaming ecosystem. + +- **Parent**: A node, running Netdata, that receives streamed metric data. +- **Child**: A node, running Netdata, that streams metric data to one or more parent. +- **Proxy**: A node, running Netdata, that receives metric data from a child and "forwards" them on to a + separate parent node. + +Netdata uses API keys, which are just random GUIDs, to authorize the communication between child and parent nodes. We +recommend using `uuidgen` for generating API keys, which can then be used across any number of streaming connections. +Or, you can generate unique API keys for each parent-child relationship. + +Once the parent node authorizes the child's API key, the child can start streaming metrics. + +It's important to note that the streaming connection uses TCP, UDP, or Unix sockets, _not HTTP_. To proxy streaming +metrics, you need to use a proxy that tunnels [OSI layer 4-7 +traffic](https://en.wikipedia.org/wiki/OSI_model#Layer_4:_Transport_Layer) without interfering with it, such as +[SOCKS](https://en.wikipedia.org/wiki/SOCKS) or Nginx's +[TCP/UDP load balancing](https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-udp-load-balancer/). + +## Supported streaming configurations + +Netdata supports any combination of parent, child, and proxy nodes that you can imagine. Any node can act as both a +parent, child, or proxy at the same time, sending or receiving streaming metrics from any number of other nodes. + +Here are a few example streaming configurations: + +- **Headless collector**: + - Child `A`, _without_ a database or web dashboard, streams metrics to parent `B`. + - `A` metrics are only available via the local Agent dashboard for `B`. + - `B` generates alarms for `A`. +- **Replication**: + - Child `A`, _with_ a database and web dashboard, streams metrics to parent `B`. + - `A` metrics are available on both local Agent dashboards, and can be stored with the same or different metrics + retention policies. + - Both `A` and `B` generate alarms. +- **Proxy**: + - Child `A`, _with or without_ a database, sends metrics to proxy `C`, also _with or without_ a database. `C` sends + metrics to parent `B`. + - Any node with a database can generate alarms. + + + +### A basic parent child setup + +![simple-parent-child](https://user-images.githubusercontent.com/43294513/232492152-11886282-29bc-401f-9577-24237e43a501.jpg) + +For a predictable number of non-ephemeral nodes, install a Netdata agent on each node and replicate its data to a +Netdata parent, preferrably on a management/admin node outside your production infrastructure. +There are two variations of the basic setup: + +- When your nodes have sufficient RAM and disk IO the Netdata agents on each node can run with the default + settings for data collection and retention. + +- When your nodes have severe RAM and disk IO limitations (e.g. Raspberry Pis), you should + [optimize the Netdata agent's performance](https://github.com/netdata/netdata/blob/master/docs/guides/configure/performance.md). + +[Secure your nodes](https://github.com/netdata/netdata/blob/master/docs/category-overview-pages/secure-nodes.md) to +protect them from the internet by making their UI accessible only via an nginx proxy, with potentially different subdomains +for the parent and even each child, if necessary. + +Both children and the parent are connected to the cloud, to enable infrastructure observability, +[without transferring the collected data](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md). +Requests for data are always serverd by a connected Netdata agent. When both a child and a parent are connected, +the cloud will always select the parent to query the user requested data. + +### An advanced setup + +![Ephemeral nodes with two parents](https://user-images.githubusercontent.com/43294513/228891974-590bf0de-4e5a-46b2-a07a-7bb3dffde2bf.jpg) + +When the nodes are ephemeral, we recommend using two parents in an active-active setup, and having the children not store data at all. + +Both parents are configured on each child, so that if one is not available, they connect to the other. + +The children in this set up are not connected to Netdata Cloud at all, as high availability is achieved with the second parent. + +## Enable streaming between nodes + +The simplest streaming configuration is **replication**, in which a child node streams its metrics in real time to a +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](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) from either or both nodes. + +### Enable streaming on the parent node + +First, log onto the node that will act as the parent. + +Run `uuidgen` to create a new API key, which is a randomly-generated machine GUID the Netdata Agent uses to identify +itself while initiating a streaming connection. Copy that into a separate text file for later use. + +> 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`](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 +sudo ./edit-config stream.conf +``` + +Scroll down to the section beginning with `[API_KEY]`. Paste the API key you generated earlier between the brackets, so +that it looks like the following: + +```conf +[11111111-2222-3333-4444-555555555555] +``` + +Set `enabled` to `yes`, and `default memory mode` to `dbengine`. Leave all the other settings as their defaults. A +simplified version of the configuration, minus the commented lines, looks like the following: + +```conf +[11111111-2222-3333-4444-555555555555] + enabled = yes + default memory mode = dbengine +``` + +Save the file and close it, then 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. + +### Enable streaming on the child node + +Connect to your child node with SSH. + +Open `stream.conf` again. Scroll down to the `[stream]` section and set `enabled` to `yes`. Paste the IP address of your +parent node at the end of the `destination` line, and paste the API key generated on the parent node onto the `api key` +line. + +Leave all the other settings as their defaults. A simplified version of the configuration, minus the commented lines, +looks like the following: + +```conf +[stream] + enabled = yes + destination = 203.0.113.0 + api key = 11111111-2222-3333-4444-555555555555 +``` + +Save the file and close it, then 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. + +### Enable TLS/SSL on streaming (optional) + +While encrypting the connection between your parent and child nodes is recommended for security, it's not required to +get started. If you're not interested in encryption, skip ahead to [view streamed +metrics](#view-streamed-metrics-in-netdatas-dashboard). + +In this example, we'll use self-signed certificates. + +On the **parent** node, use OpenSSL to create the key and certificate, then use `chown` to make the new files readable +by the `netdata` user. + +```bash +sudo openssl req -newkey rsa:2048 -nodes -sha512 -x509 -days 365 -keyout /etc/netdata/ssl/key.pem -out /etc/netdata/ssl/cert.pem +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](https://github.com/netdata/netdata/blob/master/web/server/README.md#enabling-tls-support) for other TLS/SSL options. + +```conf +[web] + bind to = *=dashboard|registry|badges|management|streaming|netdata.conf^SSL=force +``` + +Next, connect to the **child** node and open `stream.conf`. Add `:SSL` to the end of the existing `destination` setting +to connect to the parent using TLS/SSL. Uncomment the `ssl skip certificate verification` line to allow the use of +self-signed certificates. + +```conf +[stream] + enabled = yes + destination = 203.0.113.0:SSL + ssl skip certificate verification = yes + api key = 11111111-2222-3333-4444-555555555555 +``` + +Restart both the parent and child nodes 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 stream encrypted metrics using TLS/SSL. + +### View streamed metrics in Netdata Cloud + +In Netdata Cloud you should now be able to see a new parent showing up in the Home tab under "Nodes by data replication". +The replication factor for the child node has now increased to 2, meaning that its data is now highly available. + +You don't need to do anything else, as the cloud will automatically prefer to fetch data about the child from the parent +and switch to querying the child only when the parent is unavailable, or for some reason doesn't have the requested +data (e.g. the connection between parent and the child is broken). + +### View streamed metrics in Netdata's dashboard + +At this point, the child node is streaming its metrics in real time to its parent. Open the local Agent dashboard for +the parent by navigating to `http://PARENT-NODE:19999` in your browser, replacing `PARENT-NODE` with its IP address or +hostname. + +This dashboard shows parent metrics. To see child metrics, open the left-hand sidebar with the hamburger icon +![Hamburger icon](https://raw.githubusercontent.com/netdata/netdata-ui/master/src/components/icon/assets/hamburger.svg) +in the top panel. Both nodes appear under the **Replicated Nodes** menu. Click on either of the links to switch between +separate parent and child dashboards. + +![Switching between parent and child dashboards](https://user-images.githubusercontent.com/1153921/110043346-761ec000-7d04-11eb-8e58-77670ba39161.gif) + +The child dashboard is also available directly at `http://PARENT-NODE:19999/host/CHILD-HOSTNAME`, which in this example +is `http://203.0.113.0:19999/host/netdata-child`. + diff --git a/docs/metrics-storage-management/enable-streaming.mdx b/docs/metrics-storage-management/enable-streaming.mdx deleted file mode 100644 index 3bcf19b4..00000000 --- a/docs/metrics-storage-management/enable-streaming.mdx +++ /dev/null @@ -1,158 +0,0 @@ ---- -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/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 - -The simplest streaming configuration is **replication**, in which a child node streams its metrics in real time to a -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](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) from either or both nodes. - -## Enable streaming on the parent node - -First, log onto the node that will act as the parent. - -Run `uuidgen` to create a new API key, which is a randomly-generated machine GUID the Netdata Agent uses to identify -itself while initiating a streaming connection. Copy that into a separate text file for later use. - -> 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`](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 -sudo ./edit-config stream.conf -``` - -Scroll down to the section beginning with `[API_KEY]`. Paste the API key you generated earlier between the brackets, so -that it looks like the following: - -```conf -[11111111-2222-3333-4444-555555555555] -``` - -Set `enabled` to `yes`, and `default memory mode` to `dbengine`. Leave all the other settings as their defaults. A -simplified version of the configuration, minus the commented lines, looks like the following: - -```conf -[11111111-2222-3333-4444-555555555555] - enabled = yes - default memory mode = dbengine -``` - -Save the file and close it, then 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. - -## Enable streaming on the child node - -Connect to your child node with SSH. - -Open `stream.conf` again. Scroll down to the `[stream]` section and set `enabled` to `yes`. Paste the IP address of your -parent node at the end of the `destination` line, and paste the API key generated on the parent node onto the `api key` -line. - -Leave all the other settings as their defaults. A simplified version of the configuration, minus the commented lines, -looks like the following: - -```conf -[stream] - enabled = yes - destination = 203.0.113.0 - api key = 11111111-2222-3333-4444-555555555555 -``` - -Save the file and close it, then 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. - -## Enable TLS/SSL on streaming (optional) - -While encrypting the connection between your parent and child nodes is recommended for security, it's not required to -get started. If you're not interested in encryption, skip ahead to [view streamed -metrics](#view-streamed-metrics-in-netdatas-dashboard). - -In this example, we'll use self-signed certificates. - -On the **parent** node, use OpenSSL to create the key and certificate, then use `chown` to make the new files readable -by the `netdata` user. - -```bash -sudo openssl req -newkey rsa:2048 -nodes -sha512 -x509 -days 365 -keyout /etc/netdata/ssl/key.pem -out /etc/netdata/ssl/cert.pem -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](https://github.com/netdata/netdata/blob/master/web/server/README.md#enabling-tls-support) for other TLS/SSL options. - -```conf -[web] - bind to = *=dashboard|registry|badges|management|streaming|netdata.conf^SSL=force -``` - -Next, connect to the **child** node and open `stream.conf`. Add `:SSL` to the end of the existing `destination` setting -to connect to the parent using TLS/SSL. Uncomment the `ssl skip certificate verification` line to allow the use of -self-signed certificates. - -```conf -[stream] - enabled = yes - destination = 203.0.113.0:SSL - ssl skip certificate verification = yes - api key = 11111111-2222-3333-4444-555555555555 -``` - -Restart both the parent and child nodes 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 stream encrypted metrics using TLS/SSL. - -## View streamed metrics in Netdata's dashboard - -At this point, the child node is streaming its metrics in real time to its parent. Open the local Agent dashboard for -the parent by navigating to `http://PARENT-NODE:19999` in your browser, replacing `PARENT-NODE` with its IP address or -hostname. - -This dashboard shows parent metrics. To see child metrics, open the left-hand sidebar with the hamburger icon -![Hamburger icon](https://raw.githubusercontent.com/netdata/netdata-ui/master/src/components/icon/assets/hamburger.svg) -in the top panel. Both nodes appear under the **Replicated Nodes** menu. Click on either of the links to switch between -separate parent and child dashboards. - -![Switching between parent and child -dashboards](https://user-images.githubusercontent.com/1153921/110043346-761ec000-7d04-11eb-8e58-77670ba39161.gif) - -The child dashboard is also available directly at `http://PARENT-NODE:19999/host/CHILD-HOSTNAME`, which in this example -is `http://203.0.113.0:19999/host/netdata-child`. - -## What's next? - -Now that you have a basic streaming setup with replication, you may want to tweak the configuration to eliminate the -child database, disable the child dashboard, or enable SSL on the streaming connection between the parent and child. - -See the [streaming reference -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](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md) for -details. - -### Related information & further reading - -- Streaming - - [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 deleted file mode 100644 index f181d376..00000000 --- a/docs/metrics-storage-management/how-streaming-works.mdx +++ /dev/null @@ -1,99 +0,0 @@ ---- -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/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 - -Each node running Netdata can stream the metrics it collects, in real time, to another node. Streaming allows you to -replicate metrics data across multiple nodes, or centralize all your metrics data into a single time-series database -(TSDB). - -When one node streams metrics to another, the node receiving metrics can visualize them on the -[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](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 - -There are three types of nodes in Netdata's streaming ecosystem. - -- **Parent**: A node, running Netdata, that receives streamed metric data. -- **Child**: A node, running Netdata, that streams metric data to one or more parent. -- **Proxy**: A node, running Netdata, that receives metric data from a child and "forwards" them on to a - separate parent node. - -Netdata uses API keys, which are just random GUIDs, to authorize the communication between child and parent nodes. We -recommend using `uuidgen` for generating API keys, which can then be used across any number of streaming connections. -Or, you can generate unique API keys for each parent-child relationship. - -Once the parent node authorizes the child's API key, the child can start streaming metrics. - -It's important to note that the streaming connection uses TCP, UDP, or Unix sockets, _not HTTP_. To proxy streaming -metrics, you need to use a proxy that tunnels [OSI layer 4-7 -traffic](https://en.wikipedia.org/wiki/OSI_model#Layer_4:_Transport_Layer) without interfering with it, such as -[SOCKS](https://en.wikipedia.org/wiki/SOCKS) or Nginx's [TCP/UDP load -balancing](https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-udp-load-balancer/). - -## Supported streaming configurations - -Netdata supports any combination of parent, child, and proxy nodes that you can imagine. Any node can act as both a -parent, child, or proxy at the same time, sending or receiving streaming metrics from any number of other nodes. - -Here are a few example streaming configurations: - -- **Headless collector**: - - Child `A`, _without_ a database or web dashboard, streams metrics to parent `B`. - - `A` metrics are only available via the local Agent dashboard for `B`. - - `B` generates alarms for `A`. -- **Replication**: - - Child `A`, _with_ a database and web dashboard, streams metrics to parent `B`. - - `A` metrics are available on both local Agent dashboards, and can be stored with the same or different metrics - retention policies. - - Both `A` and `B` generate alarms. -- **Proxy**: - - Child `A`, _with or without_ a database, sends metrics to proxy `C`, also _with or without_ a database. `C` sends - metrics to parent `B`. - - Any node with a database can generate alarms. - -## Viewing streamed metrics - -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](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) - -Each child dashboard is also available directly at the following URL pattern: -`http://PARENT-NODE:19999/host/CHILD-HOSTNAME`. - -## What's next? - -Now that you understand the fundamentals of streaming metrics between nodes, go ahead and [enable -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](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](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 deleted file mode 100644 index 58c89863..00000000 --- a/docs/metrics-storage-management/reference-streaming.mdx +++ /dev/null @@ -1,490 +0,0 @@ ---- -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" -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](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](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`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) to -open either `stream.conf` or `netdata.conf`. - -``` -sudo ./edit-config stream.conf -sudo ./edit-config netdata.conf -``` - -## Settings - -As mentioned above, both `stream.conf` and `netdata.conf` contain settings relevant to streaming. - -### `stream.conf` - -The `stream.conf` file contains three sections. The `[stream]` section is for configuring child nodes. - -The `[API_KEY]` and `[MACHINE_GUID]` sections are both for configuring parent nodes, and share the same settings. -`[API_KEY]` settings affect every child node using that key, whereas `[MACHINE_GUID]` settings affect only the child -node with a matching GUID. - -The file `/var/lib/netdata/registry/netdata.public.unique.id` contains a random GUID that **uniquely identifies each -node**. This file is automatically generated by Netdata the first time it is started and remains unaltered forever. - -#### `[stream]` section - -| Setting | Default | Description | -| :---------------------------------------------- | :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `enabled` | `no` | Whether this node streams metrics to any parent. Change to `yes` to enable streaming. | -| [`destination`](#destination) | ` ` | A space-separated list of parent nodes to attempt to stream to, with the first available parent receiving metrics, using the following format: `[PROTOCOL:]HOST[%INTERFACE][:PORT][:SSL]`. [Read more →](#destination) | -| `ssl skip certificate verification` | `yes` | If you want to accept self-signed or expired certificates, set to `yes` and uncomment. | -| `CApath` | `/etc/ssl/certs/` | The directory where known certificates are found. Defaults to OpenSSL's default path. | -| `CAfile` | `/etc/ssl/certs/cert.pem` | Add a parent node certificate to the list of known certificates in `CAPath`. | -| `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](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) to filter which charts are streamed. [Read more →](#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. | - -### `[API_KEY]` and `[MACHINE_GUID]` sections - -| Setting | Default | Description | -| :---------------------------------------------- | :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `enabled` | `no` | Whether this API KEY enabled or disabled. | -| [`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 →](#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](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 →](#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. | -| `default proxy destination` | ` ` | Space-separated list of `IP:PORT` for proxies. | -| `default proxy api key` | ` ` | The `API_KEY` of the proxy. | -| `default send charts matching` | `*` | See [`send charts matching`](#send-charts-matching). | - -#### `destination` - -A space-separated list of parent nodes to attempt to stream to, with the first available parent receiving metrics, using -the following format: `[PROTOCOL:]HOST[%INTERFACE][:PORT][:SSL]`. - -- `PROTOCOL`: `tcp`, `udp`, or `unix`. (only tcp and unix are supported by parent nodes) -- `HOST`: A IPv4, IPv6 IP, or a hostname, or a unix domain socket path. IPv6 IPs should be given with brackets - `[ip:address]`. -- `INTERFACE` (IPv6 only): The network interface to use. -- `PORT`: The port number or service name (`/etc/services`) to use. -- `SSL`: To enable TLS/SSL encryption of the streaming connection. - -To enable TCP streaming to a parent node at `203.0.113.0` on port `20000` and with TLS/SSL encryption: - -```conf -[stream] - destination = tcp:203.0.113.0:20000:SSL -``` - -#### `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. - -The default is a single wildcard `*`, which streams all charts. - -To send only a few charts, list them explicitly, or list a group using a wildcard. To send _only_ the `apps.cpu` chart -and charts with contexts beginning with `system.`: - -```conf -[stream] - send charts matching = apps.cpu system.* -``` - -To send all but a few charts, use `!` to create a negative match. To send _all_ charts _but_ `apps.cpu`: - -```conf -[stream] - send charts matching = !apps.cpu * -``` - -#### `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. 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`. - -To allow from only a specific IP address: - -```conf -[API_KEY] - allow from = 203.0.113.10 -``` - -To allow all IPs starting with `10.*`, except `10.1.2.3`: - -```conf -[API_KEY] - allow from = !10.1.2.3 10.* -``` - -> If you set specific IP addresses here, and also use the `allow connections` setting in the `[web]` section of -> `netdata.conf`, be sure to add the IP address there so that it can access the API port. - -#### `default memory mode` - -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 - efficiently spills them to disk for long-term storage. -- `ram`: Stores metrics _only_ in memory, which means metrics are lost when Netdata stops or restarts. Ideal for - streaming configurations that use ephemeral nodes. -- `save`: Stores metrics in memory, but saves metrics to disk when Netdata stops or restarts, and loads historical - metrics on start. -- `map`: Stores metrics in memory-mapped files, like swap, with constant disk write. -- `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](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` - -| Setting | Default | Description | -| :----------------------------------------- | :---------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`[global]` section** | | | -| `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](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 - -### Per-child settings - -While the `[API_KEY]` section applies settings for any child node using that key, you can also use per-child settings -with the `[MACHINE_GUID]` section. - -For example, the metrics streamed from only the child node with `MACHINE_GUID` are saved in memory, not using the -default `dbengine` as specified by the `API_KEY`, and alarms are disabled. - -```conf -[API_KEY] - enabled = yes - default memory mode = dbengine - health enabled by default = auto - allow from = * - -[MACHINE_GUID] - enabled = yes - memory mode = save - health enabled = no -``` - -### Securing streaming with TLS/SSL - -Netdata does not activate TLS encryption by default. To encrypt streaming connections, you first need to [enable TLS -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: - -``` -[stream] - destination = host:port:SSL -``` - -The word `SSL` appended to the end of the destination tells the child that connections must be encrypted. - -> While Netdata uses Transport Layer Security (TLS) 1.2 to encrypt communications rather than the obsolete SSL protocol, -> it's still common practice to refer to encrypted web connections as `SSL`. Many vendors, like Nginx and even Netdata -> itself, use `SSL` in configuration files, whereas documentation will always refer to encrypted communications as `TLS` -> or `TLS/SSL`. - -#### Certificate verification - -When TLS/SSL is enabled on the child, the default behavior will be to not connect with the parent unless the server's -certificate can be verified via the default chain. In case you want to avoid this check, add the following to the -child's `stream.conf` file: - -``` -[stream] - ssl skip certificate verification = yes -``` - -#### Trusted certificate - -If you've enabled [certificate verification](#certificate-verification), you might see errors from the OpenSSL library -when there's a problem with checking the certificate chain (`X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY`). More -importantly, OpenSSL will reject self-signed certificates. - -Given these known issues, you have two options. If you trust your certificate, you can set the options `CApath` and -`CAfile` to inform Netdata where your certificates, and the certificate trusted file, are stored. - -For more details about these options, you can read about [verify -locations](https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_load_verify_locations.html). - -Before you changed your streaming configuration, you need to copy your trusted certificate to your child system and add -the certificate to OpenSSL's list. - -On most Linux distributions, the `update-ca-certificates` command searches inside the `/usr/share/ca-certificates` -directory for certificates. You should double-check by reading the `update-ca-certificate` manual (`man -update-ca-certificate`), and then change the directory in the below commands if needed. - -If you have `sudo` configured on your child system, you can use that to run the following commands. If not, you'll have -to log in as `root` to complete them. - -``` -# mkdir /usr/share/ca-certificates/netdata -# cp parent_cert.pem /usr/share/ca-certificates/netdata/parent_cert.crt -# chown -R netdata.netdata /usr/share/ca-certificates/netdata/ -``` - -First, you create a new directory to store your certificates for Netdata. Next, you need to change the extension on your -certificate from `.pem` to `.crt` so it's compatible with `update-ca-certificate`. Finally, you need to change -permissions so the user that runs Netdata can access the directory where you copied in your certificate. - -Next, edit the file `/etc/ca-certificates.conf` and add the following line: - -``` -netdata/parent_cert.crt -``` - -Now you update the list of certificates running the following, again either as `sudo` or `root`: - -``` -# update-ca-certificates -``` - -> Some Linux distributions have different methods of updating the certificate list. For more details, please read this -> guide on [adding trusted root certificates](https://github.com/Busindre/How-to-Add-trusted-root-certificates). - -Once you update your certificate list, you can set the stream parameters for Netdata to trust the parent certificate. -Open `stream.conf` for editing and change the following lines: - -``` -[stream] - CApath = /etc/ssl/certs/ - CAfile = /etc/ssl/certs/parent_cert.pem -``` - -With this configuration, the `CApath` option tells Netdata to search for trusted certificates inside `/etc/ssl/certs`. -The `CAfile` option specifies the Netdata parent certificate is located at `/etc/ssl/certs/parent_cert.pem`. With this -configuration, you can skip using the system's entire list of certificates and use Netdata's parent certificate instead. - -#### Expected behaviors - -With the introduction of TLS/SSL, the parent-child communication behaves as shown in the table below, depending on the -following configurations: - -- **Parent TLS (Yes/No)**: Whether the `[web]` section in `netdata.conf` has `ssl key` and `ssl certificate`. -- **Parent port TLS (-/force/optional)**: Depends on whether the `[web]` section `bind to` contains a `^SSL=force` or - `^SSL=optional` directive on the port(s) used for streaming. -- **Child TLS (Yes/No)**: Whether the destination in the child's `stream.conf` has `:SSL` at the end. -- **Child TLS Verification (yes/no)**: Value of the child's `stream.conf` `ssl skip certificate verification` - parameter (default is no). - -| Parent TLS enabled | Parent port SSL | Child TLS | Child SSL Ver. | Behavior | -| :----------------- | :--------------- | :-------- | :------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | -| No | - | No | no | Legacy behavior. The parent-child stream is unencrypted. | -| Yes | force | No | no | The parent rejects the child connection. | -| Yes | -/optional | No | no | The parent-child stream is unencrypted (expected situation for legacy child nodes and newer parent nodes) | -| Yes | -/force/optional | Yes | no | The parent-child stream is encrypted, provided that the parent has a valid TLS/SSL certificate. Otherwise, the child refuses to connect. | -| Yes | -/force/optional | Yes | yes | The parent-child stream is encrypted. | - -### Proxy - -A proxy is a node that receives metrics from a child, then streams them onward to a parent. To configure a proxy, -configure it as a receiving and a sending Netdata at the same time. - -Netdata proxies may or may not maintain a database for the metrics passing through them. When they maintain a database, -they can also run health checks (alarms and notifications) for the remote host that is streaming the metrics. - -In the following example, the proxy receives metrics from a child node using the `API_KEY` of -`66666666-7777-8888-9999-000000000000`, then stores metrics using `dbengine`. It then uses the `API_KEY` of -`11111111-2222-3333-4444-555555555555` to proxy those same metrics on to a parent node at `203.0.113.0`. - -```conf -[stream] - enabled = yes - destination = 203.0.113.0 - api key = 11111111-2222-3333-4444-555555555555 - -[66666666-7777-8888-9999-000000000000] - enabled = yes - default memory mode = dbengine -``` - -### Ephemeral nodes - -Netdata can help you monitor ephemeral nodes, such as containers in an auto-scaling infrastructure, by always streaming -metrics to any number of permanently-running parent nodes. - -On the parent, set the following in `stream.conf`: - -```conf -[11111111-2222-3333-4444-555555555555] - # enable/disable this API key - enabled = yes - - # one hour of data for each of the child nodes - default history = 3600 - - # do not save child metrics on disk - default memory = ram - - # alarms checks, only while the child is connected - health enabled by default = auto -``` - -On the child nodes, set the following in `stream.conf`: - -```bash -[stream] - # stream metrics to another Netdata - enabled = yes - - # the IP and PORT of the parent - destination = 10.11.12.13:19999 - - # the API key to use - api key = 11111111-2222-3333-4444-555555555555 -``` - -In addition, edit `netdata.conf` on each child node to disable the database and alarms. - -```bash -[global] - # disable the local database - memory mode = none - -[health] - # disable health checks - enabled = no -``` - -## Troubleshooting - -Both parent and child nodes log information at `/var/log/netdata/error.log`. - -If the child manages to connect to the parent you will see something like (on the parent): - -``` -2017-03-09 09:38:52: netdata: INFO : STREAM [receive from [10.11.12.86]:38564]: new client connection. -2017-03-09 09:38:52: netdata: INFO : STREAM xxx [10.11.12.86]:38564: receive thread created (task id 27721) -2017-03-09 09:38:52: netdata: INFO : STREAM xxx [receive from [10.11.12.86]:38564]: client willing to stream metrics for host 'xxx' with machine_guid '1234567-1976-11e6-ae19-7cdd9077342a': update every = 1, history = 3600, memory mode = ram, health auto -2017-03-09 09:38:52: netdata: INFO : STREAM xxx [receive from [10.11.12.86]:38564]: initializing communication... -2017-03-09 09:38:52: netdata: INFO : STREAM xxx [receive from [10.11.12.86]:38564]: receiving metrics... -``` - -and something like this on the child: - -``` -2017-03-09 09:38:28: netdata: INFO : STREAM xxx [send to box:19999]: connecting... -2017-03-09 09:38:28: netdata: INFO : STREAM xxx [send to box:19999]: initializing communication... -2017-03-09 09:38:28: netdata: INFO : STREAM xxx [send to box:19999]: waiting response from remote netdata... -2017-03-09 09:38:28: netdata: INFO : STREAM xxx [send to box:19999]: established communication - sending metrics... -``` - -The following sections describe the most common issues you might encounter when connecting parent and child nodes. - -### Slow connections between parent and child - -When you have a slow connection between parent and child, Netdata raises a few different errors. Most of the -errors will appear in the child's `error.log`. - -```bash -netdata ERROR : STREAM_SENDER[CHILD HOSTNAME] : STREAM CHILD HOSTNAME [send to PARENT IP:PARENT PORT]: too many data pending - buffer is X bytes long, -Y unsent - we have sent Z bytes in total, W on this connection. Closing connection to flush the data. -``` - -On the parent side, you may see various error messages, most commonly the following: - -``` -netdata ERROR : STREAM_PARENT[CHILD HOSTNAME,[CHILD IP]:CHILD PORT] : read failed: end of file -``` - -Another common problem in slow connections is the child sending a partial message to the parent. In this case, the -parent will write the following to its `error.log`: - -``` -ERROR : STREAM_RECEIVER[CHILD HOSTNAME,[CHILD IP]:CHILD PORT] : sent command 'B' which is not known by netdata, for host 'HOSTNAME'. Disabling it. -``` - -In this example, `B` was part of a `BEGIN` message that was cut due to connection problems. - -Slow connections can also cause problems when the parent misses a message and then receives a command related to the -missed message. For example, a parent might miss a message containing the child's charts, and then doesn't know -what to do with the `SET` message that follows. When that happens, the parent will show a message like this: - -``` -ERROR : STREAM_RECEIVER[CHILD HOSTNAME,[CHILD IP]:CHILD PORT] : requested a SET on chart 'CHART NAME' of host 'HOSTNAME', without a dimension. Disabling it. -``` - -### Child cannot connect to parent - -When the child can't connect to a parent for any reason (misconfiguration, networking, firewalls, parent -down), you will see the following in the child's `error.log`. - -``` -ERROR : STREAM_SENDER[HOSTNAME] : Failed to connect to 'PARENT IP', port 'PARENT PORT' (errno 113, No route to host) -``` - -### 'Is this a Netdata?' - -This question can appear when Netdata starts the stream and receives an unexpected response. This error can appear when -the parent is using SSL and the child tries to connect using plain text. You will also see this message when -Netdata connects to another server that isn't Netdata. The complete error message will look like this: - -``` -ERROR : STREAM_SENDER[CHILD HOSTNAME] : STREAM child HOSTNAME [send to PARENT HOSTNAME:PARENT PORT]: server is not replying properly (is it a netdata?). -``` - -### Stream charts wrong - -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](https://github.com/netdata/netdata/blob/master/database/README.md). - -### Forbidding access - -You may see errors about "forbidding access" for a number of reasons. It could be because of a slow connection between -the parent and child nodes, but it could also be due to other failures. Look in your parent's `error.log` for errors -that look like this: - -``` -STREAM [receive from [child HOSTNAME]:child IP]: `MESSAGE`. Forbidding access." -``` - -`MESSAGE` will have one of the following patterns: - -- `request without KEY` : The message received is incomplete and the KEY value can be API, hostname, machine GUID. -- `API key 'VALUE' is not valid GUID`: The UUID received from child does not have the format defined in [RFC - 4122](https://tools.ietf.org/html/rfc4122) -- `machine GUID 'VALUE' is not GUID.`: This error with machine GUID is like the previous one. -- `API key 'VALUE' is not allowed`: This stream has a wrong API key. -- `API key 'VALUE' is not permitted from this IP`: The IP is not allowed to use STREAM with this parent. -- `machine GUID 'VALUE' is not allowed.`: The GUID that is trying to send stream is not allowed. -- `Machine GUID 'VALUE' is not permitted from this IP. `: The IP does not match the pattern or IP allowed to connect to - use stream. - -### Netdata could not create a stream - -The connection between parent and child is a stream. When the parent can't convert the initial connection into -a stream, it will write the following message inside `error.log`: - -``` -file descriptor given is not a valid stream -``` - -After logging this error, Netdata will close the stream. diff --git a/docs/monitor/configure-alarms.md b/docs/monitor/configure-alarms.md deleted file mode 100644 index 4b5b8134..00000000 --- a/docs/monitor/configure-alarms.md +++ /dev/null @@ -1,152 +0,0 @@ - - -# Configure health alarms - -Netdata's health watchdog is highly configurable, with support for dynamic thresholds, hysteresis, alarm templates, and -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](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](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](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: - -```bash -sudo ./edit-config health.d/cpu.conf -``` - -Each health configuration file contains one or more health _entities_, which always begin with `alarm:` or `template:`. -For example, here is the first health entity in `health.d/cpu.conf`: - -```yaml -template: 10min_cpu_usage - on: system.cpu - os: linux - hosts: * - lookup: average -10m unaligned of user,system,softirq,irq,guest - units: % - every: 1m - warn: $this > (($status >= $WARNING) ? (75) : (85)) - crit: $this > (($status == $CRITICAL) ? (85) : (95)) - delay: down 15m multiplier 1.5 max 1h - info: average cpu utilization for the last 10 minutes (excluding iowait, nice and steal) - to: sysadmin -``` - -To tune this alarm to trigger warning and critical alarms at a lower CPU utilization, change the `warn` and `crit` lines -to the values of your choosing. For example: - -```yaml - warn: $this > (($status >= $WARNING) ? (60) : (75)) - crit: $this > (($status == $CRITICAL) ? (75) : (85)) -``` - -Save the file and [reload Netdata's health configuration](#reload-health-configuration) to make your changes live. - -### Silence an individual alarm - -Instead of disabling an alarm altogether, or even disabling _all_ alarms, you can silence individual alarms by changing -one line in a given health entity. To silence any single alarm, change the `to:` line in its entity to `silent`. - -```yaml - to: silent -``` - -## 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, containers, and applications work. - -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](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. - -```bash -sudo touch health.d/ram-usage.conf -sudo ./edit-config health.d/ram-usage.conf -``` - -For example, here is a health entity that triggers a warning alarm when a node's RAM usage rises above 80%, and a -critical alarm above 90%: - -```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 being 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. This example uses simple - syntax, but most pre-configured health entities use - [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: - -> This health entity, named **ram_usage**, watches 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. - -When you finish writing this new health entity, [reload Netdata's health configuration](#reload-health-configuration) to -see it live on the local dashboard or Netdata Cloud. - -## 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 `netdatacli reload-health` or `killall -USR2 netdata`. - -## What's next? - -With your health entities configured properly, it's time to [enable -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](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 99c24b64..1174561c 100644 --- a/docs/monitor/enable-notifications.md +++ b/docs/monitor/enable-notifications.md @@ -1,51 +1,49 @@ -# Enable alarm notifications +# Alert notifications -Netdata offers two ways to receive alarm notifications on external platforms. These methods work independently _or_ in -parallel, which means you can enable both at the same time to send alarm notifications to any number of endpoints. +Netdata offers two ways to receive alert notifications on external platforms. These methods work independently _or_ in +parallel, which means you can enable both at the same time to send alert 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](https://github.com/netdata/netdata/blob/master/docs/monitor/configure-alarms.md) to change the preconfigured thresholds or to create tailored alarms for your +Both methods use a node's health alerts to generate the content of alert notifications. Read our documentation on [configuring alerts](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md) to change the preconfigured thresholds or to create tailored alerts for your infrastructure. -Netdata Cloud offers [centralized alarm notifications](#netdata-cloud) via email, which leverages the health status +Netdata Cloud offers [centralized alert notifications](#netdata-cloud) via email, which leverages the health status information already streamed to Netdata Cloud from connected nodes to send notifications to those who have enabled them. The Netdata Agent has a [notification system](#netdata-agent) that supports more than a dozen services, such as email, Slack, PagerDuty, Twilio, Amazon SNS, Discord, and much more. -For example, use centralized alarm notifications in Netdata Cloud for immediate, zero-configuration alarm notifications +For example, use centralized alert notifications in Netdata Cloud for immediate, zero-configuration alert notifications for your team, then configure individual nodes send notifications to a PagerDuty endpoint for an automated incident response process. ## Netdata Cloud -Netdata Cloud's [centralized alarm -notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx) is a zero-configuration way to +Netdata Cloud's [centralized alert +notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.md) 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 +centralized alert notifications from Netdata Cloud is that you don't have to worry about configuring each node in your infrastructure. -To enable centralized alarm notifications for a Space, click on **Manage Space** in the left-hand menu, then click on +To enable centralized alert notifications for a Space, click on **Manage Space** in the left-hand menu, then click on the **Notifications** tab. Click the toggle switch next to **E-mail** to enable this notification method. Next, enable notifications on a user level by clicking on your profile icon, then **Profile** in the dropdown. The **Notifications** tab reveals rich management settings, including the ability to enable/disable methods entirely or choose what types of notifications to receive from each War Room. -![Enabling and configuring alarm notifications in Netdata +![Enabling and configuring alert notifications in Netdata Cloud](https://user-images.githubusercontent.com/1153921/101936280-93c50900-3b9d-11eb-9ba0-d6927fa872b7.gif) -See the [centralized alarm notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.mdx) +See the [centralized alert notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.md) reference doc for further details about what information is conveyed in an email notification, flood protection, and more. @@ -53,7 +51,7 @@ more. 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. +notifications in parallel with centralized alert notifications in Netdata Cloud. > ❗ If you want to enable notifications from multiple nodes in your infrastructure, each running the Netdata Agent, you > must configure each node individually. @@ -70,7 +68,6 @@ notification platform. - [**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) @@ -91,61 +88,4 @@ notification platform. - [**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 - -First, [Add an incoming webhook](https://slack.com/apps/A0F7XDUAZ-incoming-webhooks) in Slack for the channel where you -want to see alarm notifications from Netdata. Click the green **Add to Slack** button, choose the channel, and click the -**Add Incoming WebHooks Integration** button. - -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](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 -sudo ./edit-config health_alarm_notify.conf -``` - -Look for the `SLACK_WEBHOOK_URL=" "` line and add the incoming webhook URL you got from Slack: - -```conf -SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXX" -``` - -A few lines down, edit the `DEFAULT_RECIPIENT_SLACK` line to contain a single hash `#` character. This instructs Netdata -to send a notification to the channel you configured with the incoming webhook. - -```conf -DEFAULT_RECIPIENT_SLACK="#" -``` - -To test Slack notifications, switch to the Netdata user. - -```bash -sudo su -s /bin/bash netdata -``` - -Next, run the `alarm-notify` script using the `test` option. - -```bash -/usr/libexec/netdata/plugins.d/alarm-notify.sh test -``` - -You should receive three notifications in your Slack channel for each health status change: `WARNING`, `CRITICAL`, and -`CLEAR`. - -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? - -Now that you have health entities configured to your infrastructure's needs and notifications to inform you of anomalies -or incidents, your health monitoring setup is complete. - -To make your dashboards most useful during root cause analysis, use Netdata's [distributed data -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://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 07c22fe1..cc6a2d3a 100644 --- a/docs/monitor/view-active-alarms.md +++ b/docs/monitor/view-active-alarms.md @@ -1,45 +1,46 @@ - +# View active alerts -# View active health alarms +Netdata comes with hundreds of pre-configured health alerts designed to notify you when an anomaly or performance issue affects your node or its applications. -Every Netdata Agent comes with hundreds of pre-installed health alarms designed to notify you when an anomaly or -performance issue affects your node or the applications it runs. +From the Alerts tab you can see all the active alerts in your War Room. You will be presented with a table having information about each alert that is in warning and critical state. +You can always sort the table by a certain column by clicking on the name of that column, and use the gear icon on the top right to control which columns are visible at any given time. -## Netdata Cloud +![image](https://user-images.githubusercontent.com/70198089/226340574-7e138dc7-5eab-4c47-a4a9-5f2640e38643.png) -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://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/view-active-alerts.mdx). +## Filter alerts -![The Alarms panel in Netdata -Cloud](https://user-images.githubusercontent.com/1153921/108564747-d2bfbb00-72c0-11eb-97b9-5863ad3324eb.png) +From this tab, you can also filter alerts with the right hand bar. More specifically you can filter: -The Alarms panel lists all active alarms for nodes within that War Room, and tells you which chart triggered the alarm, -what that chart's current value is, the alarm that triggered it, and when the alarm status first began. +- Alert status + - Filter based on the status of the alerts (e.g. Warning, Critical) +- Alert class + - Filter based on the class of the alert (e.g. Latency, Utilization, Workload etc.) +- Alert type & component + - Filter based on the alert's type (e.g. System, Web Server) and component (e.g. CPU, Disk, Load) +- Alert role + - Filter by the role that the alert is set to notify (e.g. Sysadmin, Webmaster etc.) +- Nodes + - Filter the alerts based on the nodes that are online, next to each node's name you can see how many alerts the node has, "critical" colored in red and "warning" colored in yellow -Use the input field in the Alarms panel to filter active alarms. You can sort by the node's name, alarm, status, chart -that triggered the alarm, or the operating system. Read more about the [filtering -syntax](https://learn.netdata.cloud/docs/cloud/war-rooms#node-filter) to build valuable filters for your infrastructure. +## View alert details -Click on the 3-dot icon (`⋮`) to view active alarm information or navigate directly to the offending chart in that -node's Cloud dashboard with the **Go to chart** button. +By clicking on the name of an entry of the table you can access that alert's details page, providing you with: -The active alarm information gives you details about the alarm that's been triggered. You can see the alarm's -configuration, how it calculates warning or critical alarms, and which configuration file you could edit on that node if -you want to tweak or disable the alarm to better suit your needs. +- Latest and Triggered time values +- The alert's description +- A link to the Community forum's alert page +- The chart at the time frame that the alert was triggered +- The alert's information: Node name, chart ID, type, component and class +- Configuration section +- Instance values - Node Instances -![Active alarm details in Netdata -Cloud](https://user-images.githubusercontent.com/1153921/108564813-f08d2000-72c0-11eb-80c8-b2af22a751fd.png) +![image](https://user-images.githubusercontent.com/70198089/226339928-bae60140-0293-42cf-9713-ac4901708aba.png) +At the bottom of the panel you can click the green button "View dedicated alert page" to open a [dynamic tab](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md#dynamic-tabs) containing all the info for this alert in a tab format, where you can also run correlations and go to the node's chart that raised the particular alert. + +![image](https://user-images.githubusercontent.com/70198089/226339794-61896c35-0b93-4ac9-92aa-07116fe63784.png) + + diff --git a/docs/netdata-for-IoT.md b/docs/netdata-for-IoT.md index 87b307b9..8dfed21e 100644 --- a/docs/netdata-for-IoT.md +++ b/docs/netdata-for-IoT.md @@ -1,6 +1,9 @@ # Netdata for IoT diff --git a/docs/netdata-security.md b/docs/netdata-security.md index 511bc772..6cd33c06 100644 --- a/docs/netdata-security.md +++ b/docs/netdata-security.md @@ -1,222 +1,167 @@ - +# Security and privacy design -# Security design +This document serves as the relevant Annex to the [Terms of Service](https://www.netdata.cloud/service-terms/), the [Privacy Policy](https://www.netdata.cloud/privacy/) and +the Data Processing Addendum, when applicable. It provides more information regarding Netdata’s technical and organizational security and privacy measures. We have given special attention to all aspects of Netdata, ensuring that everything throughout its operation is as secure as possible. Netdata has been designed with security in mind. -**Table of Contents** +> When running Netdata in environments requiring Payment Card Industry Data Security Standard (**PCI DSS**), Systems and Organization Controls (**SOC 2**), +or Health Insurance Portability and Accountability Act (**HIPAA**) compliance, please keep in mind that +**even when the user uses Netdata Cloud, all collected data is always stored inside their infrastructure**. -1. [Your data is safe with Netdata](#your-data-is-safe-with-netdata) -2. [Your systems are safe with Netdata](#your-systems-are-safe-with-netdata) -3. [Netdata is read-only](#netdata-is-read-only) -4. [Netdata viewers authentication](#netdata-viewers-authentication) - * [Why Netdata should be protected](#why-netdata-should-be-protected) - * [Protect Netdata from the internet](#protect-netdata-from-the-internet) - * [Expose Netdata only in a private LAN](#expose-netdata-only-in-a-private-lan) - * [Use an authenticating web server in proxy mode](#use-an-authenticating-web-server-in-proxy-mode) - * [Other methods](#other-methods) -5. [Registry or how to not send any information to a third party server](#registry-or-how-to-not-send-any-information-to-a-third-party-server) +Dashboard data a user views and alert notifications do travel +over Netdata Cloud, as they also travel over third party networks, to reach the user's web browser or the notification integrations the user has configured, +but Netdata Cloud does not store metric data. It only transforms them as they pass through it, aggregating them from multiple Agents and Parents, +to appear as one data source on the user's browser. -## Your data is safe with Netdata +## Cloud design -Netdata collects raw data from many sources. For each source, Netdata uses a plugin that connects to the source (or reads the relative files produced by the source), receives raw data and processes them to calculate the metrics shown on Netdata dashboards. +### User identification and authorization -Even if Netdata plugins connect to your database server, or read your application log file to collect raw data, the product of this data collection process is always a number of **chart metadata and metric values** (summarized data for dashboard visualization). All Netdata plugins (internal to the Netdata daemon, and external ones written in any computer language), convert raw data collected into metrics, and only these metrics are stored in Netdata databases, sent to upstream Netdata servers, or archived to external time-series databases. +Netdata ensures that only an email address is stored to create an account and use the Service. +User identification and authorization is done +either via third parties (Google, GitHub accounts), or short-lived access tokens, sent to the user’s email account. -> The **raw data** collected by Netdata, does not leave the host when collected. **The only data Netdata exposes are chart metadata and metric values.** +### Personal Data stored -This means that Netdata can safely be used in environments that require the highest level of data isolation (like PCI Level 1). - -## Your systems are safe with Netdata - -We are very proud that **the Netdata daemon runs as a normal system user, without any special privileges**. This is quite an achievement for a monitoring system that collects all kinds of system and application metrics. - -There are a few cases, however, that raw source data are only exposed to processes with escalated privileges. To support these cases, Netdata attempts to minimize and completely isolate the code that runs with escalated privileges. - -So, Netdata **plugins**, even those running with escalated capabilities or privileges, perform a **hard coded data collection job**. They do not accept commands from Netdata. The communication is strictly **unidirectional**: from the plugin towards the Netdata daemon. The original application data collected by each plugin do not leave the process they are collected, are not saved and are not transferred to the Netdata daemon. The communication from the plugins to the Netdata daemon includes only chart metadata and processed metric values. - -Child nodes use the same protocol when streaming metrics to their parent nodes. The raw data collected by the plugins of -child Netdata servers are **never leaving the host they are collected**. The only data appearing on the wire are chart -metadata and metric values. This communication is also **unidirectional**: child nodes never accept commands from -parent Netdata servers. - -## Netdata is read-only - -Netdata **dashboards are read-only**. Dashboard users can view and examine metrics collected by Netdata, but cannot instruct Netdata to do something other than present the already collected metrics. - -Netdata dashboards do not expose sensitive information. Business data of any kind, the kernel version, O/S version, application versions, host IPs, etc are not stored and are not exposed by Netdata on its dashboards. - -## Netdata viewers authentication - -Netdata is a monitoring system. It should be protected, the same way you protect all your admin apps. We assume Netdata will be installed privately, for your eyes only. - -### Why Netdata should be protected - -Viewers will be able to get some information about the system Netdata is running. This information is everything the dashboard provides. The dashboard includes a list of the services each system runs (the legends of the charts under the `Systemd Services` section), the applications running (the legends of the charts under the `Applications` section), the disks of the system and their names, the user accounts of the system that are running processes (the `Users` and `User Groups` section of the dashboard), the network interfaces and their names (not the IPs) and detailed information about the performance of the system and its applications. - -This information is not sensitive (meaning that it is not your business data), but **it is important for possible attackers**. It will give them clues on what to check, what to try and in the case of DDoS against your applications, they will know if they are doing it right or not. - -Also, viewers could use Netdata itself to stress your servers. Although the Netdata daemon runs unprivileged, with the minimum process priority (scheduling priority `idle` - lower than nice 19) and adjusts its OutOfMemory (OOM) score to 1000 (so that it will be first to be killed by the kernel if the system starves for memory), some pressure can be applied on your systems if someone attempts a DDoS against Netdata. - -### Protect Netdata from the internet - -Netdata is a distributed application. Most likely you will have many installations of it. Since it is distributed and you are expected to jump from server to server, there is very little usability to add authentication local on each Netdata. - -Until we add a distributed authentication method to Netdata, you have the following options: - -#### Expose Netdata only in a private LAN - -If your organisation has a private administration and management LAN, you can bind Netdata on this network interface on all your servers. This is done in `Netdata.conf` with these settings: +Netdata ensures that only an email address is stored to create an account and use the Service. The same email +address is used for Netdata product and marketing communications (via Hubspot and Sendgrid). -``` -[web] - bind to = 10.1.1.1:19999 localhost:19999 -``` +Email addresses are stored in our production database on AWS and copied to Google BigQuery, our data lake, +for analytics purposes. These analytics are crucial for our product development process. -You can bind Netdata to multiple IPs and ports. If you use hostnames, Netdata will resolve them and use all the IPs (in the above example `localhost` usually resolves to both `127.0.0.1` and `::1`). +If the user accepts the use of analytical cookies, the email address is also stored in the systems we use to track the +usage of the application (Posthog and Gainsight PX) -**This is the best and the suggested way to protect Netdata**. Your systems **should** have a private administration and management LAN, so that all management tasks are performed without any possibility of them being exposed on the internet. +The IP address used to access Netdata Cloud is stored in web proxy access logs. If the user accepts the use of analytical +cookies, the IP is also stored in the systems we use to track the usage of the application (Posthog and Gainsight PX). -For cloud based installations, if your cloud provider does not provide such a private LAN (or if you use multiple providers), you can create a virtual management and administration LAN with tools like `tincd` or `gvpe`. These tools create a mesh VPN allowing all servers to communicate securely and privately. Your administration stations join this mesh VPN to get access to management and administration tasks on all your cloud servers. +### Infrastructure data stored -For `gvpe` we have developed a [simple provisioning tool](https://github.com/netdata/netdata-demo-site/tree/master/gvpe) you may find handy (it includes statically compiled `gvpe` binaries for Linux and FreeBSD, and also a script to compile `gvpe` on your macOS system). We use this to create a management and administration LAN for all Netdata demo sites (spread all over the internet using multiple hosting providers). +The metric data that a user sees in the web browser when using Netdata Cloud is streamed directly from the Netdata Agent +to the Netdata Cloud dashboard, via the Agent-Cloud link (see [data transfer](#data-transfer)). The data passes through our systems, but it isn’t stored. ---- +The metadata we do store for each node connected to the user's Spaces in Netdata Cloud is: + - 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). + - Metric metadata information shown in `/api/v1/contexts`. For example: [https://frankfurt.my-netdata.io/api/v1/contexts](https://frankfurt.my-netdata.io/api/v1/contexts). + - 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). -In Netdata v1.9+ there is also access list support, like this: +The infrastructure data is stored in our production database on AWS and copied to Google BigQuery, our data lake, for + analytics purposes. -``` -[web] - bind to = * - allow connections from = localhost 10.* 192.168.* -``` +### Data transfer -#### Fine-grained access control +All infrastructure data visible on Netdata Cloud has to pass through the Agent-Cloud link (ACLK) mechanism, which +securely connects a Netdata Agent to Netdata Cloud. The Netdata agent initiates and establishes an outgoing secure +WebSocket (WSS) connection to Netdata Cloud. The ACLK is encrypted, safe, and is only established if the user connects their node. -The access list support allows filtering of all incoming connections, by specific IP addresses, ranges -or validated DNS lookups. Only connections that match an entry on the list will be allowed: +Data is encrypted when in transit between a user and Netdata Cloud using TLS. -``` -[web] - allow connections from = localhost 192.168.* 1.2.3.4 homeip.net -``` +### Data retention -Connections from the IP addresses are allowed if the connection IP matches one of the patterns given. -The alias localhost is always checked against 127.0.0.1, any other symbolic names need to resolve in -both directions using DNS. In the above example the IP address of `homeip.net` must reverse DNS resolve -to the incoming IP address and a DNS lookup on `homeip.net` must return the incoming IP address as -one of the resolved addresses. +Netdata may maintain backups of Netdata Cloud Customer Content, which would remain in place for approximately ninety +(90) days following a deletion in Netdata Cloud. -More specific control of what each incoming connection can do can be specified through the access control -list settings: +### Data portability and erasure -``` -[web] - allow connections from = 160.1.* - allow badges from = 160.1.1.2 - allow streaming from = 160.1.2.* - allow management from = control.subnet.ip - allow netdata.conf from = updates.subnet.ip - allow dashboard from = frontend.subnet.ip -``` +Netdata will, as necessary to enable the Customer to meet its obligations under Data Protection Law, provide the Customer +via the availability of Netdata Cloud with the ability to access, retrieve, correct and delete the Personal Data stored in +Netdata Cloud. The Customer acknowledges that such ability may from time to time be limited due to temporary service outages +for maintenance or other updates to Netdata Cloud, or technically not feasible. -In this example only connections from `160.1.x.x` are allowed, only the specific IP address `160.1.1.2` -can access badges, only IP addresses in the smaller range `160.1.2.x` can stream data. The three -hostnames shown can access specific features, this assumes that DNS is setup to resolve these names -to IP addresses within the `160.1.x.x` range and that reverse DNS is setup for these hosts. +To the extent that the Customer, in its fulfillment of its Data Protection Law obligations, is unable to access, retrieve, +correct or delete Customer Personal Data in Netdata Cloud due to prolonged unavailability of Netdata Cloud due to an issue +within Netdata’s control, Netdata will where possible use reasonable efforts to provide, correct or delete such Customer Personal Data. +If a Customer is unable to delete Personal Data via the self-services functionality, then Netdata deletes Personal Data upon +the Customer’s written request, within the timeframe specified in the DPA and in accordance with applicable data protection law. -#### Use an authenticating web server in proxy mode +#### Delete all personal data -Use one web server to provide authentication in front of **all your Netdata servers**. So, you will be accessing all your Netdata with URLs like `http://{HOST}/netdata/{NETDATA_HOSTNAME}/` and authentication will be shared among all of them (you will sign-in once for all your servers). Instructions are provided on how to set the proxy configuration to have Netdata run behind [nginx](Running-behind-nginx.md), [Apache](Running-behind-apache.md), [lighttpd](Running-behind-lighttpd.md) and [Caddy](Running-behind-caddy.md). +To remove all personal info we have about a user (email and activities) they need to delete their cloud account by logging into https://app.netdata.cloud and accessing their profile, at the bottom left of the screen. -To use this method, you should firewall protect all your Netdata servers, so that only the web server IP will be allowed to directly access Netdata. To do this, run this on each of your servers (or use your firewall manager): -```sh -PROXY_IP="1.2.3.4" -iptables -t filter -I INPUT -p tcp --dport 19999 \! -s ${PROXY_IP} -m conntrack --ctstate NEW -j DROP -``` +## Agent design -*commands to allow direct access to Netdata from a web server proxy* +### User data is safe with Netdata -The above will prevent anyone except your web server to access a Netdata dashboard running on the host. +Netdata collects raw data from many sources. For each source, Netdata uses a plugin that connects to the source (or reads the +relative files produced by the source), receives raw data and processes them to calculate the metrics shown on Netdata dashboards. -For Netdata v1.9+ you can also use `netdata.conf`: +Even if Netdata plugins connect to the user's database server, or read user's application log file to collect raw data, the product of +this data collection process is always a number of **chart metadata and metric values** (summarized data for dashboard visualization). +All Netdata plugins (internal to the Netdata daemon, and external ones written in any computer language), convert raw data collected +into metrics, and only these metrics are stored in Netdata databases, sent to upstream Netdata servers, or archived to external +time-series databases. -``` -[web] - allow connections from = localhost 1.2.3.4 -``` +The **raw data** collected by Netdata does not leave the host when collected. **The only data Netdata exposes are chart metadata and metric values.** -Of course you can add more IPs. - -For Netdata prior to v1.9, if you want to allow multiple IPs, use this: - -```sh -# space separated list of IPs to allow access Netdata -NETDATA_ALLOWED="1.2.3.4 5.6.7.8 9.10.11.12" -NETDATA_PORT=19999 +This means that Netdata can safely be used in environments that require the highest level of data isolation (like PCI Level 1). -# create a new filtering chain || or empty an existing one named netdata -iptables -t filter -N netdata 2>/dev/null || iptables -t filter -F netdata -for x in ${NETDATA_ALLOWED} -do - # allow this IP - iptables -t filter -A netdata -s ${x} -j ACCEPT -done +### User systems are safe with Netdata -# drop all other IPs -iptables -t filter -A netdata -j DROP +We are very proud that **the Netdata daemon runs as a normal system user, without any special privileges**. This is quite an +achievement for a monitoring system that collects all kinds of system and application metrics. -# delete the input chain hook (if it exists) -iptables -t filter -D INPUT -p tcp --dport ${NETDATA_PORT} -m conntrack --ctstate NEW -j netdata 2>/dev/null +There are a few cases, however, that raw source data are only exposed to processes with escalated privileges. To support these +cases, Netdata attempts to minimize and completely isolate the code that runs with escalated privileges. -# add the input chain hook (again) -# to send all new Netdata connections to our filtering chain -iptables -t filter -I INPUT -p tcp --dport ${NETDATA_PORT} -m conntrack --ctstate NEW -j netdata -``` +So, Netdata **plugins**, even those running with escalated capabilities or privileges, perform a **hard coded data collection job**. +They do not accept commands from Netdata. The communication is **unidirectional** from the plugin towards the Netdata daemon, except +for Functions (see below). The original application data collected by each plugin do not leave the process they are collected, are +not saved and are not transferred to the Netdata daemon. The communication from the plugins to the Netdata daemon includes only chart +metadata and processed metric values. -_script to allow access to Netdata only from a number of hosts_ +Child nodes use the same protocol when streaming metrics to their parent nodes. The raw data collected by the plugins of +child Netdata servers are **never leaving the host they are collected**. The only data appearing on the wire are chart +metadata and metric values. This communication is also **unidirectional**: child nodes never accept commands from +parent Netdata servers (except for Functions). -You can run the above any number of times. Each time it runs it refreshes the list of allowed hosts. +[Functions](https://github.com/netdata/netdata/blob/master/docs/cloud/netdata-functions.md) is currently +the only feature that routes requests back to origin Netdata Agents via Netdata Parents. The feature allows Netdata Cloud to send +a request to the Netdata Agent data collection plugin running at the +edge, to provide additional information, such as the process tree of a server, or the long queries of a DB. -#### Other methods + -Of course, there are many more methods you could use to protect Netdata: +### Netdata is read-only -- bind Netdata to localhost and use `ssh -L 19998:127.0.0.1:19999 remote.netdata.ip` to forward connections of local port 19998 to remote port 19999. This way you can ssh to a Netdata server and then use `http://127.0.0.1:19998/` on your computer to access the remote Netdata dashboard. +Netdata **dashboards are read-only**. Dashboard users can view and examine metrics collected by Netdata, but cannot +instruct Netdata to do something other than present the already collected metrics. -- If you are always under a static IP, you can use the script given above to allow direct access to your Netdata servers without authentication, from all your static IPs. +Netdata dashboards do not expose sensitive information. Business data of any kind, the kernel version, O/S version, +application versions, host IPs, etc. are not stored and are not exposed by Netdata on its dashboards. -- install all your Netdata in **headless data collector** mode, forwarding all metrics in real-time to a parent - Netdata server, which will be protected with authentication using an nginx server running locally at the parent - Netdata server. This requires more resources (you will need a bigger parent Netdata server), but does not require - any firewall changes, since all the child Netdata servers will not be listening for incoming connections. +### Protect Netdata from the internet -## Anonymous Statistics +Users are responsible to take all appropriate measures to secure their Netdata agent installations and especially the Netdata web user interface and API against unauthorized access. Netdata comes with a wide range of options to +[secure user nodes](https://github.com/netdata/netdata/blob/master/docs/category-overview-pages/secure-nodes.md) in +compliance with the user organization's security policy. -### Registry or how to not send any information to a third party server +### Anonymous statistics -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: +#### Netdata registry -- The url where you open the web-ui in the browser (via http request referrer) -- The hostnames of the Netdata servers +The default configuration uses a public [registry](https://github.com/netdata/netdata/blob/master/registry/README.md) under registry.my-netdata.io. +If the user uses that public registry, they submit the following information to a third party server: + - The URL of the agent's web user interface (via http request referrer) + - The hostnames of the user's Netdata servers -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). +If sending this information to the central Netdata registry violates user's security policies, they can configure Netdata to +[run their own registry](https://github.com/netdata/netdata/blob/master/registry/README.md#run-your-own-registry). -### Opt-out of anonymous statistics +#### Anonymous telemetry events 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](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 -_actively_ contributing to Netdata's future. +about the information collected and learn how to opt-out, on our +[anonymous telemetry events](https://github.com/netdata/netdata/blob/master/docs/anonymous-statistics.md) page. -## Netdata directories +### Netdata directories +The agent stores data in 6 different directories on the user's system. + | path|owner|permissions|Netdata|comments| |:---|:----|:----------|:------|:-------| | `/etc/netdata`|user `root`
group `netdata`|dirs `0755`
files `0640`|reads|**Netdata config files**
may contain sensitive information, so group `netdata` is allowed to read them.| @@ -226,4 +171,26 @@ _actively_ contributing to Netdata's future. | `/var/lib/netdata`|user `netdata`
group `netdata`|dirs `0750`
files `0660`|reads, writes, creates, deletes|**Netdata permanent database files**
Netdata stores here the registry data, health alarm log db, etc.| | `/var/log/netdata`|user `netdata`
group `root`|dirs `0755`
files `0644`|writes, creates|**Netdata log files**
all the Netdata applications, logs their errors or other informational messages to files in this directory. These files should be log rotated.| +## Organization processes + +### Employee identification and authorization + +Netdata operates technical and organizational measures for employee identification and authentication, such as logs, policies, +assigning distinct usernames for each employee and utilizing password complexity requirements for access to all platforms. + +The COO or HR are the primary system owners for all platforms and may designate additional system owners, as needed. Additional +user access is also established on a role basis, requires the system owner’s approval, and is tracked by HR. User access to each +platform is subject to periodic review and testing. When an employee changes roles, HR updates the employee’s access to all systems. +Netdata uses on-boarding and off-boarding processes to regulate access by Netdata Personnel. + +Second-layer authentication is employed where available, by way of multi-factor authentication. + +Netdata’s IT control environment is based upon industry-accepted concepts, such as multiple layers of preventive and detective +controls, working in concert to provide for the overall protection of Netdata’s computing environment and data assets. + +### Systems security +Netdata maintains a risk-based assessment security program. The framework for Netdata’s security program includes administrative, +organizational, technical, and physical safeguards reasonably designed to protect the services and confidentiality, integrity, +and availability of user data. The program is intended to be appropriate to the nature of the services and the size and complexity +of Netdata’s business operations. diff --git a/docs/overview/netdata-monitoring-stack.md b/docs/overview/netdata-monitoring-stack.md deleted file mode 100644 index 36f5b5f0..00000000 --- a/docs/overview/netdata-monitoring-stack.md +++ /dev/null @@ -1,62 +0,0 @@ - - -# Use Netdata standalone or as part of your monitoring stack - -Netdata is an extremely powerful monitoring, visualization, and troubleshooting platform. While you can use it as an -effective standalone tool, we also designed it to be open and interoperable with other tools you might already be using. - -Netdata helps you collect everything and scales to infrastructure of any size, but it doesn't lock-in data or force you -to use specific tools or methodologies. Each feature is extensible and interoperable so they can work in parallel with -other tools. For example, you can use Netdata to collect metrics, visualize metrics with a second open-source program, -and centralize your metrics in a cloud-based time-series database solution for long-term storage or further analysis. - -You can build a new monitoring stack, including Netdata, or integrate Netdata's metrics with your existing monitoring -stack. No matter which route you take, Netdata helps you monitor infrastructure of any size. - -Here are a few ways to enrich your existing monitoring and troubleshooting stack with Netdata: - -## Collect metrics from Prometheus endpoints - -Netdata automatically detects 600 popular endpoints and collects per-second metrics from them via the [generic -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 -configuring Netdata. Netdata will detect these Prometheus metrics endpoints and collect even more granular metrics than -your existing solutions. You can now use all of Netdata's meaningfully-visualized charts to diagnose issues and -troubleshoot anomalies. - -## Export metrics to external time-series databases - -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](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. - -Once you have Netdata's metrics in a secondary time-series database, you can use them however you'd like, such as -additional visualization/dashboarding tools or aggregation of data from multiple sources. - -## Visualize metrics with Grafana - -One popular monitoring stack is Netdata, Graphite, and Grafana. Netdata acts as the stack's metrics collection -powerhouse, Graphite the time-series database, and Grafana the visualization platform. With Netdata at the core, you can -be confident that your monitoring stack is powered by all possible metrics, from all possible sources, from every node -in your infrastructure. - -Of course, just because you export or visualize metrics elsewhere, it doesn't mean Netdata's equivalent features -disappear. You can always build new dashboards in Netdata Cloud, drill down into per-second metrics using Netdata's -charts, or use Netdata's health watchdog to send notifications whenever an anomaly strikes. - -## 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**](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 deleted file mode 100644 index f8e67159..00000000 --- a/docs/overview/what-is-netdata.md +++ /dev/null @@ -1,76 +0,0 @@ - - -# What is Netdata? - -Netdata helps sysadmins, SREs, DevOps engineers, and IT professionals collect all possible metrics from systems and -applications, visualize these metrics in real-time, and troubleshoot complex performance problems. - -Netdata's solution uses two components, the Netdata Agent and Netdata Cloud, to deliver real-time performance and health -monitoring for both single nodes and entire infrastructure. - -## 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](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. - -![The Netdata -Agent](https://user-images.githubusercontent.com/1153921/94492596-72a86b00-019f-11eb-91ab-224e6ac9ea21.png) - -## 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](https://www.netdata.cloud/blog/why-netdata-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. - -![Netdata Cloud](https://user-images.githubusercontent.com/1153921/94492597-73410180-019f-11eb-9a9e-032420baa489.png) - -## What you can do with Netdata - -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. - -## What's next? - -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 deleted file mode 100644 index 158bc50d..00000000 --- a/docs/overview/why-netdata.md +++ /dev/null @@ -1,63 +0,0 @@ - - -# Why use Netdata? - -Netdata takes a different approach to helping people build extraordinary infrastructure. It was built out of frustration -with existing monitoring tools that are too complex, too expensive, and don't help their users actually troubleshoot -complex performance and health issues. - -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. - -## 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 | - -## What's next? - -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](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 23986b00..c76948f6 100644 --- a/docs/quickstart/infrastructure.md +++ b/docs/quickstart/infrastructure.md @@ -1,11 +1,9 @@ - +import { Grid, Box, BoxList, BoxListItemRegexLink } from '@site/src/components/Grid/' +import { RiExternalLinkLine } from 'react-icons/ri' -# Infrastructure monitoring with Netdata +# Monitor your infrastructure + +Learn how to view key metrics, insightful charts, and active alarms from all your nodes, with Netdata Cloud's real-time infrastructure monitoring. [Netdata Cloud](https://app.netdata.cloud) provides scalable infrastructure monitoring for any number of distributed nodes running the Netdata Agent. A node is any system in your infrastructure that you want to monitor, whether it's a @@ -14,7 +12,7 @@ physical or virtual machine (VM), container, cloud deployment, or edge/IoT devic The Netdata Agent uses zero-configuration collectors to gather metrics from every application and container instantly, 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. +resources you need to invest in, and the complexity of, monitoring your infrastructure. Netdata Cloud unifies infrastructure monitoring by _centralizing the interface_ you use to query and visualize your nodes' metrics, not the data. By streaming metrics values to your browser, with Netdata Cloud acting as the secure proxy @@ -25,14 +23,8 @@ In this quickstart guide, you'll learn the basics of using Netdata Cloud to moni composite charts, and alarm viewing. You'll then learn about the most critical ways to configure the Agent on each of 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://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](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). +This quickstart assumes you've [installed Netdata](https://github.com/netdata/netdata/edit/master/packaging/installer/README.md) +on more than one node in your infrastructure, and connected those nodes to your Space in Netdata Cloud. ## Set up your Netdata Cloud experience @@ -67,32 +59,49 @@ To [invite new users](https://github.com/netdata/netdata/blob/master/docs/cloud/ 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://github.com/netdata/netdata/blob/master/docs/cloud/manage/sign-in.mdx) doc. +in](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/sign-in.md) 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**](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. +Netdata Cloud utilizes "tabs" in order to provide you with informative sections based on your infrastructure. +These tabs can be separated into "static", meaning they are by default presented, and "non-static" which are tabs that get presented by user action (e.g clicking on a custom dashboard) + +#### Static tabs + +- The default tab for any War Room is the [Home tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#home), which gives you an overview of this Space. + Here you can see the number of Nodes claimed, data retention statics, users by role, alerts and more. + +- The second and most important tab is the [Overview tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md#overview-and-single-node-view) 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 regarding 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](https://github.com/netdata/netdata/blob/master/docs/monitor/view-active-alarms.md) provides you with an overview for all the active alerts you receive for the nodes in this War Room, you can also see all the alerts that are configured to be triggered in any given moment. + +- The [Anomalies tab](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/anomaly-advisor.md) is dedicated to the Anomaly Advisor tool. -![The War Room -Overview](https://user-images.githubusercontent.com/1153921/108732681-09791980-74eb-11eb-9ba2-98cb1b6608de.png) +- The [Functions tab](https://github.com/netdata/netdata/blob/master/docs/cloud/netdata-functions.md) gives you the ability to visualize functions that the Netdata Agent collectors are able to expose. -Read more about the Overview in the [infrastructure overview](https://github.com/netdata/netdata/blob/master/docs/visualize/overview-infrastructure.md) doc. +- The [Feed & events](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/events-feed.md) tab lets you investigate events that occurred in the past, which is invaluable for troubleshooting. -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. +#### Dynamic 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, a new tab will open in War Room bar. + +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. ### Drill down to specific nodes -Both the Overview and Nodes view offer easy access to **single-node dashboards** for targeted analysis. You can use +Both the Overview and the Nodes tab offer easy access to **single-node dashboards** for targeted analysis. 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. Read about the process in the [infrastructure 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 +dashboards and charts](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md) to get the most from all of Netdata's real-time metrics. ### Create new dashboards @@ -104,7 +113,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](https://github.com/netdata/netdata/blob/master/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/cloud/visualize/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 @@ -134,7 +143,7 @@ sudo ./edit-config netdata.conf 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](https://github.com/netdata/netdata/blob/master/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/netdata-security.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. @@ -144,42 +153,74 @@ Netdata has [300+ pre-installed collectors](https://github.com/netdata/netdata/b 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](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. +Most collectors work without configuration, should you want more info, you can read more on [how Netdata's metrics collectors work](https://github.com/netdata/netdata/blob/master/collectors/README.md) and the [Collectors configuration reference](https://github.com/netdata/netdata/blob/master/collectors/REFERENCE.md) documentation. 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? - -Netdata has many features that help you monitor the health of your nodes and troubleshoot complex performance problems. -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](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](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](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](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](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://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://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) - - +## Netdata Cloud features + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +- Spaces and War Rooms + - [Spaces](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md) + - [War Rooms](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md) +- Dashboards + - [Overview](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md) + - [Nodes tab](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md) + - [Kubernetes](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/kubernetes.md) + - [Create new dashboards](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/dashboards.md) +- Alerts and notifications + - [View active alerts](https://github.com/netdata/netdata/blob/master/docs/monitor/view-active-alarms.md#netdata-cloud) + - [Alert notifications](https://github.com/netdata/netdata/blob/master/docs/cloud/alerts-notifications/notifications.md) +- Troubleshooting with Netdata Cloud + - [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.md) + - [Events Feed](https://github.com/netdata/netdata/blob/master/docs/cloud/insights/events-feed.md) +- Management and settings + - [Sign in with email, Google, or GitHub](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/sign-in.md) + - [Invite your team](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/invite-your-team.md) + - [Choose your Netdata Cloud theme](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/themes.md) + - [Role-Based Access](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/role-based-access.md) + - [Paid Plans](https://github.com/netdata/netdata/blob/master/docs/cloud/manage/plans.md) diff --git a/docs/quickstart/single-node.md b/docs/quickstart/single-node.md deleted file mode 100644 index 29373191..00000000 --- a/docs/quickstart/single-node.md +++ /dev/null @@ -1,92 +0,0 @@ - - -# Single-node monitoring with Netdata - -Because it's free, open-source, and requires only 1% CPU utilization to collect thousands of metrics every second, -Netdata is a superb single-node monitoring tool. - -In this quickstart guide, you'll learn how to access your single node's metrics through dashboards, configure your node -to your liking, and make sure the Netdata Agent is collecting metrics from the applications or containers you're running -on your node. - -## See your node's metrics - -To see your node's real-time metrics, you need to access its dashboard. You can either view the local dashboard, which -runs on the node itself, or see the dashboard through Netdata Cloud. Both methods feature real-time, interactive, and -synchronized charts, with the same metrics, and use the same UI. - -The primary difference is that Netdata Cloud also has a few extra features, like creating new dashboards using a -drag-and-drop editor, that enhance your monitoring and troubleshooting experience. - -To see your node's local dashboard, open up your web browser of choice and navigate to `http://NODE:19999`, replacing -`NODE` with the IP address or hostname of your Agent. Hit `Enter`. - -![Animated GIF of navigating to the -dashboard](https://user-images.githubusercontent.com/1153921/80825153-abaec600-8b94-11ea-8b17-1b770a2abaa9.gif) - -To see a node's dashboard in Netdata Cloud, [sign in](https://app.netdata.cloud). From the **Nodes** view in your -**General** War Room, click on the hostname of your node to access its dashboard through Netdata Cloud. - -![Screenshot of an embedded node -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](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 - -The Netdata Agent is highly configurable so that you can match its behavior to your node. You will find most -configuration options in the `netdata.conf` file, which is typically at `/etc/netdata/netdata.conf`. The best way to -edit this file is using the `edit-config` script, which ensures updates to the Netdata Agent do not overwrite your -changes. For example: - -```bash -cd /etc/netdata -sudo ./edit-config netdata.conf -``` - -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](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](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](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](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? - -Netdata has many features that help you monitor the health of your node and troubleshoot complex performance problems. -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](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](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](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](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 e82393a6..5e14fe24 100644 --- a/docs/store/change-metrics-storage.md +++ b/docs/store/change-metrics-storage.md @@ -1,102 +1,207 @@ - - # Change how long Netdata stores 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 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 -data. +To see the number of metrics stored and the retention in days per tier, use the `/api/v1/dbengine_stats` endpoint. -The Netdata Agent uses the following three fundamental settings in `netdata.conf` to change the behavior of the database engine: +To increase or decrease the metric retention time, you just [configure](#configure-metric-retention) +the number of storage tiers and the space allocated to each one. The effect of these two parameters +on the maximum retention and the memory used by Netdata is described in detail, below. + +## Calculate the system resources (RAM, disk space) needed to store metrics + +### Effect of storage tiers and disk space on retention + +3 tiers are enabled by default in Netdata, with the following configuration: -```conf -[global] - dbengine page cache size = 32 - dbengine multihost disk space = 256 - storage tiers = 1 +``` +[db] + mode = dbengine + + # per second data collection + update every = 1 + + # number of tiers used (1 to 5, 3 being default) + storage tiers = 3 + + # Tier 0, per second data + dbengine multihost disk space MB = 256 + + # Tier 1, per minute data + dbengine tier 1 multihost disk space MB = 128 + dbengine tier 1 update every iterations = 60 + + # Tier 2, per hour data + dbengine tier 2 multihost disk space MB = 64 + dbengine tier 2 update every iterations = 60 ``` -`dbengine page cache size` sets the maximum amount of RAM (in MiB) the database engine uses to cache and index recent -metrics. -`dbengine multihost disk space` sets the maximum disk space (again, in MiB) the database engine uses to store -historical, compressed metrics and `storage tiers` specifies the number of storage tiers you want to have in -your `dbengine`. When the size of stored metrics exceeds the allocated disk space, the database engine removes the -oldest metrics on a rolling basis. +The default "update every iterations" of 60 means that if a metric is collected per second in Tier 0, then +we will have a data point every minute in tier 1 and every minute in tier 2. -## Calculate the system resources (RAM, disk space) needed to store metrics +Up to 5 tiers are supported. You may add, or remove tiers and/or modify these multipliers, as long as the +product of all the "update every iterations" does not exceed 65535 (number of points for each tier0 point). + +e.g. If you simply add a fourth tier by setting `storage tiers = 4` and defining the disk space for the new tier, +the product of the "update every iterations" will be 60 * 60 * 60 = 216,000, which is > 65535. So you'd need to reduce +the `update every iterations` of the tiers, to stay under the limit. + +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 sec per day ~= 3 days +``` + +At tier 1 (per minute): + +``` +128MB on disk / 4 bytes per point / 1000 metrics => 32k points per metric / (24 hr * 60 min) ~= 22 days +``` + +At tier 2 (per hour): + +``` +64MB on disk / 4 bytes per point / 1000 metrics => 16k points per metric / 24 hr per day ~= 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. + +To see the number of metrics stored and the retention in days per tier, use the `/api/v1/dbengine_stats` endpoint. -You can store more or less metrics using the database engine by changing the allocated disk space. Use the calculator -below to find the appropriate value for the `dbengine` based on how many metrics your node(s) collect, whether you are -streaming metrics to a parent node, and more. +### Effect of storage tiers and retention on memory usage -You do not need to edit the `dbengine page cache size` setting to store more metrics using the database engine. However, -if you want to store more metrics _specifically in memory_, you can increase the cache size. +The total memory Netdata uses is heavily influenced by the memory consumed by the DBENGINE. +The 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. -:::tip +The precise analysis of how much memory will be used by the DBENGINE itself is described in +[DBENGINE memory requirements](https://github.com/netdata/netdata/blob/master/database/engine/README.md#memory-requirements). -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. +In addition to the DBENGINE, Netdata uses memory for contexts, metric labels (e.g. in a Kubernetes setup), +other Netdata structures/processes (e.g. Health) and system overhead. -::: +The quick rule of thumb, for a high level estimation is -:::caution +``` +DBENGINE memory in MiB = METRICS x (TIERS - 1) x 8 / 1024 MiB +Total Netdata memory in MiB = Metric ephemerality factor x DBENGINE memory in MiB + "dbengine page cache size MB" from netdata.conf +``` + +You can get the currently collected **METRICS** from the "dbengine metrics" chart of the Netdata dashboard. You just need to divide the +value of the "collected" dimension with the number of tiers. For example, at the specific point highlighted in the chart below, 608k metrics +were being collected across all 3 tiers, which means that `METRICS = 608k / 3 = 203667`. + +image -This calculator provides an estimation of disk and RAM usage for **metrics usage**. Real-life usage may vary based on -the accuracy of the values you enter below, changes in the compression ratio, and the types of metrics stored. -::: +The **ephemerality factor** is usually between 3 or 4 and depends on how frequently the identifiers of the collected metrics change, increasing their +cardinality. The more ephemeral the infrastructure, the more short-lived metrics you have, increasing the ephemerality factor. If the metric cardinality is +extremely high due for example to a lot of extremely short lived containers (hundreds started every minute), the ephemerality factor can be much higher than 4. +In such cases, we recommend splitting the load across multiple Netdata parents, until we can provide a way to lower the metric cardinality, +by aggregating similar metrics. -Visit the [Netdata Storage Calculator](https://netdata-storage-calculator.herokuapp.com/) app to customize -data retention according to your preferences. +#### Small agent RAM usage -## Edit `netdata.conf` with recommended database engine settings +For 2000 metrics (dimensions) in 3 storage tiers and the default cache size: + +``` +DBENGINE memory for 2k metrics = 2000 x (3 - 1) x 8 / 1024 MiB = 32 MiB +dbengine page cache size MB = 32 MiB +Total Netdata memory in MiB = 3*32 + 32 = 128 MiB (low ephemerality) +``` + +#### Large parent RAM usage + +The Netdata parent in our production infrastructure at the time of writing: + - Collects 206k metrics per second, most from children streaming data + - The metrics include moderately ephemeral Kubernetes containers, leading to an ephemerality factor of about 4 + - 3 tiers are used for retention + - The `dbengine page cache size MB` in `netdata.conf` is configured to be 4GB + +Netdata parents can end up collecting millions of metrics per second. See also [scaling dedicated parent nodes](#scaling-dedicated-parent-nodes). + +The rule of thumb calculation for this set up gives us +``` +DBENGINE memory = 206,000 x 16 / 1024 MiB = 3,217 MiB = about 3 GiB +Extra cache = 4 GiB +Metric ephemerality factor = 4 +Estimated total Netdata memory = 3 * 4 + 4 = 16 GiB +``` -Now that you have a recommended setting for your Agent's `dbengine`, open `netdata.conf` with -[`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: +The actual measurement during a low usage time was the following: + +Purpose|RAM|Note +:--- | ---: | :--- +DBENGINE usage | 5.9 GiB | Out of 7GB max +Cardinality/ephemerality related memory (k8s contexts, labels, strings) | 3.4 GiB +Buffer for queries | 0 GiB | Out of 0.5 GiB max, when heavily queried +Other | 0.5 GiB | +System overhead | 4.4 GiB | Calculated by subtracting all of the above from the total +**Total Netdata memory usage** | 14.2 GiB | + +All the figures above except for the system memory management overhead were retrieved from Netdata itself. +The overhead can't be directly calculated, so we subtracted all the other figures from the total Netdata memory usage to get it. +This overhead is usually around 50% of the memory actually useable by Netdata, but could range from 20% in small +setups, all the way to 100% in some edge cases. + +## Configure metric retention + +Once you have decided how to size each tier, open `netdata.conf` with +[`edit-config`](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) +and make your changes in the `[db]` subsection. + +Save the file and restart the 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 change the database engine's size. + +## Legacy configuration + +### v1.35.1 and prior + +These versions of the Agent do not support 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. + +To configure the database engine, look for the `page cache size MB` and `dbengine multihost disk space MB` settings in +the `[db]` section of your `netdata.conf`. ```conf [db] - mode = dbengine - storage tiers = 3 - update every = 1 - dbengine multihost disk space MB = 1024 - dbengine page cache size MB = 32 - dbengine tier 1 update every iterations = 60 - dbengine tier 1 multihost disk space MB = 384 - dbengine tier 1 page cache size MB = 32 - dbengine tier 2 update every iterations = 60 - dbengine tier 2 multihost disk space MB = 16 - dbengine tier 2 page cache size MB = 32 + dbengine page cache size MB = 32 + dbengine multihost disk space MB = 256 ``` -Save the file and restart the 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 change the database engine's size. +### 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`). -## What's next? +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`. -If you have multiple nodes with the Netdata Agent installed, you -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. +#### Backward compatibility -Storing metrics with the database engine is completely interoperable -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. +All existing metrics belonging to child nodes are automatically converted to legacy dbengine instances and the localhost +metrics are transferred to the multihost dbengine instance. -### Related reference documentation +All new child nodes are automatically transferred to the multihost dbengine instance and share its page cache and disk +space. If you want to migrate a child node from its legacy dbengine instance to the multihost dbengine instance, you +must delete the instance's directory, which is located in `/var/cache/netdata/MACHINE_GUID/dbengine`, after stopping the +Agent. -- [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) +## Scaling dedicated parent nodes +When you use streaming in medium to large infrastructures, you can have potentially millions of metrics per second reaching each parent node. +In the lab we have reliably collected 1 million metrics/sec with 16cores and 32GB RAM. +Our suggestion for scaling parents is to have them running on dedicated VMs, using a maximum of 50% of cpu, and ensuring you have enough RAM +for the desired retention. When your infrastructure can lead a parent to exceed these characteristics, split the load to multiple parents that +do not communicate with each other. With each child sending data to only one of the parents, you can still have replication, high availability, +and infrastructure level observability via the Netdata Cloud UI. diff --git a/docs/store/distributed-data-architecture.md b/docs/store/distributed-data-architecture.md index 96ae4d99..b08a265a 100644 --- a/docs/store/distributed-data-architecture.md +++ b/docs/store/distributed-data-architecture.md @@ -1,20 +1,12 @@ - - # Distributed data architecture -Netdata uses a distributed data architecture to help you collect and store per-second metrics from any number of nodes. +Learn how Netdata's distributed data architecture enables us to store metrics on the edge nodes for security, high performance and scalability. + +This way, it helps you collect and store per-second metrics from any number of nodes. 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](https://github.com/netdata/netdata/blob/master/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/cloud/visualize/interact-new-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. @@ -77,16 +69,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](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 +or [filtering by hostname/service](https://github.com/netdata/netdata/blob/master/docs/cloud/war-rooms.md), Netdata Cloud does store configured alarms, their status, and a list of active collectors. Netdata does not and never will sell your personal data or data about your deployment. - -## 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](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 deleted file mode 100644 index f4306f33..00000000 --- a/docs/visualize/create-dashboards.md +++ /dev/null @@ -1,69 +0,0 @@ - - -# Create new dashboards - -With Netdata Cloud, you can build new dashboards that put key metrics from any number of distributed systems in one -place for a bird's eye view of your infrastructure. You can create more meaningful visualizations for troubleshooting or -keep a watchful eye on your infrastructure's most meaningful metrics without moving from node to node. - -In the War Room you want to monitor with this dashboard, click on your War Room's dropdown, then click on the green **+ -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. - -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 - -![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](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 -automatically. You can also resize any card by grabbing the bottom-right corner and dragging it to its new size. - -Hit the **Save** button to finalize your dashboard. Any other member of the War Room can now access it and make changes. - -## Jump to single-node Cloud dashboards - -While dashboards help you associate essential charts from distributed nodes on a single pane of glass, you might need -more detail when troubleshooting an issue. 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. - -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](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 Dashboard
-icon to quickly add that chart to a new or existing dashboard. You might find this useful when -investigating an anomaly and want to quickly populate a dashboard with potentially correlated metrics. - -## Pin dashboards and navigate through Netdata Cloud - -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 and discover root causes -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](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 deleted file mode 100644 index bf6d7a01..00000000 --- a/docs/visualize/interact-dashboards-charts.md +++ /dev/null @@ -1,131 +0,0 @@ - - -# Interact with dashboards and charts - -> ⚠️ 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](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](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](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: - -![Time & date picker on the local Netdata -dashboard](https://user-images.githubusercontent.com/1153921/101512538-5875d080-3938-11eb-8daf-0fbd0948a04b.png) - -Netdata Cloud: - -![Time & date picker on Netdata -Cloud](https://user-images.githubusercontent.com/1153921/101512689-86f3ab80-3938-11eb-8abc-12171a9b8a5e.png) - -Their behavior is identical. Use the Quick Selector to visualize generic timeframes, or use the calendar or inputs to -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](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 - -A **chart** is an interactive visualization of one or more collected/calculated metrics. You can see the name (also -known as its unique ID) of a chart by looking at the top-left corner of a chart and finding the parenthesized text. On a -Linux system, one of the first charts on the dashboard will be the system CPU chart, with the name `system.cpu`. - -A **dimension** is any value that gets shown on a chart. The value can be raw data or calculated values, such as -percentages, aggregates, and more. Most charts will have more than one dimension, in which case it will display each in -a different color. You can disable or enable showing these dimensions by clicking on them. - -A **family** is _one_ instance of a monitored hardware or software resource that needs to be monitored and displayed -separately from similar instances. For example, if your node has multiple partitions, Netdata will create different -families for `/`, `/boot`, `/home`, and so on. Same goes for entire disks, network devices, and more. - -A **context** groups several charts based on the types of metrics being collected and displayed. For example, the -**Disk** section often has many contexts: `disk.io`, `disk.ops`, `disk.backlog`, `disk.util`, and so on. Netdata uses -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](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 - -Netdata's charts are fully interactive to help you find meaningful information about complex problems. You can pan -through historical metrics, zoom in and out, select specific timeframes for further analysis, resize charts, and more. -Whenever you use a chart in this way, Netdata synchronizes all the other charts to match it. - -| Change | Method #1 | Method #2 | Method #3 | -| ------------------------------------------------- | ----------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------- | -| **Stop** a chart from updating | `click` | | | -| **Reset** charts to default auto-refreshing state | `double click` | `double tap` (touchpad/touchscreen) | | -| **Select** a certain timeframe | `ALT` + `mouse selection` | `⌘` + `mouse selection` (macOS) | | -| **Pan** forward or back in time | `click and drag` | `touch and drag` (touchpad/touchscreen) | | -| **Zoom** to a specific timeframe | `SHIFT` + `mouse selection` | | | -| **Zoom** in/out | `SHIFT`/`ALT` + `mouse scrollwheel` | `SHIFT`/`ALT` + `two-finger pinch` (touchpad/touchscreen) | `SHIFT`/`ALT` + `two-finger scroll` (touchpad/touchscreen) | - -![Animated GIF of interacting with Netdata -charts](https://user-images.githubusercontent.com/1153921/102652236-051b3380-412b-11eb-8f7c-a2372ed92cd0.gif) - -These interactions can also be triggered using the icons on the bottom-right corner of every chart. They are, -respectively, `Pan Left`, `Reset`, `Pan Right`, `Zoom In`, and `Zoom Out`. - -You can show and hide individual dimensions by clicking on their names. Use `SHIFT + click` to hide or show dimensions -one at a time. Hiding dimensions simplifies the chart and can help you better discover exactly which aspect of your -system is behaving strangely. - -You can resize any chart by clicking-and-dragging the icon on the bottom-right corner of any chart. To restore the chart -to its original height, double-click the same icon. - -![Resizing a chart and resetting it to the default -height](https://user-images.githubusercontent.com/1153921/102652691-24b25c00-412b-11eb-9e2c-95325fcedc67.gif) - -### Composite charts in Netdata Cloud - -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://github.com/netdata/netdata/blob/master/docs/cloud/visualize/overview.md) doc. - -## What's next? - -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](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](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 0daddd97..c09e9aea 100644 --- a/docs/visualize/overview-infrastructure.md +++ b/docs/visualize/overview-infrastructure.md @@ -2,6 +2,10 @@ title: "See an overview of your infrastructure" description: "With Netdata Cloud's War Rooms, you can see real-time metrics, from any number of nodes in your infrastructure, in composite charts." custom_edit_url: https://github.com/netdata/netdata/edit/master/docs/visualize/overview-infrastructure.md +sidebar_label: "See an overview of your infrastructure" +learn_status: "Published" +learn_topic_type: "Tasks" +learn_rel_path: "Operations/Netdata Cloud Visualizations" --> # See an overview of your infrastructure @@ -80,32 +84,12 @@ 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](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). +All of the familiar [interactions](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/interact-new-charts.md) are available, as is adding any chart +to a [new dashboard](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/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://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](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://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_](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://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) +## Nodes tab +You can also use the **Nodes tab** to monitor the health status and user-configurable key metrics from multiple nodes +in a War Room. Read the [Nodes tab documentation](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/nodes.md) for details. +![The Nodes tab](https://user-images.githubusercontent.com/1153921/108733066-5fe65800-74eb-11eb-98e0-abaccd36deaf.png) diff --git a/docs/why-netdata/1s-granularity.md b/docs/why-netdata/1s-granularity.md deleted file mode 100644 index 4fc7fab2..00000000 --- a/docs/why-netdata/1s-granularity.md +++ /dev/null @@ -1,59 +0,0 @@ - - -# 1s granularity - -High resolution metrics are required to effectively monitor and troubleshoot systems and applications. - -## Why? - -- The world is going real-time. Today, customer experience is significantly affected by response time, so SLAs are tighter than ever before. It is just not practical to monitor a 2-second SLA with 10-second metrics. - -- IT goes virtual. Unlike real hardware, virtual environments are not linear, nor predictable. You cannot expect resources to be available when your applications need them. They will eventually be, but not exactly at the time they are needed. The latency of virtual environments is affected by many factors, most of which are outside our control, like: the maintenance policy of the hosting provider, the work load of third party virtual machines running on the same physical servers combined with the resource allocation and throttling policy among virtual machines, the provisioning system of the hosting provider, etc. - -## What do others do? - -So, why don't most monitoring platforms and monitoring SaaS providers offer high resolution metrics? - -They want to, but they can't, at least not massively. - -The reasons lie in their design decisions: - -1. Time-series databases (prometheus, graphite, opentsdb, influxdb, etc) centralize all the metrics. At scale, these databases can easily become the bottleneck of the whole infrastructure. - -2. SaaS providers base their business models on centralizing all the metrics. On top of the time-series database bottleneck they also have increased bandwidth costs. So, massively supporting high resolution metrics, destroys their business model. - -Of course, since a couple of decades the world has fixed this kind of scaling problems: instead of scaling up, scale out, horizontally. That is, instead of investing on bigger and bigger central components, decentralize the application so that it can scale by adding more smaller nodes to it. - -There have been many attempts to fix this problem for monitoring. But so far, all solutions required centralization of metrics, which can only scale up. So, although the problem is somehow managed, it is still the key problem of all monitoring platforms and one of the key reasons for increased monitoring costs. - -Another important factor is how resource efficient data collection can be when running per second. Most solutions fail to do it properly. The data collection agent is consuming significant system resources when running "per second", influencing the monitored systems and applications to a great degree. - -Finally, per second data collection is a lot harder. Busy virtual environments have [a constant latency of about 100ms, spread randomly to all data sources](https://docs.google.com/presentation/d/18C8bCTbtgKDWqPa57GXIjB2PbjjpjsUNkLtZEz6YK8s/edit#slide=id.g422e696d87_0_57). If data collection is not implemented properly, this latency introduces a random error of +/- 10%, which is quite significant for a monitoring system. - -So, the monitoring industry fails to massively provide high resolution metrics, mainly for 3 reasons: - -1. Centralization of metrics makes monitoring cost inefficient at that rate. -2. Data collection needs optimization, otherwise it will significantly affect the monitored systems. -3. Data collection is a lot harder, especially on busy virtual environments. - -## What does Netdata do differently? - -Netdata decentralizes monitoring completely. Each Netdata node is autonomous. It collects metrics locally, it stores them locally, it runs checks against them to trigger alarms locally, and provides an API for the dashboards to visualize them. This allows Netdata to scale to infinity. - -Of course, Netdata can centralize metrics when needed. For example, it is not practical to keep metrics locally on ephemeral nodes. For these cases, Netdata streams the metrics in real-time, from the ephemeral nodes to one or more non-ephemeral nodes nearby. This centralization is again distributed. On a large infrastructure, there may be many centralization points. - -To eliminate the error introduced by data collection latencies on busy virtual environments, Netdata interpolates collected metrics. It does this using microsecond timings, per data source, offering measurements with an error rate of 0.0001%. When running [in debug mode, Netdata calculates this error rate](https://github.com/netdata/netdata/blob/36199f449852f8077ea915a3a14a33fa2aff6d85/database/rrdset.c#L1070-L1099) for every point collected, ensuring that the database works with acceptable accuracy. - -Finally, Netdata is really fast. Optimization is a core product feature. On modern hardware, Netdata can collect metrics with a rate of above 1M metrics per second per core (this includes everything, parsing data sources, interpolating data, storing data in the time series database, etc). So, for a few thousands metrics per second per node, Netdata needs negligible CPU resources (just 1-2% of a single core). - -Netdata has been designed to: - -- Solve the centralization problem of monitoring -- Replace the console for performance troubleshooting. - -So, for Netdata 1s granularity is easy, the natural outcome... - - diff --git a/docs/why-netdata/README.md b/docs/why-netdata/README.md deleted file mode 100644 index 9c3af5e7..00000000 --- a/docs/why-netdata/README.md +++ /dev/null @@ -1,35 +0,0 @@ - - -# Why Netdata - -> Any performance monitoring solution that does not go down to per second -> collection and visualization of the data, is useless. -> It will make you happy to have it, but it will not help you more than that. - -Netdata is built around 4 principles: - -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.](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.](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.](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._ - -Unlike other monitoring solutions that focus on metrics visualization, -Netdata's helps us troubleshoot slowdowns without touching the console. - -So, everything is a bit different. - - diff --git a/docs/why-netdata/immediate-results.md b/docs/why-netdata/immediate-results.md deleted file mode 100644 index b35aa538..00000000 --- a/docs/why-netdata/immediate-results.md +++ /dev/null @@ -1,46 +0,0 @@ - - -# Immediate results - -Most of our infrastructure is based on standardized systems and applications. - -It is a tremendous waste of time and effort, in a global scale, to require from all users to configure their infrastructure dashboards and alarms metric by metric. - -## Why? - -Most of the existing monitoring solutions, focus on providing a platform "for building your monitoring". So, they provide the tools to collect metrics, store them, visualize them, check them and query them. And we are all expected to go through this process. - -However, most of our infrastructure is standardized. We run well known Linux distributions, the same kernel, the same database, the same web server, etc. - -So, why can't we have a monitoring system that can be installed and instantly provide feature rich dashboards and alarms about everything we use? Is there any reason you would like to monitor your web server differently than me? - -What a waste of time and money! Hundreds of thousands of people doing the same thing over and over again, trying to understand what the metrics are, how to visualize them, how to configure alarms for them and how to query them when issues arise. - -## What do others do? - -Open-source solutions rely almost entirely on configuration. So, you have to go through endless metric-by-metric configuration yourself. The result will reflect your skills, your experience, your understanding. - -Monitoring SaaS providers offer a very basic set of pre-configured metrics, dashboards and alarms. They assume that you will configure the rest you may need. So, once more, the result will reflect your skills, your experience, your understanding. - -## What does Netdata do? - -1. Metrics are auto-detected, so for 99% of the cases data collection works out of the box. -2. Metrics are converted to human readable units, right after data collection, before storing them into the database. -3. Metrics are structured, organized in charts, families and applications, so that they can be browsed. -4. Dashboards are automatically generated, so all metrics are available for exploration immediately after installation. -5. Dashboards are not just visualizing metrics; they are a tool, optimized for visual anomaly detection. -6. Hundreds of pre-configured alarm templates are automatically attached to collected metrics. - -The result is that Netdata can be used immediately after installation! - -Netdata: - -- Helps engineers understand and learn what the metrics are. -- Does not require any configuration. Of course there are thousands of options to tweak, but the defaults are pretty good for most systems. -- Does not introduce any query languages or any other technology to be learned. Of course some familiarity with the tool is required, but nothing too complicated. -- Includes all the community expertise and experience for monitoring systems and applications. - - diff --git a/docs/why-netdata/meaningful-presentation.md b/docs/why-netdata/meaningful-presentation.md deleted file mode 100644 index fc670e33..00000000 --- a/docs/why-netdata/meaningful-presentation.md +++ /dev/null @@ -1,68 +0,0 @@ - - -# Meaningful presentation - -Metrics are a lot more than name-value pairs over time. It is just not practical to require from all users to have a deep understanding of all metrics for monitoring their systems and applications. - -## Why? - -There is a plethora of metrics. And each of them has a context, a meaning, a way to be interpreted. - -Traditionally, monitoring solutions instruct engineers to collect only the metrics they understand. This is a good strategy as long as you have a clear understanding of what you need and you have the skills, the expertise and the experience to select them. - -For most people, this is an impossible task. It is just not practical to assume that any engineer will have a deep understanding of how the kernel works, how the networking stack works, how the system manages its memory, how it schedules processes, how web servers work, how databases work, etc. - -The result is that for most of the world, monitoring sucks. It is incomplete, inefficient, and in most of the cases only useful for providing an illusion that the infrastructure is being monitored. It is not! According to the [State of Monitoring 2017](http://start.bigpanda.io/state-of-monitoring-report-2017), only 11% of the companies are satisfied with their existing monitoring infrastructure, and on the average they use 6-7 monitoring tools. - -But even if all the metrics are collected, an even bigger challenge is revealed: What to do with them? How to use them? - -The existing monitoring solutions, assume the engineers will: - -- Design dashboards -- Configure alarms -- Use a query language to investigate issues - -However, all these have to be configured metric by metric. - -The monitoring industry believes there is this "IT Operations Hero", a person combining these abilities: - -1. Has a deep understanding of IT architectures and is a skillful SysAdmin. -2. Is a superb Network Administrator (can even read and understand the Linux kernel networking stack). -3. Is an exceptional database administrator. -4. Is fluent in software engineering, capable of understanding the internal workings of applications. -5. Masters Data Science, statistical algorithms and is fluent in writing advanced mathematical queries to reveal the meaning of metrics. - -Of course this person does not exist! - -## What do others do? - -Most solutions are based on a time-series database. A database that tracks name-value pairs, over time. - -Data collection blindly collects metrics and stores them into the database, dashboard editors query the database to visualize the metrics. They may also provide a query editor, that users can use to query the database by hand. - -Of course, it is just not practical to work that way when the database has 10,000 unique metrics. Most of them will be just noise, not because they are not useful, but because no one understands them! - -So, they collect very limited metrics. Basic dashboards can be created with these metrics, but for any issue that needs to be troubleshooted, the monitoring system is just not adequate. It cannot help. So, engineers are using the console to access the rest of the metrics and find the root cause. - -## What does Netdata do? - -In Netdata, the meaning of metrics is incorporated into the database: - -1. all metrics are converted and stored to human-friendly units. This is a data-collection process, not a visualization process. For example, cpu utilization in Netdata is stored as percentage, not as kernel ticks. - -2. all metrics are organized into human-friendly charts, sharing the same context and units (similar to what other monitoring solutions call `cardinality`). So, when Netdata developer collect metrics, they configure the correlation of the metrics right in data collection, which is stored in the database too. - -3. all charts are then organized in families, and chart families are organized in applications. These structures are responsible for providing the menu at the right side of Netdata dashboards for exploring the whole database. - -The result is a system that can be browsed by humans, even if the database has 100,000 unique metrics. It is pretty natural for everyone to browse them, understand their meaning and their scope. - -Of course, this process makes data collection significantly more time consuming. Netdata developers need to normalize and correlate and categorize every single metric Netdata collects. - -But it simplifies everything else. Data collection, metrics database and visualization are de-coupled, thus the query engine is simpler, and the visualization is straight forward. - -Netdata goes a step further, by enriching the dashboard with information that is useful for most people. So, to improve clarity and help users be more effective, Netdata includes right in the dashboard the community knowledge and expertise about the metrics. So, that Netdata users can focus on solving their infrastructure problem, not on the technicalities of data collection and visualization. - - diff --git a/docs/why-netdata/unlimited-metrics.md b/docs/why-netdata/unlimited-metrics.md deleted file mode 100644 index b79a4ede..00000000 --- a/docs/why-netdata/unlimited-metrics.md +++ /dev/null @@ -1,49 +0,0 @@ - - -# Unlimited metrics - -All metrics are important and all metrics should be available when you need them. - -## Why? - -Collecting all the metrics breaks the first rule of every monitoring text book: "collect only the metrics you need", "collect only the metrics you understand". - -Unfortunately, this does not work! Filtering out most metrics is like reading a book by skipping most of its pages... - -For many people, monitoring is about: - -- Detecting outages -- Capacity planning - -However, **slowdowns are 10 times more common** compared to outages (check slide 14 of [Online Performance is Business Performance ](https://www.slideshare.net/KenGodskind/alertsitetrac) reported by Trac Research/AlertSite). Designing a monitoring system targeting only outages and capacity planning solves just a tiny part of the operational problems we face. Check also [Downtime vs. Slowtime: Which Hurts More?](https://dzone.com/articles/downtime-vs-slowtime-which-hurts-more). - -To troubleshoot a slowdown, a lot more metrics are needed. Actually all the metrics are needed, since the real cause of a slowdown is most probably quite complex. If we knew the possible reasons, chances are we would have fixed them before they become a problem. - -## What do others do? - -Most monitoring solutions, when they are able to detect something, provide just a hint (e.g. "hey, there is a 20% drop in requests per second over the last minute") and they expect us to use the console for determining the root cause. - -Of course this introduces a lot more problems: how to troubleshoot a slowdown using the console, if the slowdown lifetime is just a few seconds, randomly spread throughout the day? - -You can't! You will spend your entire day on the console, waiting for the problem to happen again while you are logged in. A blame war starts: developers blame the systems, sysadmins blame the hosting provider, someone says it is a DNS problem, another one believes it is network related, etc. We have all experienced this, multiple times... - -So, why do monitoring solutions and SaaS providers filter out metrics? - -They can't do otherwise! - -1. Centralization of metrics depends on metrics filtering, to control monitoring costs. Time-series databases limit the number of metrics collected, because the number of metrics influences their performance significantly. They get congested at scale. -2. It is a lot easier to provide an illusion of monitoring by using a few basic metrics. -3. Troubleshooting slowdowns is the hardest IT problem to solve, so most solutions just avoid it. - -## What does Netdata do? - -Netdata collects, stores and visualizes everything, every single metric exposed by systems and applications. - -Due to Netdata's distributed nature, the number of metrics collected does not have any noticeable effect on the performance or the cost of the monitoring infrastructure. - -Of course, since Netdata is also about [meaningful presentation](meaningful-presentation.md), the number of metrics makes Netdata development slower. We, the Netdata developers, need to have a good understanding of the metrics before adding them into Netdata. We need to organize the metrics, add information related to them, configure alarms for them, so that you, the Netdata users, will have the best out-of-the-box experience and all the information required to kill the console for troubleshooting slowdowns. - - diff --git a/exporting/README.md b/exporting/README.md index bc3ca1c7..c6ce32b6 100644 --- a/exporting/README.md +++ b/exporting/README.md @@ -1,11 +1,10 @@ @@ -45,7 +44,7 @@ connector to enable and configure for your database of choice. - [**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](https://github.com/netdata/netdata/blob/master/docs/guides/export/export-netdata-metrics-graphite.md). + Graphite](https://github.com/netdata/netdata/blob/master/exporting/graphite/README.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`. diff --git a/exporting/TIMESCALE.md b/exporting/TIMESCALE.md index 2bd6db8c..8ca61b75 100644 --- a/exporting/TIMESCALE.md +++ b/exporting/TIMESCALE.md @@ -4,9 +4,7 @@ description: "Send Netdata metrics to TimescaleDB for long-term archiving and fu 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}" +learn_rel_path: "Integrations/Export" --> # Writing metrics to TimescaleDB diff --git a/exporting/WALKTHROUGH.md b/exporting/WALKTHROUGH.md index 5afd2604..49cf6587 100644 --- a/exporting/WALKTHROUGH.md +++ b/exporting/WALKTHROUGH.md @@ -1,13 +1,3 @@ - - # Netdata, Prometheus, Grafana stack ## Intro diff --git a/exporting/aws_kinesis/README.md b/exporting/aws_kinesis/README.md index 7921a265..29b191b8 100644 --- a/exporting/aws_kinesis/README.md +++ b/exporting/aws_kinesis/README.md @@ -4,9 +4,7 @@ description: "Archive your Agent's metrics to AWS Kinesis Data Streams for long- 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}" +learn_rel_path: "Integrations/Export" --> # Export metrics to AWS Kinesis Data Streams diff --git a/exporting/check_filters.c b/exporting/check_filters.c index 009a010b..9b573f02 100644 --- a/exporting/check_filters.c +++ b/exporting/check_filters.c @@ -65,7 +65,7 @@ int rrdset_is_exportable(struct instance *instance, RRDSET *st) if(unlikely(!(*flags & RRDSET_FLAG_EXPORTING_SEND))) { // we have not checked this chart - if(simple_pattern_matches(instance->config.charts_pattern, rrdset_id(st)) || simple_pattern_matches(instance->config.charts_pattern, rrdset_name(st))) + if(simple_pattern_matches_string(instance->config.charts_pattern, st->id) || simple_pattern_matches_string(instance->config.charts_pattern, st->name)) *flags |= RRDSET_FLAG_EXPORTING_SEND; else { *flags |= RRDSET_FLAG_EXPORTING_IGNORE; diff --git a/exporting/graphite/README.md b/exporting/graphite/README.md index afcdf798..2ef436cf 100644 --- a/exporting/graphite/README.md +++ b/exporting/graphite/README.md @@ -1,29 +1,39 @@ - - # Export metrics to Graphite providers 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. +## Prerequisites + +You have already [installed Netdata](https://github.com/netdata/netdata/edit/master/packaging/installer/README.md) and +Graphite. + ## Configuration -To enable data exporting to a Graphite database, run `./edit-config exporting.conf` in the Netdata configuration -directory and set the following options: +Begin by using `edit-config` to open the `exporting.conf` file. + +```bash +cd /etc/netdata # Replace this path with your Netdata config directory +sudo ./edit-config exporting.conf +``` + +Enable the exporting engine by setting `enabled` to `yes` in the `[exporting:global]` section. ```conf -[graphite:my_graphite_instance] +[exporting:global] + enabled = yes +``` + +Next, configure the connector. Find the `[graphite:my_graphite_instance]` example section and uncomment the line. +Set the `destination` setting to `localhost:2003`. By default, the Docker image for Graphite listens on port `2003` for +incoming metrics. If you installed Graphite a different way, you may need to change the port accordingly. + +```conf +[graphite:netdata] enabled = yes destination = localhost:2003 + ... ``` Add `:http` or `:https` modifiers to the connector type if you need to use other than a plaintext protocol. For @@ -35,7 +45,84 @@ example: `graphite:http:my_graphite_instance`, password = my_password ``` -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. +The final result for a remote, secured host should be the following: + +```conf +[graphite:https:netdata] + enabled = yes + username = my_username + password = my_password + destination = remote_host_url:2003 + # data source = average + # prefix = netdata + # hostname = my_hostname + # update every = 10 + # buffer on failures = 10 + # timeout ms = 20000 + # send names instead of ids = yes + # send charts matching = * + # send hosts matching = localhost * +``` + +We'll not worry about the [rest of the settings](https://github.com/netdata/netdata/blob/master/exporting/README.md#options) + for now. Restart the Agent using `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 spin up the exporting engine. + +## See and organize Netdata metrics in Graphite + +Head back to the Graphite interface again, then click on the **Dashboard** link to get started with Netdata's exported +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) + +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`. + +Let's add some system CPU charts so you can monitor the long-term health of your system. Click through the tree to find +**hostname → system → cpu** metrics, then click on the **user** context. A chart with metrics from that context appears +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) + +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). + +## 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) + +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. + +Add more metrics to a War Room's Nodes tab 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) + +## 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. +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/exporting/json/README.md b/exporting/json/README.md index 23ff555c..4e830fb7 100644 --- a/exporting/json/README.md +++ b/exporting/json/README.md @@ -5,8 +5,7 @@ custom_edit_url: "https://github.com/netdata/netdata/edit/master/exporting/json/ 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}" +learn_rel_path: "Integrations/Export" --> # Export metrics to JSON document databases diff --git a/exporting/mongodb/README.md b/exporting/mongodb/README.md index 0cbe8f05..aeca34c5 100644 --- a/exporting/mongodb/README.md +++ b/exporting/mongodb/README.md @@ -4,9 +4,7 @@ description: "Archive your Agent's metrics to a MongoDB database for long-term s 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}" +learn_rel_path: "Integrations/Export" --> # Export metrics to MongoDB diff --git a/exporting/opentsdb/README.md b/exporting/opentsdb/README.md index c6069f37..e1f12b2b 100644 --- a/exporting/opentsdb/README.md +++ b/exporting/opentsdb/README.md @@ -4,9 +4,7 @@ description: "Archive your Agent's metrics to an OpenTSDB database for long-term 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}" +learn_rel_path: "Integrations/Export" --> # Export metrics to OpenTSDB diff --git a/exporting/process_data.c b/exporting/process_data.c index eb492535..22129cff 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_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 first_t = storage_engine_oldest_time_s(rd->tiers[0].backend, rd->tiers[0].db_metric_handle); + time_t last_t = storage_engine_latest_time_s(rd->tiers[0].backend, rd->tiers[0].db_metric_handle); time_t update_every = st->update_every; struct storage_engine_query_handle handle; @@ -126,8 +126,8 @@ 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, STORAGE_PRIORITY_LOW); !rd->tiers[0].query_ops->is_finished(&handle);) { - STORAGE_POINT sp = rd->tiers[0].query_ops->next_metric(&handle); + for (storage_engine_query_init(rd->tiers[0].backend, rd->tiers[0].db_metric_handle, &handle, after, before, STORAGE_PRIORITY_LOW); !storage_engine_query_is_finished(&handle);) { + STORAGE_POINT sp = storage_engine_query_next_metric(&handle); points_read++; if (unlikely(storage_point_is_gap(sp))) { @@ -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); + storage_engine_query_finalize(&handle); global_statistics_exporters_query_completed(points_read); if (unlikely(!counter)) { diff --git a/exporting/prometheus/README.md b/exporting/prometheus/README.md index 97e9c632..d3b37f12 100644 --- a/exporting/prometheus/README.md +++ b/exporting/prometheus/README.md @@ -1,227 +1,21 @@ - - -import { OneLineInstallWget, OneLineInstallCurl } from '@site/src/components/OneLineInstall/' - # Using Netdata with Prometheus -Prometheus is a distributed monitoring system which offers a very simple setup along with a robust data model. Recently -Netdata added support for Prometheus. I'm going to quickly show you how to install both Netdata and Prometheus on the -same server. We can then use Grafana pointed at Prometheus to obtain long term metrics Netdata offers. I'm assuming we -are starting at a fresh ubuntu shell (whether you'd like to follow along in a VM or a cloud instance is up to you). - -## Installing Netdata and Prometheus - -### Installing Netdata - -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. - - - -To install Netdata, run the following as your normal user: - - - -Or, if you have cURL but not wget (such as on macOS): - - - -At this point we should have Netdata listening on port 19999. Attempt to take your browser here: - -```sh -http://your.netdata.ip:19999 -``` - -_(replace `your.netdata.ip` with the IP or hostname of the server running Netdata)_ - -### Installing Prometheus - -In order to install Prometheus we are going to introduce our own systemd startup script along with an example of -prometheus.yaml configuration. Prometheus needs to be pointed to your server at a specific target url for it to scrape -Netdata's api. Prometheus is always a pull model meaning Netdata is the passive client within this architecture. -Prometheus always initiates the connection with Netdata. - -#### Download Prometheus - -```sh -cd /tmp && curl -s https://api.github.com/repos/prometheus/prometheus/releases/latest \ -| grep "browser_download_url.*linux-amd64.tar.gz" \ -| cut -d '"' -f 4 \ -| wget -qi - -``` - -#### Create prometheus system user - -```sh -sudo useradd -r prometheus -``` - -#### Create prometheus directory - -```sh -sudo mkdir /opt/prometheus -sudo chown prometheus:prometheus /opt/prometheus -``` - -#### Untar prometheus directory - -```sh -sudo tar -xvf /tmp/prometheus-*linux-amd64.tar.gz -C /opt/prometheus --strip=1 -``` - -#### Install prometheus.yml - -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. - -```yaml -# my global config -global: - 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' - -# Load rules once and periodically evaluate them according to the global 'evaluation_interval'. -rule_files: -# - "first.rules" -# - "second.rules" - -# A scrape configuration containing exactly one endpoint to scrape: -# Here it's Prometheus itself. -scrape_configs: - # The job name is added as a label `job=` to any timeseries scraped from this config. - - job_name: 'prometheus' - - # metrics_path defaults to '/metrics' - # scheme defaults to 'http'. - - static_configs: - - targets: [ '0.0.0.0:9090' ] - - - job_name: 'netdata-scrape' - - metrics_path: '/api/v1/allmetrics' - 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 ] - # - # sources: as-collected | raw | average | sum | volume - # default is: average - #source: [as-collected] - # - # server name for this prometheus - the default is the client IP - # for Netdata to uniquely identify it - #server: ['prometheus1'] - honor_labels: true - - static_configs: - - targets: [ '{your.netdata.ip}:19999' ] -``` - -#### Install nodes.yml - -The following is completely optional, it will enable Prometheus to generate alerts from some Netdata sources. Tweak the -values to your own needs. We will use the following `nodes.yml` file below. Save it at `/opt/prometheus/nodes.yml`, and -add a _- "nodes.yml"_ entry under the _rule_files:_ section in the example prometheus.yml file above. - -```yaml -groups: - - name: nodes - - rules: - - alert: node_high_cpu_usage_70 - expr: sum(sum_over_time(netdata_system_cpu_percentage_average{dimension=~"(user|system|softirq|irq|guest)"}[10m])) by (job) / sum(count_over_time(netdata_system_cpu_percentage_average{dimension="idle"}[10m])) by (job) > 70 - for: 1m - annotations: - description: '{{ $labels.job }} on ''{{ $labels.job }}'' CPU usage is at {{ humanize $value }}%.' - summary: CPU alert for container node '{{ $labels.job }}' - - - alert: node_high_memory_usage_70 - expr: 100 / sum(netdata_system_ram_MB_average) by (job) - * sum(netdata_system_ram_MB_average{dimension=~"free|cached"}) by (job) < 30 - for: 1m - annotations: - description: '{{ $labels.job }} memory usage is {{ humanize $value}}%.' - summary: Memory alert for container node '{{ $labels.job }}' - - - alert: node_low_root_filesystem_space_20 - expr: 100 / sum(netdata_disk_space_GB_average{family="/"}) by (job) - * sum(netdata_disk_space_GB_average{family="/",dimension=~"avail|cached"}) by (job) < 20 - for: 1m - annotations: - description: '{{ $labels.job }} root filesystem space is {{ humanize $value}}%.' - summary: Root filesystem alert for container node '{{ $labels.job }}' - - - alert: node_root_filesystem_fill_rate_6h - expr: predict_linear(netdata_disk_space_GB_average{family="/",dimension=~"avail|cached"}[1h], 6 * 3600) < 0 - for: 1h - labels: - severity: critical - annotations: - description: Container node {{ $labels.job }} root filesystem is going to fill up in 6h. - summary: Disk fill alert for Swarm node '{{ $labels.job }}' -``` +Netdata supports exporting metrics to Prometheus in two ways: -#### Install prometheus.service + - You can [configure Prometheus to scrape Netdata metrics](#configure-prometheus-to-scrape-netdata-metrics). -Save this service file as `/etc/systemd/system/prometheus.service`: - -```sh -[Unit] -Description=Prometheus Server -AssertPathExists=/opt/prometheus - -[Service] -Type=simple -WorkingDirectory=/opt/prometheus -User=prometheus -Group=prometheus -ExecStart=/opt/prometheus/prometheus --config.file=/opt/prometheus/prometheus.yml --log.level=info -ExecReload=/bin/kill -SIGHUP $MAINPID -ExecStop=/bin/kill -SIGINT $MAINPID - -[Install] -WantedBy=multi-user.target -``` - -##### Start Prometheus - -```sh -sudo systemctl start prometheus -sudo systemctl enable prometheus -``` - -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. - ---- + - You can [configure Netdata to push metrics to Prometheus](https://github.com/netdata/netdata/blob/master/exporting/prometheus/remote_write/README.md) + , using the Prometheus remote write API. ## Netdata support for Prometheus -Before explaining the changes, we have to understand the key differences between Netdata and Prometheus. +Regardless of the methodology, you first need to understand how Netdata structures the metrics it exports to Prometheus +and the capabilities it provides. The examples provided in this document assume that you will be using Netdata as +a metrics endpoint, but the concepts apply as well to the remote write API method. -### understanding Netdata metrics +### Understanding Netdata metrics -#### charts +#### Charts Each chart in Netdata has several properties (common to all its metrics): @@ -236,7 +30,7 @@ Each chart in Netdata has several properties (common to all its metrics): - `units` is the units for all the metrics attached to the chart. -#### dimensions +#### Dimensions Then each Netdata chart contains metrics called `dimensions`. All the dimensions of a chart have the same units of measurement, and are contextually in the same category (ie. the metrics for disk bandwidth are `read` and `write` and @@ -467,4 +261,101 @@ through a web proxy, or when multiple Prometheus servers are NATed to a single I `&server=NAME` to the URL. This `NAME` is used by Netdata to uniquely identify each Prometheus server and keep track of its last access time. +## Configure Prometheus to scrape Netdata metrics + +The following `prometheus.yml` file will scrape all netdata metrics "as collected". + +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. + 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' + +# Load rules once and periodically evaluate them according to the global 'evaluation_interval'. +rule_files: +# - "first.rules" +# - "second.rules" + +# A scrape configuration containing exactly one endpoint to scrape: +# Here it's Prometheus itself. +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + - job_name: 'prometheus' + + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + + static_configs: + - targets: [ '0.0.0.0:9090' ] + + - job_name: 'netdata-scrape' + + metrics_path: '/api/v1/allmetrics' + 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 ] + # + # sources: as-collected | raw | average | sum | volume + # default is: average + #source: [as-collected] + # + # server name for this prometheus - the default is the client IP + # for Netdata to uniquely identify it + #server: ['prometheus1'] + honor_labels: true + static_configs: + - targets: [ '{your.netdata.ip}:19999' ] +``` + +### Prometheus alerts for Netdata metrics + +The following is an example of a `nodes.yml` file that will allow Prometheus to generate alerts from some Netdata sources. +Save it at `/opt/prometheus/nodes.yml`, and add a _- "nodes.yml"_ entry under the _rule_files:_ section in the example prometheus.yml file above. + +```yaml +groups: + - name: nodes + + rules: + - alert: node_high_cpu_usage_70 + expr: sum(sum_over_time(netdata_system_cpu_percentage_average{dimension=~"(user|system|softirq|irq|guest)"}[10m])) by (job) / sum(count_over_time(netdata_system_cpu_percentage_average{dimension="idle"}[10m])) by (job) > 70 + for: 1m + annotations: + description: '{{ $labels.job }} on ''{{ $labels.job }}'' CPU usage is at {{ humanize $value }}%.' + summary: CPU alert for container node '{{ $labels.job }}' + + - alert: node_high_memory_usage_70 + expr: 100 / sum(netdata_system_ram_MB_average) by (job) + * sum(netdata_system_ram_MB_average{dimension=~"free|cached"}) by (job) < 30 + for: 1m + annotations: + description: '{{ $labels.job }} memory usage is {{ humanize $value}}%.' + summary: Memory alert for container node '{{ $labels.job }}' + + - alert: node_low_root_filesystem_space_20 + expr: 100 / sum(netdata_disk_space_GB_average{family="/"}) by (job) + * sum(netdata_disk_space_GB_average{family="/",dimension=~"avail|cached"}) by (job) < 20 + for: 1m + annotations: + description: '{{ $labels.job }} root filesystem space is {{ humanize $value}}%.' + summary: Root filesystem alert for container node '{{ $labels.job }}' + + - alert: node_root_filesystem_fill_rate_6h + expr: predict_linear(netdata_disk_space_GB_average{family="/",dimension=~"avail|cached"}[1h], 6 * 3600) < 0 + for: 1h + labels: + severity: critical + annotations: + description: Container node {{ $labels.job }} root filesystem is going to fill up in 6h. + summary: Disk fill alert for Swarm node '{{ $labels.job }}' +``` diff --git a/exporting/prometheus/prometheus.c b/exporting/prometheus/prometheus.c index dc675dd3..24bd215f 100644 --- a/exporting/prometheus/prometheus.c +++ b/exporting/prometheus/prometheus.c @@ -9,9 +9,9 @@ static int is_matches_rrdset(struct instance *instance, RRDSET *st, SIMPLE_PATTERN *filter) { if (instance->config.options & EXPORTING_OPTION_SEND_NAMES) { - return simple_pattern_matches(filter, rrdset_name(st)); + return simple_pattern_matches_string(filter, st->name); } - return simple_pattern_matches(filter, rrdset_id(st)); + return simple_pattern_matches_string(filter, st->id); } /** @@ -514,7 +514,7 @@ static void rrd_stats_api_v1_charts_allmetrics_prometheus( int allhosts, PROMETHEUS_OUTPUT_OPTIONS output_options) { - SIMPLE_PATTERN *filter = simple_pattern_create(filter_string, NULL, SIMPLE_PATTERN_EXACT); + SIMPLE_PATTERN *filter = simple_pattern_create(filter_string, NULL, SIMPLE_PATTERN_EXACT, true); char hostname[PROMETHEUS_ELEMENT_MAX + 1]; prometheus_label_copy(hostname, rrdhost_hostname(host), PROMETHEUS_ELEMENT_MAX); diff --git a/exporting/prometheus/remote_write/README.md b/exporting/prometheus/remote_write/README.md index 9bda02d4..c2ad22a6 100644 --- a/exporting/prometheus/remote_write/README.md +++ b/exporting/prometheus/remote_write/README.md @@ -4,11 +4,10 @@ description: "Send Netdata metrics to your choice of more than 20 external stora 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" +learn_rel_path: "Integrations/Export" --> -# Prometheus remote write exporting connector +# Export metrics to Prometheus remote write providers The Prometheus remote write exporting connector uses the exporting engine to send Netdata metrics to your choice of more than 20 external storage providers for long-term archiving and further analysis. diff --git a/exporting/pubsub/README.md b/exporting/pubsub/README.md index 10252f16..c4d4ed78 100644 --- a/exporting/pubsub/README.md +++ b/exporting/pubsub/README.md @@ -4,9 +4,7 @@ description: "Export Netdata metrics to the Google Cloud Pub/Sub Service for lon 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}" +learn_rel_path: "Integrations/Export" --> # Export metrics to Google Cloud Pub/Sub Service diff --git a/exporting/read_config.c b/exporting/read_config.c index 1cba1682..eab2cdfc 100644 --- a/exporting/read_config.c +++ b/exporting/read_config.c @@ -264,11 +264,11 @@ struct engine *read_exporting_config() prometheus_exporter_instance->config.options &= ~EXPORTING_OPTION_SEND_AUTOMATIC_LABELS; prometheus_exporter_instance->config.charts_pattern = simple_pattern_create( - prometheus_config_get("send charts matching", "*"), - NULL, - SIMPLE_PATTERN_EXACT); + prometheus_config_get("send charts matching", "*"), + NULL, + SIMPLE_PATTERN_EXACT, true); prometheus_exporter_instance->config.hosts_pattern = simple_pattern_create( - prometheus_config_get("send hosts matching", "localhost *"), NULL, SIMPLE_PATTERN_EXACT); + prometheus_config_get("send hosts matching", "localhost *"), NULL, SIMPLE_PATTERN_EXACT, true); prometheus_exporter_instance->config.prefix = prometheus_config_get("prefix", global_exporting_prefix); @@ -369,10 +369,12 @@ struct engine *read_exporting_config() tmp_instance->config.timeoutms = exporter_get_number(instance_name, "timeout ms", 10000); tmp_instance->config.charts_pattern = - simple_pattern_create(exporter_get(instance_name, "send charts matching", "*"), NULL, SIMPLE_PATTERN_EXACT); + simple_pattern_create(exporter_get(instance_name, "send charts matching", "*"), NULL, + SIMPLE_PATTERN_EXACT, + true); tmp_instance->config.hosts_pattern = simple_pattern_create( - exporter_get(instance_name, "send hosts matching", "localhost *"), NULL, SIMPLE_PATTERN_EXACT); + exporter_get(instance_name, "send hosts matching", "localhost *"), NULL, SIMPLE_PATTERN_EXACT, true); char *data_source = exporter_get(instance_name, "data source", "average"); diff --git a/health/Makefile.am b/health/Makefile.am index f0cbb771..ea1b6e96 100644 --- a/health/Makefile.am +++ b/health/Makefile.am @@ -40,7 +40,7 @@ dist_healthconfig_DATA = \ health.d/disks.conf \ health.d/dnsmasq_dhcp.conf \ health.d/dns_query.conf \ - health.d/dockerd.conf \ + health.d/docker.conf \ health.d/elasticsearch.conf \ health.d/entropy.conf \ health.d/exporting.conf \ @@ -97,7 +97,7 @@ dist_healthconfig_DATA = \ health.d/vsphere.conf \ health.d/web_log.conf \ health.d/whoisquery.conf \ - health.d/wmi.conf \ + health.d/windows.conf \ health.d/x509check.conf \ health.d/zfs.conf \ health.d/dbengine.conf \ diff --git a/health/README.md b/health/README.md index 460f6568..96f71f87 100644 --- a/health/README.md +++ b/health/README.md @@ -1,13 +1,4 @@ - - -# Health monitoring +# Alerts and notifications The Netdata Agent is a health watchdog for the health and performance of your systems, services, and applications. We've worked closely with our community of DevOps engineers, SREs, and developers to define hundreds of production-ready @@ -18,23 +9,6 @@ community-configured alarms for every app/service [the Agent collects metrics fr 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? - -[Configuration reference](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md) - -## Guides - -Every infrastructure is different, so we're not interested in mandating how you should configure Netdata's health -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](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/stop-notifications-alarms.md) - -[Use dimension templates to create dynamic alarms](https://github.com/netdata/netdata/blob/master/docs/guides/monitor/dimension-templates.md) - -## Related features - -**[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. - - +You can [use various alert notification methods](https://github.com/netdata/netdata/edit/master/docs/monitor/enable-notifications.md), +[customize alerts](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md), and +[disable/silence](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md#disable-or-silence-alerts) alerts. diff --git a/health/REFERENCE.md b/health/REFERENCE.md index 27031cd1..b95dc852 100644 --- a/health/REFERENCE.md +++ b/health/REFERENCE.md @@ -1,34 +1,190 @@ - +# Configure alerts -# Health configuration reference +Netdata's health watchdog is highly configurable, with support for dynamic thresholds, hysteresis, alarm templates, and +more. You can tweak any of the existing alarms based on your infrastructure's topology or specific monitoring needs, or +create new entities. -Welcome to the health configuration reference. +You can use health alarms in conjunction with any of Netdata's [collectors](https://github.com/netdata/netdata/blob/master/collectors/README.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. -This guide contains information about editing health configuration files to tweak existing alarms or create new health -entities that are customized to the needs of your infrastructure. +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](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md), you must configure each node with the same health configuration +files. -To learn the basics of locating and editing health configuration files, see the [health -quickstart](https://github.com/netdata/netdata/blob/master/health/QUICKSTART.md). - -## Health configuration files +## Edit health configuration files You can configure the Agent's health watchdog service by editing files in two locations: -- 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](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 +- 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](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` script. For example: `sudo ./edit-config health.d/cpu.conf`. +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. + +### Edit individual alerts + +For example, to edit the `cpu.conf` health configuration file, run: + +```bash +sudo ./edit-config health.d/cpu.conf +``` + +Each health configuration file contains one or more health _entities_, which always begin with `alarm:` or `template:`. +For example, here is the first health entity in `health.d/cpu.conf`: + +```yaml +template: 10min_cpu_usage + on: system.cpu + os: linux + hosts: * + lookup: average -10m unaligned of user,system,softirq,irq,guest + units: % + every: 1m + warn: $this > (($status >= $WARNING) ? (75) : (85)) + crit: $this > (($status == $CRITICAL) ? (85) : (95)) + delay: down 15m multiplier 1.5 max 1h + info: average cpu utilization for the last 10 minutes (excluding iowait, nice and steal) + to: sysadmin +``` + +To tune this alarm to trigger warning and critical alarms at a lower CPU utilization, change the `warn` and `crit` lines +to the values of your choosing. For example: + +```yaml + warn: $this > (($status >= $WARNING) ? (60) : (75)) + crit: $this > (($status == $CRITICAL) ? (75) : (85)) +``` + +Save the file and [reload Netdata's health configuration](#reload-health-configuration) to apply your changes. + +## Disable or silence alerts + +Alerts and notifications can be disabled permanently via configuration changes, or temporarily, via the +[health management API](https://github.com/netdata/netdata/blob/master/web/api/health/README.md). The +available options are described below. + +### Disable all alerts + +In the `netdata.conf` `[health]` section, set `enabled` to `no`, and restart the agent. + +### Disable some alerts + +In the `netdata.conf` `[health]` section, set `enabled alarms` to a +[simple pattern](https://github.com/netdata/netdata/edit/master/libnetdata/simple_pattern/README.md) that +excludes one or more alerts. e.g. `enabled alarms = !oom_kill *` will load all alarms except `oom_kill`. + +You can also [edit the file where the alert is defined](#edit-individual-alerts), comment out its definition, +and [reload Netdata's health configuration](#reload-health-configuration). + +### Silence an individual alert + +You can stop receiving notification for an individual alert by [changing](#edit-individual-alerts) the `to:` line to `silent`. + +```yaml + to: silent +``` + +This action requires that you [reload Netdata's health configuration](#reload-health-configuration). + +### Temporarily disable alerts at runtime + +When you need to frequently disable all or some alerts from triggering during certain times (for instance +when running backups) you can use the +[health management API](https://github.com/netdata/netdata/blob/master/web/api/health/README.md). +The API allows you to issue commands to control the health engine's behavior without changing configuration, +or restarting the agent. + +### Temporarily silence notifications at runtime + +If you want health checks to keep running and alerts to keep getting triggered, but notifications to be +suppressed temporarily, you can use the +[health management API](https://github.com/netdata/netdata/blob/master/web/api/health/README.md). +The API allows you to issue commands to control the health engine's behavior without changing configuration, +or restarting the agent. + +## 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, containers, and applications work. + +Read the [health entity reference](#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](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. + +```bash +sudo touch health.d/ram-usage.conf +sudo ./edit-config health.d/ram-usage.conf +``` + +For example, here is a health entity that triggers a warning alarm when a node's RAM usage rises above 80%, and a +critical alarm above 90%: + +```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 being 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. This example uses simple + syntax, but most pre-configured health entities use + [hysteresis](#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: + +> This health entity, named **ram_usage**, watches 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. + +When you finish writing this new health entity, [reload Netdata's health configuration](#reload-health-configuration) to +see it live on the local dashboard or Netdata Cloud. + +## 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 `netdatacli reload-health` or `killall -USR2 netdata`. + ## Health entity reference The following reference contains information about the syntax and options of _health entities_, which Netdata attaches @@ -51,14 +207,14 @@ to the same chart, Netdata will use the alarm. Netdata parses the following lines. Beneath the table is an in-depth explanation of each line's purpose and syntax. -- The `alarm` or `template` line must be the first line of any entity. -- The `on` line is **always required**. -- 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 +- The `alarm` or `template` line must be the first line of any entity. +- The `on` line is **always required**. +- 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](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 +- 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. This comes in handy if your `info` line consists of several sentences. @@ -106,7 +262,7 @@ alarm: NAME template: NAME ``` -`NAME` can be any alpha character, with `.` (period) and `_` (underscore) as the only allowed symbols, but the names +`NAME` can be any alpha character, with `.` (period) and `_` (underscore) as the only allowed symbols, but the names cannot be `chart name`, `dimension name`, `family name`, or `chart variables names`. #### Alarm line `on` @@ -138,7 +294,7 @@ shows a disk I/O chart, the tooltip reads: `proc:/proc/diskstats, disk.io`. ![Finding the context of a chart via the tooltip](https://user-images.githubusercontent.com/1153921/68882856-2b230880-06cd-11ea-923b-b28c4632d479.png) -You're interested in what comes after the comma: `disk.io`. That's the name of the chart's context. +You're interested in what comes after the comma: `disk.io`. That's the name of the chart's context. If you create a template using the `disk.io` context, it will apply an alarm to every disk available on your system. @@ -160,7 +316,6 @@ class: Latency | Utilization | | Workload | -
`class` will default to `Unknown` if the line is missing from the alarm configuration. @@ -172,34 +327,35 @@ Type can be used to indicate the broader area of the system that the alarm appli ```yaml type: Database ``` +
Netdata's stock alarms use the following `type` attributes by default, but feel free to adjust for your own requirements. -| Type | Description | -| ------------------------ | ------------------------------------------------------------------------------------------------ | -| Ad Filtering | Services related to Ad Filtering (like pi-hole) | -| Certificates | Certificates monitoring related | -| Cgroups | Alerts for cpu and memory usage of control groups | -| Computing | Alerts for shared computing applications (e.g. boinc) | -| Containers | Container related alerts (e.g. docker instances) | -| Database | Database systems (e.g. MySQL, PostgreSQL, etc) | -| Data Sharing | Used to group together alerts for data sharing applications | -| DHCP | Alerts for dhcp related services | -| DNS | Alerts for dns related services | -| Kubernetes | Alerts for kubernetes nodes monitoring | -| KV Storage | Key-Value pairs services alerts (e.g. memcached) | -| Linux | Services specific to Linux (e.g. systemd) | -| Messaging | Alerts for message passing services (e.g. vernemq) | -| Netdata | Internal Netdata components monitoring | -| Other | When an alert doesn't fit in other types. | -| Power Supply | Alerts from power supply related services (e.g. apcupsd) | -| Search engine | Alerts for search services (e.g. elasticsearch) | -| Storage | Class for alerts dealing with storage services (storage devices typically live under `System`) | -| System | General system alarms (e.g. cpu, network, etc.) | -| Virtual Machine | Virtual Machine software | -| Web Proxy | Web proxy software (e.g. squid) | -| Web Server | Web server software (e.g. Apache, ngnix, etc.) | -| Windows | Alerts for monitor of wmi services | +| Type | Description | +|-----------------|------------------------------------------------------------------------------------------------| +| Ad Filtering | Services related to Ad Filtering (like pi-hole) | +| Certificates | Certificates monitoring related | +| Cgroups | Alerts for cpu and memory usage of control groups | +| Computing | Alerts for shared computing applications (e.g. boinc) | +| Containers | Container related alerts (e.g. docker instances) | +| Database | Database systems (e.g. MySQL, PostgreSQL, etc) | +| Data Sharing | Used to group together alerts for data sharing applications | +| DHCP | Alerts for dhcp related services | +| DNS | Alerts for dns related services | +| Kubernetes | Alerts for kubernetes nodes monitoring | +| KV Storage | Key-Value pairs services alerts (e.g. memcached) | +| Linux | Services specific to Linux (e.g. systemd) | +| Messaging | Alerts for message passing services (e.g. vernemq) | +| Netdata | Internal Netdata components monitoring | +| Other | When an alert doesn't fit in other types. | +| Power Supply | Alerts from power supply related services (e.g. apcupsd) | +| Search engine | Alerts for search services (e.g. elasticsearch) | +| Storage | Class for alerts dealing with storage services (storage devices typically live under `System`) | +| System | General system alarms (e.g. cpu, network, etc.) | +| Virtual Machine | Virtual Machine software | +| Web Proxy | Web proxy software (e.g. squid) | +| Web Server | Web server software (e.g. Apache, ngnix, etc.) | +| Windows | Alerts for monitor of windows services |
@@ -212,6 +368,7 @@ Component can be used to narrow down what the previous `type` value specifies fo ```yaml component: MySQL ``` + As with the `class` and `type` line, if `component` is missing from the configuration, its value will default to `Unknown`. #### Alarm line `os` @@ -264,7 +421,7 @@ module: isc_dhcpd #### Alarm line `charts` -The `charts` line filters which chart this alarm should apply to. It is only available on entities using the +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](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: @@ -299,35 +456,36 @@ The format is: lookup: METHOD AFTER [at BEFORE] [every DURATION] [OPTIONS] [of DIMENSIONS] [foreach DIMENSIONS] ``` -Everything is the same with [badges](https://github.com/netdata/netdata/blob/master/web/api/badges/README.md). In short: +The full [database query API](https://github.com/netdata/netdata/blob/master/web/api/queries/README.md) is supported. In short: -- `METHOD` is one of `average`, `min`, `max`, `sum`, `incremental-sum`. +- `METHOD` is one of the available [grouping methods](https://github.com/netdata/netdata/blob/master/web/api/queries/README.md#grouping-methods) such as `average`, `min`, `max` etc. This is required. -- `AFTER` is a relative number of seconds, but it also accepts a single letter for changing +- `AFTER` is a relative number of seconds, but it also accepts a single letter for changing the units, like `-1s` = 1 second in the past, `-1m` = 1 minute in the past, `-1h` = 1 hour in the past, `-1d` = 1 day in the past. You need a negative number (i.e. how far in the past to look for the value). **This is required**. -- `at BEFORE` is by default 0 and is not required. Using this you can define the end of the +- `at BEFORE` is by default 0 and is not required. Using this you can define the end of the lookup. So data will be evaluated between `AFTER` and `BEFORE`. -- `every DURATION` sets the updated frequency of the lookup (supports single letter units as +- `every DURATION` sets the updated frequency of the lookup (supports single letter units as above too). -- `OPTIONS` is a space separated list of `percentage`, `absolute`, `min2max`, `unaligned`, +- `OPTIONS` is a space separated list of `percentage`, `absolute`, `min2max`, `unaligned`, `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 +- `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 have spaces in their names). This accepts Netdata simple patterns _(with `words` separated by `,` or `|` instead of spaces)_ and the `match-ids` and `match-names` options affect the searches for dimensions. -- `foreach DIMENSIONS` is optional, will always be the last parameter, and uses the same `,`/`|` +- `foreach DIMENSIONS` is optional, will always be the last parameter, and uses the same `,`/`|` rules as the `of` parameter. Each dimension you specify in `foreach` will use the same rule to trigger an alarm. If you set both `of` and `foreach`, Netdata will ignore the `of` parameter - and replace it with one of the dimensions you gave to `foreach`. + and replace it with one of the dimensions you gave to `foreach`. This option allows you to + [use dimension templates to create dynamic alarms](#use-dimension-templates-to-create-dynamic-alarms). The result of the lookup will be available as `$this` and `$NAME` in expressions. The timestamps of the timeframe evaluated by the database lookup is available as variables @@ -427,21 +585,21 @@ Format: delay: [[[up U] [down D] multiplier M] max X] ``` -- `up U` defines the delay to be applied to a notification for an alarm that raised its status +- `up U` defines the delay to be applied to a notification for an alarm that raised its status (i.e. CLEAR to WARNING, CLEAR to CRITICAL, WARNING to CRITICAL). For example, `up 10s`, the notification for this event will be sent 10 seconds after the actual event. This is used in hope the alarm will get back to its previous state within the duration given. The default `U` is zero. -- `down D` defines the delay to be applied to a notification for an alarm that moves to lower +- `down D` defines the delay to be applied to a notification for an alarm that moves to lower state (i.e. CRITICAL to WARNING, CRITICAL to CLEAR, WARNING to CLEAR). For example, `down 1m` will delay the notification by 1 minute. This is used to prevent notifications for flapping alarms. The default `D` is zero. -- `multiplier M` multiplies `U` and `D` when an alarm changes state, while a notification is +- `multiplier M` multiplies `U` and `D` when an alarm changes state, while a notification is delayed. The default multiplier is `1.0`. -- `max X` defines the maximum absolute notification delay an alarm may get. The default `X` +- `max X` defines the maximum absolute notification delay an alarm may get. The default `X` is `max(U * M, D * M)` (i.e. the max duration of `U` or `D` multiplied once with `M`). Example: @@ -459,9 +617,9 @@ delay: [[[up U] [down D] multiplier M] max X] So: - - `U` and `D` are multiplied by `M` every time the alarm changes state (any state, not just + - `U` and `D` are multiplied by `M` every time the alarm changes state (any state, not just their matching one) and a delay is in place. - - All are reset to their defaults when the alarm switches state without a delay in place. + - All are reset to their defaults when the alarm switches state without a delay in place. #### Alarm line `repeat` @@ -477,11 +635,11 @@ Format: repeat: [off] [warning DURATION] [critical DURATION] ``` -- `off`: Turns off the repeating feature for the current alarm. This is effective when the default repeat settings has +- `off`: Turns off the repeating feature for the current alarm. This is effective when the default repeat settings has been enabled in health configuration. -- `warning DURATION`: Defines the interval when the alarm is in WARNING state. Use `0s` to turn off the repeating +- `warning DURATION`: Defines the interval when the alarm is in WARNING state. Use `0s` to turn off the repeating notification for WARNING mode. -- `critical DURATION`: Defines the interval when the alarm is in CRITICAL state. Use `0s` to turn off the repeating +- `critical DURATION`: Defines the interval when the alarm is in CRITICAL state. Use `0s` to turn off the repeating notification for CRITICAL mode. #### Alarm line `options` @@ -529,7 +687,7 @@ line to any alarms you'd like to apply to hosts that have the label `room = serv host labels: room = server ``` -The `host labels` is a space-separated list that accepts simple patterns. For example, you can create an alarm +The `host labels` is a space-separated list that accepts simple patterns. For example, you can create an alarm that will be applied to all hosts installed in the last decade with the following line: ```yaml @@ -584,7 +742,7 @@ info: average ratio of HTTP responses with unexpected status over the last 5 min ## Expressions -Netdata has an internal [infix expression parser](/libnetdata/eval). This parses expressions and creates an internal +Netdata has an internal infix expression parser under `libnetdata/eval`. This parses expressions and creates an internal structure that allows fast execution of them. These operators are supported `+`, `-`, `*`, `/`, `<`, `==`, `<=`, `<>`, `!=`, `>`, `>=`, `&&`, `||`, `!`, `AND`, `OR`, `NOT`. @@ -605,10 +763,10 @@ Expressions can have variables. Variables start with `$`. Check below for more i There are two special values you can use: -- `nan`, for example `$this != nan` will check if the variable `this` is available. A variable can be `nan` if the +- `nan`, for example `$this != nan` will check if the variable `this` is available. A variable can be `nan` if the database lookup failed. All calculations (i.e. addition, multiplication, etc) with a `nan` result in a `nan`. -- `inf`, for example `$this != inf` will check if `this` is not infinite. A value or variable can be set to infinite +- `inf`, for example `$this != inf` will check if `this` is not infinite. A value or variable can be set to infinite if divided by zero. All calculations (i.e. addition, multiplication, etc) with a `inf` result in a `inf`. ### Special use of the conditional operator @@ -627,21 +785,21 @@ crit: $this > (($status == $CRITICAL) ? (85) : (95)) The above say: -- If the alarm is currently a warning, then the threshold for being considered a warning is 75, otherwise it's 85. +- If the alarm is currently a warning, then the threshold for being considered a warning is 75, otherwise it's 85. -- If the alarm is currently critical, then the threshold for being considered critical is 85, otherwise it's 95. +- If the alarm is currently critical, then the threshold for being considered critical is 85, otherwise it's 95. Which in turn, results in the following behavior: -- While the value is rising, it will trigger a warning when it exceeds 85, and a critical alert when it exceeds 95. +- While the value is rising, it will trigger a warning when it exceeds 85, and a critical alert when it exceeds 95. -- While the value is falling, it will return to a warning state when it goes below 85, and a normal state when it goes +- While the value is falling, it will return to a warning state when it goes below 85, and a normal state when it goes below 75. -- If the value is constantly varying between 80 and 90, then it will trigger a warning the first time it goes above +- If the value is constantly varying between 80 and 90, then it will trigger a warning the first time it goes above 85, but will remain a warning until it goes below 75 (or goes above 85). -- If the value is constantly varying between 90 and 100, then it will trigger a critical alert the first time it goes +- If the value is constantly varying between 90 and 100, then it will trigger a critical alert the first time it goes above 95, but will remain a critical alert goes below 85 (at which point it will return to being a warning). ## Variables @@ -665,15 +823,15 @@ unless if you explicitly limit an alarm with the [alarm line `families`](#alarm- -- **chart local variables**. All the dimensions of the chart are exposed as local variables. The value of `$this` for +- **chart local variables**. All the dimensions of the chart are exposed as local variables. The value of `$this` for the other configured alarms of the chart also appears, under the name of each configured alarm. Charts also define a few special variables: - - `$last_collected_t` is the unix timestamp of the last data collection - - `$collected_total_raw` is the sum of all the dimensions (their last collected values) - - `$update_every` is the update frequency of the chart - - `$green` and `$red` the threshold defined in alarms (these are per chart - the charts + - `$last_collected_t` is the unix timestamp of the last data collection + - `$collected_total_raw` is the sum of all the dimensions (their last collected values) + - `$update_every` is the update frequency of the chart + - `$green` and `$red` the threshold defined in alarms (these are per chart - the charts inherits them from the the first alarm that defined them) Chart dimensions define their last calculated (i.e. interpolated) value, exactly as @@ -682,43 +840,43 @@ unless if you explicitly limit an alarm with the [alarm line `families`](#alarm- that resolves to unix timestamp the dimension was last collected (there may be dimensions that fail to be collected while others continue normally). -- **family variables**. Families are used to group charts together. For example all `eth0` +- **family variables**. Families are used to group charts together. For example all `eth0` charts, have `family = eth0`. This index includes all local variables, but if there are overlapping variables, only the first are exposed. -- **host variables**. All the dimensions of all charts, including all alarms, in fullname. +- **host variables**. All the dimensions of all charts, including all alarms, in fullname. Fullname is `CHART.VARIABLE`, where `CHART` is either the chart id or the chart name (both are supported). -- **special variables\*** are: +- **special variables\*** are: - - `$this`, which is resolved to the value of the current alarm. + - `$this`, which is resolved to the value of the current alarm. - - `$status`, which is resolved to the current status of the alarm (the current = the last + - `$status`, which is resolved to the current status of the alarm (the current = the last status, i.e. before the current database lookup and the evaluation of the `calc` line). This values can be compared with `$REMOVED`, `$UNINITIALIZED`, `$UNDEFINED`, `$CLEAR`, `$WARNING`, `$CRITICAL`. These values are incremental, ie. `$status > $CLEAR` works as expected. - - `$now`, which is resolved to current unix timestamp. + - `$now`, which is resolved to current unix timestamp. ## Alarm statuses Alarms can have the following statuses: -- `REMOVED` - the alarm has been deleted (this happens when a SIGUSR2 is sent to Netdata +- `REMOVED` - the alarm has been deleted (this happens when a SIGUSR2 is sent to Netdata to reload health configuration) -- `UNINITIALIZED` - the alarm is not initialized yet +- `UNINITIALIZED` - the alarm is not initialized yet -- `UNDEFINED` - the alarm failed to be calculated (i.e. the database lookup failed, +- `UNDEFINED` - the alarm failed to be calculated (i.e. the database lookup failed, a division by zero occurred, etc) -- `CLEAR` - the alarm is not armed / raised (i.e. is OK) +- `CLEAR` - the alarm is not armed / raised (i.e. is OK) -- `WARNING` - the warning expression resulted in true or non-zero +- `WARNING` - the warning expression resulted in true or non-zero -- `CRITICAL` - the critical expression resulted in true or non-zero +- `CRITICAL` - the critical expression resulted in true or non-zero The external script will be called for all status changes. @@ -762,9 +920,9 @@ The above applies the **template** to all charts that have `context = apache.req calc: $now - $last_collected_t ``` -- `$now` is a standard variable that resolves to the current timestamp. +- `$now` is a standard variable that resolves to the current timestamp. -- `$last_collected_t` is the last data collection timestamp of the chart. +- `$last_collected_t` is the last data collection timestamp of the chart. So this calculation gives the number of seconds passed since the last data collection. ```yaml @@ -780,7 +938,7 @@ The alarm will be evaluated every 10 seconds. If these result in non-zero or true, they trigger the alarm. -- `$this` refers to the value of this alarm (i.e. the result of the `calc` line. +- `$this` refers to the value of this alarm (i.e. the result of the `calc` line. We could also use `$apache_last_collected_secs`. `$update_every` is the update frequency of the chart, in seconds. @@ -935,9 +1093,9 @@ lookup: mean -10s of user Since [`z = (x - mean) / stddev`](https://en.wikipedia.org/wiki/Standard_score) we create two input alarms, one for `mean` and one for `stddev` and then use them both as inputs in our final `cpu_user_zscore` alarm. -### Example 8 - [Anomaly rate](https://learn.netdata.cloud/docs/agent/ml#anomaly-rate) based CPU dimensions alarm +### Example 8 - [Anomaly rate](https://github.com/netdata/netdata/blob/master/ml/README.md#anomaly-rate) based CPU dimensions alarm -Warning if 5 minute rolling [anomaly rate](https://learn.netdata.cloud/docs/agent/ml#anomaly-rate) for any CPU dimension is above 5%, critical if it goes above 20%: +Warning if 5 minute rolling [anomaly rate](https://github.com/netdata/netdata/blob/master/ml/README.md#anomaly-rate) for any CPU dimension is above 5%, critical if it goes above 20%: ```yaml template: ml_5min_cpu_dims @@ -956,9 +1114,9 @@ template: ml_5min_cpu_dims The `lookup` line will calculate the average anomaly rate of each `system.cpu` dimension over the last 5 minues. In this case Netdata will create alarms for all dimensions of the chart. -### Example 9 - [Anomaly rate](https://learn.netdata.cloud/docs/agent/ml#anomaly-rate) based CPU chart alarm +### Example 9 - [Anomaly rate](https://github.com/netdata/netdata/blob/master/ml/README.md#anomaly-rate) based CPU chart alarm -Warning if 5 minute rolling [anomaly rate](https://learn.netdata.cloud/docs/agent/ml#anomaly-rate) averaged across all CPU dimensions is above 5%, critical if it goes above 20%: +Warning if 5 minute rolling [anomaly rate](https://github.com/netdata/netdata/blob/master/ml/README.md#anomaly-rate) averaged across all CPU dimensions is above 5%, critical if it goes above 20%: ```yaml template: ml_5min_cpu_chart @@ -977,9 +1135,9 @@ template: ml_5min_cpu_chart The `lookup` line will calculate the average anomaly rate across all `system.cpu` dimensions over the last 5 minues. In this case Netdata will create one alarm for the chart. -### Example 10 - [Anomaly rate](https://learn.netdata.cloud/docs/agent/ml#anomaly-rate) based node level alarm +### Example 10 - [Anomaly rate](https://github.com/netdata/netdata/blob/master/ml/README.md#anomaly-rate) based node level alarm -Warning if 5 minute rolling [anomaly rate](https://learn.netdata.cloud/docs/agent/ml#anomaly-rate) averaged across all ML enabled dimensions is above 5%, critical if it goes above 20%: +Warning if 5 minute rolling [anomaly rate](https://github.com/netdata/netdata/blob/master/ml/README.md#anomaly-rate) averaged across all ML enabled dimensions is above 5%, critical if it goes above 20%: ```yaml template: ml_5min_node @@ -995,7 +1153,168 @@ template: ml_5min_node info: rolling 5min anomaly rate for all ML enabled dims ``` -The `lookup` line will use the `anomaly_rate` dimension of the `anomaly_detection.anomaly_rate` ML chart to calculate the average [node level anomaly rate](https://learn.netdata.cloud/docs/agent/ml#node-anomaly-rate) over the last 5 minues. +The `lookup` line will use the `anomaly_rate` dimension of the `anomaly_detection.anomaly_rate` ML chart to calculate the average [node level anomaly rate](https://github.com/netdata/netdata/blob/master/ml/README.md#node-anomaly-rate) over the last 5 minues. + +## Use dimension templates to create dynamic alarms + +In v1.18 of Netdata, we introduced **dimension templates** for alarms, which simplifies the process of +writing [alarm entities](#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. + +### The fundamentals of `foreach` + +Our dimension templates update creates a new `foreach` parameter to the +existing [`lookup` line](#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](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. + +Let's get into some examples so you can see how the new parameter works. + +> ⚠️ The following entities are examples to showcase the functionality and syntax of dimension templates. They are not +> meant to be run as-is on production systems. + +### Condensing entities with `foreach` + +Let's say you want to monitor the `system`, `user`, and `nice` dimensions in your system's overall CPU utilization. +Before dimension templates, you would need the following three entities: + +```yaml + alarm: cpu_system + on: system.cpu +lookup: average -10m percentage of system + every: 1m + warn: $this > 50 + crit: $this > 80 + + alarm: cpu_user + on: system.cpu +lookup: average -10m percentage of user + every: 1m + warn: $this > 50 + crit: $this > 80 + + alarm: cpu_nice + on: system.cpu +lookup: average -10m percentage of nice + every: 1m + warn: $this > 50 + crit: $this > 80 +``` + +With dimension templates, you can condense these into a single alarm. Take note of the `alarm` and `lookup` lines. + +```yaml + alarm: cpu_template + on: system.cpu +lookup: average -10m percentage foreach system,user,nice + every: 1m + warn: $this > 50 + crit: $this > 80 +``` + +The `alarm` line specifies the naming scheme Netdata will use. You can use whatever naming scheme you'd like, with `.` +and `_` being the only allowed symbols. + +The `lookup` line has changed from `of` to `foreach`, and we're now passing three dimensions. + +In this example, Netdata will create three alarms with the names `cpu_template_system`, `cpu_template_user`, and +`cpu_template_nice`. Every minute, each alarm will use the same database query to calculate the average CPU usage for +the `system`, `user`, and `nice` dimensions over the last 10 minutes and send out alarms if necessary. + +You can find these three alarms active by clicking on the **Alarms** button in the top navigation, and then clicking on +the **All** tab and scrolling to the **system - cpu** collapsible section. + +![Three new alarms created from the dimension template](https://user-images.githubusercontent.com/1153921/66218994-29523800-e67f-11e9-9bcb-9bca23e2c554.png) + +Let's look at some other examples of how `foreach` works so you can best apply it in your configurations. + +### Using a Netdata simple pattern in `foreach` + +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](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 +wildcard as the simple pattern tells Netdata to create a separate alarm for _every_ process on your system: + +```yaml + alarm: app_cpu + on: apps.cpu +lookup: average -10m percentage foreach * + every: 1m + warn: $this > 50 + crit: $this > 80 +``` + +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](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md). + +### Using `foreach` with alarm templates + +Dimension templates also work +with [alarm templates](#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 +words, every CPU core. + +```yaml +template: cpu_template + on: cpu.cpu + lookup: average -10m percentage foreach system,user,nice + every: 1m + warn: $this > 50 + crit: $this > 80 +``` + +On a system with a 6-core, 12-thread Ryzen 5 1600 CPU, this one entity creates alarms on the following charts and +dimensions: + +- `cpu.cpu0` + - `cpu_template_user` + - `cpu_template_system` + - `cpu_template_nice` + +- `cpu.cpu1` + - `cpu_template_user` + - `cpu_template_system` + - `cpu_template_nice` + +- `cpu.cpu2` + - `cpu_template_user` + - `cpu_template_system` + - `cpu_template_nice` + +- ... + +- `cpu.cpu11` + - `cpu_template_user` + - `cpu_template_system` + - `cpu_template_nice` + +And how just a few of those dimension template-generated alarms look like in the Netdata dashboard. + +![A few of the created alarms in the Netdata dashboard](https://user-images.githubusercontent.com/1153921/66219669-708cf880-e680-11e9-8b3a-7bfe178fa28b.png) + +All in all, this single entity creates 36 individual alarms. Much easier than writing 36 separate entities in your +health configuration files! ## Troubleshooting @@ -1016,12 +1335,3 @@ You can find how Netdata interpreted the expressions by examining the alarm at `http://NODE:19999/api/v1/alarms?all`. For each expression, Netdata will return the expression as given in its config file, and the same expression with additional parentheses added to indicate the evaluation flow of the expression. - -## Disabling health checks or silencing notifications at runtime - -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](https://github.com/netdata/netdata/blob/master/web/api/health/README.md). - - diff --git a/health/health.c b/health/health.c index b34f54ab..5c2b85bc 100644 --- a/health/health.c +++ b/health/health.c @@ -17,6 +17,11 @@ #error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 10 #endif +unsigned int default_health_enabled = 1; +char *silencers_filename; +SIMPLE_PATTERN *conf_enabled_alarms = NULL; +DICTIONARY *health_rrdvars; + static bool prepare_command(BUFFER *wb, const char *exec, const char *recipient, @@ -157,10 +162,6 @@ static bool prepare_command(BUFFER *wb, return true; } -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 struct { ALARM_ENTRY *head; // oldest @@ -346,6 +347,15 @@ static void health_reload_host(RRDHOST *host) { rrdcalctemplate_link_matching_templates_to_rrdset(st); } rrdset_foreach_done(st); + +#ifdef ENABLE_ACLK + if (netdata_cloud_setting) { + struct aclk_sync_host_config *wc = (struct aclk_sync_host_config *)host->aclk_sync_host_config; + if (likely(wc)) { + wc->alert_queue_removed = SEND_REMOVED_AFTER_HEALTH_LOOPS; + } + } +#endif } /** @@ -356,19 +366,11 @@ static void health_reload_host(RRDHOST *host) { void health_reload(void) { sql_refresh_hashes(); - rrd_rdlock(); - RRDHOST *host; - rrdhost_foreach_read(host) + dfe_start_reentrant(rrdhost_root_index, host){ health_reload_host(host); - - rrd_unlock(); - -#ifdef ENABLE_ACLK - if (netdata_cloud_setting) { - aclk_alert_reloaded = 1; } -#endif + dfe_done(host); } // ---------------------------------------------------------------------------- @@ -752,7 +754,8 @@ static void health_main_cleanup(void *ptr) { log_health("Health thread ended."); } -static void initialize_health(RRDHOST *host, int is_localhost) { +static void initialize_health(RRDHOST *host) +{ if(!host->health.health_enabled || rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH) || !service_running(SERVICE_HEALTH)) @@ -779,25 +782,13 @@ 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); + conf_enabled_alarms = simple_pattern_create(config_get(CONFIG_SECTION_HEALTH, "enabled alarms", "*"), NULL, + SIMPLE_PATTERN_EXACT, true); netdata_rwlock_init(&host->health_log.alarm_log_rwlock); char filename[FILENAME_MAX + 1]; - if(!is_localhost) { - int r = mkdir(host->varlib_dir, 0775); - if (r != 0 && errno != EEXIST) - error("Host '%s': cannot create directory '%s'", rrdhost_hostname(host), host->varlib_dir); - } - - { - snprintfz(filename, FILENAME_MAX, "%s/health", host->varlib_dir); - int r = mkdir(filename, 0775); - if(r != 0 && errno != EEXIST) - error("Host '%s': cannot create directory '%s'", rrdhost_hostname(host), filename); - } - snprintfz(filename, FILENAME_MAX, "%s/alarm-notify.sh", netdata_configured_primary_plugins_dir); 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"); @@ -814,7 +805,7 @@ static void initialize_health(RRDHOST *host, int is_localhost) { // link the loaded alarms to their charts RRDSET *st; - rrdset_foreach_write(st, host) { + rrdset_foreach_reentrant(st, host) { if (rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED)) continue; @@ -849,11 +840,11 @@ static SILENCE_TYPE check_silenced(RRDCALC *rc, const char *host, SILENCERS *sil for (s = silencers->silencers; s!=NULL; s=s->next){ if ( - (!s->alarms_pattern || (rc->name && s->alarms_pattern && simple_pattern_matches(s->alarms_pattern, rrdcalc_name(rc)))) && - (!s->contexts_pattern || (rc->rrdset && rc->rrdset->context && s->contexts_pattern && simple_pattern_matches(s->contexts_pattern, rrdset_context(rc->rrdset)))) && - (!s->hosts_pattern || (host && s->hosts_pattern && simple_pattern_matches(s->hosts_pattern,host))) && - (!s->charts_pattern || (rc->chart && s->charts_pattern && simple_pattern_matches(s->charts_pattern, rrdcalc_chart_name(rc)))) && - (!s->families_pattern || (rc->rrdset && rc->rrdset->family && s->families_pattern && simple_pattern_matches(s->families_pattern, rrdset_family(rc->rrdset)))) + (!s->alarms_pattern || (rc->name && s->alarms_pattern && simple_pattern_matches_string(s->alarms_pattern, rc->name))) && + (!s->contexts_pattern || (rc->rrdset && rc->rrdset->context && s->contexts_pattern && simple_pattern_matches_string(s->contexts_pattern, rc->rrdset->context))) && + (!s->hosts_pattern || (host && s->hosts_pattern && simple_pattern_matches(s->hosts_pattern, host))) && + (!s->charts_pattern || (rc->chart && s->charts_pattern && simple_pattern_matches_string(s->charts_pattern, rc->chart))) && + (!s->families_pattern || (rc->rrdset && rc->rrdset->family && s->families_pattern && simple_pattern_matches_string(s->families_pattern, rc->rrdset->family))) ) { debug(D_HEALTH, "Alarm matches command API silence entry %s:%s:%s:%s:%s", s->alarms,s->charts, s->contexts, s->hosts, s->families); if (unlikely(silencers->stype == STYPE_NONE)) { @@ -925,19 +916,6 @@ static void health_execute_delayed_initializations(RRDHOST *host) { worker_is_busy(WORKER_HEALTH_JOB_DELAYED_INIT_RRDSET); - if(!st->rrdfamily) - st->rrdfamily = rrdfamily_add_and_acquire(host, rrdset_family(st)); - - if(!st->rrdvars) - st->rrdvars = rrdvariables_create(); - - rrddimvar_index_init(st); - - rrdsetvar_add_and_leave_released(st, "last_collected_t", RRDVAR_TYPE_TIME_T, &st->last_collected_time.tv_sec, RRDVAR_FLAG_NONE); - rrdsetvar_add_and_leave_released(st, "green", RRDVAR_TYPE_CALCULATED, &st->green, RRDVAR_FLAG_NONE); - rrdsetvar_add_and_leave_released(st, "red", RRDVAR_TYPE_CALCULATED, &st->red, RRDVAR_FLAG_NONE); - rrdsetvar_add_and_leave_released(st, "update_every", RRDVAR_TYPE_INT, &st->update_every, RRDVAR_FLAG_NONE); - rrdcalc_link_matching_alerts_to_rrdset(st); rrdcalctemplate_link_matching_templates_to_rrdset(st); @@ -948,19 +926,19 @@ static void health_execute_delayed_initializations(RRDHOST *host) { worker_is_busy(WORKER_HEALTH_JOB_DELAYED_INIT_RRDDIM); - rrddimvar_add_and_leave_released(rd, RRDVAR_TYPE_CALCULATED, NULL, NULL, &rd->last_stored_value, RRDVAR_FLAG_NONE); - rrddimvar_add_and_leave_released(rd, RRDVAR_TYPE_COLLECTED, NULL, "_raw", &rd->last_collected_value, RRDVAR_FLAG_NONE); - rrddimvar_add_and_leave_released(rd, RRDVAR_TYPE_TIME_T, NULL, "_last_collected_t", &rd->last_collected_time.tv_sec, RRDVAR_FLAG_NONE); - RRDCALCTEMPLATE *rt; foreach_rrdcalctemplate_read(host, rt) { if(!rt->foreach_dimension_pattern) continue; - if(rrdcalctemplate_check_rrdset_conditions(rt, st, host)) + if(rrdcalctemplate_check_rrdset_conditions(rt, st, host)) { rrdcalctemplate_check_rrddim_conditions_and_link(rt, st, rd, host); + } } foreach_rrdcalctemplate_done(rt); + + if (health_variable_check(health_rrdvars, st, rd)) + rrdvar_store_for_chart(host, st); } rrddim_foreach_done(rd); } @@ -1002,9 +980,7 @@ void *health_main(void *ptr) { 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(service_running(SERVICE_HEALTH)) { loop++; debug(D_HEALTH, "Health monitoring iteration no %u started", loop); @@ -1033,15 +1009,8 @@ void *health_main(void *ptr) { } } -#ifdef ENABLE_ACLK - if (aclk_alert_reloaded && !marked_aclk_reload_loop) - marked_aclk_reload_loop = loop; -#endif - worker_is_busy(WORKER_HEALTH_JOB_RRD_LOCK); - rrd_rdlock(); - - rrdhost_foreach_read(host) { + dfe_start_reentrant(rrdhost_root_index, host) { if(unlikely(!service_running(SERVICE_HEALTH))) break; @@ -1049,11 +1018,8 @@ void *health_main(void *ptr) { if (unlikely(!host->health.health_enabled)) continue; - if (unlikely(!rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH))) { - rrd_unlock(); - initialize_health(host, host == localhost); - rrd_rdlock(); - } + if (unlikely(!rrdhost_flag_check(host, RRDHOST_FLAG_INITIALIZED_HEALTH))) + initialize_health(host); health_execute_delayed_initializations(host); @@ -1147,7 +1113,7 @@ void *health_main(void *ptr) { rc->value = NAN; #ifdef ENABLE_ACLK - if (netdata_cloud_setting && likely(!aclk_alert_reloaded)) + if (netdata_cloud_setting) sql_queue_alarm_to_aclk(host, ae, 1); #endif } @@ -1518,9 +1484,28 @@ void *health_main(void *ptr) { } break; } - } //for each host +#ifdef ENABLE_ACLK + if (netdata_cloud_setting) { + struct aclk_sync_host_config *wc = (struct aclk_sync_host_config *)host->aclk_sync_host_config; + if (unlikely(!wc)) { + continue; + } + + if (wc->alert_queue_removed == 1) { + sql_queue_removed_alerts_to_aclk(host); + } else if (wc->alert_queue_removed > 1) { + wc->alert_queue_removed--; + } - rrd_unlock(); + if (wc->alert_checkpoint_req == 1) { + aclk_push_alarm_checkpoint(host); + } else if (wc->alert_checkpoint_req > 1) { + wc->alert_checkpoint_req--; + } + } +#endif + } + dfe_done(host); // wait for all notifications to finish before allowing health to be cleaned up ALARM_ENTRY *ae; @@ -1531,22 +1516,6 @@ void *health_main(void *ptr) { health_alarm_wait_for_execution(ae); } -#ifdef ENABLE_ACLK - 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(!service_running(SERVICE_HEALTH))) break; diff --git a/health/health.d/btrfs.conf b/health/health.d/btrfs.conf index 8d197aa8..ab63ff28 100644 --- a/health/health.d/btrfs.conf +++ b/health/health.d/btrfs.conf @@ -66,3 +66,78 @@ component: File system delay: up 1m down 15m multiplier 1.5 max 1h info: utilization of BTRFS system space to: sysadmin + + template: btrfs_device_read_errors + on: btrfs.device_errors + class: Errors + type: System +component: File system + os: * + hosts: * + families: * + units: errors + lookup: max -10m every 1m of read_errs + warn: $this > 0 + delay: up 1m down 15m multiplier 1.5 max 1h + info: number of encountered BTRFS read errors + to: sysadmin + + template: btrfs_device_write_errors + on: btrfs.device_errors + class: Errors + type: System +component: File system + os: * + hosts: * + families: * + units: errors + lookup: max -10m every 1m of write_errs + warn: $this > 0 + delay: up 1m down 15m multiplier 1.5 max 1h + info: number of encountered BTRFS write errors + to: sysadmin + + template: btrfs_device_flush_errors + on: btrfs.device_errors + class: Errors + type: System +component: File system + os: * + hosts: * + families: * + units: errors + lookup: max -10m every 1m of flush_errs + warn: $this > 0 + delay: up 1m down 15m multiplier 1.5 max 1h + info: number of encountered BTRFS flush errors + to: sysadmin + + template: btrfs_device_corruption_errors + on: btrfs.device_errors + class: Errors + type: System +component: File system + os: * + hosts: * + families: * + units: errors + lookup: max -10m every 1m of corruption_errs + warn: $this > 0 + delay: up 1m down 15m multiplier 1.5 max 1h + info: number of encountered BTRFS corruption errors + to: sysadmin + + template: btrfs_device_generation_errors + on: btrfs.device_errors + class: Errors + type: System +component: File system + os: * + hosts: * + families: * + units: errors + lookup: max -10m every 1m of generation_errs + warn: $this > 0 + delay: up 1m down 15m multiplier 1.5 max 1h + info: number of encountered BTRFS generation errors + to: sysadmin diff --git a/health/health.d/docker.conf b/health/health.d/docker.conf new file mode 100644 index 00000000..f1702847 --- /dev/null +++ b/health/health.d/docker.conf @@ -0,0 +1,11 @@ + template: docker_container_unhealthy + on: docker.container_health_status + class: Errors + type: Containers +component: Docker + units: status + every: 10s + lookup: average -10s of unhealthy + crit: $this > 0 + info: ${label:container_name} docker container health status is unhealthy + to: sysadmin diff --git a/health/health.d/dockerd.conf b/health/health.d/dockerd.conf deleted file mode 100644 index 220ddd66..00000000 --- a/health/health.d/dockerd.conf +++ /dev/null @@ -1,11 +0,0 @@ - template: docker_unhealthy_containers - on: docker.unhealthy_containers - class: Errors - type: Containers -component: Docker - units: unhealthy containers - every: 10s - lookup: average -10s - crit: $this > 0 - info: average number of unhealthy docker containers over the last 10 seconds - to: sysadmin diff --git a/health/health.d/windows.conf b/health/health.d/windows.conf new file mode 100644 index 00000000..d678ac3a --- /dev/null +++ b/health/health.d/windows.conf @@ -0,0 +1,139 @@ + +## CPU + + template: windows_10min_cpu_usage + on: windows.cpu_utilization_total + class: Utilization + type: Windows +component: CPU + os: linux + hosts: * + lookup: average -10m unaligned match-names of dpc,user,privileged,interrupt + units: % + every: 1m + warn: $this > (($status >= $WARNING) ? (75) : (85)) + crit: $this > (($status == $CRITICAL) ? (85) : (95)) + delay: down 15m multiplier 1.5 max 1h + info: average CPU utilization over the last 10 minutes + to: sysadmin + + +## Memory + + template: windows_ram_in_use + on: windows.memory_utilization + class: Utilization + type: Windows +component: Memory + os: linux + hosts: * + calc: ($used) * 100 / ($used + $available) + units: % + every: 10s + warn: $this > (($status >= $WARNING) ? (80) : (90)) + crit: $this > (($status == $CRITICAL) ? (90) : (98)) + delay: down 15m multiplier 1.5 max 1h + info: memory utilization + to: sysadmin + + template: windows_swap_in_use + on: windows.memory_swap_utilization + class: Utilization + type: Windows +component: Memory + os: linux + hosts: * + calc: ($used) * 100 / ($used + $available) + units: % + every: 10s + warn: $this > (($status >= $WARNING) ? (80) : (90)) + crit: $this > (($status == $CRITICAL) ? (90) : (98)) + delay: down 15m multiplier 1.5 max 1h + info: swap memory utilization + to: sysadmin + + +## Network + + template: windows_inbound_packets_discarded + on: windows.net_discarded + class: Errors + type: Windows +component: Network + os: linux + hosts: * + families: * + lookup: sum -10m unaligned absolute match-names of inbound + units: packets + every: 1m + warn: $this >= 5 + delay: down 1h multiplier 1.5 max 2h + info: number of inbound discarded packets for the network interface in the last 10 minutes + to: sysadmin + + template: windows_outbound_packets_discarded + on: windows.net_discarded + class: Errors + type: Windows +component: Network + os: linux + hosts: * + families: * + lookup: sum -10m unaligned absolute match-names of outbound + units: packets + every: 1m + warn: $this >= 5 + delay: down 1h multiplier 1.5 max 2h + info: number of outbound discarded packets for the network interface in the last 10 minutes + to: sysadmin + + template: windows_inbound_packets_errors + on: windows.net_errors + class: Errors + type: Windows +component: Network + os: linux + hosts: * + families: * + lookup: sum -10m unaligned absolute match-names of inbound + units: packets + every: 1m + warn: $this >= 5 + delay: down 1h multiplier 1.5 max 2h + info: number of inbound errors for the network interface in the last 10 minutes + to: sysadmin + + template: windows_outbound_packets_errors + on: windows.net_errors + class: Errors + type: Windows +component: Network + os: linux + hosts: * + families: * + lookup: sum -10m unaligned absolute match-names of outbound + units: packets + every: 1m + warn: $this >= 5 + delay: down 1h multiplier 1.5 max 2h + info: number of outbound errors for the network interface in the last 10 minutes + to: sysadmin + + +## Disk + + template: windows_disk_in_use + on: windows.logical_disk_utilization + class: Utilization + type: Windows +component: Disk + os: linux + hosts: * + calc: ($used) * 100 / ($used + $free) + units: % + every: 10s + warn: $this > (($status >= $WARNING) ? (80) : (90)) + crit: $this > (($status == $CRITICAL) ? (90) : (98)) + delay: down 15m multiplier 1.5 max 1h + info: disk space utilization + to: sysadmin diff --git a/health/health.d/wmi.conf b/health/health.d/wmi.conf deleted file mode 100644 index 90d39ce9..00000000 --- a/health/health.d/wmi.conf +++ /dev/null @@ -1,139 +0,0 @@ - -## CPU - - template: wmi_10min_cpu_usage - on: wmi.cpu_utilization_total - class: Utilization - type: Windows -component: CPU - os: linux - hosts: * - lookup: average -10m unaligned match-names of dpc,user,privileged,interrupt - units: % - every: 1m - warn: $this > (($status >= $WARNING) ? (75) : (85)) - crit: $this > (($status == $CRITICAL) ? (85) : (95)) - delay: down 15m multiplier 1.5 max 1h - info: average CPU utilization over the last 10 minutes - to: sysadmin - - -## Memory - - template: wmi_ram_in_use - on: wmi.memory_utilization - class: Utilization - type: Windows -component: Memory - os: linux - hosts: * - calc: ($used) * 100 / ($used + $available) - units: % - every: 10s - warn: $this > (($status >= $WARNING) ? (80) : (90)) - crit: $this > (($status == $CRITICAL) ? (90) : (98)) - delay: down 15m multiplier 1.5 max 1h - info: memory utilization - to: sysadmin - - template: wmi_swap_in_use - on: wmi.memory_swap_utilization - class: Utilization - type: Windows -component: Memory - os: linux - hosts: * - calc: ($used) * 100 / ($used + $available) - units: % - every: 10s - warn: $this > (($status >= $WARNING) ? (80) : (90)) - crit: $this > (($status == $CRITICAL) ? (90) : (98)) - delay: down 15m multiplier 1.5 max 1h - info: swap memory utilization - to: sysadmin - - -## Network - - template: wmi_inbound_packets_discarded - on: wmi.net_discarded - class: Errors - type: Windows -component: Network - os: linux - hosts: * - families: * - lookup: sum -10m unaligned absolute match-names of inbound - units: packets - every: 1m - warn: $this >= 5 - delay: down 1h multiplier 1.5 max 2h - info: number of inbound discarded packets for the network interface in the last 10 minutes - to: sysadmin - - template: wmi_outbound_packets_discarded - on: wmi.net_discarded - class: Errors - type: Windows -component: Network - os: linux - hosts: * - families: * - lookup: sum -10m unaligned absolute match-names of outbound - units: packets - every: 1m - warn: $this >= 5 - delay: down 1h multiplier 1.5 max 2h - info: number of outbound discarded packets for the network interface in the last 10 minutes - to: sysadmin - - template: wmi_inbound_packets_errors - on: wmi.net_errors - class: Errors - type: Windows -component: Network - os: linux - hosts: * - families: * - lookup: sum -10m unaligned absolute match-names of inbound - units: packets - every: 1m - warn: $this >= 5 - delay: down 1h multiplier 1.5 max 2h - info: number of inbound errors for the network interface in the last 10 minutes - to: sysadmin - - template: wmi_outbound_packets_errors - on: wmi.net_errors - class: Errors - type: Windows -component: Network - os: linux - hosts: * - families: * - lookup: sum -10m unaligned absolute match-names of outbound - units: packets - every: 1m - warn: $this >= 5 - delay: down 1h multiplier 1.5 max 2h - info: number of outbound errors for the network interface in the last 10 minutes - to: sysadmin - - -## Disk - - template: wmi_disk_in_use - on: wmi.logical_disk_utilization - class: Utilization - type: Windows -component: Disk - os: linux - hosts: * - calc: ($used) * 100 / ($used + $free) - units: % - every: 10s - warn: $this > (($status >= $WARNING) ? (80) : (90)) - crit: $this > (($status == $CRITICAL) ? (90) : (98)) - delay: down 15m multiplier 1.5 max 1h - info: disk space utilization - to: sysadmin diff --git a/health/health.h b/health/health.h index 50c3e345..902e36c6 100644 --- a/health/health.h +++ b/health/health.h @@ -32,6 +32,7 @@ extern unsigned int default_health_enabled; extern char *silencers_filename; extern SIMPLE_PATTERN *conf_enabled_alarms; +extern DICTIONARY *health_rrdvars; void health_init(void); diff --git a/health/health_config.c b/health/health_config.c index 55d5e10e..38857fc9 100644 --- a/health/health_config.c +++ b/health/health_config.c @@ -185,6 +185,51 @@ static inline int health_parse_repeat( return 1; } +static inline int isvariableterm(const char s) { + if(isalnum(s) || s == '.' || s == '_') + return 0; + + return 1; +} + +static inline void parse_variables_and_store_in_health_rrdvars(char *value, size_t len) { + const char *s = value; + char buffer[RRDVAR_MAX_LENGTH]; + + // $ + while (*s) { + if(*s == '$') { + size_t i = 0; + s++; + + if(*s == '{') { + // ${variable_name} + + s++; + while (*s && *s != '}' && i < len) + buffer[i++] = *s++; + + if(*s == '}') + s++; + } + else { + // $variable_name + + while (*s && !isvariableterm(*s) && i < len) + buffer[i++] = *s++; + } + + buffer[i] = '\0'; + + //TODO: check and try to store only variables + STRING *name_string = rrdvar_name_to_string(buffer); + rrdvar_add("health", health_rrdvars, name_string, RRDVAR_TYPE_CALCULATED, RRDVAR_FLAG_CONFIG_VAR, NULL); + string_freez(name_string); + } else + s++; + } +} + /** * Health pattern from Foreach * @@ -206,7 +251,7 @@ static SIMPLE_PATTERN *health_pattern_from_foreach(const char *s) { if(convert) { dimension_remove_pipe_comma(convert); - val = simple_pattern_create(convert, NULL, SIMPLE_PATTERN_EXACT); + val = simple_pattern_create(convert, NULL, SIMPLE_PATTERN_EXACT, true); freez(convert); } @@ -215,7 +260,7 @@ static SIMPLE_PATTERN *health_pattern_from_foreach(const char *s) { static inline int health_parse_db_lookup( size_t line, const char *filename, char *string, - RRDR_GROUPING *group_method, int *after, int *before, int *every, + RRDR_TIME_GROUPING *group_method, int *after, int *before, int *every, RRDCALC_OPTIONS *options, STRING **dimensions, STRING **foreachdim ) { debug(D_HEALTH, "Health configuration parsing database lookup %zu@%s: %s", line, filename, string); @@ -241,7 +286,7 @@ static inline int health_parse_db_lookup( return 0; } - if((*group_method = web_client_api_request_v1_data_group(key, RRDR_GROUPING_UNDEFINED)) == RRDR_GROUPING_UNDEFINED) { + if((*group_method = time_grouping_parse(key, RRDR_GROUPING_UNDEFINED)) == RRDR_GROUPING_UNDEFINED) { error("Health configuration at line %zu of file '%s': invalid group method '%s'", line, filename, key); return 0; @@ -634,9 +679,9 @@ static int health_readfile(const char *filename, void *data) { else if(hash == hash_os && !strcasecmp(key, HEALTH_OS_KEY)) { char *os_match = value; if (alert_cfg) alert_cfg->os = string_strdupz(value); - SIMPLE_PATTERN *os_pattern = simple_pattern_create(os_match, NULL, SIMPLE_PATTERN_EXACT); + SIMPLE_PATTERN *os_pattern = simple_pattern_create(os_match, NULL, SIMPLE_PATTERN_EXACT, true); - if(!simple_pattern_matches(os_pattern, rrdhost_os(host))) { + if(!simple_pattern_matches_string(os_pattern, host->os)) { if(rc) debug(D_HEALTH, "HEALTH on '%s' ignoring alarm '%s' defined at %zu@%s: host O/S does not match '%s'", rrdhost_hostname(host), rrdcalc_name(rc), line, filename, os_match); @@ -651,9 +696,9 @@ static int health_readfile(const char *filename, void *data) { else if(hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) { char *host_match = value; if (alert_cfg) alert_cfg->host = string_strdupz(value); - SIMPLE_PATTERN *host_pattern = simple_pattern_create(host_match, NULL, SIMPLE_PATTERN_EXACT); + SIMPLE_PATTERN *host_pattern = simple_pattern_create(host_match, NULL, SIMPLE_PATTERN_EXACT, true); - if(!simple_pattern_matches(host_pattern, rrdhost_hostname(host))) { + if(!simple_pattern_matches_string(host_pattern, host->hostname)) { if(rc) debug(D_HEALTH, "HEALTH on '%s' ignoring alarm '%s' defined at %zu@%s: hostname does not match '%s'", rrdhost_hostname(host), rrdcalc_name(rc), line, filename, host_match); @@ -728,7 +773,7 @@ static int health_readfile(const char *filename, void *data) { if (rc->dimensions) alert_cfg->p_db_lookup_dimensions = string_dup(rc->dimensions); if (rc->group) - alert_cfg->p_db_lookup_method = string_strdupz(group_method2string(rc->group)); + alert_cfg->p_db_lookup_method = string_strdupz(time_grouping_method2string(rc->group)); alert_cfg->p_db_lookup_options = rc->options; alert_cfg->p_db_lookup_after = rc->after; alert_cfg->p_db_lookup_before = rc->before; @@ -769,6 +814,7 @@ static int health_readfile(const char *filename, void *data) { error("Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'", line, filename, rrdcalc_name(rc), key, value, expression_strerror(error), failed_at); } + parse_variables_and_store_in_health_rrdvars(value, HEALTH_CONF_MAX_LINE); } else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) { alert_cfg->warn = string_strdupz(value); @@ -779,6 +825,7 @@ static int health_readfile(const char *filename, void *data) { error("Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'", line, filename, rrdcalc_name(rc), key, value, expression_strerror(error), failed_at); } + parse_variables_and_store_in_health_rrdvars(value, HEALTH_CONF_MAX_LINE); } else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) { alert_cfg->crit = string_strdupz(value); @@ -789,6 +836,7 @@ static int health_readfile(const char *filename, void *data) { error("Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' has unparse-able expression '%s': %s at '%s'", line, filename, rrdcalc_name(rc), key, value, expression_strerror(error), failed_at); } + parse_variables_and_store_in_health_rrdvars(value, HEALTH_CONF_MAX_LINE); } else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) { alert_cfg->exec = string_strdupz(value); @@ -870,7 +918,8 @@ static int health_readfile(const char *filename, void *data) { rc->host_labels = string_strdupz(tmp); freez(tmp); } - rc->host_labels_pattern = simple_pattern_create(rrdcalc_host_labels(rc), NULL, SIMPLE_PATTERN_EXACT); + rc->host_labels_pattern = simple_pattern_create(rrdcalc_host_labels(rc), NULL, SIMPLE_PATTERN_EXACT, + true); } else if(hash == hash_plugin && !strcasecmp(key, HEALTH_PLUGIN_KEY)) { alert_cfg->plugin = string_strdupz(value); @@ -878,7 +927,7 @@ static int health_readfile(const char *filename, void *data) { simple_pattern_free(rc->plugin_pattern); rc->plugin_match = string_strdupz(value); - rc->plugin_pattern = simple_pattern_create(rrdcalc_plugin_match(rc), NULL, SIMPLE_PATTERN_EXACT); + rc->plugin_pattern = simple_pattern_create(rrdcalc_plugin_match(rc), NULL, SIMPLE_PATTERN_EXACT, true); } else if(hash == hash_module && !strcasecmp(key, HEALTH_MODULE_KEY)) { alert_cfg->module = string_strdupz(value); @@ -886,7 +935,7 @@ static int health_readfile(const char *filename, void *data) { simple_pattern_free(rc->module_pattern); rc->module_match = string_strdupz(value); - rc->module_pattern = simple_pattern_create(rrdcalc_module_match(rc), NULL, SIMPLE_PATTERN_EXACT); + rc->module_pattern = simple_pattern_create(rrdcalc_module_match(rc), NULL, SIMPLE_PATTERN_EXACT, true); } else { error("Health configuration at line %zu of file '%s' for alarm '%s' has unknown key '%s'.", @@ -950,7 +999,8 @@ static int health_readfile(const char *filename, void *data) { simple_pattern_free(rt->family_pattern); rt->family_match = string_strdupz(value); - rt->family_pattern = simple_pattern_create(rrdcalctemplate_family_match(rt), NULL, SIMPLE_PATTERN_EXACT); + rt->family_pattern = simple_pattern_create(rrdcalctemplate_family_match(rt), NULL, SIMPLE_PATTERN_EXACT, + true); } else if(hash == hash_plugin && !strcasecmp(key, HEALTH_PLUGIN_KEY)) { alert_cfg->plugin = string_strdupz(value); @@ -958,7 +1008,8 @@ static int health_readfile(const char *filename, void *data) { simple_pattern_free(rt->plugin_pattern); rt->plugin_match = string_strdupz(value); - rt->plugin_pattern = simple_pattern_create(rrdcalctemplate_plugin_match(rt), NULL, SIMPLE_PATTERN_EXACT); + rt->plugin_pattern = simple_pattern_create(rrdcalctemplate_plugin_match(rt), NULL, SIMPLE_PATTERN_EXACT, + true); } else if(hash == hash_module && !strcasecmp(key, HEALTH_MODULE_KEY)) { alert_cfg->module = string_strdupz(value); @@ -966,7 +1017,8 @@ static int health_readfile(const char *filename, void *data) { simple_pattern_free(rt->module_pattern); rt->module_match = string_strdupz(value); - rt->module_pattern = simple_pattern_create(rrdcalctemplate_module_match(rt), NULL, SIMPLE_PATTERN_EXACT); + rt->module_pattern = simple_pattern_create(rrdcalctemplate_module_match(rt), NULL, SIMPLE_PATTERN_EXACT, + true); } else if(hash == hash_charts && !strcasecmp(key, HEALTH_CHARTS_KEY)) { alert_cfg->charts = string_strdupz(value); @@ -974,7 +1026,8 @@ static int health_readfile(const char *filename, void *data) { simple_pattern_free(rt->charts_pattern); rt->charts_match = string_strdupz(value); - rt->charts_pattern = simple_pattern_create(rrdcalctemplate_charts_match(rt), NULL, SIMPLE_PATTERN_EXACT); + rt->charts_pattern = simple_pattern_create(rrdcalctemplate_charts_match(rt), NULL, SIMPLE_PATTERN_EXACT, + true); } else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) { alert_cfg->lookup = string_strdupz(value); @@ -989,7 +1042,7 @@ static int health_readfile(const char *filename, void *data) { alert_cfg->p_db_lookup_dimensions = string_dup(rt->dimensions); if (rt->group) - alert_cfg->p_db_lookup_method = string_strdupz(group_method2string(rt->group)); + alert_cfg->p_db_lookup_method = string_strdupz(time_grouping_method2string(rt->group)); alert_cfg->p_db_lookup_options = rt->options; alert_cfg->p_db_lookup_after = rt->after; @@ -1031,6 +1084,7 @@ static int health_readfile(const char *filename, void *data) { error("Health configuration at line %zu of file '%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'", line, filename, rrdcalctemplate_name(rt), key, value, expression_strerror(error), failed_at); } + parse_variables_and_store_in_health_rrdvars(value, HEALTH_CONF_MAX_LINE); } else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) { alert_cfg->warn = string_strdupz(value); @@ -1041,6 +1095,7 @@ static int health_readfile(const char *filename, void *data) { error("Health configuration at line %zu of file '%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'", line, filename, rrdcalctemplate_name(rt), key, value, expression_strerror(error), failed_at); } + parse_variables_and_store_in_health_rrdvars(value, HEALTH_CONF_MAX_LINE); } else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) { alert_cfg->crit = string_strdupz(value); @@ -1051,6 +1106,7 @@ static int health_readfile(const char *filename, void *data) { error("Health configuration at line %zu of file '%s' for template '%s' at key '%s' has unparse-able expression '%s': %s at '%s'", line, filename, rrdcalctemplate_name(rt), key, value, expression_strerror(error), failed_at); } + parse_variables_and_store_in_health_rrdvars(value, HEALTH_CONF_MAX_LINE); } else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) { alert_cfg->exec = string_strdupz(value); @@ -1130,7 +1186,8 @@ static int health_readfile(const char *filename, void *data) { rt->host_labels = string_strdupz(tmp); freez(tmp); } - rt->host_labels_pattern = simple_pattern_create(rrdcalctemplate_host_labels(rt), NULL, SIMPLE_PATTERN_EXACT); + rt->host_labels_pattern = simple_pattern_create(rrdcalctemplate_host_labels(rt), NULL, + SIMPLE_PATTERN_EXACT, true); } else { error("Health configuration at line %zu of file '%s' for template '%s' has unknown key '%s'.", @@ -1185,6 +1242,9 @@ void health_readdir(RRDHOST *host, const char *user_path, const char *stock_path stock_path = user_path; } + if (!health_rrdvars) + health_rrdvars = health_rrdvariables_create(); + recursive_config_double_dir_load(user_path, stock_path, subpath, health_readfile, (void *) host, 0); log_health("[%s]: Read health configuration.", rrdhost_hostname(host)); sql_store_hashes = 0; diff --git a/health/health_json.c b/health/health_json.c index 8cabaa0b..ba18bddb 100644 --- a/health/health_json.c +++ b/health/health_json.c @@ -103,11 +103,11 @@ void health_alarm_entry2json_nolock(BUFFER *wb, ALARM_ENTRY *ae, RRDHOST *host) } buffer_strcat(wb, "\t\t\"value\":"); - buffer_rrd_value(wb, ae->new_value); + buffer_print_netdata_double(wb, ae->new_value); buffer_strcat(wb, ",\n"); buffer_strcat(wb, "\t\t\"old_value\":"); - buffer_rrd_value(wb, ae->old_value); + buffer_print_netdata_double(wb, ae->old_value); buffer_strcat(wb, "\n"); buffer_strcat(wb, "\t}"); @@ -152,7 +152,7 @@ static inline void health_rrdcalc_values2json_nolock(RRDHOST *host, BUFFER *wb, , (unsigned long)rc->id); buffer_strcat(wb, "\t\t\t\"value\":"); - buffer_rrd_value(wb, rc->value); + buffer_print_netdata_double(wb, rc->value); buffer_strcat(wb, ",\n"); buffer_strcat(wb, "\t\t\t\"last_updated\":"); @@ -257,11 +257,11 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC "\t\t\t\"lookup_after\": %d,\n" "\t\t\t\"lookup_before\": %d,\n" "\t\t\t\"lookup_options\": \"", - (unsigned long) rc->db_after, - (unsigned long) rc->db_before, - group_method2string(rc->group), - rc->after, - rc->before + (unsigned long) rc->db_after, + (unsigned long) rc->db_before, + time_grouping_method2string(rc->group), + rc->after, + rc->before ); buffer_data_options2string(wb, rc->options); buffer_strcat(wb, "\",\n"); @@ -283,15 +283,15 @@ static inline void health_rrdcalc2json_nolock(RRDHOST *host, BUFFER *wb, RRDCALC } buffer_strcat(wb, "\t\t\t\"green\":"); - buffer_rrd_value(wb, rc->green); + buffer_print_netdata_double(wb, rc->green); buffer_strcat(wb, ",\n"); buffer_strcat(wb, "\t\t\t\"red\":"); - buffer_rrd_value(wb, rc->red); + buffer_print_netdata_double(wb, rc->red); buffer_strcat(wb, ",\n"); buffer_strcat(wb, "\t\t\t\"value\":"); - buffer_rrd_value(wb, rc->value); + buffer_print_netdata_double(wb, rc->value); buffer_strcat(wb, "\n"); buffer_strcat(wb, "\t\t}"); @@ -309,7 +309,7 @@ void health_aggregate_alarms(RRDHOST *host, BUFFER *wb, BUFFER* contexts, RRDCAL if (contexts) { p = (char*)buffer_tostring(contexts); - while(p && *p && (tok = mystrsep(&p, ", |"))) { + while(p && *p && (tok = strsep_skip_consecutive_separators(&p, ", |"))) { if(!*tok) continue; STRING *tok_string = string_strdupz(tok); diff --git a/health/health_log.c b/health/health_log.c index d3417493..b1f59a1a 100644 --- a/health/health_log.c +++ b/health/health_log.c @@ -95,6 +95,8 @@ inline void health_alarm_log_add_entry( ) { debug(D_HEALTH, "Health adding alarm log entry with id: %u", ae->unique_id); + __atomic_add_fetch(&host->health_transitions, 1, __ATOMIC_RELAXED); + // link it netdata_rwlock_wrlock(&host->health_log.alarm_log_rwlock); ae->next = host->health_log.alarms; diff --git a/health/notifications/Makefile.am b/health/notifications/Makefile.am index f026171a..3114abc4 100644 --- a/health/notifications/Makefile.am +++ b/health/notifications/Makefile.am @@ -37,6 +37,7 @@ include irc/Makefile.inc include kavenegar/Makefile.inc include messagebird/Makefile.inc include msteams/Makefile.inc +include ntfy/Makefile.inc include opsgenie/Makefile.inc include pagerduty/Makefile.inc include pushbullet/Makefile.inc diff --git a/health/notifications/README.md b/health/notifications/README.md index c59fecce..05efb3a0 100644 --- a/health/notifications/README.md +++ b/health/notifications/README.md @@ -1,72 +1,107 @@ - +# Agent alert notifications -# Alarm notifications +This is a reference documentation for Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. -The `exec` line in health configuration defines an external script that will be called once -the alarm is triggered. The default script is `alarm-notify.sh`. +The `script to execute on alarm` line in `netdata.conf` defines the external script that will be called once the alert is triggered. -You can change the default script globally by editing `/etc/netdata/netdata.conf`. +The default script is `alarm-notify.sh`. + +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> - Please also note that after most configuration changes you will need to [restart the Agent](https://github.com/netdata/netdata/blob/master/docs/configure/start-stop-restart.md) for the changes to take effect. +> +> It is recommended to use this way for configuring Netdata. + +You can change the default script globally by editing `netdata.conf` and changing the `script to execute on alarm` in the `[health]` section. `alarm-notify.sh` is capable of sending notifications: -- to multiple recipients -- using multiple notification methods -- filtering severity per recipient +- to multiple recipients +- using multiple notification methods +- filtering severity per recipient It uses **roles**. For example `sysadmin`, `webmaster`, `dba`, etc. -Each alarm is assigned to one or more roles, using the `to` line of the alarm configuration. Then `alarm-notify.sh` uses -its own configuration file `/etc/netdata/health_alarm_notify.conf`. To edit it on your system, run -`/etc/netdata/edit-config health_alarm_notify.conf` and find the destination address of the notification for each -method. +Each alert is assigned to one or more roles, using the `to` line of the alert configuration. For example, here is the alert configuration for `ram.conf` that defaults to the role `sysadmin`: + +```conf + alarm: ram_in_use + on: system.ram + class: Utilization + type: System +component: Memory + os: linux + hosts: * + calc: $used * 100 / ($used + $cached + $free + $buffers) + units: % + every: 10s + warn: $this > (($status >= $WARNING) ? (80) : (90)) + crit: $this > (($status == $CRITICAL) ? (90) : (98)) + delay: down 15m multiplier 1.5 max 1h + info: system memory utilization + to: sysadmin +``` + +Then `alarm-notify.sh` uses its own configuration file `health_alarm_notify.conf`, which at the bottom of the file stores the recipients per role, for all notification methods. + +Here is an example, of the `sysadmin`'s role recipients for the email notification. +You can send the notification to multiple recipients by separating the emails with a space. + +```conf + +############################################################################### +# RECIPIENTS PER ROLE + +# ----------------------------------------------------------------------------- +# generic system alarms +# CPU, disks, network interfaces, entropy, etc -Each role may have one or more destinations. +role_recipients_email[sysadmin]="someone@exaple.com someoneelse@example.com" +``` + +Each role may have one or more destinations and one or more notification methods. So, for example the `sysadmin` role may send: -1. emails to admin1@example.com and admin2@example.com -2. pushover.net notifications to USERTOKENS `A`, `B` and `C`. -3. pushbullet.com push notifications to admin1@example.com and admin2@example.com -4. messages to slack.com channel `#alarms` and `#systems`. -5. messages to Discord channels `#alarms` and `#systems`. +1. emails to admin1@example.com and admin2@example.com +2. pushover.net notifications to USERTOKENS `A`, `B` and `C`. +3. pushbullet.com push notifications to admin1@example.com and admin2@example.com +4. messages to the `#alerts` and `#systems` channels of a Slack workspace. +5. messages to Discord channels `#alerts` and `#systems`. ## Configuration -Edit `/etc/netdata/health_alarm_notify.conf` by running `/etc/netdata/edit-config health_alarm_notify.conf`: +You can edit `health_alarm_notify.conf` using the `edit-config` script to configure: -- settings per notification method: +- **Settings** per notification method: - all notification methods except email, require some configuration - (i.e. API keys, tokens, destination rooms, channels, etc). + All notification methods except email, require some configuration (i.e. API keys, tokens, destination rooms, channels, etc). Please check this section's content to find the configuration guides for your notification option of choice -- **recipients** per **role** per **notification method** +- **Recipients** per role per notification method -```sh -grep sysadmin /etc/netdata/health_alarm_notify.conf - -role_recipients_email[sysadmin]="${DEFAULT_RECIPIENT_EMAIL}" -role_recipients_pushover[sysadmin]="${DEFAULT_RECIPIENT_PUSHOVER}" -role_recipients_pushbullet[sysadmin]="${DEFAULT_RECIPIENT_PUSHBULLET}" -role_recipients_telegram[sysadmin]="${DEFAULT_RECIPIENT_TELEGRAM}" -role_recipients_slack[sysadmin]="${DEFAULT_RECIPIENT_SLACK}" -... -``` + ```conf + role_recipients_email[sysadmin]="${DEFAULT_RECIPIENT_EMAIL}" + role_recipients_pushover[sysadmin]="${DEFAULT_RECIPIENT_PUSHOVER}" + role_recipients_pushbullet[sysadmin]="${DEFAULT_RECIPIENT_PUSHBULLET}" + role_recipients_telegram[sysadmin]="${DEFAULT_RECIPIENT_TELEGRAM}" + role_recipients_slack[sysadmin]="${DEFAULT_RECIPIENT_SLACK}" + ... + ``` -## Testing Notifications + Here you can change the `${DEFAULT_...}` values to the values of the recipients you want, separated by a space if you have multiple recipients. -You can run the following command by hand, to test alarms configuration: +## Testing Alert Notifications + +You can run the following command by hand, to test alerts configuration: ```sh # become user netdata -su -s /bin/bash netdata +sudo su -s /bin/bash netdata # enable debugging info on the console export NETDATA_ALARM_NOTIFY_DEBUG=1 @@ -78,13 +113,95 @@ export NETDATA_ALARM_NOTIFY_DEBUG=1 /usr/libexec/netdata/plugins.d/alarm-notify.sh test "ROLE" ``` -Note that in versions before 1.16, the plugins.d directory may be installed in a different location in certain OSs (e.g. under `/usr/lib/netdata`). You can always find the location of the alarm-notify.sh script in `netdata.conf`. +If you are [running your own registry](https://github.com/netdata/netdata/blob/master/registry/README.md#run-your-own-registry), add `export NETDATA_REGISTRY_URL=[YOUR_URL]` before calling `alarm-notify.sh`. + +> If you need to dig even deeper, you can trace the execution with `bash -x`. Note that in test mode, `alarm-notify.sh` calls itself with many more arguments. So first do: +> +>```sh +>bash -x /usr/libexec/netdata/plugins.d/alarm-notify.sh test +>``` +> +> And then look in the output for the alarm-notify.sh calls and run the one you want to trace with `bash -x`. + +## Global configuration options + +### Notification Filtering + +When you define recipients per role for notification methods, you can append `|critical` to limit the notifications that are sent. + +In the following examples, the first recipient receives all the alarms, while the second one receives only notifications for alarms that have at some point become critical. +The second user may still receive warning and clear notifications, but only for the event that previously caused a critical alarm. + +```conf + email : "user1@example.com user2@example.com|critical" + pushover : "2987343...9437837 8756278...2362736|critical" + telegram : "111827421 112746832|critical" + slack : "alarms disasters|critical" + alerta : "alarms disasters|critical" + flock : "alarms disasters|critical" + discord : "alarms disasters|critical" + twilio : "+15555555555 +17777777777|critical" + messagebird: "+15555555555 +17777777777|critical" + kavenegar : "09155555555 09177777777|critical" + pd : " |critical" + irc : " |critical" +``` + +If a per role recipient is set to an empty string, the default recipient of the given +notification method (email, pushover, telegram, slack, alerta, etc) will be used. -If you need to dig even deeper, you can trace the execution with `bash -x`. Note that in test mode, alarm-notify.sh calls itself with many more arguments. So first do +To disable a notification, use the recipient called: disabled +This works for all notification methods (including the default recipients). -```sh -bash -x /usr/libexec/netdata/plugins.d/alarm-notify.sh test +### Proxy configuration + +If you need to send curl based notifications (pushover, pushbullet, slack, alerta, +flock, discord, telegram) via a proxy, you should set these variables to your proxy address: + +```conf +export http_proxy="http://10.0.0.1:3128/" +export https_proxy="http://10.0.0.1:3128/" +``` + +### Notification images + +Images in notifications need to be downloaded from an Internet facing site. + +To allow notification providers to fetch the icons/images, by default we set the URL of the global public netdata registry. + +If you have an Internet facing netdata (or you have copied the images/ folder +of netdata to your web server), set its URL here, to fetch the notification +images from it. + +```conf +images_base_url="http://my.public.netdata.server:19999" ``` - Then look in the output for the alarm-notify.sh calls and run the one you want to trace with `bash -x`. +### Date handling + +You can configure netdata alerts to send dates in any format you want via editing the `date_format` variable. + +This uses standard `date` command format strings. See `man date` for +more info on what formats are supported. + +Note that this has to start with a '+', otherwise it won't work. + +- For ISO 8601 dates, use `+%FT%T%z` +- For RFC 5322 dates, use `+%a, %d %b %Y %H:%M:%S %z` +- For RFC 3339 dates, use `+%F %T%:z` +- For RFC 1123 dates, use `+%a, %d %b %Y %H:%M:%S %Z` +- For RFC 1036 dates, use `+%A, %d-%b-%y %H:%M:%S %Z` +- For a reasonably local date and time (in that order), use `+%x %X` +- For the old default behavior (compatible with ANSI C's `asctime()` function), leave the `date_format` field empty. + +### Hostname handling + +By default, Netdata will use the simple hostname for the system (the hostname with everything after the first `.` removed) when displaying the hostname in alert notifications. + +If you instead prefer to have Netdata use the host's fully qualified domain name, you can set `use_fdqn` to `YES`. + +This setting does not account for child systems for which the system you are configuring is a parent. +> ### Note +> +> If the system's host name is overridden in `/etc/netdata.conf` with the `hostname` option, that name will be used unconditionally. diff --git a/health/notifications/alarm-notify.sh.in b/health/notifications/alarm-notify.sh.in index 0090427a..51c00021 100755 --- a/health/notifications/alarm-notify.sh.in +++ b/health/notifications/alarm-notify.sh.in @@ -39,6 +39,7 @@ # - Stackpulse Event by @thiagoftsm # - Opsgenie by @thiaoftsm #9858 # - Gotify by @coffeegrind123 +# - ntfy.sh by @Dim-P # ----------------------------------------------------------------------------- # testing notifications @@ -176,6 +177,7 @@ sms hangouts dynatrace matrix +ntfy " # ----------------------------------------------------------------------------- @@ -199,7 +201,7 @@ fi [ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@" [ -z "${NETDATA_CACHE_DIR}" ] && NETDATA_CACHE_DIR="@cachedir_POST@" [ -z "${NETDATA_REGISTRY_URL}" ] && NETDATA_REGISTRY_URL="https://registry.my-netdata.io" -[ -z "${NETDATA_REGISTRY_CLOUD_BASE_URL}" ] && NETDATA_REGISTRY_CLOUD_BASE_URL="https://api.netdata.cloud" +[ -z "${NETDATA_REGISTRY_CLOUD_BASE_URL}" ] && NETDATA_REGISTRY_CLOUD_BASE_URL="https://app.netdata.cloud" # ----------------------------------------------------------------------------- # parse command line parameters @@ -654,6 +656,9 @@ filter_recipient_by_criticality() { # check gotify { [ -z "${GOTIFY_APP_TOKEN}" ] || [ -z "${GOTIFY_APP_URL}" ]; } && SEND_GOTIFY="NO" +# check ntfy +[ -z "${DEFAULT_RECIPIENT_NTFY}" ] && SEND_NTFY="NO" + # check stackpulse [ -z "${STACKPULSE_WEBHOOK}" ] && SEND_STACKPULSE="NO" @@ -692,7 +697,8 @@ if [ "${SEND_PUSHOVER}" = "YES" ] || [ "${SEND_DYNATRACE}" = "YES" ] || [ "${SEND_STACKPULSE}" = "YES" ] || [ "${SEND_OPSGENIE}" = "YES" ] || - [ "${SEND_GOTIFY}" = "YES" ]; then + [ "${SEND_GOTIFY}" = "YES" ] || + [ "${SEND_NTFY}" = "YES" ]; then # if we need curl, check for the curl command if [ -z "${curl}" ]; then curl="$(command -v curl 2>/dev/null)" @@ -723,6 +729,7 @@ if [ "${SEND_PUSHOVER}" = "YES" ] || SEND_STACKPULSE="NO" SEND_OPSGENIE="NO" SEND_GOTIFY="NO" + SEND_NTFY="NO" fi fi @@ -863,7 +870,8 @@ for method in "${SEND_EMAIL}" \ "${SEND_DYNATRACE}" \ "${SEND_STACKPULSE}" \ "${SEND_OPSGENIE}" \ - "${SEND_GOTIFY}" ; do + "${SEND_GOTIFY}" \ + "${SEND_NTFY}" ; do if [ "${method}" == "YES" ]; then proceed=1 @@ -2313,7 +2321,7 @@ EOF # Opsgenie sender send_opsgenie() { - local payload httpcode oldv currv + local payload httpcode oldv currv priority [ "${SEND_OPSGENIE}" != "YES" ] && return 1 if [ -z "${OPSGENIE_API_KEY}" ] ; then @@ -2321,6 +2329,14 @@ send_opsgenie() { return 1 fi + # Priority for OpsGenie alert (https://support.atlassian.com/opsgenie/docs/update-alert-priority-level/) + case "${status}" in + CRITICAL) priority="P1" ;; # Critical is P1 + WARNING) priority="P3" ;; # Warning is P3 + CLEAR) priority="P5" ;; # Clear is P5 + *) priority="P3" ;; # OpsGenie's default alert level is P3 + esac + # We are sending null when values are nan to avoid errors while JSON message is parsed [ "${old_value}" != "nan" ] && oldv="${old_value}" || oldv="null" [ "${value}" != "nan" ] && currv="${value}" || currv="null" @@ -2335,6 +2351,7 @@ send_opsgenie() { "when": ${when}, "name" : "${name}", "family" : "${family}", + "priority" : "${priority}", "status" : "${status}", "old_status" : "${old_status}", "value" : ${currv}, @@ -2402,6 +2419,50 @@ EOF return 0 } +# ----------------------------------------------------------------------------- +# ntfy sender + +send_ntfy() { + local httpcode priority recipients=${1} sent=0 msg + + [ "${SEND_NTFY}" != "YES" ] && return 1 + + case "${status}" in + WARNING) emoji="warning" ;; + CRITICAL) emoji="red_circle" ;; + CLEAR) emoji="white_check_mark" ;; + *) emoji="white_circle" ;; + esac + + case ${status} in + WARNING) priority="high";; + CRITICAL) priority="urgent";; + *) priority="default" ;; + esac + + for recipient in ${recipients}; do + msg="${host} ${status_message}: ${alarm} - ${info}" + httpcode=$(docurl -X POST \ + -H "Icon: https://raw.githubusercontent.com/netdata/netdata/master/web/gui/dashboard/images/favicon-196x196.png" \ + -H "Title: ${host}: ${name}" \ + -H "Tags: ${emoji}" \ + -H "Priority: ${priority}" \ + -H "Actions: view, View node, ${goto_url}, clear=true;" \ + -d "${msg}" \ + ${recipient}) + if [ "${httpcode}" == "200" ]; then + info "sent ntfy notification for: ${host} ${chart}.${name} is ${status} to '${recipient}'" + sent=$((sent + 1)) + else + error "failed to send ntfy notification for: ${host} ${chart}.${name} is ${status} to '${recipient}', with HTTP response status code ${httpcode}." + fi + done + + [ ${sent} -gt 0 ] && return 0 + + return 1 +} + # ----------------------------------------------------------------------------- # prepare the content of the notification @@ -3608,6 +3669,11 @@ SENT_OPSGENIE=$? send_gotify SENT_GOTIFY=$? +# ----------------------------------------------------------------------------- +# send messages to ntfy +send_ntfy "${DEFAULT_RECIPIENT_NTFY}" +SENT_NTFY=$? + # ----------------------------------------------------------------------------- # let netdata know for state in "${SENT_EMAIL}" \ @@ -3638,7 +3704,8 @@ for state in "${SENT_EMAIL}" \ "${SENT_DYNATRACE}" \ "${SENT_STACKPULSE}" \ "${SENT_OPSGENIE}" \ - "${SENT_GOTIFY}"; do + "${SENT_GOTIFY}" \ + "${SENT_NTFY}"; do if [ "${state}" -eq 0 ]; then # we sent something exit 0 diff --git a/health/notifications/alerta/README.md b/health/notifications/alerta/README.md index 5ecf55ee..237b9a78 100644 --- a/health/notifications/alerta/README.md +++ b/health/notifications/alerta/README.md @@ -1,86 +1,76 @@ - +# Alerta Agent alert notifications -# alerta.io +Learn how to send notifications to Alerta using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. -The [Alerta](https://alerta.io) monitoring system is a tool used to -consolidate and de-duplicate alerts from multiple sources for quick -‘at-a-glance’ visualisation. With just one system you can monitor -alerts from many other monitoring tools on a single screen. +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. -![Alerta dashboard](https://docs.alerta.io/_images/alerta-screen-shot-3.png "Alerta dashboard showing several alerts.") +The [Alerta](https://alerta.io) monitoring system is a tool used to consolidate and de-duplicate alerts from multiple sources for quick ‘at-a-glance’ visualization. +With just one system you can monitor alerts from many other monitoring tools on a single screen. -Alerta's advantage is the main view, where you can see all active alarms with the most recent state. You can also view an alert history. You can send Netdata alerts to Alerta to see alerts coming from many Netdata hosts or also from a multi-host -Netdata configuration. +![Alerta dashboard showing several alerts](https://docs.alerta.io/_images/alerta-screen-shot-3.png) -## Deploying Alerta +Alerta's advantage is the main view, where you can see all active alert with the most recent state. +You can also view an alert history. -The recommended setup is using a dedicated server, VM or container. If you have other NGINX or Apache servers in your organization, -it is recommended to proxy to this new server. +You can send Netdata alerts to Alerta to see alerts coming from many Netdata hosts or also from a multi-host Netdata configuration. -You can install Alerta in several ways: -- **Docker**: Alerta provides a [Docker image](https://hub.docker.com/r/alerta/alerta-web/) to get you started quickly. -- **Deployment on Ubuntu server**: Alerta's [getting started tutorial](https://docs.alerta.io/gettingstarted/tutorial-1-deploy-alerta.html) walks you through this process. -- **Advanced deployment scenarios**: More ways to install and deploy Alerta are documented on the [Alerta docs](http://docs.alerta.io/en/latest/deployment.html). +## Prerequisites -## Sending alerts to Alerta +You need: -### Step 1. Create an API key (if authentication in Alerta is enabled) +- an Alerta instance +- an Alerta API key (if authentication in Alerta is enabled) +- terminal access to the Agent you wish to configure -You will need an API key to send messages from any source, if -Alerta is configured to use authentication (recommended). +## Configure Netdata to send alert notifications to Alerta -Create a new API key in Alerta: -1. Go to *Configuration* > *API Keys* -2. Create a new API key called "netdata" with `write:alerts` permission. +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. -### Step 2. Configure Netdata to send alerts to Alerta -1. Edit the `health_alarm_notify.conf` by running: -```sh -/etc/netdata/edit-config health_alarm_notify.conf -``` +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: -2. Modify the file as below: -``` -# enable/disable sending alerta notifications -SEND_ALERTA="YES" +1. Set `SEND_ALERTA` to `YES`. +2. set `ALERTA_WEBHOOK_URL` to the API url you defined when you installed the Alerta server. +3. Set `ALERTA_API_KEY` to your API key. + You will need an API key to send messages from any source, if Alerta is configured to use authentication (recommended). To create a new API key: + 1. Go to *Configuration* > *API Keys*. + 2. Create a new API key called "netdata" with `write:alerts` permission. +4. Set `DEFAULT_RECIPIENT_ALERTA` to the default recipient environment you want the alert notifications to be sent to. + All roles will default to this variable if left unconfigured. -# here set your alerta server API url -# this is the API url you defined when installed Alerta server, -# it is the same for all users. Do not include last slash. -ALERTA_WEBHOOK_URL="http://yourserver/alerta/api" +You can then have different recipient environments per **role**, by editing `DEFAULT_RECIPIENT_CUSTOM` with the environment name you want, in the following entries at the bottom of the same file: -# Login with an administrative user to you Alerta server and create an API KEY -# with write permissions. -ALERTA_API_KEY="INSERT_YOUR_API_KEY_HERE" - -# you can define environments in /etc/alertad.conf option ALLOWED_ENVIRONMENTS -# standard environments are Production and Development -# if a role's recipients are not configured, a notification will be send to -# this Environment (empty = do not send a notification for unconfigured roles): -DEFAULT_RECIPIENT_ALERTA="Production" +```conf +role_recipients_alerta[sysadmin]="Systems" +role_recipients_alerta[domainadmin]="Domains" +role_recipients_alerta[dba]="Databases Systems" +role_recipients_alerta[webmaster]="Marketing Development" +role_recipients_alerta[proxyadmin]="Proxy" +role_recipients_alerta[sitemgr]="Sites" ``` -## Test alarms +The values you provide should be defined as environments in `/etc/alertad.conf` option `ALLOWED_ENVIRONMENTS`. -We can test alarms using the standard approach: +An example working configuration would be: -```sh -/opt/netdata/netdata-plugins/plugins.d/alarm-notify.sh test -``` - -> **Note** This script will send 3 alarms. -> Alerta will not show the alerts in the main page, because last alarm is "CLEAR". -> To see the test alarms, you need to select "closed" alarms in the top-right lookup. +```conf +#------------------------------------------------------------------------------ +# alerta (alerta.io) global notification options -For more information see the [Alerta documentation](https://docs.alerta.io) +SEND_ALERTA="YES" +ALERTA_WEBHOOK_URL="http://yourserver/alerta/api" +ALERTA_API_KEY="INSERT_YOUR_API_KEY_HERE" +DEFAULT_RECIPIENT_ALERTA="Production" +``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/awssns/README.md b/health/notifications/awssns/README.md index 97768399..f02e7091 100644 --- a/health/notifications/awssns/README.md +++ b/health/notifications/awssns/README.md @@ -1,31 +1,29 @@ - - -# Amazon SNS - -As part of its AWS suite, Amazon provides a notification broker service called 'Simple Notification Service' (SNS). Amazon SNS works similarly to Netdata's own notification system, allowing to dispatch a single notification to multiple subscribers of different types. While Amazon SNS supports sending differently formatted messages for different delivery methods, Netdata does not currently support this functionality. -Among other things, SNS supports sending notifications to: - -- Email addresses. -- Mobile Phones via SMS. -- HTTP or HTTPS web hooks. -- AWS Lambda functions. -- AWS SQS queues. -- Mobile applications via push notifications. - -For email notification support, we recommend using Netdata's email notifications, as it is has the following benefits: +# Amazon SNS Agent alert notifications + +Learn how to send notifications through Amazon SNS using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. + +As part of its AWS suite, Amazon provides a notification broker service called 'Simple Notification Service' (SNS). Amazon SNS works similarly to Netdata's own notification system, allowing to dispatch a single notification to multiple subscribers of different types. Among other things, SNS supports sending notifications to: + +- email addresses +- mobile Phones via SMS +- HTTP or HTTPS web hooks +- AWS Lambda functions +- AWS SQS queues +- mobile applications via push notifications + +> ### Note +> +> While Amazon SNS supports sending differently formatted messages for different delivery methods, Netdata does not currently support this functionality. + +For email notification support, we recommend using Netdata's [email notifications](https://github.com/netdata/netdata/blob/master/health/notifications/email/README.md), as it is has the following benefits: - In most cases, it requires less configuration. - Netdata's emails are nicely pre-formatted and support features like threading, which requires a lot of manual effort in SNS. -- It is less resource intensive and more cost-efficient than SNS. +- It is less resource intensive and more cost-efficient than SNS. Read on to learn how to set up Amazon SNS in Netdata. @@ -33,26 +31,97 @@ Read on to learn how to set up Amazon SNS in Netdata. Before you can enable SNS, you need: -- The [Amazon Web Services CLI tools](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) (`awscli`). -- An actual home directory for the user you run Netdata as, instead of just using `/` as a home directory. The setup depends on the distribution, but `/var/lib/netdata` is the recommended directory. If you are using Netdata as a dedicated user, the permissions will already be correct. -- An Amazon SNS topic to send notifications to with one or more subscribers. The [Getting Started](https://docs.aws.amazon.com/sns/latest/dg/sns-getting-started.html) section of the Amazon SNS documentation covers the basics of how to set this up. Make note of the **Topic ARN** when you create the topic. -- While not mandatory, it is highly recommended to create a dedicated IAM user on your account for Netdata to send notifications. This user needs to have programmatic access, and should only allow access to SNS. For an additional layer of security, you can create one for each system or group of systems. - -## Enabling Amazon SNS - -To enable SNS: -1. Run the following command as the user Netdata runs under: - ``` - aws configure - ``` -2. Enter the access key and secret key for accessing Amazon SNS. The system also prompts you to enter the default region and output format, but you can leave those blank because Netdata doesn't use them. - -3. Specify the desired topic ARN as a recipient, see [SNS documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/US_SetupSNS.html#set-up-sns-topic-cli). -4. Optional: To change the notification format for SNS notifications, change the `AWSSNS_MESSAGE_FORMAT` variable in `health_alarm_notify.conf`. -This variable supports all the same variables you can use in custom notifications. - - The default format looks like this: - ```bash - AWSSNS_MESSAGE_FORMAT="${status} on ${host} at ${date}: ${chart} ${value_string}" - ``` - +- The [Amazon Web Services CLI tools](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) (`awscli`). +- An actual home directory for the user you run Netdata as, instead of just using `/` as a home directory. + The setup depends on the distribution, but `/var/lib/netdata` is the recommended directory. If you are using Netdata as a dedicated user, the permissions will already be correct. +- An Amazon SNS topic to send notifications to with one or more subscribers. + The [Getting Started](https://docs.aws.amazon.com/sns/latest/dg/sns-getting-started.html) section of the Amazon SNS documentation covers the basics of how to set this up. Make note of the **Topic ARN** when you create the topic. +- While not mandatory, it is highly recommended to create a dedicated IAM user on your account for Netdata to send notifications. + This user needs to have programmatic access, and should only allow access to SNS. For an additional layer of security, you can create one for each system or group of systems. +- Terminal access to the Agent you wish to configure. + +## Configure Netdata to send alert notifications to Amazon SNS + +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. + +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: + +1. Set `SEND_AWSNS` to `YES`. +2. Set `AWSSNS_MESSAGE_FORMAT` to the string that you want the alert to be sent into. + + The supported variables are: + + | Variable name | Description | + |:---------------------------:|:---------------------------------------------------------------------------------| + | `${alarm}` | Like "name = value units" | + | `${status_message}` | Like "needs attention", "recovered", "is critical" | + | `${severity}` | Like "Escalated to CRITICAL", "Recovered from WARNING" | + | `${raised_for}` | Like "(alarm was raised for 10 minutes)" | + | `${host}` | The host generated this event | + | `${url_host}` | Same as ${host} but URL encoded | + | `${unique_id}` | The unique id of this event | + | `${alarm_id}` | The unique id of the alarm that generated this event | + | `${event_id}` | The incremental id of the event, for this alarm id | + | `${when}` | The timestamp this event occurred | + | `${name}` | The name of the alarm, as given in netdata health.d entries | + | `${url_name}` | Same as ${name} but URL encoded | + | `${chart}` | The name of the chart (type.id) | + | `${url_chart}` | Same as ${chart} but URL encoded | + | `${family}` | The family of the chart | + | `${url_family}` | Same as ${family} but URL encoded | + | `${status}` | The current status : REMOVED, UNINITIALIZED, UNDEFINED, CLEAR, WARNING, CRITICAL | + | `${old_status}` | The previous status: REMOVED, UNINITIALIZED, UNDEFINED, CLEAR, WARNING, CRITICAL | + | `${value}` | The current value of the alarm | + | `${old_value}` | The previous value of the alarm | + | `${src}` | The line number and file the alarm has been configured | + | `${duration}` | The duration in seconds of the previous alarm state | + | `${duration_txt}` | Same as ${duration} for humans | + | `${non_clear_duration}` | The total duration in seconds this is/was non-clear | + | `${non_clear_duration_txt}` | Same as ${non_clear_duration} for humans | + | `${units}` | The units of the value | + | `${info}` | A short description of the alarm | + | `${value_string}` | Friendly value (with units) | + | `${old_value_string}` | Friendly old value (with units) | + | `${image}` | The URL of an image to represent the status of the alarm | + | `${color}` | A color in AABBCC format for the alarm | + | `${goto_url}` | The URL the user can click to see the netdata dashboard | + | `${calc_expression}` | The expression evaluated to provide the value for the alarm | + | `${calc_param_values}` | The value of the variables in the evaluated expression | + | `${total_warnings}` | The total number of alarms in WARNING state on the host | + | `${total_critical}` | The total number of alarms in CRITICAL state on the host | + +3. Set `DEFAULT_RECIPIENT_AWSSNS` to the Topic ARN you noted down upon creating the Topic. + All roles will default to this variable if left unconfigured. + +You can then have different recipient Topics per **role**, by editing `DEFAULT_RECIPIENT_AWSSNS` with the Topic ARN you want, in the following entries at the bottom of the same file: + +```conf +role_recipients_awssns[sysadmin]="arn:aws:sns:us-east-2:123456789012:Systems" +role_recipients_awssns[domainadmin]="arn:aws:sns:us-east-2:123456789012:Domains" +role_recipients_awssns[dba]="arn:aws:sns:us-east-2:123456789012:Databases" +role_recipients_awssns[webmaster]="arn:aws:sns:us-east-2:123456789012:Development" +role_recipients_awssns[proxyadmin]="arn:aws:sns:us-east-2:123456789012:Proxy" +role_recipients_awssns[sitemgr]="arn:aws:sns:us-east-2:123456789012:Sites" +``` + +An example working configuration would be: + +```conf +#------------------------------------------------------------------------------ +# Amazon SNS notifications + +SEND_AWSSNS="YES" +AWSSNS_MESSAGE_FORMAT="${status} on ${host} at ${date}: ${chart} ${value_string}" +DEFAULT_RECIPIENT_AWSSNS="arn:aws:sns:us-east-2:123456789012:MyTopic" +``` + +## Test the notification method + +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/custom/README.md b/health/notifications/custom/README.md index df8f88e4..ad64cea2 100644 --- a/health/notifications/custom/README.md +++ b/health/notifications/custom/README.md @@ -1,29 +1,128 @@ - - -# Custom - -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`](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`. +# Custom Agent alert notifications + +Netdata Agent's alert notification feature allows you to send custom notifications to any endpoint you choose. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. + +## Prerequisites + +You need to have terminal access to the Agent you wish to configure. + +## Configure Netdata to send alert notifications to a custom endpoint + +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. + +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: + +1. Set `SEND_CUSTOM` to `YES`. +2. The `DEFAULT_RECIPIENT_CUSTOM`'s value is dependent on how you handle the `${to}` variable inside the `custom_sender()` function. + All roles will default to this variable if left unconfigured. +3. Edit the `custom_sender()` function. + You can look at the other senders in `/usr/libexec/netdata/plugins.d/alarm-notify.sh` for examples of how to modify the function in this configuration file. + + The following is a sample `custom_sender()` function in `health_alarm_notify.conf`, to send an SMS via an imaginary HTTPS endpoint to the SMS gateway: + + ```sh + custom_sender() { + # example human readable SMS + local msg="${host} ${status_message}: ${alarm} ${raised_for}" + + # limit it to 160 characters and encode it for use in a URL + urlencode "${msg:0:160}" >/dev/null; msg="${REPLY}" + + # a space separated list of the recipients to send alarms to + to="${1}" + + for phone in ${to}; do + httpcode=$(docurl -X POST \ + --data-urlencode "From=XXX" \ + --data-urlencode "To=${phone}" \ + --data-urlencode "Body=${msg}" \ + -u "${accountsid}:${accounttoken}" \ + https://domain.website.com/) + + if [ "${httpcode}" = "200" ]; then + info "sent custom notification ${msg} to ${phone}" + sent=$((sent + 1)) + else + error "failed to send custom notification ${msg} to ${phone} with HTTP error code ${httpcode}." + fi + done + } + ``` + + The supported variables that you can use for the function's `msg` variable are: + + | Variable name | Description | + |:---------------------------:|:---------------------------------------------------------------------------------| + | `${alarm}` | Like "name = value units" | + | `${status_message}` | Like "needs attention", "recovered", "is critical" | + | `${severity}` | Like "Escalated to CRITICAL", "Recovered from WARNING" | + | `${raised_for}` | Like "(alarm was raised for 10 minutes)" | + | `${host}` | The host generated this event | + | `${url_host}` | Same as ${host} but URL encoded | + | `${unique_id}` | The unique id of this event | + | `${alarm_id}` | The unique id of the alarm that generated this event | + | `${event_id}` | The incremental id of the event, for this alarm id | + | `${when}` | The timestamp this event occurred | + | `${name}` | The name of the alarm, as given in netdata health.d entries | + | `${url_name}` | Same as ${name} but URL encoded | + | `${chart}` | The name of the chart (type.id) | + | `${url_chart}` | Same as ${chart} but URL encoded | + | `${family}` | The family of the chart | + | `${url_family}` | Same as ${family} but URL encoded | + | `${status}` | The current status : REMOVED, UNINITIALIZED, UNDEFINED, CLEAR, WARNING, CRITICAL | + | `${old_status}` | The previous status: REMOVED, UNINITIALIZED, UNDEFINED, CLEAR, WARNING, CRITICAL | + | `${value}` | The current value of the alarm | + | `${old_value}` | The previous value of the alarm | + | `${src}` | The line number and file the alarm has been configured | + | `${duration}` | The duration in seconds of the previous alarm state | + | `${duration_txt}` | Same as ${duration} for humans | + | `${non_clear_duration}` | The total duration in seconds this is/was non-clear | + | `${non_clear_duration_txt}` | Same as ${non_clear_duration} for humans | + | `${units}` | The units of the value | + | `${info}` | A short description of the alarm | + | `${value_string}` | Friendly value (with units) | + | `${old_value_string}` | Friendly old value (with units) | + | `${image}` | The URL of an image to represent the status of the alarm | + | `${color}` | A color in AABBCC format for the alarm | + | `${goto_url}` | The URL the user can click to see the netdata dashboard | + | `${calc_expression}` | The expression evaluated to provide the value for the alarm | + | `${calc_param_values}` | The value of the variables in the evaluated expression | + | `${total_warnings}` | The total number of alarms in WARNING state on the host | + | `${total_critical}` | The total number of alarms in CRITICAL state on the host | + +You can then have different `${to}` variables per **role**, by editing `DEFAULT_RECIPIENT_CUSTOM` with the variable you want, in the following entries at the bottom of the same file: + +```conf +role_recipients_custom[sysadmin]="systems" +role_recipients_custom[domainadmin]="domains" +role_recipients_custom[dba]="databases systems" +role_recipients_custom[webmaster]="marketing development" +role_recipients_custom[proxyadmin]="proxy-admin" +role_recipients_custom[sitemgr]="sites" +``` -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`. +An example working configuration would be: -As with other notifications, you will also need to define the recipient list in `DEFAULT_RECIPIENT_CUSTOM` and/or the `role_recipients_custom` array. +```conf +#------------------------------------------------------------------------------ +# custom notifications -The following is a sample `custom_sender` function in `health_alarm_notify.conf`, to send an SMS via an imaginary HTTPS endpoint to the SMS gateway: +SEND_CUSTOM="YES" +DEFAULT_RECIPIENT_CUSTOM="" -``` - custom_sender() { +# The custom_sender() is a custom function to do whatever you need to do +custom_sender() { # example human readable SMS local msg="${host} ${status_message}: ${alarm} ${raised_for}" @@ -35,63 +134,22 @@ The following is a sample `custom_sender` function in `health_alarm_notify.conf` for phone in ${to}; do httpcode=$(docurl -X POST \ - --data-urlencode "From=XXX" \ - --data-urlencode "To=${phone}" \ - --data-urlencode "Body=${msg}" \ - -u "${accountsid}:${accounttoken}" \ + --data-urlencode "From=XXX" \ + --data-urlencode "To=${phone}" \ + --data-urlencode "Body=${msg}" \ + -u "${accountsid}:${accounttoken}" \ https://domain.website.com/) - if [ "${httpcode}" = "200" ]; then - info "sent custom notification ${msg} to ${phone}" - sent=$((sent + 1)) - else - error "failed to send custom notification ${msg} to ${phone} with HTTP error code ${httpcode}." - fi + if [ "${httpcode}" = "200" ]; then + info "sent custom notification ${msg} to ${phone}" + sent=$((sent + 1)) + else + error "failed to send custom notification ${msg} to ${phone} with HTTP error code ${httpcode}." + fi done } ``` -Variables available to the custom_sender: - -- `${to_custom}` the list of recipients for the alarm -- `${host}` the host generated this event -- `${url_host}` same as `${host}` but URL encoded -- `${unique_id}` the unique id of this event -- `${alarm_id}` the unique id of the alarm that generated this event -- `${event_id}` the incremental id of the event, for this alarm id -- `${when}` the timestamp this event occurred -- `${name}` the name of the alarm, as given in Netdata health.d entries -- `${url_name}` same as `${name}` but URL encoded -- `${chart}` the name of the chart (type.id) -- `${url_chart}` same as `${chart}` but URL encoded -- `${family}` the family of the chart -- `${url_family}` same as `${family}` but URL encoded -- `${status}` the current status : REMOVED, UNINITIALIZED, UNDEFINED, CLEAR, WARNING, CRITICAL -- `${old_status}` the previous status: REMOVED, UNINITIALIZED, UNDEFINED, CLEAR, WARNING, CRITICAL -- `${value}` the current value of the alarm -- `${old_value}` the previous value of the alarm -- `${src}` the line number and file the alarm has been configured -- `${duration}` the duration in seconds of the previous alarm state -- `${duration_txt}` same as `${duration}` for humans -- `${non_clear_duration}` the total duration in seconds this is/was non-clear -- `${non_clear_duration_txt}` same as `${non_clear_duration}` for humans -- `${units}` the units of the value -- `${info}` a short description of the alarm -- `${value_string}` friendly value (with units) -- `${old_value_string}` friendly old value (with units) -- `${image}` the URL of an image to represent the status of the alarm -- `${color}` a color in #AABBCC format for the alarm -- `${goto_url}` the URL the user can click to see the Netdata dashboard -- `${calc_expression}` the expression evaluated to provide the value for the alarm -- `${calc_param_values}` the value of the variables in the evaluated expression -- `${total_warnings}` the total number of alarms in WARNING state on the host -- `${total_critical}` the total number of alarms in CRITICAL state on the host - -The following are more human friendly: - -- `${alarm}` like "name = value units" -- `${status_message}` like "needs attention", "recovered", "is critical" -- `${severity}` like "Escalated to CRITICAL", "Recovered from WARNING" -- `${raised_for}` like "(alarm was raised for 10 minutes)" - +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/discord/README.md b/health/notifications/discord/README.md index b4cbce53..b4aa7fd9 100644 --- a/health/notifications/discord/README.md +++ b/health/notifications/discord/README.md @@ -1,53 +1,71 @@ - - -# Discord.com +# Discord Agent alert notifications + +Learn how to send notifications to Discord using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. This is what you will get: ![image](https://cloud.githubusercontent.com/assets/7321975/22215935/b49ede7e-e162-11e6-98d0-ae8541e6b92e.png) -You need: +## Prerequisites -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. +You will need: -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: +- 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). +- one or more Discord channels to post the messages to +- terminal access to the Agent you wish to configure -``` -############################################################################### -# sending discord notifications +## Configure Netdata to send alert notifications to Discord -# note: multiple recipients can be given like this: -# "CHANNEL1 CHANNEL2 ..." +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. -# enable/disable sending discord notifications -SEND_DISCORD="YES" +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: -# Create a webhook by following the official documentation - -# https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks -DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/XXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +1. Set `SEND_DISCORD` to `YES`. +2. Set `DISCORD_WEBHOOK_URL` to your webhook URL. +3. Set `DEFAULT_RECIPIENT_DISCORD` to the channel you want the alert notifications to be sent to. + You can define multiple channels like this: `alerts systems`. + All roles will default to this variable if left unconfigured. -# 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 -# roles): -DEFAULT_RECIPIENT_DISCORD="alarms" -``` + > ### Note + > + > You don't have to include the hashtag "#" of the channel, just its name. -You can define multiple channels like this: `alarms systems`. -You can give different channels per **role** using these (at the same file): +You can then have different channels per **role**, by editing `DEFAULT_RECIPIENT_DISCORD` with the channel you want, in the following entries at the bottom of the same file: -``` +```conf role_recipients_discord[sysadmin]="systems" +role_recipients_discord[domainadmin]="domains" role_recipients_discord[dba]="databases systems" role_recipients_discord[webmaster]="marketing development" +role_recipients_discord[proxyadmin]="proxy-admin" +role_recipients_discord[sitemgr]="sites" ``` -The keywords `systems`, `databases`, `marketing`, `development` are discord.com channels (they should already exist within your discord server). +The values you provide should already exist as Discord channels in your server. + +An example of a working configuration would be: + +```conf +#------------------------------------------------------------------------------ +# discord (discordapp.com) global notification options + +SEND_DISCORD="YES" +DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/XXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +DEFAULT_RECIPIENT_DISCORD="alerts" +``` + +## Test the notification method + +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/dynatrace/README.md b/health/notifications/dynatrace/README.md index a3668393..7665d0ca 100644 --- a/health/notifications/dynatrace/README.md +++ b/health/notifications/dynatrace/README.md @@ -1,39 +1,66 @@ - - -# Dynatrace +# Dynatrace Agent alert notifications + +Learn how to send notifications to Dynatrace using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. Dynatrace allows you to receive notifications using their Events REST API. -See [the Dynatrace documentation](https://www.dynatrace.com/support/help/extend-dynatrace/dynatrace-api/environment-api/events/post-event/) about POSTing an event in the Events API for more details. - - - -You need: - -1. Dynatrace Server. You can use the same on all your Netdata servers but make sure the server is network visible from your Netdata hosts. -The Dynatrace server should be with protocol prefixed (`http://` or `https://`). For example: `https://monitor.example.com` -This is a required parameter. -2. API Token. Generate a secure access API token that enables access to your Dynatrace monitoring data via the REST-based API. -Generate a Dynatrace API authentication token. On your Dynatrace server, go to **Settings** --> **Integration** --> **Dynatrace API** --> **Generate token**. -See [Dynatrace API - Authentication](https://www.dynatrace.com/support/help/extend-dynatrace/dynatrace-api/basics/dynatrace-api-authentication/) for more details. -This is a required parameter. -3. API Space. This is the URL part of the page you have access in order to generate the API Token. For example, the URL - for a generated API token might look like: - `https://monitor.illumineit.com/e/2a93fe0e-4cd5-469a-9d0d-1a064235cfce/#settings/integration/apikeys;gf=all` In that - case, my space is _2a93fe0e-4cd5-469a-9d0d-1a064235cfce_ This is a required parameter. -4. Generate a Server Tag. On your Dynatrace Server, go to **Settings** --> **Tags** --> **Manually applied tags** and create the Tag. -The Netdata alarm is sent as a Dynatrace Event to be correlated with all those hosts tagged with this Tag you have created. -This is a required parameter. -5. Specify the Dynatrace event. This can be one of `CUSTOM_INFO`, `CUSTOM_ANNOTATION`, `CUSTOM_CONFIGURATION`, and `CUSTOM_DEPLOYMENT`. -The default value is `CUSTOM_INFO`. -This is a required parameter. -6. Specify the annotation type. This is the source of the Dynatrace event. Put whatever it fits you, for example, -_Netdata Alarm_, which is also the default value. +See [the Dynatrace documentation](https://www.dynatrace.com/support/help/dynatrace-api/environment-api/events-v2/post-event) about POSTing an event in the Events API for more details. + +## Prerequisites + +You will need: + +- A Dynatrace Server. You can use the same on all your Netdata servers but make sure the server is network visible from your Netdata hosts. + The Dynatrace server should be with protocol prefixed (`http://` or `https://`), for example: `https://monitor.example.com`. +- An API Token. Generate a secure access API token that enables access to your Dynatrace monitoring data via the REST-based API. + See [Dynatrace API - Authentication](https://www.dynatrace.com/support/help/extend-dynatrace/dynatrace-api/basics/dynatrace-api-authentication/) for more details. +- An API Space. This is the URL part of the page you have access in order to generate the API Token. + For example, the URL for a generated API token might look like: `https://monitor.illumineit.com/e/2a93fe0e-4cd5-469a-9d0d-1a064235cfce/#settings/integration/apikeys;gf=all` In that case, the Space is `2a93fe0e-4cd5-469a-9d0d-1a064235cfce`. +- A Server Tag. To generate one on your Dynatrace Server, go to **Settings** --> **Tags** --> **Manually applied tags** and create the Tag. + The Netdata alarm is sent as a Dynatrace Event to be correlated with all those hosts tagged with this Tag you have created. +- terminal access to the Agent you wish to configure + +## Configure Netdata to send alert notifications to Dynatrace + +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. + +Edit `health_alarm_notify.conf`: + +1. Set `SEND_DYNATRACE` to `YES`. +2. Set `DYNATRACE_SERVER` to the Dynatrace server with the protocol prefix, for example `https://monitor.example.com`. +3. Set `DYNATRACE_TOKEN` to your Dynatrace API authentication token +4. Set `DYNATRACE_SPACE` to the API Space, it is the URL part of the page you have access in order to generate the API Token. For example, the URL for a generated API token might look like: `https://monitor.illumineit.com/e/2a93fe0e-4cd5-469a-9d0d-1a064235cfce/#settings/integration/apikeys;gf=all` In that case, the Space is `2a93fe0e-4cd5-469a-9d0d-1a064235cfce`. +5. Set `DYNATRACE_TAG_VALUE` to your Dynatrace Server Tag. +6. `DYNATRACE_ANNOTATION_TYPE` can be left to its default value `Netdata Alarm`, but you can change it to better fit your needs. +7. Set `DYNATRACE_EVENT` to the Dynatrace `eventType` you want, possible values are: + `AVAILABILITY_EVENT`, `CUSTOM_ALERT`, `CUSTOM_ANNOTATION`, `CUSTOM_CONFIGURATION`, `CUSTOM_DEPLOYMENT`, `CUSTOM_INFO`, `ERROR_EVENT`, `MARKED_FOR_TERMINATION`, `PERFORMANCE_EVENT`, `RESOURCE_CONTENTION_EVENT`. You can read more [here](https://www.dynatrace.com/support/help/dynatrace-api/environment-api/events-v2/post-event#request-body-objects) + +An example of a working configuration would be: + +```conf +#------------------------------------------------------------------------------ +# Dynatrace global notification options + +SEND_DYNATRACE="YES" +DYNATRACE_SERVER="https://monitor.example.com" +DYNATRACE_TOKEN="XXXXXXX" +DYNATRACE_SPACE="2a93fe0e-4cd5-469a-9d0d-1a064235cfce" +DYNATRACE_TAG_VALUE="SERVERTAG" +DYNATRACE_ANNOTATION_TYPE="Netdata Alert" +DYNATRACE_EVENT="AVAILABILITY_EVENT" +``` + +## Test the notification method + +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/email/README.md b/health/notifications/email/README.md index 01dfd0e6..2470ac4d 100644 --- a/health/notifications/email/README.md +++ b/health/notifications/email/README.md @@ -1,53 +1,83 @@ - +# Email Agent alert notifications -# Email +Learn how to send notifications via Email using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. -You need a working `sendmail` command for email alerts to work. Almost all MTAs provide a `sendmail` interface. +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. -Netdata sends all emails as user `netdata`, so make sure your `sendmail` works for local users. +Email notifications look like this: -email notifications look like this: +Email notification screenshot -![image](https://user-images.githubusercontent.com/1905463/133216974-a2ca0e4f-787b-4dce-b1b2-9996a8c5f718.png) +## Prerequisites -## Configuration +You will need: -To edit `health_alarm_notify.conf` on your system run `/etc/netdata/edit-config health_alarm_notify.conf`. +- A working `sendmail` command for email alerts to work. Almost all MTAs provide a `sendmail` interface. + Netdata sends all emails as user `netdata`, so make sure your `sendmail` works for local users. -You can configure recipients in [`/etc/netdata/health_alarm_notify.conf`](https://github.com/netdata/netdata/blob/99d44b7d0c4e006b11318a28ba4a7e7d3f9b3bae/conf.d/health_alarm_notify.conf#L101). + > ### Note + > + > If you are using our Docker images, or are running Netdata on a system that does not have a working `sendmail` command, see [the section below about using msmtp in place of sendmail](#using-msmtp-instead-of-sendmail). +- terminal access to the Agent you wish to configure -You can also configure per role recipients [in the same file, a few lines below](https://github.com/netdata/netdata/blob/99d44b7d0c4e006b11318a28ba4a7e7d3f9b3bae/conf.d/health_alarm_notify.conf#L313). +## Configure Netdata to send alerts via Email -Changes to this file do not require a Netdata restart. +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. -You can test your configuration by issuing the commands: +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: -```sh -# become user netdata -sudo su -s /bin/bash netdata +1. You can change `EMAIL_SENDER` to the email address sending the notifications, the default is the system user Netdata runs as, usually being `netdata`. + Supported formats are: -# send a test alarm -/usr/libexec/netdata/plugins.d/alarm-notify.sh test [ROLE] + ```conf + EMAIL_SENDER="user@domain" + EMAIL_SENDER="User Name " + EMAIL_SENDER="'User Name' " + EMAIL_SENDER="\"User Name\" " + ``` + +2. Set `SEND_EMAIL` to `YES`. +3. Set `DEFAULT_RECIPIENT_EMAIL` to the email address you want the email to be sent by default. + You can define multiple email addresses like this: `alarms@example.com systems@example.com`. + All roles will default to this variable if left unconfigured. +4. There are also other optional configuration entries that can be found in the same section of the file. + +You can then have different email addresses per **role**, by editing `DEFAULT_RECIPIENT_EMAIL` with the email address you want, in the following entries at the bottom of the same file: + +```conf +role_recipients_email[sysadmin]="systems@example.com" +role_recipients_email[domainadmin]="domains@example.com" +role_recipients_email[dba]="databases@example.com systems@example.com" +role_recipients_email[webmaster]="marketing@example.com development@example.com" +role_recipients_email[proxyadmin]="proxy-admin@example.com" +role_recipients_email[sitemgr]="sites@example.com" ``` -Where `[ROLE]` is the role you want to test. The default (if you don't give a `[ROLE]`) is `sysadmin`. +An example of a working configuration would be: + +```conf +#------------------------------------------------------------------------------ +# email global notification options -Note that in versions before 1.16, the plugins.d directory may be installed in a different location in certain OSs (e.g. under `/usr/lib/netdata`). -You can always find the location of the alarm-notify.sh script in `netdata.conf`. +EMAIL_SENDER="example@domain.com" +SEND_EMAIL="YES" +DEFAULT_RECIPIENT_EMAIL="recipient@example.com" +``` -## Filtering +### Filtering Every notification email (both the plain text and the rich html versions) from the Netdata agent, contain a set of custom email headers that can be used for filtering using an email client. Example: -``` +```conf X-Netdata-Severity: warning X-Netdata-Alert-Name: inbound_packets_dropped_ratio X-Netdata-Chart: net_packets.enp2s0 @@ -57,26 +87,37 @@ X-Netdata-Host: winterland X-Netdata-Role: sysadmin ``` -## Simple SMTP transport configuration +### Using msmtp instead of sendmail -If you want an alternative to `sendmail` in order to have a simple MTA configuration for sending emails and auth to an existing SMTP server, you can do the following: +[msmtp](https://marlam.de/msmtp/) provides a simple alternative to a full-blown local mail server and `sendmail` +that will still allow you to send email notifications. It comes pre-installed in our Docker images, and is available +on most distributions in the system package repositories. -- Install `msmtp`. -- Modify the `sendmail` path in `health_alarm_notify.conf` to point to the location of `msmtp`: -``` -# The full path to the sendmail command. -# If empty, the system $PATH will be searched for it. -# If not found, email notifications will be disabled (silently). -sendmail="/usr/bin/msmtp" -``` -- Login as netdata : -```sh -(sudo) su -s /bin/bash netdata -``` -- Configure `~/.msmtprc` as shown [in the documentation](https://marlam.de/msmtp/documentation/). -- Finally set the appropriate permissions on the `.msmtprc` file : -```sh -chmod 600 ~/.msmtprc -``` +To use msmtp with Netdata for sending email alerts: + +1. If it’s not already installed, install msmtp. Most distributions have it in their package repositories with the package name `msmtp`. +2. Modify the `sendmail` path in `health_alarm_notify.conf` to point to the location of `msmtp`: + + ```conf + # The full path to the sendmail command. + # If empty, the system $PATH will be searched for it. + # If not found, email notifications will be disabled (silently). + sendmail="/usr/bin/msmtp" + ``` + +3. Login as netdata: + + ```sh + (sudo) su -s /bin/bash netdata + ``` + +4. Configure `~/.msmtprc` as shown [in the documentation](https://marlam.de/msmtp/documentation/). +5. Finally set the appropriate permissions on the `.msmtprc` file : + + ```sh + chmod 600 ~/.msmtprc + ``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/flock/README.md b/health/notifications/flock/README.md index 175f8a46..daf50abf 100644 --- a/health/notifications/flock/README.md +++ b/health/notifications/flock/README.md @@ -1,42 +1,66 @@ - - -# Flock +# Flock Agent alert notifications + +Learn how to send notifications to Flock using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. This is what you will get: ![Flock](https://i.imgur.com/ok9bRzw.png) -You need: +## Prerequisites + +You will need: + +- The **incoming webhook URL** as given by flock.com + You can use the same on all your Netdata servers (or you can have multiple if you like - your decision). + Read more about flock webhooks and how to get one [here](https://admin.flock.com/webhooks). +- Terminal access to the Agent you wish to configure + +## Configure Netdata to send alert notifications to Flock + +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. -The **incoming webhook URL** as given by flock.com. -You can use the same on all your Netdata servers (or you can have multiple if you like - your decision). +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: -Get them here: +1. Set `SEND_FLOCK` to `YES`. +2. Set `FLOCK_WEBHOOK_URL` to your webhook URL. +3. Set `DEFAULT_RECIPIENT_FLOCK` to the Flock channel you want the alert notifications to be sent to. + All roles will default to this variable if left unconfigured. -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: +You can then have different channels per **role**, by editing `DEFAULT_RECIPIENT_FLOCK` with the channel you want, in the following entries at the bottom of the same file: +```conf +role_recipients_flock[sysadmin]="systems" +role_recipients_flock[domainadmin]="domains" +role_recipients_flock[dba]="databases systems" +role_recipients_flock[webmaster]="marketing development" +role_recipients_flock[proxyadmin]="proxy-admin" +role_recipients_flock[sitemgr]="sites" ``` -############################################################################### -# sending flock notifications -# enable/disable sending pushover notifications -SEND_FLOCK="YES" +The values you provide should already exist as Flock channels. -# Login to flock.com and create an incoming webhook. -# You need only one for all your Netdata servers. -# Without it, Netdata cannot send flock notifications. -FLOCK_WEBHOOK_URL="https://api.flock.com/hooks/sendMessage/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +An example of a working configuration would be: + +```conf +#------------------------------------------------------------------------------ +# flock (flock.com) global notification options -# if a role recipient is not configured, no notification will be sent +SEND_FLOCK="YES" +FLOCK_WEBHOOK_URL="https://api.flock.com/hooks/sendMessage/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" DEFAULT_RECIPIENT_FLOCK="alarms" ``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/gotify/README.md b/health/notifications/gotify/README.md index d01502b6..4f6760f6 100644 --- a/health/notifications/gotify/README.md +++ b/health/notifications/gotify/README.md @@ -1,66 +1,49 @@ - - -# Send notifications to Gotify +# Gotify agent alert notifications -[Gotify](https://gotify.net/) is a self-hosted push notification service created for sending and receiving messages in real time. - -## Configuring Gotify +Learn how to send alerts to your Gotify instance using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. -### Prerequisites +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. -To use Gotify as your notification service, you need an application token. -You can generate a new token in the Gotify Web UI. +[Gotify](https://gotify.net/) is a self-hosted push notification service created for sending and receiving messages in real time. -### Configuration +This is what you will get: -To set up Gotify in Netdata: +Example alarm notifications in Gotify -1. Switch to your [config -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 - ``` +## Prerequisites -2. Change the variable `GOTIFY_APP_TOKEN` to the application token you generated in the Gotify Web UI. Change -`GOTIFY_APP_URL` to point to your Gotify instance. +You will need: - ```conf - SEND_GOTIFY="YES" +- An application token. You can generate a new token in the Gotify Web UI. +- terminal access to the Agent you wish to configure - # Application token - # Gotify instance url - GOTIFY_APP_TOKEN=XXXXXXXXXXXXXXX - GOTIFY_APP_URL=https://push.example.de/ - ``` +## Configure Netdata to send alert notifications to Gotify - Changes to `health_alarm_notify.conf` do not require a Netdata restart. - -3. Test your Gotify notifications configuration by running the following commands, replacing `ROLE` with your preferred role: +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. - ```sh - # become user netdata - sudo su -s /bin/bash netdata +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: - # send a test alarm - /usr/libexec/netdata/plugins.d/alarm-notify.sh test ROLE - ``` +1. Set `SEND_GOTIFY` to `YES` +2. Set `GOTIFY_APP_TOKEN` to the app token you generated +3. `GOTIFY_APP_URL` to point to your Gotify instance, for example `https://push.example.domain/` - 🟢 If everything works, you'll see alarms in Gotify: +An example of a working configuration would be: - ![Example alarm notifications in Gotify](https://user-images.githubusercontent.com/103264516/162509205-1e88e5d9-96b6-4f7f-9426-182776158128.png) +```conf +SEND_GOTIFY="YES" +GOTIFY_APP_TOKEN="XXXXXXXXXXXXXXX" +GOTIFY_APP_URL="https://push.example.domain/" +``` - 🔴 If sending the test notifications fails, check `/var/log/netdata/error.log` to find the relevant error message: +## Test the notification method - ```log - 2020-09-03 23:07:00: alarm-notify.sh: ERROR: failed to send Gotify notification for: hades test.chart.test_alarm is CRITICAL, with HTTP error code 401. - ``` +To test this alert refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/hangouts/README.md b/health/notifications/hangouts/README.md index 45da1bfa..491b738b 100644 --- a/health/notifications/hangouts/README.md +++ b/health/notifications/hangouts/README.md @@ -1,15 +1,15 @@ -# Send notifications to Google Hangouts +# Google Hangouts agent alert notifications [Google Hangouts](https://hangouts.google.com/) is a cross-platform messaging app developed by Google. You can configure Netdata to send alarm notifications to a Hangouts room in order to stay aware of possible health or performance issues diff --git a/health/notifications/health_alarm_notify.conf b/health/notifications/health_alarm_notify.conf index 4878661a..b7fa6e79 100755 --- a/health/notifications/health_alarm_notify.conf +++ b/health/notifications/health_alarm_notify.conf @@ -22,6 +22,7 @@ # - message to Microsoft Teams (through webhook) # - message to Rocket.Chat (through webhook) # - message to Google Hangouts Chat (through webhook) +# - push notifications to your mobile phone or desktop (ntfy.sh) # # The 'to' line given at netdata alarms defines a *role*, so that many # people can be notified for each role. @@ -853,6 +854,18 @@ MATRIX_ACCESSTOKEN= # The format is !roomid:homeservername DEFAULT_RECIPIENT_MATRIX="" +#------------------------------------------------------------------------------ +# ntfy.sh global notification options + +# enable/disable sending ntfy notifications +SEND_NTFY="YES" + +# if a role's recipients are not configured, a notification will be sent to +# this ntfy server / topic combination (empty = do not send a notification for +# unconfigured roles). +# Multiple recipients can be given like this: "https://SERVER1/TOPIC1 https://SERVER2/TOPIC2 ..." +DEFAULT_RECIPIENT_NTFY="" + #------------------------------------------------------------------------------ # custom notifications # @@ -997,6 +1010,8 @@ role_recipients_stackpulse[sysadmin]="${DEFAULT_RECIPIENT_STACKPULSE}" role_recipients_gotify[sysadmin]="${DEFAULT_RECIPIENT_GOTIFY}" +role_recipients_ntfy[sysadmin]="${DEFAULT_RECIPIENT_NTFY}" + # ----------------------------------------------------------------------------- # DNS related alarms @@ -1056,6 +1071,8 @@ role_recipients_stackpulse[domainadmin]="${DEFAULT_RECIPIENT_STACKPULSE}" role_recipients_gotify[domainadmin]="${DEFAULT_RECIPIENT_GOTIFY}" +role_recipients_ntfy[domainadmin]="${DEFAULT_RECIPIENT_NTFY}" + # ----------------------------------------------------------------------------- # database servers alarms # mysql, redis, memcached, postgres, etc @@ -1116,6 +1133,8 @@ role_recipients_stackpulse[dba]="${DEFAULT_RECIPIENT_STACKPULSE}" role_recipients_gotify[dba]="${DEFAULT_RECIPIENT_GOTIFY}" +role_recipients_ntfy[dba]="${DEFAULT_RECIPIENT_NTFY}" + # ----------------------------------------------------------------------------- # web servers alarms # apache, nginx, lighttpd, etc @@ -1176,6 +1195,8 @@ role_recipients_stackpulse[webmaster]="${DEFAULT_RECIPIENT_STACKPULSE}" role_recipients_gotify[webmaster]="${DEFAULT_RECIPIENT_GOTIFY}" +role_recipients_ntfy[webmaster]="${DEFAULT_RECIPIENT_NTFY}" + # ----------------------------------------------------------------------------- # proxy servers alarms # squid, etc @@ -1236,6 +1257,8 @@ role_recipients_stackpulse[proxyadmin]="${DEFAULT_RECIPIENT_STACKPULSE}" role_recipients_gotify[proxyadmin]="${DEFAULT_RECIPIENT_GOTIFY}" +role_recipients_ntfy[proxyadmin]="${DEFAULT_RECIPIENT_NTFY}" + # ----------------------------------------------------------------------------- # peripheral devices # UPS, photovoltaics, etc @@ -1293,3 +1316,5 @@ role_recipients_matrix[sitemgr]="${DEFAULT_RECIPIENT_MATRIX}" role_recipients_stackpulse[sitemgr]="${DEFAULT_RECIPIENT_STACKPULSE}" role_recipients_gotify[sitemgr]="${DEFAULT_RECIPIENT_GOTIFY}" + +role_recipients_ntfy[sitemgr]="${DEFAULT_RECIPIENT_NTFY}" diff --git a/health/notifications/irc/README.md b/health/notifications/irc/README.md index a4877f48..bf40bfb6 100644 --- a/health/notifications/irc/README.md +++ b/health/notifications/irc/README.md @@ -1,83 +1,88 @@ - - -# IRC +# IRC Agent alert notifications + +Learn how to send notifications to IRC using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. This is what you will get: -IRCCloud web client:\ +IRCCloud web client: ![image](https://user-images.githubusercontent.com/31221999/36793487-3735673e-1ca6-11e8-8880-d1d8b6cd3bc0.png) -Irssi terminal client: +Irssi terminal client: ![image](https://user-images.githubusercontent.com/31221999/36793486-3713ada6-1ca6-11e8-8c12-70d956ad801e.png) -You need: - -1. The `nc` utility. If you do not set the path, Netdata will search for it in your system `$PATH`. - -Set the path for `nc` in `/etc/netdata/health_alarm_notify.conf` (to edit it on your system run `/etc/netdata/edit-config health_alarm_notify.conf`), like this: - -``` -#------------------------------------------------------------------------------ -# external commands -# -# The full path of the nc command. -# If empty, the system $PATH will be searched for it. -# If not found, irc notifications will be silently disabled. -nc="/usr/bin/nc" +## Prerequisites + +You will need: + +- The `nc` utility. + You can set the path to it, or Netdata will search for it in your system `$PATH`. +- terminal access to the Agent you wish to configure + +## Configure Netdata to send alert notifications to IRC + +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. + +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: + +1. Set the path for `nc`, otherwise Netdata will search for it in your system `$PATH`: + + ```conf + #------------------------------------------------------------------------------ + # external commands + # + # The full path of the nc command. + # If empty, the system $PATH will be searched for it. + # If not found, irc notifications will be silently disabled. + nc="/usr/bin/nc" + ``` + +2. Set `SEND_IRC` to `YES` +3. Set `DEFAULT_RECIPIENT_IRC` to one or more channels to post the messages to. + You can define multiple channels like this: `#alarms #systems`. + All roles will default to this variable if left unconfigured. +4. Set `IRC_NETWORK` to the IRC network which your preferred channels belong to. +5. Set `IRC_PORT` to the IRC port to which a connection will occur. +6. Set `IRC_NICKNAME` to the IRC nickname which is required to send the notification. + It must not be an already registered name as the connection's `MODE` is defined as a `guest`. +7. Set `IRC_REALNAME` to the IRC realname which is required in order to make he connection. + +You can then have different channels per **role**, by editing `DEFAULT_RECIPIENT_IRC` with the channel you want, in the following entries at the bottom of the same file: + +```conf +role_recipients_irc[sysadmin]="#systems" +role_recipients_irc[domainadmin]="#domains" +role_recipients_irc[dba]="#databases #systems" +role_recipients_irc[webmaster]="#marketing #development" +role_recipients_irc[proxyadmin]="#proxy-admin" +role_recipients_irc[sitemgr]="#sites" ``` -2. Αn `IRC_NETWORK` to which your preferred channels belong to. -3. One or more channels ( `DEFAULT_RECIPIENT_IRC` ) to post the messages to. -4. An `IRC_NICKNAME` and an `IRC_REALNAME` to identify in IRC. +The values you provide should be IRC channels which belong to the specified IRC network. -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: +An example of a working configuration would be: -``` +```conf #------------------------------------------------------------------------------ # irc notification options # -# irc notifications require only the nc utility to be installed. - -# multiple recipients can be given like this: -# " ..." - -# enable/disable sending irc notifications SEND_IRC="YES" - -# if a role's recipients are not configured, a notification will not be sent. -# (empty = do not send a notification for unconfigured roles): DEFAULT_RECIPIENT_IRC="#system-alarms" - -# The irc network to which the recipients belong. It must be the full network. IRC_NETWORK="irc.freenode.net" - -# The irc nickname which is required to send the notification. It must not be -# an already registered name as the connection's MODE is defined as a 'guest'. IRC_NICKNAME="netdata-alarm-user" - -# The irc realname which is required in order to make the connection and is an -# extra identifier. IRC_REALNAME="netdata-user" ``` -You can define multiple channels like this: `#system-alarms #networking-alarms`.\ -You can also filter the notifications like this: `#system-alarms|critical`.\ -You can give different channels per **role** using these (at the same file): - -``` -role_recipients_irc[sysadmin]="#user-alarms #networking-alarms #system-alarms" -role_recipients_irc[dba]="#databases-alarms" -role_recipients_irc[webmaster]="#networking-alarms" -``` - -The keywords `#user-alarms`, `#networking-alarms`, `#system-alarms`, `#databases-alarms` are irc channels which belong to the specified IRC network. - +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/kavenegar/README.md b/health/notifications/kavenegar/README.md index 443fcdba..434354f6 100644 --- a/health/notifications/kavenegar/README.md +++ b/health/notifications/kavenegar/README.md @@ -1,51 +1,67 @@ - - -# Kavenegar +# Kavenegar Agent alert notifications + +Learn how to send notifications to Kavenegar using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. [Kavenegar](https://kavenegar.com/) as service for software developers, based in Iran, provides send and receive SMS, calling voice by using its APIs. -Will look like this on your Android device: +This is what you will get: + +![image](https://user-images.githubusercontent.com/70198089/229841323-6c4b1956-dd91-423e-abaf-2799000f72a8.png) -![image](https://cloud.githubusercontent.com/assets/17090999/20034652/620b6100-a39b-11e6-96af-4f83b8e830e2.png) +## Prerequisites You will need: -1. Signup and Login to kavenegar.com -2. Get your APIKEY and Sender from `http://panel.kavenegar.com/client/setting/account` -3. Fill in KAVENEGAR_API_KEY="" KAVENEGAR_SENDER="" -4. Add the recipient phone numbers to DEFAULT_RECIPIENT_KAVENEGAR="" +- the `APIKEY` and Sender from +- terminal access to the Agent you wish to configure + +## Configure Netdata to send alert notifications to Kavenegar + +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. -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: +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: +1. Set `SEND_KAVENEGAR` to `YES`. +2. Set `KAVENEGAR_API_KEY` to your `APIKEY`. +3. Set `KAVENEGAR_SENDER` to the value of your Sender. +4. Set `DEFAULT_RECIPIENT_KAVENEGAR` to the SMS recipient you want the alert notifications to be sent to. + You can define multiple recipients like this: `09155555555 09177777777`. + All roles will default to this variable if lest unconfigured. + +You can then have different SMS recipients per **role**, by editing `DEFAULT_RECIPIENT_KAVENEGAR` with the SMS recipients you want, in the following entries at the bottom of the same file: + +```conf +role_recipients_kavenegar[sysadmin]="09100000000" +role_recipients_kavenegar[domainadmin]="09111111111" +role_recipients_kavenegar[dba]="0922222222" +role_recipients_kavenegar[webmaster]="0933333333" +role_recipients_kavenegar[proxyadmin]="0944444444" +role_recipients_kavenegar[sitemgr]="0955555555" ``` -############################################################################### -# Kavenegar (kavenegar.com) SMS options -# multiple recipients can be given like this: -# "09155555555 09177777777" +An example of a working configuration would be: -# enable/disable sending kavenegar SMS -SEND_KAVENEGAR="YES" +```conf +#------------------------------------------------------------------------------ +# Kavenegar (Kavenegar.com) SMS options -# to get an access key, after selecting and purchasing your desired service -# at http://kavenegar.com/pricing.html -# login to your account, go to your dashboard and my account are -# https://panel.kavenegar.com/Client/setting/account from API Key -# copy your api key. You can generate new API Key too. -# You can find and select kevenegar sender number from this place. - -# Without an API key, Netdata cannot send KAVENEGAR text messages. -KAVENEGAR_API_KEY="" -KAVENEGAR_SENDER="" -DEFAULT_RECIPIENT_KAVENEGAR="" +SEND_KAVENEGAR="YES" +KAVENEGAR_API_KEY="XXXXXXXXXXXX" +KAVENEGAR_SENDER="YYYYYYYY" +DEFAULT_RECIPIENT_KAVENEGAR="0912345678" ``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/matrix/README.md b/health/notifications/matrix/README.md index 80e22da3..714d8c22 100644 --- a/health/notifications/matrix/README.md +++ b/health/notifications/matrix/README.md @@ -1,62 +1,74 @@ - +# Matrix Agent alert notifications -# Matrix +Learn how to send notifications to Matrix network rooms using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. -Send notifications to [Matrix](https://matrix.org/) network rooms. +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. -The requirements for this notification method are: +## Prerequisites -1. The url of the homeserver (`https://homeserver:port`). -2. Credentials for connecting to the homeserver, in the form of a valid access token for your account (or for a - dedicated notification account). These tokens usually don't expire. -3. The room ids that you want to sent the notification to. +You will need: -To obtain the access token, you can use the following `curl` command: +- The url of the homeserver (`https://homeserver:port`). +- Credentials for connecting to the homeserver, in the form of a valid access token for your account (or for a dedicated notification account). These tokens usually don't expire. +- The room ids that you want to sent the notification to. -```bash -curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "https://homeserver:8448/_matrix/client/r0/login" -``` +## Configure Netdata to send alert notifications to Matrix + +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. + +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: + +1. Set `SEND_MATRIX` to `YES`. +2. Set `MATRIX_HOMESERVER` to the URL of the Matrix homeserver. +3. Set `MATRIX_ACCESSTOKEN` to the access token from your Matrix account. + To obtain the access token, you can use the following `curl` command: -The room ids are unique identifiers and can be obtained from the room settings in a Matrix client (e.g. Riot). Their -format is `!uniqueid:homeserver`. + ```bash + curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "https://homeserver:8448/_matrix/client/r0/login" + ``` -Multiple room ids can be defined by separating with a space character. +4. Set `DEFAULT_RECIPIENT_MATRIX` to the rooms you want the alert notifications to be sent to. + The format is `!roomid:homeservername`. -Detailed information about the Matrix client API is available at the [official -site](https://matrix.org/docs/guides/client-server.html). + The room ids are unique identifiers and can be obtained from the room settings in a Matrix client (e.g. Riot). -Your `health_alarm_notify.conf` should look like this: + You can define multiple rooms like this: `!roomid1:homeservername !roomid2:homeservername`. + All roles will default to this variable if left unconfigured. + +Detailed information about the Matrix client API is available at the [official site](https://matrix.org/docs/guides/client-server.html). + +You can then have different rooms per **role**, by editing `DEFAULT_RECIPIENT_MATRIX` with the `!roomid:homeservername` you want, in the following entries at the bottom of the same file: ```conf -############################################################################### +role_recipients_matrix[sysadmin]="!roomid1:homeservername" +role_recipients_matrix[domainadmin]="!roomid2:homeservername" +role_recipients_matrix[dba]="!roomid3:homeservername" +role_recipients_matrix[webmaster]="!roomid4:homeservername" +role_recipients_matrix[proxyadmin]="!roomid5:homeservername" +role_recipients_matrix[sitemgr]="!roomid6:homeservername" +``` + +An example of a working configuration would be: + +```conf +#------------------------------------------------------------------------------ # Matrix notifications -# -# enable/disable Matrix notifications SEND_MATRIX="YES" - -# The url of the Matrix homeserver -# e.g https://matrix.org:8448 MATRIX_HOMESERVER="https://matrix.org:8448" - -# A access token from a valid Matrix account. Tokens usually don't expire, -# can be controlled from a Matrix client. -# See https://matrix.org/docs/guides/client-server.html MATRIX_ACCESSTOKEN="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" - -# Specify the default rooms to receive the notification if no rooms are provided -# in a role's recipients. -# The format is !roomid:homeservername DEFAULT_RECIPIENT_MATRIX="!XXXXXXXXXXXX:matrix.org" ``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/messagebird/README.md b/health/notifications/messagebird/README.md index 01430198..6b96c0d9 100644 --- a/health/notifications/messagebird/README.md +++ b/health/notifications/messagebird/README.md @@ -1,50 +1,65 @@ - +# MessageBird Agent alert notifications -# Messagebird +Learn how to send notifications to MessageBird using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. -The messagebird notifications will look like this on your Android device: +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. -![image](https://cloud.githubusercontent.com/assets/17090999/20034652/620b6100-a39b-11e6-96af-4f83b8e830e2.png) +This is what you will get: + +![image](https://user-images.githubusercontent.com/70198089/229841323-6c4b1956-dd91-423e-abaf-2799000f72a8.png) + +## Prerequisites You will need: -1. Signup and Login to messagebird.com -2. Pick an SMS capable number after sign up to get some free credits -3. Go to -4. Create a new access key under 'API ACCESS (REST)' (you will want a live key) -5. Fill in MESSAGEBIRD_ACCESS_KEY="XXXXXXXX" MESSAGEBIRD_NUMBER="+XXXXXXXXXXX" -6. Add the recipient phone numbers to DEFAULT_RECIPIENT_MESSAGEBIRD="+XXXXXXXXXXX" +- an access key under 'API ACCESS (REST)' (you will want a live key), you can read more [here](https://developers.messagebird.com/quickstarts/sms/test-credits-api-keys/) +- terminal access to the Agent you wish to configure + +## Configure Netdata to send alert notifications to MessageBird + +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. + +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: + +1. Set `SEND_MESSAGEBIRD` to `YES`. +2. Set `MESSAGEBIRD_ACCESS_KEY` to your API access key. +3. Set `MESSAGEBIRD_NUMBER` to the MessageBird number you want to use for the alert. +4. Set `DEFAULT_RECIPIENT_MESSAGEBIRD` to the number you want the alert notification to be sent as an SMS. + You can define multiple recipients like this: `+15555555555 +17777777777`. + All roles will default to this variable if left unconfigured. -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: +You can then have different recipients per **role**, by editing `DEFAULT_RECIPIENT_MESSAGEBIRD` with the number you want, in the following entries at the bottom of the same file: +```conf +role_recipients_messagebird[sysadmin]="+15555555555" +role_recipients_messagebird[domainadmin]="+15555555556" +role_recipients_messagebird[dba]="+15555555557" +role_recipients_messagebird[webmaster]="+15555555558" +role_recipients_messagebird[proxyadmin]="+15555555559" +role_recipients_messagebird[sitemgr]="+15555555550" ``` + +An example of a working configuration would be: + +```conf #------------------------------------------------------------------------------ # Messagebird (messagebird.com) SMS options -# multiple recipients can be given like this: -# "+15555555555 +17777777777" - -# enable/disable sending messagebird SMS SEND_MESSAGEBIRD="YES" - -# to get an access key, create a free account at https://www.messagebird.com -# verify and activate the account (no CC info needed) -# login to your account and enter your phonenumber to get some free credits -# to get the API key, click on 'API' in the sidebar, then 'API Access (REST)' -# click 'Add access key' and fill in data (you want a live key to send SMS) - -# Without an access key, Netdata cannot send Messagebird text messages. MESSAGEBIRD_ACCESS_KEY="XXXXXXXX" MESSAGEBIRD_NUMBER="XXXXXXX" -DEFAULT_RECIPIENT_MESSAGEBIRD="XXXXXXX" +DEFAULT_RECIPIENT_MESSAGEBIRD="+15555555555" ``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/msteams/README.md b/health/notifications/msteams/README.md index 75e652a7..5511a97b 100644 --- a/health/notifications/msteams/README.md +++ b/health/notifications/msteams/README.md @@ -1,48 +1,67 @@ - - -# Microsoft Teams +# Microsoft Teams Agent alert notifications + +Learn how to send notifications to Microsoft Teams using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. This is what you will get: ![image](https://user-images.githubusercontent.com/1122372/92710359-0385e680-f358-11ea-8c52-f366a4fb57dd.png) -You need: +## Prerequisites -1. The **incoming webhook URL** as given by Microsoft Teams. You can use the same on all your Netdata servers (or you can have multiple if you like - your decision). -2. One or more channels to post the messages to. +You will need: -In Microsoft Teams the channel name is encoded in the URI after `/IncomingWebhook/` (for clarity the marked with `[]` in the following example): `https://outlook.office.com/webhook/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX@XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/IncomingWebhook/[XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX]/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX` +- the **incoming webhook URL** as given by Microsoft Teams. You can use the same on all your Netdata servers (or you can have multiple if you like - your decision) +- one or more channels to post the messages to +- terminal access to the Agent you wish to configure -You have to replace the encoded channel name by the placeholder `CHANNEL` in `MSTEAMS_WEBHOOK_URL`. The placeholder `CHANNEL` will be replaced by the actual encoded channel name before sending the notification. This makes it possible to publish to several channels in the same team. +## Configure Netdata to send alert notifications to Microsoft Teams -The encoded channel name must then be added to `DEFAULT_RECIPIENTS_MSTEAMS` or to one of the specific variables `role_recipients_msteams[]`. **At least one channel is mandatory for `DEFAULT_RECIPIENTS_MSTEAMS`.** +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. -Set the webhook and the recipients in `/etc/netdata/health_alarm_notify.conf` (to edit it on your system run `/etc/netdata/edit-config health_alarm_notify.conf`), like this: +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: -``` -SEND_MSTEAMS="YES" +1. Set `SEND_MSTEAMS` to `YES`. +2. Set `MSTEAMS_WEBHOOK_URL` to the incoming webhook URL as given by Microsoft Teams. +3. Set `DEFAULT_RECIPIENT_MSTEAMS` to the **encoded** Microsoft Teams channel name you want the alert notifications to be sent to. + In Microsoft Teams the channel name is encoded in the URI after `/IncomingWebhook/`. + You can define multiple channels like this: `CHANNEL1 CHANNEL2`. + All roles will default to this variable if left unconfigured. +4. You can also set the icons and colors for the different alerts in the same section of the file. -MSTEAMS_WEBHOOK_URL="https://outlook.office.com/webhook/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX@XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/IncomingWebhook/CHANNEL/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" +You can then have different channels per **role**, by editing `DEFAULT_RECIPIENT_MSTEAMS` with the channel you want, in the following entries at the bottom of the same file: -DEFAULT_RECIPIENT_MSTEAMS="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +```conf +role_recipients_msteams[sysadmin]="CHANNEL1" +role_recipients_msteams[domainadmin]="CHANNEL2" +role_recipients_msteams[dba]="databases CHANNEL3" +role_recipients_msteams[webmaster]="CHANNEL4" +role_recipients_msteams[proxyadmin]="CHANNEL5" +role_recipients_msteams[sitemgr]="CHANNEL6" ``` -You can define multiple recipients by listing the encoded channel names like this: `XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY`. -This example will send the alarm to the two channels specified by their encoded channel names. +The values you provide should already exist as Microsoft Teams channels in the same Team. -You can give different recipients per **role** using these (in the same file): +An example of a working configuration would be: -``` -role_recipients_msteams[sysadmin]="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" -role_recipients_msteams[dba]="YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" -role_recipients_msteams[webmaster]="ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" +```conf +#------------------------------------------------------------------------------ +# Microsoft Teams (office.com) global notification options + +SEND_MSTEAMS="YES" +MSTEAMS_WEBHOOK_URL="https://outlook.office.com/webhook/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX@XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/IncomingWebhook/CHANNEL/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" +DEFAULT_RECIPIENT_MSTEAMS="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/ntfy/Makefile.inc b/health/notifications/ntfy/Makefile.inc new file mode 100644 index 00000000..b2045192 --- /dev/null +++ b/health/notifications/ntfy/Makefile.inc @@ -0,0 +1,12 @@ +# 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_noinst_DATA += \ + ntfy/README.md \ + ntfy/Makefile.inc \ + $(NULL) + diff --git a/health/notifications/ntfy/README.md b/health/notifications/ntfy/README.md new file mode 100644 index 00000000..156fb09e --- /dev/null +++ b/health/notifications/ntfy/README.md @@ -0,0 +1,66 @@ +# ntfy agent alert notifications + +Learn how to send alerts to an ntfy server using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. + +[ntfy](https://ntfy.sh/) (pronounce: notify) is a simple HTTP-based [pub-sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) notification service. It allows you to send notifications to your phone or desktop via scripts from any computer, entirely without signup, cost or setup. It's also [open source](https://github.com/binwiederhier/ntfy) if you want to run your own server. + +This is what you will get: + +Example alarm notifications in Ntfy + +## Prerequisites + +You will need: + +- (Optional) A [self-hosted ntfy server](https://docs.ntfy.sh/faq/#can-i-self-host-it), in case you don't want to use https://ntfy.sh +- A new [topic](https://ntfy.sh/#subscribe) for the notifications to be published to +- terminal access to the Agent you wish to configure + +## Configure Netdata to send alert notifications to ntfy + +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. + +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: + +1. Set `SEND_NTFY` to `YES` +2. Set `DEFAULT_RECIPIENT_NTFY` to the URL formed by the server-topic combination you want the alert notifications to be sent to. Unless you are hosting your own server, the server should always be set to [https://ntfy.sh](https://ntfy.sh) + + You can define multiple recipient URLs like this: `https://SERVER1/TOPIC1 https://SERVER2/TOPIC2` + All roles will default to this variable if left unconfigured. + +> ### Warning +> All topics published on https://ntfy.sh are public, so anyone can subscribe to them and follow your notifications. To avoid that, ensure the topic is unique enough using a long, randomly generated ID, like in the following examples. +> + +An example of a working configuration with two topics as recipients, using the [https://ntfy.sh](https://ntfy.sh) server would be: + +```conf +SEND_NTFY="YES" +DEFAULT_RECIPIENT_NTFY="https://ntfy.sh/netdata-X7seHg7d3Tw9zGOk https://ntfy.sh/netdata-oIPm4IK1IlUtlA30" +``` + +You can then have different servers and/or topics per **role**, by editing `DEFAULT_RECIPIENT_NTFY` with the server-topic combination you want, in the following entries at the bottom of the same file: + +```conf +role_recipients_ntfy[sysadmin]="https://SERVER1/TOPIC1" +role_recipients_ntfy[domainadmin]="https://SERVER2/TOPIC2" +role_recipients_ntfy[dba]="https://SERVER3/TOPIC3" +role_recipients_ntfy[webmaster]="https://SERVER4/TOPIC4" +role_recipients_ntfy[proxyadmin]="https://SERVER5/TOPIC5" +role_recipients_ntfy[sitemgr]="https://SERVER6/TOPIC6" +``` + +## Test the notification method + +To test this alert refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/opsgenie/README.md b/health/notifications/opsgenie/README.md index 20f14b39..5b030324 100644 --- a/health/notifications/opsgenie/README.md +++ b/health/notifications/opsgenie/README.md @@ -1,66 +1,51 @@ - - -# Send notifications to Opsgenie - -[Opsgenie](https://www.atlassian.com/software/opsgenie) is an alerting and incident response tool. It is designed to -group and filter alarms, build custom routing rules for on-call teams, and correlate deployments and commits to -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](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md): - -```bash -./edit-config health_alarm_notify.conf -``` +# Opsgenie Agent alert notifications -Change the variable `OPSGENIE_API_KEY` with the API key you got from Opsgenie. `OPSGENIE_API_URL` defaults to -`https://api.opsgenie.com`, however there are region-specific API URLs such as `https://eu.api.opsgenie.com`, so set -this if required. +Learn how to send notifications to Opsgenie using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. -```conf -SEND_OPSGENIE="YES" +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. -# Api key -# Default Opsgenie API -OPSGENIE_API_KEY="11111111-2222-3333-4444-555555555555" -OPSGENIE_API_URL="" -``` +[Opsgenie](https://www.atlassian.com/software/opsgenie) is an alerting and incident response tool. +It is designed to group and filter alarms, build custom routing rules for on-call teams, and correlate deployments and commits to incidents. -Changes to `health_alarm_notify.conf` do not require a Netdata restart. You can test your Opsgenie notifications -configuration by issuing the commands, replacing `ROLE` with your preferred role: +This is what you will get: +![Example alarm notifications in +Opsgenie](https://user-images.githubusercontent.com/49162938/92184518-f725f900-ee40-11ea-9afa-e7c639c72206.png) -```sh -# become user netdata -sudo su -s /bin/bash netdata +## Prerequisites -# send a test alarm -/usr/libexec/netdata/plugins.d/alarm-notify.sh test ROLE -``` +You will need: -If everything works, you'll see alarms in your Opsgenie platform: +- An Opsgenie integration. You can create an [integration](https://docs.opsgenie.com/docs/api-integration) in the [Opsgenie](https://www.atlassian.com/software/opsgenie) dashboard. -![Example alarm notifications in -Opsgenie](https://user-images.githubusercontent.com/49162938/92184518-f725f900-ee40-11ea-9afa-e7c639c72206.png) +- terminal access to the Agent you wish to configure -If sending the test notifications fails, you can look in `/var/log/netdata/error.log` to find the relevant error -message: +## Configure Netdata to send alert notifications to your Opsgenie account -```log -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. -``` +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. + +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: + +1. Set `SEND_OPSGENIE` to `YES`. +2. Set `OPSGENIE_API_KEY` to the API key you got from Opsgenie. +3. `OPSGENIE_API_URL` defaults to `https://api.opsgenie.com`, however there are region-specific API URLs such as `https://eu.api.opsgenie.com`, so set this if required. + +An example of a working configuration would be: -You can find more details about the Opsgenie error codes in -their [response docs](https://docs.opsgenie.com/docs/response). +```conf +SEND_OPSGENIE="YES" +OPSGENIE_API_KEY="11111111-2222-3333-4444-555555555555" +OPSGENIE_API_URL="" +``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/pagerduty/README.md b/health/notifications/pagerduty/README.md index c6190e83..70d6090d 100644 --- a/health/notifications/pagerduty/README.md +++ b/health/notifications/pagerduty/README.md @@ -1,67 +1,69 @@ - - -# Send alert notifications to PagerDuty +# PagerDuty Agent alert notifications + +Learn how to send notifications to PagerDuty using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. [PagerDuty](https://www.pagerduty.com/company/) is an enterprise incident resolution service that integrates with ITOps and DevOps monitoring stacks to improve operational reliability and agility. From enriching and aggregating events to correlating them into incidents, PagerDuty streamlines the incident management process by reducing alert noise and resolution times. -## What you need to get started +## Prerequisites + +You will need: + +- an installation of the [PagerDuty agent](https://www.pagerduty.com/docs/guides/agent-install-guide/) on the node running the Netdata Agent +- a PagerDuty `Generic API` service using either the `Events API v2` or `Events API v1` +- terminal access to the Agent you wish to configure -- 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`. +## Configure Netdata to send alert notifications to PagerDuty -## Setup +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. -[Add a new service](https://support.pagerduty.com/docs/services-and-integrations#section-configuring-services-and-integrations) +Firstly, [Add a new service](https://support.pagerduty.com/docs/services-and-integrations#section-configuring-services-and-integrations) 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](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`. +Then, edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: -```bash -cd /etc/netdata -sudo ./edit-config health_alarm_notify.conf -``` - -Scroll down to the `# pagerduty.com notification options` section. +1. Set `SEND_PD` to `YES`. +2. Set `DEFAULT_RECIPIENT_PD` to the PagerDuty service key you want the alert notifications to be sent to. + You can define multiple service keys like this: `pd_service_key_1 pd_service_key_2`. + All roles will default to this variable if left unconfigured. +3. If you chose `Events API v2` during service setup on PagerDuty, change `USE_PD_VERSION` to `2`. -Ensure `SEND_PD` is set to `YES`, then copy your Integration Key into `DEFAULT_RECIPIENT_ID`. Change `USE_PD_VERSION` to -`2` if you chose `Events API v2` during service setup on PagerDuty. Minus comments, the section should look like this: +You can then have different PagerDuty service keys per **role**, by editing `DEFAULT_RECIPIENT_PD` with the service key you want, in the following entries at the bottom of the same file: ```conf -SEND_PD="YES" -DEFAULT_RECIPIENT_PD="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -USE_PD_VERSION="2" +role_recipients_pd[sysadmin]="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxa" +role_recipients_pd[domainadmin]="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxb" +role_recipients_pd[dba]="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxc" +role_recipients_pd[webmaster]="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxd" +role_recipients_pd[proxyadmin]="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxe" +role_recipients_pd[sitemgr]="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxf" ``` -## Testing +An example of a working configuration would be: -To test alert notifications to PagerDuty, run the following: +```conf +#------------------------------------------------------------------------------ +# pagerduty.com notification options -```bash -sudo su -s /bin/bash netdata -/usr/libexec/netdata/plugins.d/alarm-notify.sh test +SEND_PD="YES" +DEFAULT_RECIPIENT_PD="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +USE_PD_VERSION="2" ``` -## Configuration - -Aside from the three values set in `health_alarm_notify.conf`, there is no further configuration required to send alert -notifications to PagerDuty. +## Test the notification method -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. +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/prowl/README.md b/health/notifications/prowl/README.md index 8656c131..a5740529 100644 --- a/health/notifications/prowl/README.md +++ b/health/notifications/prowl/README.md @@ -1,21 +1,17 @@ - +# Prowl Agent alert notifications -# Prowl +Learn how to send notifications to Prowl using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. -[Prowl](https://www.prowlapp.com/) is a push notification service for iOS devices. Netdata -supports delivering notifications to iOS devices through Prowl. +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. + +[Prowl](https://www.prowlapp.com/) is a push notification service for iOS devices. +Netdata supports delivering notifications to iOS devices through Prowl. Because of how Netdata integrates with Prowl, there is a hard limit of at most 1000 notifications per hour (starting from the first notification -sent). Any alerts beyond the first thousand in an hour will be dropped. +sent). Any alerts beyond the first thousand in an hour will be dropped. Warning messages will be sent with the 'High' priority, critical messages will be sent with the 'Emergency' priority, and all other messages will @@ -23,10 +19,52 @@ be sent with the normal priority. Opening the notification's associated URL will take you to the Netdata dashboard of the system that issued the alert, directly to the chart that it triggered on. -## configuration +## Prerequisites + +You will need: + +- a Prowl API key, which can be requested through the Prowl website after registering +- terminal access to the Agent you wish to configure + +## Configure Netdata to send alert notifications to Prowl + +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. + +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: + +1. Set `SEND_PROWL` to `YES`. +2. Set `DEFAULT_RECIPIENT_PROWL` to the Prowl API key you want the alert notifications to be sent to. + You can define multiple API keys like this: `APIKEY1, APIKEY2`. + All roles will default to this variable if left unconfigured. + +You can then have different API keys per **role**, by editing `DEFAULT_RECIPIENT_PROWL` with the API keys you want, in the following entries at the bottom of the same file: + +```conf +role_recipients_prowl[sysadmin]="AAAAAAAA" +role_recipients_prowl[domainadmin]="BBBBBBBBB" +role_recipients_prowl[dba]="CCCCCCCCC" +role_recipients_prowl[webmaster]="DDDDDDDDDD" +role_recipients_prowl[proxyadmin]="EEEEEEEEEE" +role_recipients_prowl[sitemgr]="FFFFFFFFFF" +``` + +An example of a working configuration would be: + +```conf +#------------------------------------------------------------------------------ +# iOS Push Notifications + +SEND_PROWL="YES" +DEFAULT_RECIPIENT_PROWL="XXXXXXXXXX" +``` -To use this, you will need a Prowl API key, which can be requested through -the Prowl website after registering. +## Test the notification method -Once you have an API key, simply specify that as a recipient for Prowl -notifications. +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/pushbullet/README.md b/health/notifications/pushbullet/README.md index 17ed9364..6b19536a 100644 --- a/health/notifications/pushbullet/README.md +++ b/health/notifications/pushbullet/README.md @@ -1,55 +1,72 @@ - +# Pushbullet Agent alert notifications -# PushBullet +Learn how to send notifications to Pushbullet using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. -Will look like this on your browser: -![image](https://cloud.githubusercontent.com/assets/4300670/19109636/278b1c0c-8aee-11e6-8a09-7fc94fdbfec8.png) +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. -And like this on your Android device: +This is what it will look like this on your browser: +![image](https://user-images.githubusercontent.com/70198089/229842827-e9c93e44-3c86-4ab6-9b44-d8b36a00b015.png) -![image](https://cloud.githubusercontent.com/assets/4300670/19109635/278a1dde-8aee-11e6-9984-0bc87a13312d.png) +And this is what it will look like on your Android device: + +![image](https://user-images.githubusercontent.com/70198089/229842936-ea7e8f92-a353-43ca-a993-b1cc08e8508b.png) + +## Prerequisites You will need: -1. Sign up and log in to [pushbullet.com](https://www.pushbullet.com/) -2. Create a new access token in your [account settings](https://www.pushbullet.com/#settings/account). -3. Fill in the `PUSHBULLET_ACCESS_TOKEN` with the newly generated access token. -4. Add the recipient emails or channel tags (each channel tag must be prefixed with #, e.g. #channeltag) to `DEFAULT_RECIPIENT_PUSHBULLET`. - > 🚨 The pushbullet notification service will send emails to the email recipient, regardless of if they have a pushbullet account. +- a Pushbullet access token that can be created in your [account settings](https://www.pushbullet.com/#settings/account) +- terminal access to the Agent you wish to configure -To add notification channels, run `/etc/netdata/edit-config health_alarm_notify.conf` +## Configure Netdata to send alert notifications to Pushbullet -You can change the configuration like this: +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. -``` -############################################################################### -# pushbullet (pushbullet.com) push notification options +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: -# multiple recipients (a combination of email addresses or channel tags) can be given like this: -# "user1@email.com user2@mail.com #channel1 #channel2" +1. Set `Send_PUSHBULLET` to `YES`. +2. Set `PUSHBULLET_ACCESS_TOKEN` to the token you generated. +3. Set `DEFAULT_RECIPIENT_PUSHBULLET` to the email (e.g. `example@domain.com`) or the channel tag (e.g. `#channel`) you want the alert notifications to be sent to. -# enable/disable sending pushbullet notifications -SEND_PUSHBULLET="YES" + > ### Note + > + > Please note that the Pushbullet notification service will send emails to the email recipient, regardless of if they have a Pushbullet account or not. + + You can define multiple entries like this: `user1@email.com user2@email.com`. + All roles will default to this variable if left unconfigured. +4. While optional, you can also set `PUSHBULLET_SOURCE_DEVICE` to the identifier of the sending device. -# Signup and Login to pushbullet.com -# To get your Access Token, go to https://www.pushbullet.com/#settings/account -# And create a new access token -# Then just set the recipients emails and/or channel tags (channel tags must be prefixed with #) -# Please note that the if an email in the DEFAULT_RECIPIENT_PUSHBULLET does -# not have a pushbullet account, the pushbullet service will send an email -# to that address instead +You can then have different recipients per **role**, by editing `DEFAULT_RECIPIENT_PUSHBULLET` with the recipients you want, in the following entries at the bottom of the same file: -# Without an access token, Netdata cannot send pushbullet notifications. -PUSHBULLET_ACCESS_TOKEN="o.Sometokenhere" +```conf +role_recipients_pushbullet[sysadmin]="user1@email.com" +role_recipients_pushbullet[domainadmin]="user2@mail.com" +role_recipients_pushbullet[dba]="#channel1" +role_recipients_pushbullet[webmaster]="#channel2" +role_recipients_pushbullet[proxyadmin]="user3@mail.com" +role_recipients_pushbullet[sitemgr]="user4@mail.com" +``` + +An example of a working configuration would be: + +```conf +#------------------------------------------------------------------------------ +# pushbullet (pushbullet.com) push notification options + +SEND_PUSHBULLET="YES" +PUSHBULLET_ACCESS_TOKEN="XXXXXXXXX" DEFAULT_RECIPIENT_PUSHBULLET="admin1@example.com admin3@somemail.com #examplechanneltag #anotherchanneltag" ``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/pushover/README.md b/health/notifications/pushover/README.md index 4d5ea5a9..cd3621ef 100644 --- a/health/notifications/pushover/README.md +++ b/health/notifications/pushover/README.md @@ -1,28 +1,67 @@ - +# Pushover Agent alert notifications -# PushOver +Learn how to send notification to Pushover using Netdata's Agent alert notification +feature, which supports dozens of endpoints, user roles, and more. -pushover.net allows you to receive push notifications on your mobile phone. The service seems free for up to 7.500 messages per month. +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. -Netdata will send warning messages with priority `0` and critical messages with priority `1`. pushover.net allows you to select do-not-disturb hours. The way this is configured, critical notifications will ring and vibrate your phone, even during the do-not-disturb-hours. All other notifications will be delivered silently. +This is what you will get: -You need: +![image](https://user-images.githubusercontent.com/70198089/229842244-4ac998bb-6158-4955-ac2d-766a9999cc98.png) -1. APP TOKEN. You can use the same on all your Netdata servers. -2. USER TOKEN for each user you are going to send notifications to. This is the actual recipient of the notification. +Netdata will send warning messages with priority `0` and critical messages with priority `1`. Pushover allows you to select do-not-disturb hours. The way this is configured, critical notifications will ring and vibrate your phone, even during the do-not-disturb-hours. All other notifications will be delivered silently. -The configuration is like above (slack messages). +## Prerequisites -pushover.net notifications look like this: +You will need: -![image](https://cloud.githubusercontent.com/assets/2662304/18407319/839c10c4-7715-11e6-92c0-12f8215128d3.png) +- An Application token. You can use the same on all your Netdata servers. +- A User token for each user you are going to send notifications to. This is the actual recipient of the notification. +- terminal access to the Agent you wish to configure +## Configure Netdata to send alert notifications to Pushover +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. + +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: + +1. Set `SEND_PUSHOVER` to `YES`. +2. Set `PUSHOVER_APP_TOKEN` to your Pushover Application token. +3. Set `DEFAULT_RECIPIENT_PUSHOVER` to the Pushover User token you want the alert notifications to be sent to. + You can define multiple User tokens like this: `USERTOKEN1 USERTOKEN2`. + All roles will default to this variable if left unconfigured. + +You can then have different User tokens per **role**, by editing `DEFAULT_RECIPIENT_PUSHOVER` with the token you want, in the following entries at the bottom of the same file: + +```conf +role_recipients_pushover[sysadmin]="USERTOKEN1" +role_recipients_pushover[domainadmin]="USERTOKEN2" +role_recipients_pushover[dba]="USERTOKEN3 USERTOKEN4" +role_recipients_pushover[webmaster]="USERTOKEN5" +role_recipients_pushover[proxyadmin]="USERTOKEN6" +role_recipients_pushover[sitemgr]="USERTOKEN7" +``` + +An example of a working configuration would be: + +```conf +#------------------------------------------------------------------------------ +# pushover (pushover.net) global notification options + +SEND_PUSHOVER="YES" +PUSHOVER_APP_TOKEN="XXXXXXXXX" +DEFAULT_RECIPIENT_PUSHOVER="USERTOKEN" +``` + +## Test the notification method + +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/rocketchat/README.md b/health/notifications/rocketchat/README.md index 0f7867d0..6f722aa8 100644 --- a/health/notifications/rocketchat/README.md +++ b/health/notifications/rocketchat/README.md @@ -1,57 +1,66 @@ - - -# Rocket.Chat +# Rocket.Chat Agent alert notifications + +Learn how to send notifications to Rocket.Chat using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. This is what you will get: ![Netdata on RocketChat](https://i.imgur.com/Zu4t3j3.png) -You need: -1. The **incoming webhook URL** as given by RocketChat. You can use the same on all your Netdata servers (or you can have multiple if you like - your decision). -2. One or more channels to post the messages to. +## Prerequisites -Get them here: +You will need: -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: +- The **incoming webhook URL** as given by RocketChat. You can use the same on all your Netdata servers (or you can have multiple if you like - your decision). +- one or more channels to post the messages to. +- terminal access to the Agent you wish to configure -``` -#------------------------------------------------------------------------------ -# rocketchat (rocket.chat) global notification options +## Configure Netdata to send alert notifications to Rocket.Chat -# multiple recipients can be given like this: -# "CHANNEL1 CHANNEL2 ..." +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. -# enable/disable sending rocketchat notifications -SEND_ROCKETCHAT="YES" +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: -# Login to rocket.chat and create an incoming webhook. You need only one for all -# your Netdata servers (or you can have one for each of your Netdata). -# Without it, Netdata cannot send rocketchat notifications. -ROCKETCHAT_WEBHOOK_URL="" +1. Set `SEND_ROCKETCHAT` to `YES`. +2. Set `ROCKETCHAT_WEBHOOK_URL` to your webhook URL. +3. Set `DEFAULT_RECIPIENT_ROCKETCHAT` to the channel you want the alert notifications to be sent to. + You can define multiple channels like this: `alerts systems`. + All roles will default to this variable if left unconfigured. -# if a role's recipients are not configured, a notification will be send to -# this rocketchat channel (empty = do not send a notification for unconfigured -# roles). -DEFAULT_RECIPIENT_ROCKETCHAT="monitoring_alarms" -``` +You can then have different channels per **role**, by editing `DEFAULT_RECIPIENT_ROCKETCHAT` with the channel you want, in the following entries at the bottom of the same file: -You can define multiple channels like this: `alarms systems`. -You can give different channels per **role** using these (at the same file): - -``` +```conf role_recipients_rocketchat[sysadmin]="systems" +role_recipients_rocketchat[domainadmin]="domains" role_recipients_rocketchat[dba]="databases systems" role_recipients_rocketchat[webmaster]="marketing development" +role_recipients_rocketchat[proxyadmin]="proxy_admin" +role_recipients_rocketchat[sitemgr]="sites" +``` + +The values you provide should already exist as Rocket.Chat channels. + +An example of a working configuration would be: + +```conf +#------------------------------------------------------------------------------ +# rocketchat (rocket.chat) global notification options + +SEND_ROCKETCHAT="YES" +ROCKETCHAT_WEBHOOK_URL="" +DEFAULT_RECIPIENT_ROCKETCHAT="monitoring_alarms" ``` -The keywords `systems`, `databases`, `marketing`, `development` are RocketChat channels (they should already exist). -Both public and private channels can be used, even if they differ from the channel configured in your RocketChat incoming webhook. +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/slack/README.md b/health/notifications/slack/README.md index ad9a2134..66fdcc02 100644 --- a/health/notifications/slack/README.md +++ b/health/notifications/slack/README.md @@ -1,55 +1,54 @@ - - -# Slack +# Slack Agent alert notifications + +Learn how to send notifications to a Slack workspace using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. This is what you will get: -![image](https://cloud.githubusercontent.com/assets/2662304/18407116/bbd0fee6-7710-11e6-81cf-58c0defaee2b.png) -You need: +![image](https://user-images.githubusercontent.com/70198089/229841857-77ed2562-ee62-427b-803a-cef03d08238d.png) -1. The **incoming webhook URL** as given by slack.com. You can use the same on all your Netdata servers (or you can have multiple if you like - your decision). -2. One or more channels to post the messages to. -To get a webhook that works on multiple channels, you will need to login to your slack.com workspace and create an incoming webhook using the [Incoming Webhooks App](https://slack.com/apps/A0F7XDUAZ-incoming-webhooks). -Do NOT use the instructions in , as the particular webhooks work only for a single channel. +## Prerequisites -Set the webhook and the recipients in `/etc/netdata/health_alarm_notify.conf` (to edit it on your system run `/etc/netdata/edit-config health_alarm_notify.conf`), like this: +You will need: -``` -SEND_SLACK="YES" +- a Slack app along with an incoming webhook, read Slack's guide on the topic [here](https://api.slack.com/messaging/webhooks) +- one or more channels to post the messages to +- terminal access to the Agent you wish to configure -SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXXXXXXX/XXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +## Configure Netdata to send alert notifications to Slack -# if a role's recipients are not configured, a notification will be send to: -# - A slack channel (syntax: '#channel' or 'channel') -# - A slack user (syntax: '@user') -# - The channel or user defined in slack for the webhook (syntax: '#') -# empty = do not send a notification for unconfigured roles -DEFAULT_RECIPIENT_SLACK="alarms" -``` +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. -You can define multiple recipients like this: `# #alarms systems @myuser`. -This example will send the alarm to: +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: -- The recipient defined in slack for the webhook (not known to Netdata) -- The channel 'alarms' -- The channel 'systems' -- The user @myuser +1. Set `SEND_SLACK` to `YES`. +2. Set `SLACK_WEBHOOK_URL` to your Slack app's webhook URL. +3. Set `DEFAULT_RECIPIENT_SLACK` to the Slack channel your Slack app is set to send messages to. + The syntax for channels is `#channel` or `channel`. + All roles will default to this variable if left unconfigured. -You can give different recipients per **role** using these (at the same file): +An example of a working configuration would be: -``` -role_recipients_slack[sysadmin]="systems" -role_recipients_slack[dba]="databases systems" -role_recipients_slack[webmaster]="marketing development" +```conf +#------------------------------------------------------------------------------ +# slack (slack.com) global notification options + +SEND_SLACK="YES" +SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXXXXXXX/XXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +DEFAULT_RECIPIENT_SLACK="#alarms" ``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/smstools3/README.md b/health/notifications/smstools3/README.md index 9535c954..d72df4a6 100644 --- a/health/notifications/smstools3/README.md +++ b/health/notifications/smstools3/README.md @@ -1,49 +1,73 @@ - - -# SMS Server Tools 3 +# SMS Server Tools 3 Agent alert notifications + +Learn how to send notifications to `smstools3` using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. The [SMS Server Tools 3](http://smstools3.kekekasvi.com/) is a SMS Gateway software which can send and receive short messages through GSM modems and mobile phones. -To have Netdata send notifications via SMS Server Tools 3, you'll first need to [install](http://smstools3.kekekasvi.com/index.php?p=compiling) and [configure](http://smstools3.kekekasvi.com/index.php?p=configure) smsd. +## Prerequisites -Ensure that the user `netdata` can execute `sendsms`. Any user executing `sendsms` needs to: +You will need: -- Have write permissions to `/tmp` and `/var/spool/sms/outgoing` -- Be a member of group `smsd` +- to [install](http://smstools3.kekekasvi.com/index.php?p=compiling) and [configure](http://smstools3.kekekasvi.com/index.php?p=configure) smsd -To ensure that the steps above are successful, just `su netdata` and execute `sendsms phone message`. +- To ensure that the user `netdata` can execute `sendsms`. Any user executing `sendsms` needs to: + - have write permissions to `/tmp` and `/var/spool/sms/outgoing` + - be a member of group `smsd` -You then just need to configure the recipient phone numbers in `health_alarm_notify.conf`: + To ensure that the steps above are successful, just `su netdata` and execute `sendsms phone message`. +- terminal access to the Agent you wish to configure -```sh -#------------------------------------------------------------------------------ -# SMS Server Tools 3 (smstools3) global notification options +## Configure Netdata to send alert notifications to SMS Server Tools 3 -# enable/disable sending SMS Server Tools 3 SMS notifications -SEND_SMS="YES" +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. + +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: -# if a role's recipients are not configured, a notification will be sent to -# this SMS channel (empty = do not send a notification for unconfigured -# roles). Multiple recipients can be given like this: "PHONE1 PHONE2 ..." +1. Set the path for `sendsms`, otherwise Netdata will search for it in your system `$PATH`: -DEFAULT_RECIPIENT_SMS="" + ```conf + # The full path of the sendsms command (smstools3). + # If empty, the system $PATH will be searched for it. + # If not found, SMS notifications will be silently disabled. + sendsms="/usr/bin/sendsms" + ``` + +2. Set `SEND_SMS` to `YES`. +3. Set `DEFAULT_RECIPIENT_SMS` to the phone number you want the alert notifications to be sent to. + You can define multiple phone numbers like this: `PHONE1 PHONE2`. + All roles will default to this variable if left unconfigured. + +You can then have different phone numbers per **role**, by editing `DEFAULT_RECIPIENT_IRC` with the phone number you want, in the following entries at the bottom of the same file: + +```conf +role_recipients_sms[sysadmin]="PHONE1" +role_recipients_sms[domainadmin]="PHONE2" +role_recipients_sms[dba]="PHONE3" +role_recipients_sms[webmaster]="PHONE4" +role_recipients_sms[proxyadmin]="PHONE5" +role_recipients_sms[sitemgr]="PHONE6" ``` -Netdata uses the script `sendsms` that is installed by `smstools3` and just passes a phone number and a message to it. If `sendsms` is not in `$PATH`, you can pass its location in `health_alarm_notify.conf`: +An example of a working configuration would be: -```sh -# The full path of the sendsms command (smstools3). -# If empty, the system $PATH will be searched for it. -# If not found, SMS notifications will be silently disabled. -sendsms="" +```conf +#------------------------------------------------------------------------------ +# SMS Server Tools 3 (smstools3) global notification options +SEND_SMS="YES" +DEFAULT_RECIPIENT_SMS="1234567890" ``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/stackpulse/README.md b/health/notifications/stackpulse/README.md index 25266e82..b488ca19 100644 --- a/health/notifications/stackpulse/README.md +++ b/health/notifications/stackpulse/README.md @@ -1,15 +1,15 @@ -# Send notifications to StackPulse +# StackPulse agent alert notifications [StackPulse](https://stackpulse.com/) is a software-as-a-service platform for site reliability engineering. It helps SREs, DevOps Engineers and Software Developers reduce toil and alert fatigue while improving reliability of diff --git a/health/notifications/syslog/README.md b/health/notifications/syslog/README.md index 3527decc..4cda14b3 100644 --- a/health/notifications/syslog/README.md +++ b/health/notifications/syslog/README.md @@ -1,39 +1,77 @@ - +# Syslog Agent alert notifications -# Syslog +Learn how to send notifications to Syslog using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. -You need a working `logger` command for this to work. This is the case on pretty much every Linux system in existence, and most BSD systems. +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. Logged messages will look like this: -``` +```bash netdata WARNING on hostname at Tue Apr 3 09:00:00 EDT 2018: disk_space._ out of disk space time = 5h ``` -## configuration +## Prerequisites -System log targets are configured as recipients in [`/etc/netdata/health_alarm_notify.conf`](https://github.com/netdata/netdata/blob/36bedc044584dea791fd29455bdcd287c3306cb2/conf.d/health_alarm_notify.conf#L534) (to edit it on your system run `/etc/netdata/edit-config health_alarm_notify.conf`). +You will need: -You can also configure per-role targets in the same file a bit further down. +- A working `logger` command for this to work. This is the case on pretty much every Linux system in existence, and most BSD systems. +- terminal access to the Agent you wish to configure -Targets are defined as follows: +## Configure Netdata to send alert notifications to Syslog -``` -[[facility.level][@host[:port]]/]prefix +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. + +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: + +1. Set `SYSLOG_FACILITY` to the facility used for logging, by default this value is set to `local6`. +2. Set `DEFAULT_RECIPIENT_SYSLOG` to the recipient you want the alert notifications to be sent to. + Targets are defined as follows: + + ```conf + [[facility.level][@host[:port]]/]prefix + ``` + + `prefix` defines what the log messages are prefixed with. By default, all lines are prefixed with 'netdata'. + + The `facility` and `level` are the standard syslog facility and level options, for more info on them see your local `logger` and `syslog` documentation. By default, Netdata will log to the `local6` facility, with a log level dependent on the type of message (`crit` for CRITICAL, `warning` for WARNING, and `info` for everything else). + + You can configure sending directly to remote log servers by specifying a host (and optionally a port). However, this has a somewhat high overhead, so it is much preferred to use your local syslog daemon to handle the forwarding of messages to remote systems (pretty much all of them allow at least simple forwarding, and most of the really popular ones support complex queueing and routing of messages to remote log servers). + + You can define multiple recipients like this: `daemon.notice@loghost:514/netdata daemon.notice@loghost2:514/netdata`. + All roles will default to this variable if left unconfigured. +3. Lastly, set `SEND_SYSLOG` to `YES`, make sure you have everything else configured _before_ turning this on. + +You can then have different recipients per **role**, by editing `DEFAULT_RECIPIENT_SYSLOG` with the recipient you want, in the following entries at the bottom of the same file: + +```conf +role_recipients_syslog[sysadmin]="daemon.notice@loghost1:514/netdata" +role_recipients_syslog[domainadmin]="daemon.notice@loghost2:514/netdata" +role_recipients_syslog[dba]="daemon.notice@loghost3:514/netdata" +role_recipients_syslog[webmaster]="daemon.notice@loghost4:514/netdata" +role_recipients_syslog[proxyadmin]="daemon.notice@loghost5:514/netdata" +role_recipients_syslog[sitemgr]="daemon.notice@loghost6:514/netdata" ``` -`prefix` defines what the log messages are prefixed with. By default, all lines are prefixed with 'netdata'. +An example of a working configuration would be: -The `facility` and `level` are the standard syslog facility and level options, for more info on them see your local `logger` and `syslog` documentation. By default, Netdata will log to the `local6` facility, with a log level dependent on the type of message (`crit` for CRITICAL, `warning` for WARNING, and `info` for everything else). +```conf +#------------------------------------------------------------------------------ +# syslog notifications -You can configure sending directly to remote log servers by specifying a host (and optionally a port). However, this has a somewhat high overhead, so it is much preferred to use your local syslog daemon to handle the forwarding of messages to remote systems (pretty much all of them allow at least simple forwarding, and most of the really popular ones support complex queueing and routing of messages to remote log servers). +SEND_SYSLOG="YES" +SYSLOG_FACILITY='local6' +DEFAULT_RECIPIENT_SYSLOG="daemon.notice@loghost6:514/netdata" +``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/telegram/README.md b/health/notifications/telegram/README.md index f80a2838..9cc77d68 100644 --- a/health/notifications/telegram/README.md +++ b/health/notifications/telegram/README.md @@ -1,50 +1,71 @@ - - -# Telegram +# Telegram Agent alert notifications + +Learn how to send notifications to Telegram using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. [Telegram](https://telegram.org/) is a messaging app with a focus on speed and security, it’s super-fast, simple and free. You can use Telegram on all your devices at the same time — your messages sync seamlessly across any number of your phones, tablets or computers. -With Telegram, you can send messages, photos, videos and files of any type (doc, zip, mp3, etc), as well as create groups for up to 100,000 people or channels for broadcasting to unlimited audiences. You can write to your phone contacts and find people by their usernames. As a result, Telegram is like SMS and email combined — and can take care of all your personal or business messaging needs. +Telegram messages look like this: + + Netdata will send warning messages without vibration. -You need to: +## Prerequisites + +You will need: + +- A bot token. To get one, contact the [@BotFather](https://t.me/BotFather) bot and send the command `/newbot` and follow the instructions. + Start a conversation with your bot or invite it into a group where you want it to send messages. +- The chat ID for every chat you want to send messages to. Contact the [@myidbot](https://t.me/myidbot) bot and send the `/getid` command to get your personal chat ID or invite it into a group and use the `/getgroupid` command to get the group chat ID. Group IDs start with a hyphen, supergroup IDs start with `-100`. -1. Get a bot token. To get one, contact the [@BotFather](https://t.me/BotFather) bot and send the command `/newbot`. Follow the instructions. -2. Start a conversation with your bot or invite it into a group where you want it to send messages. -3. Find the chat ID for every chat you want to send messages to. Contact the [@myidbot](https://t.me/myidbot) bot and send the `/getid` command to get your personal chat ID or invite it into a group and use the `/getgroupid` command to get the group chat ID. Group IDs start with a hyphen, supergroup IDs start with `-100`. Alternatively, you can get the chat ID directly from the bot API. Send *your* bot a command in the chat you want to use, then check `https://api.telegram.org/bot{YourBotToken}/getUpdates`, eg. `https://api.telegram.org/bot111122223:7OpFlFFRzRBbrUUmIjj5HF9Ox2pYJZy5/getUpdates` -4. Set the bot token and the chat ID of the recipient in `/etc/netdata/health_alarm_notify.conf` (to edit it on your system run `/etc/netdata/edit-config health_alarm_notify.conf`), like this: -``` -SEND_TELEGRAM="YES" -TELEGRAM_BOT_TOKEN="111122223:7OpFlFFRzRBbrUUmIjj5HF9Ox2pYJZy5" -DEFAULT_RECIPIENT_TELEGRAM="-100233335555" -``` +- terminal access to the Agent you wish to configure -You can define multiple recipients like this: `"-100311112222 212341234|critical"`. -This example will send: +## Configure Netdata to send alert notifications to Telegram -- All alerts to the group with ID -100311112222 -- Critical alerts to the user with ID 212341234 +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. -You can give different recipients per **role** using these (in the same file): +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: -``` -role_recipients_telegram[sysadmin]="212341234" -role_recipients_telegram[dba]="-1004444333321" -role_recipients_telegram[webmaster]="49999333322 -1009999222255" +1. Set `SEND_TELEGRAM` to `YES`. +2. Set `TELEGRAM_BOT_TOKEN` to your bot token. +3. Set `DEFAULT_RECIPIENT_TELEGRAM` to the chat ID you want the alert notifications to be sent to. + You can define multiple chat IDs like this: `49999333322 -1009999222255`. + All roles will default to this variable if left unconfigured. + +You can then have different chats per **role**, by editing `DEFAULT_RECIPIENT_TELEGRAM` with the chat ID you want, in the following entries at the bottom of the same file: + +```conf +role_recipients_telegram[sysadmin]="49999333324" +role_recipients_telegram[domainadmin]="49999333389" +role_recipients_telegram[dba]="-1009999222255" +role_recipients_telegram[webmaster]="-1009999222255 49999333389" +role_recipients_telegram[proxyadmin]="49999333344" +role_recipients_telegram[sitemgr]="49999333876" ``` -Telegram messages look like this: +An example of a working configuration would be: -![Netdata notifications via Telegram](https://user-images.githubusercontent.com/1153921/66612223-f07dfb80-eb75-11e9-976f-5734ffd93ecd.png) +```conf +#------------------------------------------------------------------------------ +# telegram (telegram.org) global notification options + +SEND_TELEGRAM="YES" +TELEGRAM_BOT_TOKEN="111122223:7OpFlFFRzRBbrUUmIjj5HF9Ox2pYJZy5" +DEFAULT_RECIPIENT_TELEGRAM="-100233335555" +``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/twilio/README.md b/health/notifications/twilio/README.md index 470b2413..8214b6a4 100644 --- a/health/notifications/twilio/README.md +++ b/health/notifications/twilio/README.md @@ -1,52 +1,72 @@ - - -# Twilio +# Twilio Agent alert notifications + +Learn how to send notifications to Twilio using Netdata's Agent alert notification feature, which supports dozens of endpoints, user roles, and more. + +> ### Note +> +> This file assumes you have read the [Introduction to Agent alert notifications](https://github.com/netdata/netdata/blob/master/health/notifications/README.md), detailing how the Netdata Agent's alert notification method works. Will look like this on your Android device: -![image](https://cloud.githubusercontent.com/assets/17090999/20034652/620b6100-a39b-11e6-96af-4f83b8e830e2.png) +![image](https://user-images.githubusercontent.com/70198089/229841323-6c4b1956-dd91-423e-abaf-2799000f72a8.png) + + +## Prerequisites You will need: -1. Signup and Login to twilio.com -2. Pick an SMS capable number during sign up. -3. Get your SID, and Token from -4. Fill in TWILIO_ACCOUNT_SID="XXXXXXXX" TWILIO_ACCOUNT_TOKEN="XXXXXXXXX" TWILIO_NUMBER="+XXXXXXXXXXX" -5. Add the recipient phone numbers to DEFAULT_RECIPIENT_TWILIO="+XXXXXXXXXXX" +- to get your SID, and Token from +- terminal access to the Agent you wish to configure -!!PLEASE NOTE THAT IF YOUR ACCOUNT IS A TRIAL ACCOUNT YOU WILL ONLY BE ABLE TO SEND NOTIFICATIONS TO THE NUMBER YOU SIGNED UP WITH +## Configure Netdata to send alert notifications to Twilio -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: +> ### Info +> +> This file mentions editing configuration files. +> +> - To edit configuration files in a safe way, we provide the [`edit config` script](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#use-edit-config-to-edit-configuration-files) located in your [Netdata config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory) (typically is `/etc/netdata`) that creates the proper file and opens it in an editor automatically. +> Note that to run the script you need to be inside your Netdata config directory. +> +> It is recommended to use this way for configuring Netdata. -``` -############################################################################### -# Twilio (twilio.com) SMS options +Edit `health_alarm_notify.conf`, changes to this file do not require restarting Netdata: -# multiple recipients can be given like this: -# "+15555555555 +17777777777" +1. Set `SEND_TWILIO` to `YES`. +2. Set `TWILIO_ACCOUNT_SID` to your account SID. +3. Set `TWILIO_ACCOUNT_TOKEN` to your account token. +4. Set `TWILIO_NUMBER` to your account's number. +5. Set `DEFAULT_RECIPIENT_TWILIO` to the number you want the alert notifications to be sent to. + You can define multiple numbers like this: `+15555555555 +17777777777`. + All roles will default to this variable if left unconfigured. -# enable/disable sending twilio SMS -SEND_TWILIO="YES" + > ### Note + > + > Please not that if your account is a trial account you will only be able to send notifications to the number you signed up with. -# Signup for free trial and select a SMS capable Twilio Number -# To get your Account SID and Token, go to https://www.twilio.com/console -# Place your sid, token and number below. -# Then just set the recipients' phone numbers. -# The trial account is only allowed to use the number specified when set up. +You can then have different recipients per **role**, by editing `DEFAULT_RECIPIENT_TWILIO` with the recipient's number you want, in the following entries at the bottom of the same file: -# Without an account sid and token, Netdata cannot send Twilio text messages. +```conf +role_recipients_twilio[sysadmin]="+15555555555" +role_recipients_twilio[domainadmin]="+15555555556" +role_recipients_twilio[dba]="+15555555557" +role_recipients_twilio[webmaster]="+15555555558" +role_recipients_twilio[proxyadmin]="+15555555559" +role_recipients_twilio[sitemgr]="+15555555550" +``` + +An example of a working configuration would be: + +```conf +#------------------------------------------------------------------------------ +# Twilio (twilio.com) SMS options + +SEND_TWILIO="YES" TWILIO_ACCOUNT_SID="xxxxxxxxx" TWILIO_ACCOUNT_TOKEN="xxxxxxxxxx" TWILIO_NUMBER="xxxxxxxxxxx" DEFAULT_RECIPIENT_TWILIO="+15555555555" ``` +## Test the notification method +To test this alert notification method refer to the ["Testing Alert Notifications"](https://github.com/netdata/netdata/blob/master/health/notifications/README.md#testing-alert-notifications) section of the Agent alert notifications page. diff --git a/health/notifications/web/README.md b/health/notifications/web/README.md index b4afd9ea..36ca2668 100644 --- a/health/notifications/web/README.md +++ b/health/notifications/web/README.md @@ -1,14 +1,14 @@ -# Pop up notifications +# Browser pop up agent alert notifications The Netdata dashboard shows HTML notifications, when it is open. diff --git a/libnetdata/Makefile.am b/libnetdata/Makefile.am index b81d620b..4bf77913 100644 --- a/libnetdata/Makefile.am +++ b/libnetdata/Makefile.am @@ -20,6 +20,7 @@ SUBDIRS = \ locks \ log \ onewayalloc \ + parser \ popen \ procfile \ simple_pattern \ diff --git a/libnetdata/adaptive_resortable_list/README.md b/libnetdata/adaptive_resortable_list/README.md index 95757848..ceed467d 100644 --- a/libnetdata/adaptive_resortable_list/README.md +++ b/libnetdata/adaptive_resortable_list/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/adapt sidebar_label: "Adaptive Re-sortable List (ARL)" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # Adaptive Re-sortable List (ARL) diff --git a/libnetdata/adaptive_resortable_list/adaptive_resortable_list.c b/libnetdata/adaptive_resortable_list/adaptive_resortable_list.c index 7f4c6c53..6332fa17 100644 --- a/libnetdata/adaptive_resortable_list/adaptive_resortable_list.c +++ b/libnetdata/adaptive_resortable_list/adaptive_resortable_list.c @@ -9,7 +9,7 @@ inline void arl_callback_str2ull(const char *name, uint32_t hash, const char *va (void)hash; register unsigned long long *d = dst; - *d = str2ull(value); + *d = str2ull(value, NULL); // fprintf(stderr, "name '%s' with hash %u and value '%s' is %llu\n", name, hash, value, *d); } diff --git a/libnetdata/aral/README.md b/libnetdata/aral/README.md index 3b0f5bbd..e556144b 100644 --- a/libnetdata/aral/README.md +++ b/libnetdata/aral/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/aral/ sidebar_label: "Array allocator" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # Array Allocator diff --git a/libnetdata/aral/aral.c b/libnetdata/aral/aral.c index 4505ee0f..60fe5e39 100644 --- a/libnetdata/aral/aral.c +++ b/libnetdata/aral/aral.c @@ -465,6 +465,9 @@ static inline ARAL_PAGE *aral_acquire_a_free_slot(ARAL *ar TRACE_ALLOCATIONS_FUN } void *aral_mallocz_internal(ARAL *ar TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) { +#ifdef FSANITIZE_ADDRESS + return mallocz(ar->config.requested_element_size); +#endif ARAL_PAGE *page = aral_acquire_a_free_slot(ar TRACE_ALLOCATIONS_FUNCTION_CALL_PARAMS); @@ -614,6 +617,11 @@ static inline void aral_move_page_with_free_list___aral_lock_needed(ARAL *ar, AR } void aral_freez_internal(ARAL *ar, void *ptr TRACE_ALLOCATIONS_FUNCTION_DEFINITION_PARAMS) { +#ifdef FSANITIZE_ADDRESS + freez(ptr); + return; +#endif + if(unlikely(!ptr)) return; // get the page pointer @@ -877,10 +885,10 @@ void aral_by_size_release(ARAL *ar) { 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; - } +// 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); } diff --git a/libnetdata/avl/README.md b/libnetdata/avl/README.md index 2b03fec4..94c0f634 100644 --- a/libnetdata/avl/README.md +++ b/libnetdata/avl/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/avl/R sidebar_label: "AVL" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # AVL diff --git a/libnetdata/buffer/README.md b/libnetdata/buffer/README.md index 6a84fd8a..2937ae14 100644 --- a/libnetdata/buffer/README.md +++ b/libnetdata/buffer/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/buffe sidebar_label: "BUFFER library" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # BUFFER diff --git a/libnetdata/buffer/buffer.c b/libnetdata/buffer/buffer.c index eeb28320..142fbca1 100644 --- a/libnetdata/buffer/buffer.c +++ b/libnetdata/buffer/buffer.c @@ -2,39 +2,16 @@ #include "../libnetdata.h" -#define BUFFER_OVERFLOW_EOF "EOF" - static inline void buffer_overflow_init(BUFFER *b) { b->buffer[b->size] = '\0'; strcpy(&b->buffer[b->size + 1], BUFFER_OVERFLOW_EOF); } -#ifdef NETDATA_INTERNAL_CHECKS -#define buffer_overflow_check(b) _buffer_overflow_check(b, __FILE__, __FUNCTION__, __LINE__) -#else -#define buffer_overflow_check(b) -#endif - -static inline void _buffer_overflow_check(BUFFER *b, const char *file, const char *function, const unsigned long line) -{ - if(b->len > b->size) { - error("BUFFER: length %zu is above size %zu, at line %lu, at function %s() of file '%s'.", b->len, b->size, line, function, file); - b->len = b->size; - } - - if(b->buffer[b->size] != '\0' || strcmp(&b->buffer[b->size + 1], BUFFER_OVERFLOW_EOF) != 0) { - error("BUFFER: detected overflow at line %lu, at function %s() of file '%s'.", line, function, file); - buffer_overflow_init(b); - } -} - - -void buffer_reset(BUFFER *wb) -{ +void buffer_reset(BUFFER *wb) { buffer_flush(wb); - wb->contenttype = CT_TEXT_PLAIN; + wb->content_type = CT_TEXT_PLAIN; wb->options = 0; wb->date = 0; wb->expires = 0; @@ -52,8 +29,7 @@ const char *buffer_tostring(BUFFER *wb) return(wb->buffer); } -void buffer_char_replace(BUFFER *wb, char from, char to) -{ +void buffer_char_replace(BUFFER *wb, char from, char to) { char *s = wb->buffer, *end = &wb->buffer[wb->len]; while(s != end) { @@ -64,212 +40,25 @@ void buffer_char_replace(BUFFER *wb, char from, char to) buffer_overflow_check(wb); } -// This trick seems to give an 80% speed increase in 32bit systems -// print_number_llu_r() will just print the digits up to the -// point the remaining value fits in 32 bits, and then calls -// print_number_lu_r() to print the rest with 32 bit arithmetic. - -inline char *print_number_lu_r(char *str, unsigned long uvalue) { - char *wstr = str; - - // print each digit - do *wstr++ = (char)('0' + (uvalue % 10)); while(uvalue /= 10); - return wstr; -} - -inline char *print_number_llu_r(char *str, unsigned long long uvalue) { - char *wstr = str; - - // print each digit - do *wstr++ = (char)('0' + (uvalue % 10)); while((uvalue /= 10) && uvalue > (unsigned long long)0xffffffff); - if(uvalue) return print_number_lu_r(wstr, uvalue); - return wstr; -} - -inline char *print_number_llu_r_smart(char *str, unsigned long long uvalue) { - switch (sizeof(void *)) { - case 4: - str = (uvalue > (unsigned long long) 0xffffffff) ? print_number_llu_r(str, uvalue) : - print_number_lu_r(str, uvalue); - break; - case 8: - do { - *str++ = (char) ('0' + (uvalue % 10)); - } while (uvalue /= 10); - break; - default: - fatal("Netdata supports only 32-bit & 64-bit systems."); - } - - return str; -} - -void buffer_print_llu(BUFFER *wb, unsigned long long uvalue) -{ - buffer_need_bytes(wb, 50); - - char *str = &wb->buffer[wb->len]; - char *wstr = str; - - switch (sizeof(void *)) { - case 4: - wstr = (uvalue > (unsigned long long) 0xffffffff) ? print_number_llu_r(wstr, uvalue) : - print_number_lu_r(wstr, uvalue); - break; - case 8: - do { - *wstr++ = (char) ('0' + (uvalue % 10)); - } while (uvalue /= 10); - break; - default: - fatal("Netdata supports only 32-bit & 64-bit systems."); +void buffer_print_sn_flags(BUFFER *wb, SN_FLAGS flags, bool send_anomaly_bit) { + if(unlikely(flags == SN_EMPTY_SLOT)) { + buffer_fast_strcat(wb, "E", 1); + return; } - // terminate it - *wstr = '\0'; - - // reverse it - char *begin = str, *end = wstr - 1, aux; - while (end > begin) aux = *end, *end-- = *begin, *begin++ = aux; - - // return the buffer length - wb->len += wstr - str; -} - -void buffer_print_ll(BUFFER *wb, long long value) -{ - buffer_need_bytes(wb, 50); - - if(value < 0) { - buffer_fast_strcat(wb, "-", 1); - value = -value; + size_t printed = 0; + if(likely(send_anomaly_bit && (flags & SN_FLAG_NOT_ANOMALOUS))) { + buffer_fast_strcat(wb, "A", 1); + printed++; } - buffer_print_llu(wb, value); -} - -static unsigned char bits03_to_hex[16] = { - [0] = '0', - [1] = '1', - [2] = '2', - [3] = '3', - [4] = '4', - [5] = '5', - [6] = '6', - [7] = '7', - [8] = '8', - [9] = '9', - [10] = 'A', - [11] = 'B', - [12] = 'C', - [13] = 'D', - [14] = 'E', - [15] = 'F' -}; - -void buffer_print_llu_hex(BUFFER *wb, unsigned long long value) -{ - unsigned char buffer[sizeof(unsigned long long) * 2 + 2 + 1]; // 8 bytes * 2 + '0x' + '\0' - unsigned char *e = &buffer[sizeof(unsigned long long) * 2 + 2]; - unsigned char *p = e; - - *p-- = '\0'; - *p-- = bits03_to_hex[value & 0xF]; - value >>= 4; - if(value) { - *p-- = bits03_to_hex[value & 0xF]; - value >>= 4; - - while(value) { - *p-- = bits03_to_hex[value & 0xF]; - value >>= 4; - - if(value) { - *p-- = bits03_to_hex[value & 0xF]; - value >>= 4; - } - } + if(unlikely(flags & SN_FLAG_RESET)) { + buffer_fast_strcat(wb, "R", 1); + printed++; } - *p-- = 'x'; - *p = '0'; - buffer_fast_strcat(wb, (char *)p, e - p); -} - -void buffer_fast_strcat(BUFFER *wb, const char *txt, size_t len) { - if(unlikely(!txt || !*txt)) return; - - buffer_need_bytes(wb, len + 1); - - char *s = &wb->buffer[wb->len]; - const char *end = &txt[len + 1]; - - while(txt != end) - *s++ = *txt++; - - wb->len += len; - - // keep it NULL terminating - // not counting it at wb->len - wb->buffer[wb->len] = '\0'; -} - -void buffer_strcat(BUFFER *wb, const char *txt) -{ - // buffer_sprintf(wb, "%s", txt); - - if(unlikely(!txt || !*txt)) return; - - buffer_need_bytes(wb, 1); - - char *s = &wb->buffer[wb->len], *start, *end = &wb->buffer[wb->size]; - size_t len = wb->len; - - start = s; - while(*txt && s != end) - *s++ = *txt++; - - len += s - start; - - wb->len = len; - buffer_overflow_check(wb); - - if(*txt) { - debug(D_WEB_BUFFER, "strcat(): increasing web_buffer at position %zu, size = %zu\n", wb->len, wb->size); - len = strlen(txt); - buffer_fast_strcat(wb, txt, len); - } - else { - // terminate the string - // without increasing the length - buffer_need_bytes(wb, (size_t)1); - wb->buffer[wb->len] = '\0'; - } -} - -void buffer_strcat_jsonescape(BUFFER *wb, const char *txt) -{ - while(*txt) { - switch(*txt) { - case '\\': - buffer_need_bytes(wb, 2); - wb->buffer[wb->len++] = '\\'; - wb->buffer[wb->len++] = '\\'; - break; - case '"': - buffer_need_bytes(wb, 2); - wb->buffer[wb->len++] = '\\'; - wb->buffer[wb->len++] = '"'; - break; - default: { - buffer_need_bytes(wb, 1); - wb->buffer[wb->len++] = *txt; - } - } - txt++; - } - - buffer_overflow_check(wb); + if(!printed) + buffer_fast_strcat(wb, "''", 2); } void buffer_strcat_htmlescape(BUFFER *wb, const char *txt) @@ -358,25 +147,6 @@ void buffer_sprintf(BUFFER *wb, const char *fmt, ...) // the buffer is \0 terminated by vsnprintf } - -void buffer_rrd_value(BUFFER *wb, NETDATA_DOUBLE value) -{ - buffer_need_bytes(wb, 50); - - if(isnan(value) || isinf(value)) { - buffer_strcat(wb, "null"); - return; - } - else - wb->len += print_netdata_double(&wb->buffer[wb->len], value); - - // terminate it - buffer_need_bytes(wb, 1); - wb->buffer[wb->len] = '\0'; - - buffer_overflow_check(wb); -} - // generate a javascript date, the fastest possible way... void buffer_jsdate(BUFFER *wb, int year, int month, int day, int hours, int minutes, int seconds) { @@ -482,7 +252,7 @@ BUFFER *buffer_create(size_t size, size_t *statistics) b->buffer = mallocz(size + sizeof(BUFFER_OVERFLOW_EOF) + 2); b->buffer[0] = '\0'; b->size = size; - b->contenttype = CT_TEXT_PLAIN; + b->content_type = CT_TEXT_PLAIN; b->statistics = statistics; buffer_overflow_init(b); buffer_overflow_check(b); @@ -531,3 +301,205 @@ void buffer_increase(BUFFER *b, size_t free_size_required) { buffer_overflow_init(b); buffer_overflow_check(b); } + +// ---------------------------------------------------------------------------- + +void buffer_json_initialize(BUFFER *wb, const char *key_quote, const char *value_quote, int depth, + bool add_anonymous_object, bool minify) { + strncpyz(wb->json.key_quote, key_quote, BUFFER_QUOTE_MAX_SIZE); + strncpyz(wb->json.value_quote, value_quote, BUFFER_QUOTE_MAX_SIZE); + + wb->json.minify = minify; + wb->json.depth = (int8_t)(depth - 1); + _buffer_json_depth_push(wb, BUFFER_JSON_OBJECT); + + if(add_anonymous_object) + buffer_fast_strcat(wb, "{", 1); +} + +void buffer_json_finalize(BUFFER *wb) { + while(wb->json.depth >= 0) { + switch(wb->json.stack[wb->json.depth].type) { + case BUFFER_JSON_OBJECT: + buffer_json_object_close(wb); + break; + + case BUFFER_JSON_ARRAY: + buffer_json_array_close(wb); + break; + + default: + internal_fatal(true, "BUFFER: unknown json member type in stack"); + break; + } + } + + if(!wb->json.minify) + buffer_fast_strcat(wb, "\n", 1); +} + +// ---------------------------------------------------------------------------- + +const char hex_digits[16] = "0123456789ABCDEF"; +const char base64_digits[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +unsigned char hex_value_from_ascii[256]; +unsigned char base64_value_from_ascii[256]; + +__attribute__((constructor)) void initialize_ascii_maps(void) { + for(size_t i = 0 ; i < 256 ; i++) { + hex_value_from_ascii[i] = 255; + base64_value_from_ascii[i] = 255; + } + + for(size_t i = 0; i < 16 ; i++) + hex_value_from_ascii[(int)hex_digits[i]] = i; + + for(size_t i = 0; i < 64 ; i++) + base64_value_from_ascii[(int)base64_digits[i]] = i; +} + +// ---------------------------------------------------------------------------- +// unit test + +static int buffer_expect(BUFFER *wb, const char *expected) { + const char *generated = buffer_tostring(wb); + + if(strcmp(generated, expected) != 0) { + error("BUFFER: mismatch.\nGenerated:\n%s\nExpected:\n%s\n", + generated, expected); + return 1; + } + + return 0; +} + +static int buffer_uint64_roundtrip(BUFFER *wb, NUMBER_ENCODING encoding, uint64_t value, const char *expected) { + int errors = 0; + buffer_flush(wb); + buffer_print_uint64_encoded(wb, encoding, value); + + if(expected) + errors += buffer_expect(wb, expected); + + uint64_t v = str2ull_encoded(buffer_tostring(wb)); + if(v != value) { + error("BUFFER: string '%s' does resolves to %llu, expected %llu", + buffer_tostring(wb), (unsigned long long)v, (unsigned long long)value); + errors++; + } + buffer_flush(wb); + return errors; +} + +static int buffer_int64_roundtrip(BUFFER *wb, NUMBER_ENCODING encoding, int64_t value, const char *expected) { + int errors = 0; + buffer_flush(wb); + buffer_print_int64_encoded(wb, encoding, value); + + if(expected) + errors += buffer_expect(wb, expected); + + int64_t v = str2ll_encoded(buffer_tostring(wb)); + if(v != value) { + error("BUFFER: string '%s' does resolves to %lld, expected %lld", + buffer_tostring(wb), (long long)v, (long long)value); + errors++; + } + buffer_flush(wb); + return errors; +} + +static int buffer_double_roundtrip(BUFFER *wb, NUMBER_ENCODING encoding, NETDATA_DOUBLE value, const char *expected) { + int errors = 0; + buffer_flush(wb); + buffer_print_netdata_double_encoded(wb, encoding, value); + + if(expected) + errors += buffer_expect(wb, expected); + + NETDATA_DOUBLE v = str2ndd_encoded(buffer_tostring(wb), NULL); + if(v != value) { + error("BUFFER: string '%s' does resolves to %.12f, expected %.12f", + buffer_tostring(wb), v, value); + errors++; + } + buffer_flush(wb); + return errors; +} + +int buffer_unittest(void) { + int errors = 0; + BUFFER *wb = buffer_create(0, NULL); + + buffer_uint64_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 0, "0"); + buffer_uint64_roundtrip(wb, NUMBER_ENCODING_HEX, 0, "0x0"); + buffer_uint64_roundtrip(wb, NUMBER_ENCODING_BASE64, 0, "#A"); + + buffer_uint64_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 1676071986, "1676071986"); + buffer_uint64_roundtrip(wb, NUMBER_ENCODING_HEX, 1676071986, "0x63E6D432"); + buffer_uint64_roundtrip(wb, NUMBER_ENCODING_BASE64, 1676071986, "#Bj5tQy"); + + buffer_uint64_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 18446744073709551615ULL, "18446744073709551615"); + buffer_uint64_roundtrip(wb, NUMBER_ENCODING_HEX, 18446744073709551615ULL, "0xFFFFFFFFFFFFFFFF"); + buffer_uint64_roundtrip(wb, NUMBER_ENCODING_BASE64, 18446744073709551615ULL, "#P//////////"); + + buffer_int64_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 0, "0"); + buffer_int64_roundtrip(wb, NUMBER_ENCODING_HEX, 0, "0x0"); + buffer_int64_roundtrip(wb, NUMBER_ENCODING_BASE64, 0, "#A"); + + buffer_int64_roundtrip(wb, NUMBER_ENCODING_DECIMAL, -1676071986, "-1676071986"); + buffer_int64_roundtrip(wb, NUMBER_ENCODING_HEX, -1676071986, "-0x63E6D432"); + buffer_int64_roundtrip(wb, NUMBER_ENCODING_BASE64, -1676071986, "-#Bj5tQy"); + + buffer_int64_roundtrip(wb, NUMBER_ENCODING_DECIMAL, (int64_t)-9223372036854775807ULL, "-9223372036854775807"); + buffer_int64_roundtrip(wb, NUMBER_ENCODING_HEX, (int64_t)-9223372036854775807ULL, "-0x7FFFFFFFFFFFFFFF"); + buffer_int64_roundtrip(wb, NUMBER_ENCODING_BASE64, (int64_t)-9223372036854775807ULL, "-#H//////////"); + + buffer_double_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 0, "0"); + buffer_double_roundtrip(wb, NUMBER_ENCODING_HEX, 0, "%0"); + buffer_double_roundtrip(wb, NUMBER_ENCODING_BASE64, 0, "@A"); + + buffer_double_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 1.5, "1.5"); + buffer_double_roundtrip(wb, NUMBER_ENCODING_HEX, 1.5, "%3FF8000000000000"); + buffer_double_roundtrip(wb, NUMBER_ENCODING_BASE64, 1.5, "@D/4AAAAAAAA"); + + buffer_double_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 1.23e+14, "123000000000000"); + buffer_double_roundtrip(wb, NUMBER_ENCODING_HEX, 1.23e+14, "%42DBF78AD3AC0000"); + buffer_double_roundtrip(wb, NUMBER_ENCODING_BASE64, 1.23e+14, "@ELb94rTrAAA"); + + buffer_double_roundtrip(wb, NUMBER_ENCODING_DECIMAL, 9.12345678901234567890123456789e+45, "9.123456789012346128e+45"); + buffer_double_roundtrip(wb, NUMBER_ENCODING_HEX, 9.12345678901234567890123456789e+45, "%497991C25C9E4309"); + buffer_double_roundtrip(wb, NUMBER_ENCODING_BASE64, 9.12345678901234567890123456789e+45, "@El5kcJcnkMJ"); + + buffer_flush(wb); + + { + char buf[1024 + 1]; + for(size_t i = 0; i < 1024 ;i++) + buf[i] = (char)(i % 26) + 'A'; + buf[1024] = '\0'; + + buffer_strcat(wb, buf); + errors += buffer_expect(wb, buf); + } + + buffer_flush(wb); + + buffer_json_initialize(wb, "\"", "\"", 0, true, false); + buffer_json_finalize(wb); + errors += buffer_expect(wb, "{\n}\n"); + + buffer_flush(wb); + + buffer_json_initialize(wb, "\"", "\"", 0, true, false); + buffer_json_member_add_string(wb, "hello", "world"); + buffer_json_member_add_string(wb, "alpha", "this: \" is a double quote"); + buffer_json_member_add_object(wb, "object1"); + buffer_json_member_add_string(wb, "hello", "world"); + buffer_json_finalize(wb); + errors += buffer_expect(wb, "{\n \"hello\":\"world\",\n \"alpha\":\"this: \\\" is a double quote\",\n \"object1\":{\n \"hello\":\"world\"\n }\n}\n"); + + buffer_free(wb); + return errors; +} + diff --git a/libnetdata/buffer/buffer.h b/libnetdata/buffer/buffer.h index 0fa3495b..f5f83bc2 100644 --- a/libnetdata/buffer/buffer.h +++ b/libnetdata/buffer/buffer.h @@ -3,48 +3,80 @@ #ifndef NETDATA_WEB_BUFFER_H #define NETDATA_WEB_BUFFER_H 1 +#include "../string/utf8.h" #include "../libnetdata.h" #define WEB_DATA_LENGTH_INCREASE_STEP 1024 +#define BUFFER_JSON_MAX_DEPTH 32 // max is 255 + +extern const char hex_digits[16]; +extern const char base64_digits[64]; +extern unsigned char hex_value_from_ascii[256]; +extern unsigned char base64_value_from_ascii[256]; + +typedef enum __attribute__ ((__packed__)) { + BUFFER_JSON_EMPTY = 0, + BUFFER_JSON_OBJECT, + BUFFER_JSON_ARRAY, +} BUFFER_JSON_NODE_TYPE; + +typedef struct web_buffer_json_node { + BUFFER_JSON_NODE_TYPE type; + uint32_t count:24; +} BUFFER_JSON_NODE; + +#define BUFFER_QUOTE_MAX_SIZE 7 + +typedef enum __attribute__ ((__packed__)) { + WB_CONTENT_CACHEABLE = (1 << 0), + WB_CONTENT_NO_CACHEABLE = (1 << 1), +} BUFFER_OPTIONS; + +typedef enum __attribute__ ((__packed__)) { + CT_NONE = 0, + CT_APPLICATION_JSON, + CT_TEXT_PLAIN, + CT_TEXT_HTML, + CT_APPLICATION_X_JAVASCRIPT, + CT_TEXT_CSS, + CT_TEXT_XML, + CT_APPLICATION_XML, + CT_TEXT_XSL, + CT_APPLICATION_OCTET_STREAM, + CT_APPLICATION_X_FONT_TRUETYPE, + CT_APPLICATION_X_FONT_OPENTYPE, + CT_APPLICATION_FONT_WOFF, + CT_APPLICATION_FONT_WOFF2, + CT_APPLICATION_VND_MS_FONTOBJ, + CT_IMAGE_SVG_XML, + CT_IMAGE_PNG, + CT_IMAGE_JPG, + CT_IMAGE_GIF, + CT_IMAGE_XICON, + CT_IMAGE_ICNS, + CT_IMAGE_BMP, + CT_PROMETHEUS, +} HTTP_CONTENT_TYPE; + typedef struct web_buffer { - size_t size; // allocation size of buffer, in bytes - size_t len; // current data length in buffer, in bytes - char *buffer; // the buffer itself - uint8_t contenttype; // the content type of the data in the 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 size; // allocation size of buffer, in bytes + size_t len; // current data length in buffer, in bytes + char *buffer; // the buffer itself + HTTP_CONTENT_TYPE content_type; // the content type of the data in the buffer + BUFFER_OPTIONS 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 -#define WB_CONTENT_CACHEABLE 1 -#define WB_CONTENT_NO_CACHEABLE 2 - -// content-types -#define CT_APPLICATION_JSON 1 -#define CT_TEXT_PLAIN 2 -#define CT_TEXT_HTML 3 -#define CT_APPLICATION_X_JAVASCRIPT 4 -#define CT_TEXT_CSS 5 -#define CT_TEXT_XML 6 -#define CT_APPLICATION_XML 7 -#define CT_TEXT_XSL 8 -#define CT_APPLICATION_OCTET_STREAM 9 -#define CT_APPLICATION_X_FONT_TRUETYPE 10 -#define CT_APPLICATION_X_FONT_OPENTYPE 11 -#define CT_APPLICATION_FONT_WOFF 12 -#define CT_APPLICATION_FONT_WOFF2 13 -#define CT_APPLICATION_VND_MS_FONTOBJ 14 -#define CT_IMAGE_SVG_XML 15 -#define CT_IMAGE_PNG 16 -#define CT_IMAGE_JPG 17 -#define CT_IMAGE_GIF 18 -#define CT_IMAGE_XICON 19 -#define CT_IMAGE_ICNS 20 -#define CT_IMAGE_BMP 21 -#define CT_PROMETHEUS 22 + struct { + char key_quote[BUFFER_QUOTE_MAX_SIZE + 1]; + char value_quote[BUFFER_QUOTE_MAX_SIZE + 1]; + int8_t depth; + bool minify; + BUFFER_JSON_NODE stack[BUFFER_JSON_MAX_DEPTH]; + } json; +} BUFFER; #define buffer_cacheable(wb) do { (wb)->options |= WB_CONTENT_CACHEABLE; if((wb)->options & WB_CONTENT_NO_CACHEABLE) (wb)->options &= ~WB_CONTENT_NO_CACHEABLE; } while(0) #define buffer_no_cacheable(wb) do { (wb)->options |= WB_CONTENT_NO_CACHEABLE; if((wb)->options & WB_CONTENT_CACHEABLE) (wb)->options &= ~WB_CONTENT_CACHEABLE; (wb)->expires = 0; } while(0) @@ -52,12 +84,34 @@ typedef struct web_buffer { #define buffer_strlen(wb) ((wb)->len) const char *buffer_tostring(BUFFER *wb); -#define buffer_flush(wb) wb->buffer[(wb)->len = 0] = '\0' -void buffer_reset(BUFFER *wb); +#define BUFFER_OVERFLOW_EOF "EOF" + +#ifdef NETDATA_INTERNAL_CHECKS +#define buffer_overflow_check(b) _buffer_overflow_check(b) +#else +#define buffer_overflow_check(b) +#endif + +static inline void _buffer_overflow_check(BUFFER *b) { + assert(b->len <= b->size && + "BUFFER: length is above buffer size."); + + assert(!(b->buffer && (b->buffer[b->size] != '\0' || strcmp(&b->buffer[b->size + 1], BUFFER_OVERFLOW_EOF) != 0)) && + "BUFFER: detected overflow."); +} + +static inline void buffer_flush(BUFFER *wb) { + wb->len = 0; -void buffer_strcat(BUFFER *wb, const char *txt); -void buffer_fast_strcat(BUFFER *wb, const char *txt, size_t len); -void buffer_rrd_value(BUFFER *wb, NETDATA_DOUBLE value); + wb->json.depth = 0; + wb->json.stack[0].type = BUFFER_JSON_EMPTY; + wb->json.stack[0].count = 0; + + if(wb->buffer) + wb->buffer[0] = '\0'; +} + +void buffer_reset(BUFFER *wb); 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); @@ -69,22 +123,794 @@ void buffer_increase(BUFFER *b, size_t free_size_required); void buffer_snprintf(BUFFER *wb, size_t len, const char *fmt, ...) PRINTFLIKE(3, 4); void buffer_vsprintf(BUFFER *wb, const char *fmt, va_list args); void buffer_sprintf(BUFFER *wb, const char *fmt, ...) PRINTFLIKE(2,3); -void buffer_strcat_jsonescape(BUFFER *wb, const char *txt); void buffer_strcat_htmlescape(BUFFER *wb, const char *txt); void buffer_char_replace(BUFFER *wb, char from, char to); -char *print_number_lu_r(char *str, unsigned long uvalue); -char *print_number_llu_r(char *str, unsigned long long uvalue); -char *print_number_llu_r_smart(char *str, unsigned long long uvalue); - -void buffer_print_llu(BUFFER *wb, unsigned long long uvalue); -void buffer_print_ll(BUFFER *wb, long long value); -void buffer_print_llu_hex(BUFFER *wb, unsigned long long value); +void buffer_print_sn_flags(BUFFER *wb, SN_FLAGS flags, bool send_anomaly_bit); static inline void buffer_need_bytes(BUFFER *buffer, size_t needed_free_size) { - if(unlikely(buffer->size - buffer->len < needed_free_size)) - buffer_increase(buffer, needed_free_size); + if(unlikely(buffer->len + needed_free_size >= buffer->size)) + buffer_increase(buffer, needed_free_size + 1); +} + +void buffer_json_initialize(BUFFER *wb, const char *key_quote, const char *value_quote, int depth, + bool add_anonymous_object, bool minify); + +void buffer_json_finalize(BUFFER *wb); + +static inline void _buffer_json_depth_push(BUFFER *wb, BUFFER_JSON_NODE_TYPE type) { +#ifdef NETDATA_INTERNAL_CHECKS + assert(wb->json.depth <= BUFFER_JSON_MAX_DEPTH && "BUFFER JSON: max nesting reached"); +#endif + wb->json.depth++; + wb->json.stack[wb->json.depth].count = 0; + wb->json.stack[wb->json.depth].type = type; +} + +static inline void _buffer_json_depth_pop(BUFFER *wb) { + wb->json.depth--; +} + +static inline void buffer_fast_charcat(BUFFER *wb, const char c) { + + buffer_need_bytes(wb, 2); + *(&wb->buffer[wb->len]) = c; + wb->len += 1; + wb->buffer[wb->len] = '\0'; + + buffer_overflow_check(wb); +} + +static inline void buffer_fast_rawcat(BUFFER *wb, const char *txt, size_t len) { + if(unlikely(!txt || !*txt || !len)) return; + + buffer_need_bytes(wb, len + 1); + + const char *t = txt; + const char *e = &txt[len]; + + char *d = &wb->buffer[wb->len]; + + while(t != e) + *d++ = *t++; + + wb->len += len; + wb->buffer[wb->len] = '\0'; + + buffer_overflow_check(wb); +} + +static inline void buffer_fast_strcat(BUFFER *wb, const char *txt, size_t len) { + if(unlikely(!txt || !*txt || !len)) return; + + buffer_need_bytes(wb, len + 1); + + const char *t = txt; + const char *e = &txt[len]; + + char *d = &wb->buffer[wb->len]; + + while(t != e +#ifdef NETDATA_INTERNAL_CHECKS + && *t +#endif + ) + *d++ = *t++; + +#ifdef NETDATA_INTERNAL_CHECKS + assert(!(t != e && !*t) && "BUFFER: source string is shorter than the length given."); +#endif + + wb->len += len; + wb->buffer[wb->len] = '\0'; + + buffer_overflow_check(wb); +} + +static inline void buffer_strcat(BUFFER *wb, const char *txt) { + if(unlikely(!txt || !*txt)) return; + + const char *t = txt; + while(*t) { + buffer_need_bytes(wb, 100); + char *s = &wb->buffer[wb->len]; + char *d = s; + const char *e = &wb->buffer[wb->size]; + + while(*t && d < e) + *d++ = *t++; + + wb->len += d - s; + } + + buffer_need_bytes(wb, 1); + wb->buffer[wb->len] = '\0'; + + buffer_overflow_check(wb); +} + +static inline void buffer_strncat(BUFFER *wb, const char *txt, size_t len) { + if(unlikely(!txt || !*txt)) return; + + const char *t = txt; + while(*t) { + buffer_need_bytes(wb, len); + char *s = &wb->buffer[wb->len]; + char *d = s; + const char *e = &wb->buffer[wb->len + len]; + + while(*t && d < e) + *d++ = *t++; + + wb->len += d - s; + } + + buffer_need_bytes(wb, 1); + wb->buffer[wb->len] = '\0'; + + buffer_overflow_check(wb); +} + +static inline void buffer_json_strcat(BUFFER *wb, const char *txt) { + if(unlikely(!txt || !*txt)) return; + + const unsigned char *t = (const unsigned char *)txt; + while(*t) { + buffer_need_bytes(wb, 110); + unsigned char *s = (unsigned char *)&wb->buffer[wb->len]; + unsigned char *d = s; + const unsigned char *e = (unsigned char *)&wb->buffer[wb->size - 10]; // make room for the max escape sequence + + while(*t && d < e) { +#ifdef BUFFER_JSON_ESCAPE_UTF + if(unlikely(IS_UTF8_STARTBYTE(*t) && IS_UTF8_BYTE(t[1]))) { + // UTF-8 multi-byte encoded character + + // find how big this character is (2-4 bytes) + size_t utf_character_size = 2; + while(utf_character_size < 4 && t[utf_character_size] && IS_UTF8_BYTE(t[utf_character_size]) && !IS_UTF8_STARTBYTE(t[utf_character_size])) + utf_character_size++; + + uint32_t code_point = 0; + for (size_t i = 0; i < utf_character_size; i++) { + code_point <<= 6; + code_point |= (t[i] & 0x3F); + } + + t += utf_character_size; + + // encode as \u escape sequence + *d++ = '\\'; + *d++ = 'u'; + *d++ = hex_digits[(code_point >> 12) & 0xf]; + *d++ = hex_digits[(code_point >> 8) & 0xf]; + *d++ = hex_digits[(code_point >> 4) & 0xf]; + *d++ = hex_digits[code_point & 0xf]; + } + else +#endif + if(unlikely(*t < ' ')) { + uint32_t v = *t++; + *d++ = '\\'; + *d++ = 'u'; + *d++ = hex_digits[(v >> 12) & 0xf]; + *d++ = hex_digits[(v >> 8) & 0xf]; + *d++ = hex_digits[(v >> 4) & 0xf]; + *d++ = hex_digits[v & 0xf]; + } + else { + if (unlikely(*t == '\\' || *t == '\"')) + *d++ = '\\'; + + *d++ = *t++; + } + } + + wb->len += d - s; + } + + buffer_need_bytes(wb, 1); + wb->buffer[wb->len] = '\0'; + + buffer_overflow_check(wb); +} + +static inline void buffer_json_quoted_strcat(BUFFER *wb, const char *txt) { + if(unlikely(!txt || !*txt)) return; + + if(*txt == '"') + txt++; + + const char *t = txt; + while(*t) { + buffer_need_bytes(wb, 100); + char *s = &wb->buffer[wb->len]; + char *d = s; + const char *e = &wb->buffer[wb->size - 1]; // remove 1 to make room for the escape character + + while(*t && d < e) { + if(unlikely(*t == '"' && !t[1])) { + t++; + continue; + } + + if(unlikely(*t == '\\' || *t == '"')) + *d++ = '\\'; + + *d++ = *t++; + } + + wb->len += d - s; + } + + buffer_need_bytes(wb, 1); + wb->buffer[wb->len] = '\0'; + + buffer_overflow_check(wb); +} + +// This trick seems to give an 80% speed increase in 32bit systems +// print_number_llu_r() will just print the digits up to the +// point the remaining value fits in 32 bits, and then calls +// print_number_lu_r() to print the rest with 32 bit arithmetic. + +static inline char *print_uint32_reversed(char *dst, uint32_t value) { + char *d = dst; + do *d++ = (char)('0' + (value % 10)); while((value /= 10)); + return d; +} + +static inline char *print_uint64_reversed(char *dst, uint64_t value) { +#ifdef ENV32BIT + if(value <= (uint64_t)0xffffffff) + return print_uint32_reversed(dst, value); + + char *d = dst; + do *d++ = (char)('0' + (value % 10)); while((value /= 10) && value > (uint64_t)0xffffffff); + if(value) return print_uint32_reversed(d, value); + return d; +#else + char *d = dst; + do *d++ = (char)('0' + (value % 10)); while((value /= 10)); + return d; +#endif +} + +static inline char *print_uint32_hex_reversed(char *dst, uint32_t value) { + static const char *digits = "0123456789ABCDEF"; + char *d = dst; + do *d++ = digits[value & 0xf]; while((value >>= 4)); + return d; +} + +static inline char *print_uint64_hex_reversed(char *dst, uint64_t value) { +#ifdef ENV32BIT + if(value <= (uint64_t)0xffffffff) + return print_uint32_hex_reversed(dst, value); + + char *d = dst; + do *d++ = hex_digits[value & 0xf]; while((value >>= 4) && value > (uint64_t)0xffffffff); + if(value) return print_uint32_hex_reversed(d, value); + return d; +#else + char *d = dst; + do *d++ = hex_digits[value & 0xf]; while((value >>= 4)); + return d; +#endif +} + +static inline char *print_uint64_base64_reversed(char *dst, uint64_t value) { + char *d = dst; + do *d++ = base64_digits[value & 63]; while ((value >>= 6)); + return d; +} + +static inline void char_array_reverse(char *from, char *to) { + // from and to are inclusive + char *begin = from, *end = to, aux; + while (end > begin) aux = *end, *end-- = *begin, *begin++ = aux; +} + +static inline int print_netdata_double(char *dst, NETDATA_DOUBLE value) { + char *s = dst; + + if(unlikely(value < 0)) { + *s++ = '-'; + value = fabsndd(value); + } + + uint64_t fractional_precision = 10000000ULL; // fractional part 7 digits + int fractional_wanted_digits = 7; + int exponent = 0; + if(unlikely(value >= (NETDATA_DOUBLE)(UINT64_MAX / 10))) { + // the number is too big to print using 64bit numbers + // so, let's convert it to exponential notation + exponent = (int)(floorndd(log10ndd(value))); + value /= powndd(10, exponent); + + // the max precision we can support is 18 digits + // (UINT64_MAX is 20, but the first is 1) + fractional_precision = 1000000000000000000ULL; // fractional part 18 digits + fractional_wanted_digits = 18; + } + + char *d = s; + NETDATA_DOUBLE integral_d, fractional_d; + fractional_d = modfndd(value, &integral_d); + + // get the integral and the fractional parts as 64-bit integers + uint64_t integral = (uint64_t)integral_d; + uint64_t fractional = (uint64_t)llrintndd(fractional_d * (NETDATA_DOUBLE)fractional_precision); + if(unlikely(fractional >= fractional_precision)) { + integral++; + fractional -= fractional_precision; + } + + // convert the integral part to string (reversed) + d = print_uint64_reversed(d, integral); + char_array_reverse(s, d - 1); // copy reversed the integral string + + if(likely(fractional != 0)) { + *d++ = '.'; // add the dot + + // convert the fractional part to string (reversed) + d = print_uint64_reversed(s = d, fractional); + + while(d - s < fractional_wanted_digits) *d++ = '0'; // prepend zeros to reach precision + char_array_reverse(s, d - 1); // copy reversed the fractional string + + // remove trailing zeros from the fractional part + while(*(d - 1) == '0') d--; + } + + if(unlikely(exponent != 0)) { + *d++ = 'e'; + *d++ = '+'; + d = print_uint32_reversed(s = d, exponent); + char_array_reverse(s, d - 1); + } + + *d = '\0'; + return (int)(d - dst); +} + +static inline void buffer_print_uint64(BUFFER *wb, uint64_t value) { + buffer_need_bytes(wb, 50); + + char *s = &wb->buffer[wb->len]; + char *d = print_uint64_reversed(s, value); + char_array_reverse(s, d - 1); + *d = '\0'; + wb->len += d - s; + + buffer_overflow_check(wb); +} + +static inline void buffer_print_int64(BUFFER *wb, int64_t value) { + buffer_need_bytes(wb, 50); + + if(value < 0) { + buffer_fast_strcat(wb, "-", 1); + value = -value; + } + + buffer_print_uint64(wb, (uint64_t)value); + + buffer_overflow_check(wb); +} + +static inline void buffer_print_uint64_hex(BUFFER *wb, uint64_t value) { + buffer_need_bytes(wb, sizeof(uint64_t) * 2 + 2 + 1); + + buffer_fast_strcat(wb, HEX_PREFIX, sizeof(HEX_PREFIX) - 1); + + char *s = &wb->buffer[wb->len]; + char *d = print_uint64_hex_reversed(s, value); + char_array_reverse(s, d - 1); + *d = '\0'; + wb->len += d - s; + + buffer_overflow_check(wb); +} + +static inline void buffer_print_uint64_base64(BUFFER *wb, uint64_t value) { + buffer_need_bytes(wb, sizeof(uint64_t) * 2 + 2 + 1); + + buffer_fast_strcat(wb, IEEE754_UINT64_B64_PREFIX, sizeof(IEEE754_UINT64_B64_PREFIX) - 1); + + char *s = &wb->buffer[wb->len]; + char *d = print_uint64_base64_reversed(s, value); + char_array_reverse(s, d - 1); + *d = '\0'; + wb->len += d - s; + + buffer_overflow_check(wb); +} + +static inline void buffer_print_int64_hex(BUFFER *wb, int64_t value) { + buffer_need_bytes(wb, 2); + + if(value < 0) { + buffer_fast_strcat(wb, "-", 1); + value = -value; + } + + buffer_print_uint64_hex(wb, (uint64_t)value); + + buffer_overflow_check(wb); +} + +static inline void buffer_print_int64_base64(BUFFER *wb, int64_t value) { + buffer_need_bytes(wb, 2); + + if(value < 0) { + buffer_fast_strcat(wb, "-", 1); + value = -value; + } + + buffer_print_uint64_base64(wb, (uint64_t)value); + + buffer_overflow_check(wb); +} + +static inline void buffer_print_netdata_double(BUFFER *wb, NETDATA_DOUBLE value) { + buffer_need_bytes(wb, 512 + 2); + + if(isnan(value) || isinf(value)) { + buffer_fast_strcat(wb, "null", 4); + return; + } + else + wb->len += print_netdata_double(&wb->buffer[wb->len], value); + + // terminate it + buffer_need_bytes(wb, 1); + wb->buffer[wb->len] = '\0'; + + buffer_overflow_check(wb); +} + +static inline void buffer_print_netdata_double_hex(BUFFER *wb, NETDATA_DOUBLE value) { + buffer_need_bytes(wb, sizeof(uint64_t) * 2 + 2 + 1 + 1); + + uint64_t *ptr = (uint64_t *) (&value); + buffer_fast_strcat(wb, IEEE754_DOUBLE_HEX_PREFIX, sizeof(IEEE754_DOUBLE_HEX_PREFIX) - 1); + + char *s = &wb->buffer[wb->len]; + char *d = print_uint64_hex_reversed(s, *ptr); + char_array_reverse(s, d - 1); + *d = '\0'; + wb->len += d - s; + + buffer_overflow_check(wb); +} + +static inline void buffer_print_netdata_double_base64(BUFFER *wb, NETDATA_DOUBLE value) { + buffer_need_bytes(wb, sizeof(uint64_t) * 2 + 2 + 1 + 1); + + uint64_t *ptr = (uint64_t *) (&value); + buffer_fast_strcat(wb, IEEE754_DOUBLE_B64_PREFIX, sizeof(IEEE754_DOUBLE_B64_PREFIX) - 1); + + char *s = &wb->buffer[wb->len]; + char *d = print_uint64_base64_reversed(s, *ptr); + char_array_reverse(s, d - 1); + *d = '\0'; + wb->len += d - s; + + buffer_overflow_check(wb); +} + +typedef enum { + NUMBER_ENCODING_DECIMAL, + NUMBER_ENCODING_HEX, + NUMBER_ENCODING_BASE64, +} NUMBER_ENCODING; + +static inline void buffer_print_int64_encoded(BUFFER *wb, NUMBER_ENCODING encoding, int64_t value) { + if(encoding == NUMBER_ENCODING_BASE64) + return buffer_print_int64_base64(wb, value); + + if(encoding == NUMBER_ENCODING_HEX) + return buffer_print_int64_hex(wb, value); + + return buffer_print_int64(wb, value); +} + +static inline void buffer_print_uint64_encoded(BUFFER *wb, NUMBER_ENCODING encoding, uint64_t value) { + if(encoding == NUMBER_ENCODING_BASE64) + return buffer_print_uint64_base64(wb, value); + + if(encoding == NUMBER_ENCODING_HEX) + return buffer_print_uint64_hex(wb, value); + + return buffer_print_uint64(wb, value); +} + +static inline void buffer_print_netdata_double_encoded(BUFFER *wb, NUMBER_ENCODING encoding, NETDATA_DOUBLE value) { + if(encoding == NUMBER_ENCODING_BASE64) + return buffer_print_netdata_double_base64(wb, value); + + if(encoding == NUMBER_ENCODING_HEX) + return buffer_print_netdata_double_hex(wb, value); + + return buffer_print_netdata_double(wb, value); +} + +static inline void buffer_print_spaces(BUFFER *wb, size_t spaces) { + buffer_need_bytes(wb, spaces * 4 + 1); + + char *d = &wb->buffer[wb->len]; + for(size_t i = 0; i < spaces; i++) { + *d++ = ' '; + *d++ = ' '; + *d++ = ' '; + *d++ = ' '; + } + + *d = '\0'; + wb->len += spaces * 4; + + buffer_overflow_check(wb); +} + +static inline void buffer_print_json_comma_newline_spacing(BUFFER *wb) { + if(wb->json.stack[wb->json.depth].count) + buffer_fast_strcat(wb, ",", 1); + + if(wb->json.minify) + return; + + buffer_fast_strcat(wb, "\n", 1); + buffer_print_spaces(wb, wb->json.depth + 1); +} + +static inline void buffer_print_json_key(BUFFER *wb, const char *key) { + buffer_strcat(wb, wb->json.key_quote); + buffer_json_strcat(wb, key); + buffer_strcat(wb, wb->json.key_quote); +} + +static inline void buffer_json_add_string_value(BUFFER *wb, const char *value) { + if(value) { + buffer_strcat(wb, wb->json.value_quote); + buffer_json_strcat(wb, value); + buffer_strcat(wb, wb->json.value_quote); + } + else + buffer_fast_strcat(wb, "null", 4); +} + +static inline void buffer_json_add_quoted_string_value(BUFFER *wb, const char *value) { + if(value) { + buffer_strcat(wb, wb->json.value_quote); + buffer_json_quoted_strcat(wb, value); + buffer_strcat(wb, wb->json.value_quote); + } + else + buffer_fast_strcat(wb, "null", 4); +} + +static inline void buffer_json_member_add_object(BUFFER *wb, const char *key) { + buffer_print_json_comma_newline_spacing(wb); + buffer_print_json_key(wb, key); + buffer_fast_strcat(wb, ":{", 2); + wb->json.stack[wb->json.depth].count++; + + _buffer_json_depth_push(wb, BUFFER_JSON_OBJECT); +} + +static inline void buffer_json_object_close(BUFFER *wb) { +#ifdef NETDATA_INTERNAL_CHECKS + assert(wb->json.depth >= 0 && "BUFFER JSON: nothing is open to close it"); + assert(wb->json.stack[wb->json.depth].type == BUFFER_JSON_OBJECT && "BUFFER JSON: an object is not open to close it"); +#endif + if(!wb->json.minify) { + buffer_fast_strcat(wb, "\n", 1); + buffer_print_spaces(wb, wb->json.depth); + } + buffer_fast_strcat(wb, "}", 1); + _buffer_json_depth_pop(wb); +} + +static inline void buffer_json_member_add_string(BUFFER *wb, const char *key, const char *value) { + buffer_print_json_comma_newline_spacing(wb); + buffer_print_json_key(wb, key); + buffer_fast_strcat(wb, ":", 1); + buffer_json_add_string_value(wb, value); + + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_member_add_string_or_omit(BUFFER *wb, const char *key, const char *value) { + if(value && *value) + buffer_json_member_add_string(wb, key, value); +} + +static inline void buffer_json_member_add_string_or_empty(BUFFER *wb, const char *key, const char *value) { + if(!value) + value = ""; + + buffer_json_member_add_string(wb, key, value); +} + +static inline void buffer_json_member_add_quoted_string(BUFFER *wb, const char *key, const char *value) { + buffer_print_json_comma_newline_spacing(wb); + buffer_print_json_key(wb, key); + buffer_fast_strcat(wb, ":", 1); + + if(!value || strcmp(value, "null") == 0) + buffer_fast_strcat(wb, "null", 4); + else + buffer_json_add_quoted_string_value(wb, value); + + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_member_add_uuid(BUFFER *wb, const char *key, uuid_t *value) { + buffer_print_json_comma_newline_spacing(wb); + buffer_print_json_key(wb, key); + buffer_fast_strcat(wb, ":", 1); + + if(value) { + char uuid[GUID_LEN + 1]; + uuid_unparse_lower(*value, uuid); + buffer_json_add_string_value(wb, uuid); + } + else + buffer_json_add_string_value(wb, NULL); + + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_member_add_boolean(BUFFER *wb, const char *key, bool value) { + buffer_print_json_comma_newline_spacing(wb); + buffer_print_json_key(wb, key); + buffer_fast_strcat(wb, ":", 1); + buffer_strcat(wb, value?"true":"false"); + + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_member_add_array(BUFFER *wb, const char *key) { + buffer_print_json_comma_newline_spacing(wb); + buffer_print_json_key(wb, key); + buffer_fast_strcat(wb, ":[", 2); + wb->json.stack[wb->json.depth].count++; + + _buffer_json_depth_push(wb, BUFFER_JSON_ARRAY); +} + +static inline void buffer_json_add_array_item_array(BUFFER *wb) { + buffer_print_json_comma_newline_spacing(wb); + + buffer_fast_strcat(wb, "[", 1); + wb->json.stack[wb->json.depth].count++; + + _buffer_json_depth_push(wb, BUFFER_JSON_ARRAY); +} + +static inline void buffer_json_add_array_item_string(BUFFER *wb, const char *value) { + if(wb->json.stack[wb->json.depth].count) + buffer_fast_strcat(wb, ",", 1); + + buffer_json_add_string_value(wb, value); + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_add_array_item_double(BUFFER *wb, NETDATA_DOUBLE value) { + if(wb->json.stack[wb->json.depth].count) + buffer_fast_strcat(wb, ",", 1); + + buffer_print_netdata_double(wb, value); + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_add_array_item_int64(BUFFER *wb, int64_t value) { + if(wb->json.stack[wb->json.depth].count) + buffer_fast_strcat(wb, ",", 1); + + buffer_print_int64(wb, value); + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_add_array_item_uint64(BUFFER *wb, uint64_t value) { + if(wb->json.stack[wb->json.depth].count) + buffer_fast_strcat(wb, ",", 1); + + buffer_print_uint64(wb, value); + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_add_array_item_time_t(BUFFER *wb, time_t value) { + if(wb->json.stack[wb->json.depth].count) + buffer_fast_strcat(wb, ",", 1); + + buffer_print_int64(wb, value); + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_add_array_item_time_ms(BUFFER *wb, time_t value) { + if(wb->json.stack[wb->json.depth].count) + buffer_fast_strcat(wb, ",", 1); + + buffer_print_int64(wb, value); + buffer_fast_strcat(wb, "000", 3); + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_add_array_item_time_t2ms(BUFFER *wb, time_t value) { + if(wb->json.stack[wb->json.depth].count) + buffer_fast_strcat(wb, ",", 1); + + buffer_print_int64(wb, value); + buffer_fast_strcat(wb, "000", 3); + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_add_array_item_object(BUFFER *wb) { + if(wb->json.stack[wb->json.depth].count) + buffer_fast_strcat(wb, ",", 1); + + buffer_fast_strcat(wb, "{", 1); + wb->json.stack[wb->json.depth].count++; + + _buffer_json_depth_push(wb, BUFFER_JSON_OBJECT); +} + +static inline void buffer_json_member_add_time_t(BUFFER *wb, const char *key, time_t value) { + buffer_print_json_comma_newline_spacing(wb); + buffer_print_json_key(wb, key); + buffer_fast_strcat(wb, ":", 1); + buffer_print_int64(wb, value); + + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_member_add_time_t2ms(BUFFER *wb, const char *key, time_t value) { + buffer_print_json_comma_newline_spacing(wb); + buffer_print_json_key(wb, key); + buffer_fast_strcat(wb, ":", 1); + buffer_print_int64(wb, value); + buffer_fast_strcat(wb, "000", 3); + + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_member_add_uint64(BUFFER *wb, const char *key, uint64_t value) { + buffer_print_json_comma_newline_spacing(wb); + buffer_print_json_key(wb, key); + buffer_fast_strcat(wb, ":", 1); + buffer_print_uint64(wb, value); + + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_member_add_int64(BUFFER *wb, const char *key, int64_t value) { + buffer_print_json_comma_newline_spacing(wb); + buffer_print_json_key(wb, key); + buffer_fast_strcat(wb, ":", 1); + buffer_print_int64(wb, value); + + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_member_add_double(BUFFER *wb, const char *key, NETDATA_DOUBLE value) { + buffer_print_json_comma_newline_spacing(wb); + buffer_print_json_key(wb, key); + buffer_fast_strcat(wb, ":", 1); + buffer_print_netdata_double(wb, value); + + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_array_close(BUFFER *wb) { +#ifdef NETDATA_INTERNAL_CHECKS + assert(wb->json.depth >= 0 && "BUFFER JSON: nothing is open to close it"); + assert(wb->json.stack[wb->json.depth].type == BUFFER_JSON_ARRAY && "BUFFER JSON: an array is not open to close it"); +#endif + buffer_fast_strcat(wb, "]", 1); + _buffer_json_depth_pop(wb); } #endif /* NETDATA_WEB_BUFFER_H */ diff --git a/libnetdata/circular_buffer/README.md b/libnetdata/circular_buffer/README.md index 23980dff..c4738651 100644 --- a/libnetdata/circular_buffer/README.md +++ b/libnetdata/circular_buffer/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/circu sidebar_label: "Circular Buffer" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # Circular Buffer diff --git a/libnetdata/circular_buffer/circular_buffer.c b/libnetdata/circular_buffer/circular_buffer.c index b2bded17..7ffe6b8b 100644 --- a/libnetdata/circular_buffer/circular_buffer.c +++ b/libnetdata/circular_buffer/circular_buffer.c @@ -16,7 +16,10 @@ struct circular_buffer *cbuffer_new(size_t initial, size_t max, size_t *statisti } void cbuffer_free(struct circular_buffer *buf) { - if(buf && buf->statistics) + if (unlikely(!buf)) + return; + + if(buf->statistics) __atomic_sub_fetch(buf->statistics, sizeof(struct circular_buffer) + buf->size, __ATOMIC_RELAXED); freez(buf->data); diff --git a/libnetdata/clocks/README.md b/libnetdata/clocks/README.md index 3a7ce55f..33b0f0e8 100644 --- a/libnetdata/clocks/README.md +++ b/libnetdata/clocks/README.md @@ -1,5 +1,10 @@ - +# Clocks \ No newline at end of file diff --git a/libnetdata/config/README.md b/libnetdata/config/README.md index c34cf925..c3a9d147 100644 --- a/libnetdata/config/README.md +++ b/libnetdata/config/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/confi sidebar_label: "Netdata ini config files" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # Netdata ini config files diff --git a/libnetdata/config/appconfig.c b/libnetdata/config/appconfig.c index 938c7dde..d346da85 100644 --- a/libnetdata/config/appconfig.c +++ b/libnetdata/config/appconfig.c @@ -831,7 +831,7 @@ void appconfig_generate(struct config *root, BUFFER *wb, int only_changed) "#\n" "\n# global netdata configuration\n"); - for(i = 0; i <= 16 ;i++) { + for(i = 0; i <= 17 ;i++) { appconfig_wrlock(root); for(co = root->first_section; co ; co = co->next) { if(!strcmp(co->name, CONFIG_SECTION_GLOBAL)) pri = 0; @@ -845,13 +845,14 @@ void appconfig_generate(struct config *root, BUFFER *wb, int only_changed) else if(!strcmp(co->name, CONFIG_SECTION_ML)) pri = 8; else if(!strcmp(co->name, CONFIG_SECTION_HEALTH)) pri = 9; else if(!strcmp(co->name, CONFIG_SECTION_WEB)) pri = 10; - // by default, new sections will get pri = 11 (set at the end, below) - else if(!strcmp(co->name, CONFIG_SECTION_REGISTRY)) pri = 12; - else if(!strcmp(co->name, CONFIG_SECTION_GLOBAL_STATISTICS)) pri = 13; - else if(!strcmp(co->name, CONFIG_SECTION_PLUGINS)) pri = 14; - else if(!strcmp(co->name, CONFIG_SECTION_STATSD)) pri = 15; - else if(!strncmp(co->name, "plugin:", 7)) pri = 16; // << change the loop too if you change this - else pri = 11; // this is used for any new (currently unknown) sections + else if(!strcmp(co->name, CONFIG_SECTION_WEBRTC)) pri = 11; + // by default, new sections will get pri = 12 (set at the end, below) + else if(!strcmp(co->name, CONFIG_SECTION_REGISTRY)) pri = 13; + else if(!strcmp(co->name, CONFIG_SECTION_GLOBAL_STATISTICS)) pri = 14; + else if(!strcmp(co->name, CONFIG_SECTION_PLUGINS)) pri = 15; + else if(!strcmp(co->name, CONFIG_SECTION_STATSD)) pri = 16; + else if(!strncmp(co->name, "plugin:", 7)) pri = 17; // << change the loop too if you change this + else pri = 12; // this is used for any new (currently unknown) sections if(i == pri) { int loaded = 0; diff --git a/libnetdata/config/appconfig.h b/libnetdata/config/appconfig.h index 2828e107..b3a09024 100644 --- a/libnetdata/config/appconfig.h +++ b/libnetdata/config/appconfig.h @@ -88,6 +88,7 @@ #define CONFIG_SECTION_ENV_VARS "environment variables" #define CONFIG_SECTION_SQLITE "sqlite" #define CONFIG_SECTION_WEB "web" +#define CONFIG_SECTION_WEBRTC "webrtc" #define CONFIG_SECTION_STATSD "statsd" #define CONFIG_SECTION_PLUGINS "plugins" #define CONFIG_SECTION_CLOUD "cloud" diff --git a/libnetdata/dictionary/README.md b/libnetdata/dictionary/README.md index 508c4e03..3b54cf18 100644 --- a/libnetdata/dictionary/README.md +++ b/libnetdata/dictionary/README.md @@ -3,7 +3,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/dicti sidebar_label: "Dictionaries" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # Dictionaries diff --git a/libnetdata/dictionary/dictionary.c b/libnetdata/dictionary/dictionary.c index 061b671a..42e4a99f 100644 --- a/libnetdata/dictionary/dictionary.c +++ b/libnetdata/dictionary/dictionary.c @@ -10,9 +10,9 @@ typedef enum __attribute__ ((__packed__)) { DICT_FLAG_DESTROYED = (1 << 0), // this dictionary has been destroyed } DICT_FLAGS; -#define dict_flag_check(dict, flag) (__atomic_load_n(&((dict)->flags), __ATOMIC_SEQ_CST) & (flag)) -#define dict_flag_set(dict, flag) __atomic_or_fetch(&((dict)->flags), flag, __ATOMIC_SEQ_CST) -#define dict_flag_clear(dict, flag) __atomic_and_fetch(&((dict)->flags), ~(flag), __ATOMIC_SEQ_CST) +#define dict_flag_check(dict, flag) (__atomic_load_n(&((dict)->flags), __ATOMIC_RELAXED) & (flag)) +#define dict_flag_set(dict, flag) __atomic_or_fetch(&((dict)->flags), flag, __ATOMIC_RELAXED) +#define dict_flag_clear(dict, flag) __atomic_and_fetch(&((dict)->flags), ~(flag), __ATOMIC_RELAXED) // flags macros #define is_dictionary_destroyed(dict) dict_flag_check(dict, DICT_FLAG_DESTROYED) @@ -37,13 +37,13 @@ typedef enum __attribute__ ((__packed__)) item_flags { // IMPORTANT: This is 8-bit } ITEM_FLAGS; -#define item_flag_check(item, flag) (__atomic_load_n(&((item)->flags), __ATOMIC_SEQ_CST) & (flag)) -#define item_flag_set(item, flag) __atomic_or_fetch(&((item)->flags), flag, __ATOMIC_SEQ_CST) -#define item_flag_clear(item, flag) __atomic_and_fetch(&((item)->flags), ~(flag), __ATOMIC_SEQ_CST) +#define item_flag_check(item, flag) (__atomic_load_n(&((item)->flags), __ATOMIC_RELAXED) & (flag)) +#define item_flag_set(item, flag) __atomic_or_fetch(&((item)->flags), flag, __ATOMIC_RELAXED) +#define item_flag_clear(item, flag) __atomic_and_fetch(&((item)->flags), ~(flag), __ATOMIC_RELAXED) -#define item_shared_flag_check(item, flag) (__atomic_load_n(&((item)->shared->flags), __ATOMIC_SEQ_CST) & (flag)) -#define item_shared_flag_set(item, flag) __atomic_or_fetch(&((item)->shared->flags), flag, __ATOMIC_SEQ_CST) -#define item_shared_flag_clear(item, flag) __atomic_and_fetch(&((item)->shared->flags), ~(flag), __ATOMIC_SEQ_CST) +#define item_shared_flag_check(item, flag) (__atomic_load_n(&((item)->shared->flags), __ATOMIC_RELAXED) & (flag)) +#define item_shared_flag_set(item, flag) __atomic_or_fetch(&((item)->shared->flags), flag, __ATOMIC_RELAXED) +#define item_shared_flag_clear(item, flag) __atomic_and_fetch(&((item)->shared->flags), ~(flag), __ATOMIC_RELAXED) #define REFCOUNT_DELETING (-100) @@ -175,7 +175,7 @@ struct dictionary { long int referenced_items; // how many items of the dictionary are currently being used by 3rd parties long int pending_deletion_items; // how many items of the dictionary have been deleted, but have not been removed yet -#ifdef NETDATA_INTERNAL_CHECKS +#ifdef NETDATA_DICTIONARY_VALIDATE_POINTERS netdata_mutex_t global_pointer_registry_mutex; Pvoid_t global_pointer_registry; #endif @@ -205,59 +205,47 @@ static inline int item_is_not_referenced_and_can_be_removed_advanced(DICTIONARY // ---------------------------------------------------------------------------- // validate each pointer is indexed once - internal checks only +#ifdef NETDATA_DICTIONARY_VALIDATE_POINTERS static inline void pointer_index_init(DICTIONARY *dict __maybe_unused) { -#ifdef NETDATA_INTERNAL_CHECKS netdata_mutex_init(&dict->global_pointer_registry_mutex); -#else - ; -#endif } static inline void pointer_destroy_index(DICTIONARY *dict __maybe_unused) { -#ifdef NETDATA_INTERNAL_CHECKS netdata_mutex_lock(&dict->global_pointer_registry_mutex); JudyHSFreeArray(&dict->global_pointer_registry, PJE0); netdata_mutex_unlock(&dict->global_pointer_registry_mutex); -#else - ; -#endif } static inline void pointer_add(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item __maybe_unused) { -#ifdef NETDATA_INTERNAL_CHECKS netdata_mutex_lock(&dict->global_pointer_registry_mutex); Pvoid_t *PValue = JudyHSIns(&dict->global_pointer_registry, &item, sizeof(void *), PJE0); if(*PValue != NULL) fatal("pointer already exists in registry"); *PValue = item; netdata_mutex_unlock(&dict->global_pointer_registry_mutex); -#else - ; -#endif } static inline void pointer_check(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item __maybe_unused) { -#ifdef NETDATA_INTERNAL_CHECKS netdata_mutex_lock(&dict->global_pointer_registry_mutex); Pvoid_t *PValue = JudyHSGet(dict->global_pointer_registry, &item, sizeof(void *)); if(PValue == NULL) fatal("pointer is not found in registry"); netdata_mutex_unlock(&dict->global_pointer_registry_mutex); -#else - ; -#endif } static inline void pointer_del(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item __maybe_unused) { -#ifdef NETDATA_INTERNAL_CHECKS netdata_mutex_lock(&dict->global_pointer_registry_mutex); int ret = JudyHSDel(&dict->global_pointer_registry, &item, sizeof(void *), PJE0); if(!ret) fatal("pointer to be deleted does not exist in registry"); netdata_mutex_unlock(&dict->global_pointer_registry_mutex); -#else - ; -#endif } +#else // !NETDATA_DICTIONARY_VALIDATE_POINTERS +#define pointer_index_init(dict) debug_dummy() +#define pointer_destroy_index(dict) debug_dummy() +#define pointer_add(dict, item) debug_dummy() +#define pointer_check(dict, item) debug_dummy() +#define pointer_del(dict, item) debug_dummy() +#endif // !NETDATA_DICTIONARY_VALIDATE_POINTERS // ---------------------------------------------------------------------------- // memory statistics @@ -298,7 +286,7 @@ static inline void dictionary_hooks_allocate(DICTIONARY *dict) { static inline size_t dictionary_hooks_free(DICTIONARY *dict) { if(!dict->hooks) return 0; - REFCOUNT links = __atomic_sub_fetch(&dict->hooks->links, 1, __ATOMIC_SEQ_CST); + REFCOUNT links = __atomic_sub_fetch(&dict->hooks->links, 1, __ATOMIC_ACQUIRE); if(links == 0) { freez(dict->hooks); dict->hooks = NULL; @@ -356,26 +344,25 @@ size_t dictionary_version(DICTIONARY *dict) { if(unlikely(!dict)) return 0; // this is required for views to return the right number - garbage_collect_pending_deletes(dict); + // garbage_collect_pending_deletes(dict); - return __atomic_load_n(&dict->version, __ATOMIC_SEQ_CST); + return __atomic_load_n(&dict->version, __ATOMIC_RELAXED); } size_t dictionary_entries(DICTIONARY *dict) { if(unlikely(!dict)) return 0; // this is required for views to return the right number - garbage_collect_pending_deletes(dict); + // garbage_collect_pending_deletes(dict); - long int entries = __atomic_load_n(&dict->entries, __ATOMIC_SEQ_CST); - if(entries < 0) - fatal("DICTIONARY: entries is negative: %ld", entries); + long int entries = __atomic_load_n(&dict->entries, __ATOMIC_RELAXED); + internal_fatal(entries < 0, "DICTIONARY: entries is negative: %ld", entries); return entries; } size_t dictionary_referenced_items(DICTIONARY *dict) { if(unlikely(!dict)) return 0; - long int referenced_items = __atomic_load_n(&dict->referenced_items, __ATOMIC_SEQ_CST); + long int referenced_items = __atomic_load_n(&dict->referenced_items, __ATOMIC_RELAXED); if(referenced_items < 0) fatal("DICTIONARY: referenced items is negative: %ld", referenced_items); @@ -387,7 +374,7 @@ long int dictionary_stats_for_registry(DICTIONARY *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); + __atomic_fetch_add(&dict->version, 1, __ATOMIC_RELAXED); } // ---------------------------------------------------------------------------- @@ -409,9 +396,9 @@ static inline void DICTIONARY_ENTRIES_PLUS1(DICTIONARY *dict) { } else { - __atomic_fetch_add(&dict->version, 1, __ATOMIC_SEQ_CST); - __atomic_fetch_add(&dict->entries, 1, __ATOMIC_SEQ_CST); - __atomic_fetch_add(&dict->referenced_items, 1, __ATOMIC_SEQ_CST); + __atomic_fetch_add(&dict->version, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&dict->entries, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&dict->referenced_items, 1, __ATOMIC_RELAXED); } } static inline void DICTIONARY_ENTRIES_MINUS1(DICTIONARY *dict) { @@ -425,17 +412,15 @@ static inline void DICTIONARY_ENTRIES_MINUS1(DICTIONARY *dict) { entries = dict->entries++; } else { - __atomic_fetch_add(&dict->version, 1, __ATOMIC_SEQ_CST); - entries = __atomic_fetch_sub(&dict->entries, 1, __ATOMIC_SEQ_CST); + __atomic_fetch_add(&dict->version, 1, __ATOMIC_RELAXED); + entries = __atomic_fetch_sub(&dict->entries, 1, __ATOMIC_RELAXED); } -#ifdef NETDATA_INTERNAL_CHECKS - if(unlikely(entries == 0)) - fatal("DICT: negative number of entries in dictionary created from %s() (%zu@%s)", - dict->creation_function, - dict->creation_line, - dict->creation_file); -#endif + internal_fatal(entries == 0, + "DICT: negative number of entries in dictionary created from %s() (%zu@%s)", + dict->creation_function, + dict->creation_line, + dict->creation_file); } static inline void DICTIONARY_VALUE_RESETS_PLUS1(DICTIONARY *dict) { __atomic_fetch_add(&dict->stats->ops.resets, 1, __ATOMIC_RELAXED); @@ -443,7 +428,7 @@ static inline void DICTIONARY_VALUE_RESETS_PLUS1(DICTIONARY *dict) { if(unlikely(is_dictionary_single_threaded(dict))) dict->version++; else - __atomic_fetch_add(&dict->version, 1, __ATOMIC_SEQ_CST); + __atomic_fetch_add(&dict->version, 1, __ATOMIC_RELAXED); } static inline void DICTIONARY_STATS_TRAVERSALS_PLUS1(DICTIONARY *dict) { __atomic_fetch_add(&dict->stats->ops.traversals, 1, __ATOMIC_RELAXED); @@ -464,16 +449,16 @@ static inline void DICTIONARY_STATS_SEARCH_IGNORES_PLUS1(DICTIONARY *dict) { __atomic_fetch_add(&dict->stats->spin_locks.search_spins, 1, __ATOMIC_RELAXED); } static inline void DICTIONARY_STATS_CALLBACK_INSERTS_PLUS1(DICTIONARY *dict) { - __atomic_fetch_add(&dict->stats->callbacks.inserts, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&dict->stats->callbacks.inserts, 1, __ATOMIC_RELEASE); } static inline void DICTIONARY_STATS_CALLBACK_CONFLICTS_PLUS1(DICTIONARY *dict) { - __atomic_fetch_add(&dict->stats->callbacks.conflicts, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&dict->stats->callbacks.conflicts, 1, __ATOMIC_RELEASE); } static inline void DICTIONARY_STATS_CALLBACK_REACTS_PLUS1(DICTIONARY *dict) { - __atomic_fetch_add(&dict->stats->callbacks.reacts, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&dict->stats->callbacks.reacts, 1, __ATOMIC_RELEASE); } static inline void DICTIONARY_STATS_CALLBACK_DELETES_PLUS1(DICTIONARY *dict) { - __atomic_fetch_add(&dict->stats->callbacks.deletes, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&dict->stats->callbacks.deletes, 1, __ATOMIC_RELEASE); } static inline void DICTIONARY_STATS_GARBAGE_COLLECTIONS_PLUS1(DICTIONARY *dict) { __atomic_fetch_add(&dict->stats->ops.garbage_collections, 1, __ATOMIC_RELAXED); @@ -496,52 +481,48 @@ static inline void DICTIONARY_STATS_DICT_FLUSHES_PLUS1(DICTIONARY *dict) { __atomic_fetch_add(&dict->stats->ops.flushes, 1, __ATOMIC_RELAXED); } -static inline long int DICTIONARY_REFERENCED_ITEMS_PLUS1(DICTIONARY *dict) { +static inline void DICTIONARY_REFERENCED_ITEMS_PLUS1(DICTIONARY *dict) { __atomic_fetch_add(&dict->stats->items.referenced, 1, __ATOMIC_RELAXED); if(unlikely(is_dictionary_single_threaded(dict))) - return ++dict->referenced_items; + ++dict->referenced_items; else - return __atomic_add_fetch(&dict->referenced_items, 1, __ATOMIC_SEQ_CST); + __atomic_add_fetch(&dict->referenced_items, 1, __ATOMIC_RELAXED); } -static inline long int DICTIONARY_REFERENCED_ITEMS_MINUS1(DICTIONARY *dict) { +static inline void DICTIONARY_REFERENCED_ITEMS_MINUS1(DICTIONARY *dict) { __atomic_fetch_sub(&dict->stats->items.referenced, 1, __ATOMIC_RELAXED); - long int referenced_items; + long int referenced_items; (void)referenced_items; if(unlikely(is_dictionary_single_threaded(dict))) referenced_items = --dict->referenced_items; else referenced_items = __atomic_sub_fetch(&dict->referenced_items, 1, __ATOMIC_SEQ_CST); -#ifdef NETDATA_INTERNAL_CHECKS - if(unlikely(referenced_items < 0)) - fatal("DICT: negative number of referenced items (%ld) in dictionary created from %s() (%zu@%s)", - referenced_items, - dict->creation_function, - dict->creation_line, - dict->creation_file); -#endif - - return referenced_items; + internal_fatal(referenced_items < 0, + "DICT: negative number of referenced items (%ld) in dictionary created from %s() (%zu@%s)", + referenced_items, + dict->creation_function, + dict->creation_line, + dict->creation_file); } -static inline long int DICTIONARY_PENDING_DELETES_PLUS1(DICTIONARY *dict) { +static inline void DICTIONARY_PENDING_DELETES_PLUS1(DICTIONARY *dict) { __atomic_fetch_add(&dict->stats->items.pending_deletion, 1, __ATOMIC_RELAXED); if(unlikely(is_dictionary_single_threaded(dict))) - return ++dict->pending_deletion_items; + ++dict->pending_deletion_items; else - return __atomic_add_fetch(&dict->pending_deletion_items, 1, __ATOMIC_SEQ_CST); + __atomic_add_fetch(&dict->pending_deletion_items, 1, __ATOMIC_RELEASE); } static inline long int DICTIONARY_PENDING_DELETES_MINUS1(DICTIONARY *dict) { - __atomic_fetch_sub(&dict->stats->items.pending_deletion, 1, __ATOMIC_RELAXED); + __atomic_fetch_sub(&dict->stats->items.pending_deletion, 1, __ATOMIC_RELEASE); if(unlikely(is_dictionary_single_threaded(dict))) return --dict->pending_deletion_items; else - return __atomic_sub_fetch(&dict->pending_deletion_items, 1, __ATOMIC_SEQ_CST); + return __atomic_sub_fetch(&dict->pending_deletion_items, 1, __ATOMIC_ACQUIRE); } static inline long int DICTIONARY_PENDING_DELETES_GET(DICTIONARY *dict) { @@ -555,11 +536,11 @@ static inline REFCOUNT DICTIONARY_ITEM_REFCOUNT_GET(DICTIONARY *dict, DICTIONARY if(unlikely(dict && is_dictionary_single_threaded(dict))) // this is an exception, dict can be null return item->refcount; else - return (REFCOUNT)__atomic_load_n(&item->refcount, __ATOMIC_SEQ_CST); + return (REFCOUNT)__atomic_load_n(&item->refcount, __ATOMIC_ACQUIRE); } static inline REFCOUNT DICTIONARY_ITEM_REFCOUNT_GET_SOLE(DICTIONARY_ITEM *item) { - return (REFCOUNT)__atomic_load_n(&item->refcount, __ATOMIC_SEQ_CST); + return (REFCOUNT)__atomic_load_n(&item->refcount, __ATOMIC_ACQUIRE); } // ---------------------------------------------------------------------------- @@ -579,8 +560,8 @@ static void dictionary_execute_insert_callback(DICTIONARY *dict, DICTIONARY_ITEM dict->creation_line, dict->creation_file); - DICTIONARY_STATS_CALLBACK_INSERTS_PLUS1(dict); dict->hooks->ins_callback(item, item->shared->value, constructor_data?constructor_data:dict->hooks->ins_callback_data); + DICTIONARY_STATS_CALLBACK_INSERTS_PLUS1(dict); } static bool dictionary_execute_conflict_callback(DICTIONARY *dict, DICTIONARY_ITEM *item, void *new_value, void *constructor_data) { @@ -597,10 +578,13 @@ static bool dictionary_execute_conflict_callback(DICTIONARY *dict, DICTIONARY_IT dict->creation_line, dict->creation_file); - DICTIONARY_STATS_CALLBACK_CONFLICTS_PLUS1(dict); - return dict->hooks->conflict_callback( + bool ret = dict->hooks->conflict_callback( item, item->shared->value, new_value, constructor_data ? constructor_data : dict->hooks->conflict_callback_data); + + DICTIONARY_STATS_CALLBACK_CONFLICTS_PLUS1(dict); + + return ret; } static void dictionary_execute_react_callback(DICTIONARY *dict, DICTIONARY_ITEM *item, void *constructor_data) { @@ -617,9 +601,10 @@ static void dictionary_execute_react_callback(DICTIONARY *dict, DICTIONARY_ITEM dict->creation_line, dict->creation_file); - DICTIONARY_STATS_CALLBACK_REACTS_PLUS1(dict); dict->hooks->react_callback(item, item->shared->value, constructor_data?constructor_data:dict->hooks->react_callback_data); + + DICTIONARY_STATS_CALLBACK_REACTS_PLUS1(dict); } static void dictionary_execute_delete_callback(DICTIONARY *dict, DICTIONARY_ITEM *item) { @@ -637,8 +622,9 @@ static void dictionary_execute_delete_callback(DICTIONARY *dict, DICTIONARY_ITEM dict->creation_line, dict->creation_file); - DICTIONARY_STATS_CALLBACK_DELETES_PLUS1(dict); dict->hooks->del_callback(item, item->shared->value, dict->hooks->del_callback_data); + + DICTIONARY_STATS_CALLBACK_DELETES_PLUS1(dict); } // ---------------------------------------------------------------------------- @@ -648,8 +634,8 @@ static inline size_t dictionary_locks_init(DICTIONARY *dict) { if(likely(!is_dictionary_single_threaded(dict))) { netdata_rwlock_init(&dict->index.rwlock); netdata_rwlock_init(&dict->items.rwlock); - return 0; } + return 0; } @@ -657,29 +643,29 @@ static inline size_t dictionary_locks_destroy(DICTIONARY *dict) { if(likely(!is_dictionary_single_threaded(dict))) { netdata_rwlock_destroy(&dict->index.rwlock); netdata_rwlock_destroy(&dict->items.rwlock); - return 0; } + return 0; } static inline void ll_recursive_lock_set_thread_as_writer(DICTIONARY *dict) { pid_t expected = 0, desired = gettid(); - if(!__atomic_compare_exchange_n(&dict->items.writer_pid, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) - fatal("DICTIONARY: Cannot set thread %d as exclusive writer, expected %d, desired %d, found %d.", gettid(), expected, desired, __atomic_load_n(&dict->items.writer_pid, __ATOMIC_SEQ_CST)); + if(!__atomic_compare_exchange_n(&dict->items.writer_pid, &expected, desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) + fatal("DICTIONARY: Cannot set thread %d as exclusive writer, expected %d, desired %d, found %d.", gettid(), expected, desired, __atomic_load_n(&dict->items.writer_pid, __ATOMIC_RELAXED)); } static inline void ll_recursive_unlock_unset_thread_writer(DICTIONARY *dict) { pid_t expected = gettid(), desired = 0; - if(!__atomic_compare_exchange_n(&dict->items.writer_pid, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) - fatal("DICTIONARY: Cannot unset thread %d as exclusive writer, expected %d, desired %d, found %d.", gettid(), expected, desired, __atomic_load_n(&dict->items.writer_pid, __ATOMIC_SEQ_CST)); + if(!__atomic_compare_exchange_n(&dict->items.writer_pid, &expected, desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) + fatal("DICTIONARY: Cannot unset thread %d as exclusive writer, expected %d, desired %d, found %d.", gettid(), expected, desired, __atomic_load_n(&dict->items.writer_pid, __ATOMIC_RELAXED)); } static inline bool ll_recursive_lock_is_thread_the_writer(DICTIONARY *dict) { pid_t tid = gettid(); - return tid > 0 && tid == __atomic_load_n(&dict->items.writer_pid, __ATOMIC_SEQ_CST); + return tid > 0 && tid == __atomic_load_n(&dict->items.writer_pid, __ATOMIC_RELAXED); } -static void ll_recursive_lock(DICTIONARY *dict, char rw) { +static inline void ll_recursive_lock(DICTIONARY *dict, char rw) { if(unlikely(is_dictionary_single_threaded(dict))) return; @@ -699,7 +685,7 @@ static void ll_recursive_lock(DICTIONARY *dict, char rw) { } } -static void ll_recursive_unlock(DICTIONARY *dict, char rw) { +static inline void ll_recursive_unlock(DICTIONARY *dict, char rw) { if(unlikely(is_dictionary_single_threaded(dict))) return; @@ -722,10 +708,10 @@ static void ll_recursive_unlock(DICTIONARY *dict, char rw) { } } -void dictionary_write_lock(DICTIONARY *dict) { +inline void dictionary_write_lock(DICTIONARY *dict) { ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE); } -void dictionary_write_unlock(DICTIONARY *dict) { +inline void dictionary_write_unlock(DICTIONARY *dict) { ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE); } @@ -760,8 +746,8 @@ static inline void dictionary_index_wrlock_unlock(DICTIONARY *dict) { // items garbage collector static void garbage_collect_pending_deletes(DICTIONARY *dict) { - usec_t last_master_deletion_us = dict->hooks?__atomic_load_n(&dict->hooks->last_master_deletion_us, __ATOMIC_SEQ_CST):0; - usec_t last_gc_run_us = __atomic_load_n(&dict->last_gc_run_us, __ATOMIC_SEQ_CST); + usec_t last_master_deletion_us = dict->hooks?__atomic_load_n(&dict->hooks->last_master_deletion_us, __ATOMIC_RELAXED):0; + usec_t last_gc_run_us = __atomic_load_n(&dict->last_gc_run_us, __ATOMIC_RELAXED); bool is_view = is_view_dictionary(dict); @@ -773,7 +759,7 @@ static void garbage_collect_pending_deletes(DICTIONARY *dict) { ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE); - __atomic_store_n(&dict->last_gc_run_us, now_realtime_usec(), __ATOMIC_SEQ_CST); + __atomic_store_n(&dict->last_gc_run_us, now_realtime_usec(), __ATOMIC_RELAXED); if(is_view) dictionary_index_lock_wrlock(dict); @@ -819,25 +805,27 @@ static void garbage_collect_pending_deletes(DICTIONARY *dict) { (void)deleted; (void)examined; - internal_error(false, "DICTIONARY: garbage collected dictionary created by %s (%zu@%s), examined %zu items, deleted %zu items, still pending %zu items", - dict->creation_function, dict->creation_line, dict->creation_file, examined, deleted, pending); + internal_error(false, "DICTIONARY: garbage collected dictionary created by %s (%zu@%s), " + "examined %zu items, deleted %zu items, still pending %zu items", + dict->creation_function, dict->creation_line, dict->creation_file, + examined, deleted, pending); +} +void dictionary_garbage_collect(DICTIONARY *dict) { + if(!dict) return; + garbage_collect_pending_deletes(dict); } // ---------------------------------------------------------------------------- // reference counters -static inline size_t reference_counter_init(DICTIONARY *dict) { - (void)dict; - +static inline size_t reference_counter_init(DICTIONARY *dict __maybe_unused) { // allocate memory required for reference counters // return number of bytes return 0; } -static inline size_t reference_counter_free(DICTIONARY *dict) { - (void)dict; - +static inline size_t reference_counter_free(DICTIONARY *dict __maybe_unused) { // free memory required for reference counters // return number of bytes return 0; @@ -846,13 +834,13 @@ static inline size_t reference_counter_free(DICTIONARY *dict) { static void item_acquire(DICTIONARY *dict, DICTIONARY_ITEM *item) { REFCOUNT refcount; - if(unlikely(is_dictionary_single_threaded(dict))) { + if(unlikely(is_dictionary_single_threaded(dict))) refcount = ++item->refcount; - } - else { + + else // increment the refcount refcount = __atomic_add_fetch(&item->refcount, 1, __ATOMIC_SEQ_CST); - } + if(refcount <= 0) { internal_error( @@ -900,7 +888,7 @@ static void item_release(DICTIONARY *dict, DICTIONARY_ITEM *item) { is_deleted = item_flag_check(item, ITEM_FLAG_DELETED); // decrement the refcount - refcount = __atomic_sub_fetch(&item->refcount, 1, __ATOMIC_SEQ_CST); + refcount = __atomic_sub_fetch(&item->refcount, 1, __ATOMIC_RELEASE); } if(refcount < 0) { @@ -956,14 +944,14 @@ static int item_check_and_acquire_advanced(DICTIONARY *dict, DICTIONARY_ITEM *it desired = refcount + 1; - } while(!__atomic_compare_exchange_n(&item->refcount, &refcount, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)); + } while(!__atomic_compare_exchange_n(&item->refcount, &refcount, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)); // if ret == ITEM_OK, we acquired the item if(ret == RC_ITEM_OK) { - if (is_view_dictionary(dict) && + if (unlikely(is_view_dictionary(dict) && item_shared_flag_check(item, ITEM_FLAG_DELETED) && - !item_flag_check(item, ITEM_FLAG_DELETED)) { + !item_flag_check(item, ITEM_FLAG_DELETED))) { // but, we can't use this item if (having_index_lock) { @@ -979,7 +967,7 @@ static int item_check_and_acquire_advanced(DICTIONARY *dict, DICTIONARY_ITEM *it dict_item_set_deleted(dict, item); // decrement the refcount we incremented above - if (__atomic_sub_fetch(&item->refcount, 1, __ATOMIC_SEQ_CST) == 0) { + if (__atomic_sub_fetch(&item->refcount, 1, __ATOMIC_RELEASE) == 0) { // this is a deleted item, and we are the last one DICTIONARY_PENDING_DELETES_PLUS1(dict); } @@ -988,7 +976,7 @@ static int item_check_and_acquire_advanced(DICTIONARY *dict, DICTIONARY_ITEM *it } else { // this is traversal / walkthrough // decrement the refcount we incremented above - __atomic_sub_fetch(&item->refcount, 1, __ATOMIC_SEQ_CST); + __atomic_sub_fetch(&item->refcount, 1, __ATOMIC_RELEASE); } return RC_ITEM_MARKED_FOR_DELETION; @@ -998,7 +986,6 @@ static int item_check_and_acquire_advanced(DICTIONARY *dict, DICTIONARY_ITEM *it DICTIONARY_REFERENCED_ITEMS_PLUS1(dict); } - if(unlikely(spins > 1 && dict->stats)) DICTIONARY_STATS_CHECK_SPINS_PLUS(dict, spins - 1); @@ -1037,7 +1024,7 @@ static inline int item_is_not_referenced_and_can_be_removed_advanced(DICTIONARY ret = RC_ITEM_IS_CURRENTLY_BEING_CREATED; break; } - } while(!__atomic_compare_exchange_n(&item->refcount, &refcount, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)); + } while(!__atomic_compare_exchange_n(&item->refcount, &refcount, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)); #ifdef NETDATA_INTERNAL_CHECKS if(ret == RC_ITEM_OK) @@ -1055,8 +1042,8 @@ static inline int item_is_not_referenced_and_can_be_removed_advanced(DICTIONARY static inline bool item_shared_release_and_check_if_it_can_be_freed(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item) { // if we can set refcount to REFCOUNT_DELETING, we can delete this item - REFCOUNT links = __atomic_sub_fetch(&item->shared->links, 1, __ATOMIC_SEQ_CST); - if(links == 0 && __atomic_compare_exchange_n(&item->shared->links, &links, REFCOUNT_DELETING, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) { + REFCOUNT links = __atomic_sub_fetch(&item->shared->links, 1, __ATOMIC_RELEASE); + if(links == 0 && __atomic_compare_exchange_n(&item->shared->links, &links, REFCOUNT_DELETING, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) { // we can delete it return true; @@ -1290,7 +1277,7 @@ static DICTIONARY_ITEM *dict_item_create(DICTIONARY *dict __maybe_unused, size_t if(master_item) { item->shared = master_item->shared; - if(unlikely(__atomic_add_fetch(&item->shared->links, 1, __ATOMIC_SEQ_CST) <= 1)) + if(unlikely(__atomic_add_fetch(&item->shared->links, 1, __ATOMIC_ACQUIRE) <= 1)) fatal("DICTIONARY: attempted to link to a shared item structure that had zero references"); } else { @@ -1478,7 +1465,7 @@ static void dict_item_shared_set_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item item_shared_flag_set(item, ITEM_FLAG_DELETED); if(dict->hooks) - __atomic_store_n(&dict->hooks->last_master_deletion_us, now_realtime_usec(), __ATOMIC_SEQ_CST); + __atomic_store_n(&dict->hooks->last_master_deletion_us, now_realtime_usec(), __ATOMIC_RELAXED); } } @@ -1486,7 +1473,7 @@ static void dict_item_shared_set_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item static bool dict_item_set_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item) { ITEM_FLAGS expected, desired; - expected = __atomic_load_n(&item->flags, __ATOMIC_SEQ_CST); + expected = __atomic_load_n(&item->flags, __ATOMIC_RELAXED); do { @@ -1495,7 +1482,7 @@ static bool dict_item_set_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item) { desired = expected | ITEM_FLAG_DELETED; - } while(!__atomic_compare_exchange_n(&item->flags, &expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)); + } while(!__atomic_compare_exchange_n(&item->flags, &expected, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)); DICTIONARY_ENTRIES_MINUS1(dict); return true; @@ -2063,11 +2050,11 @@ DICTIONARY *dictionary_create_view(DICTIONARY *master) { dictionary_hooks_allocate(master); - if(unlikely(__atomic_load_n(&master->hooks->links, __ATOMIC_SEQ_CST)) < 1) + if(unlikely(__atomic_load_n(&master->hooks->links, __ATOMIC_RELAXED)) < 1) fatal("DICTIONARY: attempted to create a view that has %d links", master->hooks->links); dict->hooks = master->hooks; - __atomic_add_fetch(&master->hooks->links, 1, __ATOMIC_SEQ_CST); + __atomic_add_fetch(&master->hooks->links, 1, __ATOMIC_ACQUIRE); #ifdef NETDATA_INTERNAL_CHECKS dict->creation_function = function; @@ -2167,6 +2154,8 @@ DICT_ITEM_CONST DICTIONARY_ITEM *dictionary_view_set_and_acquire_item_advanced(D if(unlikely(is_master_dictionary(dict))) fatal("DICTIONARY: this dictionary is a master, you cannot add items from other dictionaries."); + garbage_collect_pending_deletes(dict); + dictionary_acquired_item_dup(dict->master, master_item); DICTIONARY_ITEM *item = dict_item_add_or_reset_value_and_acquire(dict, name, name_len, NULL, 0, NULL, master_item); dictionary_acquired_item_release(dict->master, master_item); @@ -2289,7 +2278,7 @@ void *dictionary_foreach_start_rw(DICTFE *dfe, DICTIONARY *dict, char rw) { dfe->counter = 0; dfe->dict = dict; dfe->rw = rw; - + dfe->locked = true; ll_recursive_lock(dict, dfe->rw); DICTIONARY_STATS_TRAVERSALS_PLUS1(dict); @@ -2312,8 +2301,10 @@ void *dictionary_foreach_start_rw(DICTFE *dfe, DICTIONARY *dict, char rw) { dfe->value = NULL; } - if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT)) + if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT)) { ll_recursive_unlock(dfe->dict, dfe->rw); + dfe->locked = false; + } return dfe->value; } @@ -2329,8 +2320,10 @@ void *dictionary_foreach_next(DICTFE *dfe) { return NULL; } - if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT)) + if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT) || !dfe->locked) { ll_recursive_lock(dfe->dict, dfe->rw); + dfe->locked = true; + } // the item we just did DICTIONARY_ITEM *item = dfe->item; @@ -2360,12 +2353,21 @@ void *dictionary_foreach_next(DICTFE *dfe) { dfe->value = NULL; } - if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT)) + if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT)) { ll_recursive_unlock(dfe->dict, dfe->rw); + dfe->locked = false; + } return dfe->value; } +void dictionary_foreach_unlock(DICTFE *dfe) { + if(dfe->locked) { + ll_recursive_unlock(dfe->dict, dfe->rw); + dfe->locked = false; + } +} + void dictionary_foreach_done(DICTFE *dfe) { if(unlikely(!dfe || !dfe->dict)) return; @@ -2383,8 +2385,10 @@ void dictionary_foreach_done(DICTFE *dfe) { // item_release(dfe->dict, item); } - if(likely(dfe->rw != DICTIONARY_LOCK_REENTRANT)) + if(likely(dfe->rw != DICTIONARY_LOCK_REENTRANT) && dfe->locked) { ll_recursive_unlock(dfe->dict, dfe->rw); + dfe->locked = false; + } dfe->dict = NULL; dfe->item = NULL; @@ -2472,7 +2476,7 @@ int dictionary_sorted_walkthrough_rw(DICTIONARY *dict, char rw, int (*callback)( DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict); ll_recursive_lock(dict, rw); - size_t entries = __atomic_load_n(&dict->entries, __ATOMIC_SEQ_CST); + size_t entries = __atomic_load_n(&dict->entries, __ATOMIC_RELAXED); DICTIONARY_ITEM **array = mallocz(sizeof(DICTIONARY_ITEM *) * entries); size_t i; @@ -3293,12 +3297,12 @@ static void *unittest_dict_master_thread(void *arg) { DICTIONARY_ITEM *item = NULL; int loops = 0; - while(!__atomic_load_n(&tv->join, __ATOMIC_SEQ_CST)) { + while(!__atomic_load_n(&tv->join, __ATOMIC_RELAXED)) { if(!item) item = dictionary_set_and_acquire_item(tv->master, "ITEM1", "123", strlen("123") + 1); - if(__atomic_load_n(&tv->item_master, __ATOMIC_SEQ_CST) != NULL) { + if(__atomic_load_n(&tv->item_master, __ATOMIC_RELAXED) != NULL) { dictionary_acquired_item_release(tv->master, item); dictionary_del(tv->master, "ITEM1"); item = NULL; @@ -3307,7 +3311,7 @@ static void *unittest_dict_master_thread(void *arg) { } dictionary_acquired_item_dup(tv->master, item); // for the view thread - __atomic_store_n(&tv->item_master, item, __ATOMIC_SEQ_CST); + __atomic_store_n(&tv->item_master, item, __ATOMIC_RELAXED); dictionary_del(tv->master, "ITEM1"); @@ -3333,13 +3337,13 @@ static void *unittest_dict_view_thread(void *arg) { DICTIONARY_ITEM *m_item = NULL; - while(!__atomic_load_n(&tv->join, __ATOMIC_SEQ_CST)) { - if(!(m_item = __atomic_load_n(&tv->item_master, __ATOMIC_SEQ_CST))) + while(!__atomic_load_n(&tv->join, __ATOMIC_RELAXED)) { + if(!(m_item = __atomic_load_n(&tv->item_master, __ATOMIC_RELAXED))) continue; DICTIONARY_ITEM *v_item = dictionary_view_set_and_acquire_item(tv->view, "ITEM2", m_item); dictionary_acquired_item_release(tv->master, m_item); - __atomic_store_n(&tv->item_master, NULL, __ATOMIC_SEQ_CST); + __atomic_store_n(&tv->item_master, NULL, __ATOMIC_RELAXED); for(int i = 0; i < tv->dups ; i++) { dictionary_acquired_item_dup(tv->view, v_item); @@ -3351,7 +3355,7 @@ static void *unittest_dict_view_thread(void *arg) { dictionary_del(tv->view, "ITEM2"); - while(!__atomic_load_n(&tv->join, __ATOMIC_SEQ_CST) && !(m_item = __atomic_load_n(&tv->item_master, __ATOMIC_SEQ_CST))) { + while(!__atomic_load_n(&tv->join, __ATOMIC_RELAXED) && !(m_item = __atomic_load_n(&tv->item_master, __ATOMIC_RELAXED))) { dictionary_acquired_item_dup(tv->view, v_item); dictionary_acquired_item_release(tv->view, v_item); } @@ -3522,7 +3526,7 @@ size_t dictionary_unittest_views(void) { fprintf(stderr, "\nPASS 2: Deleting master item:\n"); dictionary_del(master, "KEY 1"); - dictionary_version(view); + garbage_collect_pending_deletes(view); errors += unittest_check_dictionary("master", master, 0, 0, 1, 1, 0); errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0); errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_DELETED, false, false, true); diff --git a/libnetdata/dictionary/dictionary.h b/libnetdata/dictionary/dictionary.h index 58220def..c13d784c 100644 --- a/libnetdata/dictionary/dictionary.h +++ b/libnetdata/dictionary/dictionary.h @@ -112,7 +112,7 @@ struct dictionary_stats { #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, 0); +#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 @@ -156,6 +156,8 @@ void dictionary_flush(DICTIONARY *dict); void dictionary_version_increment(DICTIONARY *dict); +void dictionary_garbage_collect(DICTIONARY *dict); + // ---------------------------------------------------------------------------- // Set an item in the dictionary // @@ -261,19 +263,20 @@ void dictionary_write_lock(DICTIONARY *dict); void dictionary_write_unlock(DICTIONARY *dict); typedef DICTFE_CONST struct dictionary_foreach { - DICTIONARY *dict; // the dictionary upon we work + DICTIONARY *dict; // the dictionary upon we work DICTIONARY_ITEM *item; // the item we work on, to remember the position we are at // this can be used with dictionary_acquired_item_dup() to // acquire the currently working item. - DICTFE_CONST char *name; // the dictionary name of the last item used + const char *name; // the dictionary name of the last item used void *value; // the dictionary value of the last item used // same as the return value of dictfe_start() and dictfe_next() size_t counter; // counts the number of iterations made, starting from zero char rw; // the lock mode 'r' or 'w' + bool locked; // true when the dictionary is locked } DICTFE; #define dfe_start_read(dict, value) dfe_start_rw(dict, value, DICTIONARY_LOCK_READ) @@ -294,9 +297,12 @@ typedef DICTFE_CONST struct dictionary_foreach { dictionary_foreach_done(&value ## _dfe); \ } while(0) +#define dfe_unlock(value) dictionary_foreach_unlock(&value ## _dfe); + void *dictionary_foreach_start_rw(DICTFE *dfe, DICTIONARY *dict, char rw); void *dictionary_foreach_next(DICTFE *dfe); void dictionary_foreach_done(DICTFE *dfe); +void dictionary_foreach_unlock(DICTFE *dfe); // ---------------------------------------------------------------------------- // Get statistics about the dictionary diff --git a/libnetdata/ebpf/README.md b/libnetdata/ebpf/README.md index c2dabe10..bf2c6ff3 100644 --- a/libnetdata/ebpf/README.md +++ b/libnetdata/ebpf/README.md @@ -1,10 +1,10 @@ # eBPF library diff --git a/libnetdata/ebpf/ebpf.c b/libnetdata/ebpf/ebpf.c index 7cad5978..61833dd7 100644 --- a/libnetdata/ebpf/ebpf.c +++ b/libnetdata/ebpf/ebpf.c @@ -366,20 +366,22 @@ static uint32_t ebpf_select_index(uint32_t kernels, int is_rhf, uint32_t kver) * V - The kernel version in string format. * * @param out the vector where the name will be stored - * @param path * @param len the size of the out vector. + * @param path where the binaries are stored * @param kver the kernel version * @param name the eBPF program name. * @param is_return is return or entry ? */ -static void ebpf_mount_name(char *out, size_t len, char *path, uint32_t kver, const char *name, int is_return) +static void ebpf_mount_name(char *out, size_t len, char *path, uint32_t kver, const char *name, + int is_return, int is_rhf) { char *version = ebpf_select_kernel_name(kver); - snprintfz(out, len, "%s/ebpf.d/%cnetdata_ebpf_%s.%s.o", + snprintfz(out, len, "%s/ebpf.d/%cnetdata_ebpf_%s.%s%s.o", path, (is_return) ? 'r' : 'p', name, - version); + version, + (is_rhf != -1) ? ".rhf" : ""); } //---------------------------------------------------------------------------------------------------------------------- @@ -439,7 +441,7 @@ void ebpf_update_stats(ebpf_plugin_stats_t *report, ebpf_module_t *em) report->threads++; // It is not necessary to report more information. - if (!em->enabled) + if (em->enabled != NETDATA_THREAD_EBPF_RUNNING) return; report->running++; @@ -454,6 +456,91 @@ void ebpf_update_stats(ebpf_plugin_stats_t *report, ebpf_module_t *em) ebpf_stats_targets(report, em->targets); } +/** + * Update Kernel memory with memory + * + * This algorithm is an adaptation of https://elixir.bootlin.com/linux/v6.1.14/source/tools/bpf/bpftool/common.c#L402 + * to get 'memlock' data and update report. + * + * @param report the output structure + * @param map pointer to a map. + * @param action What action will be done with this map. + */ +void ebpf_update_kernel_memory(ebpf_plugin_stats_t *report, ebpf_local_maps_t *map, ebpf_stats_action_t action) +{ + char filename[FILENAME_MAX+1]; + snprintfz(filename, FILENAME_MAX, "/proc/self/fdinfo/%d", map->map_fd); + procfile *ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT); + if(unlikely(!ff)) { + error("Cannot open %s", filename); + return; + } + + ff = procfile_readall(ff); + if(unlikely(!ff)) + return; + + unsigned long j, lines = procfile_lines(ff); + char *memlock = { "memlock" }; + for (j = 0; j < lines ; j++) { + char *cmp = procfile_lineword(ff, j,0); + if (!strncmp(memlock, cmp, 7)) { + uint64_t memsize = (uint64_t) str2l(procfile_lineword(ff, j,1)); + switch (action) { + case EBPF_ACTION_STAT_ADD: { + report->memlock_kern += memsize; + report->hash_tables += 1; +#ifdef NETDATA_DEV_MODE + info("Hash table %u: %s (FD = %d) is consuming %lu bytes totalizing %lu bytes", + report->hash_tables, map->name, map->map_fd, memsize, report->memlock_kern); +#endif + break; + } + case EBPF_ACTION_STAT_REMOVE: { + report->memlock_kern -= memsize; + report->hash_tables -= 1; +#ifdef NETDATA_DEV_MODE + info("Hash table %s (FD = %d) was removed releasing %lu bytes, now we have %u tables loaded totalizing %lu bytes.", + map->name, map->map_fd, memsize, report->hash_tables, report->memlock_kern); +#endif + break; + } + default: { + break; + } + } + break; + } + } + + procfile_close(ff); +} + +/** + * Update Kernel memory with memory + * + * This algorithm is an adaptation of https://elixir.bootlin.com/linux/v6.1.14/source/tools/bpf/bpftool/common.c#L402 + * to get 'memlock' data and update report. + * + * @param report the output structure + * @param map pointer to a map. Last map must fish with name = NULL + */ +void ebpf_update_kernel_memory_with_vector(ebpf_plugin_stats_t *report, ebpf_local_maps_t *maps) +{ + if (!maps) + return; + + ebpf_local_maps_t *map; + int i = 0; + for (map = &maps[i]; maps[i].name; i++, map = &maps[i]) { + int fd = map->map_fd; + if (fd == ND_EBPF_MAP_FD_NOT_INITIALIZED) + continue; + + ebpf_update_kernel_memory(report, map, EBPF_ACTION_STAT_ADD); + } +} + //---------------------------------------------------------------------------------------------------------------------- void ebpf_update_pid_table(ebpf_local_maps_t *pid, ebpf_module_t *em) @@ -696,7 +783,7 @@ struct bpf_link **ebpf_load_program(char *plugins_dir, ebpf_module_t *em, int kv uint32_t idx = ebpf_select_index(em->kernels, is_rhf, kver); - ebpf_mount_name(lpath, 4095, plugins_dir, idx, em->thread_name, em->mode); + ebpf_mount_name(lpath, 4095, plugins_dir, idx, em->thread_name, em->mode, is_rhf); // When this function is called ebpf.plugin is using legacy code, so we should reset the variable em->load &= ~ NETDATA_EBPF_LOAD_METHODS; diff --git a/libnetdata/ebpf/ebpf.h b/libnetdata/ebpf/ebpf.h index cf3fa7cc..bf5fdc33 100644 --- a/libnetdata/ebpf/ebpf.h +++ b/libnetdata/ebpf/ebpf.h @@ -10,6 +10,7 @@ #include #endif #include // Necessary for stdtoul +#include "libnetdata/aral/aral.h" #define NETDATA_DEBUGFS "/sys/kernel/debug/tracing/" #define NETDATA_KALLSYMS "/proc/kallsyms" @@ -238,18 +239,38 @@ typedef struct ebpf_plugin_stats { uint32_t retprobes; // Number of kretprobes loaded uint32_t tracepoints; // Number of tracepoints used uint32_t trampolines; // Number of trampolines used + + uint64_t memlock_kern; // The same information reported by bpftool, but it is not accurated + // https://lore.kernel.org/linux-mm/20230112155326.26902-5-laoar.shao@gmail.com/T/ + uint32_t hash_tables; // Number of hash tables used on the system. } ebpf_plugin_stats_t; +typedef enum ebpf_stats_action { + EBPF_ACTION_STAT_ADD, + EBPF_ACTION_STAT_REMOVE, +} ebpf_stats_action_t; + typedef enum netdata_apps_integration_flags { NETDATA_EBPF_APPS_FLAG_NO, NETDATA_EBPF_APPS_FLAG_YES, NETDATA_EBPF_APPS_FLAG_CHART_CREATED } netdata_apps_integration_flags_t; +#define NETDATA_EBPF_CHART_MEM_LENGTH 48 +#define NETDATA_EBPF_STAT_DIMENSION_MEMORY "memory" +#define NETDATA_EBPF_STAT_DIMENSION_ARAL "aral" + +enum ebpf_threads_status { + NETDATA_THREAD_EBPF_RUNNING, + NETDATA_THREAD_EBPF_STOPPING, + NETDATA_THREAD_EBPF_STOPPED, + NETDATA_THREAD_EBPF_NOT_RUNNING +}; + typedef struct ebpf_module { const char *thread_name; const char *config_name; - int enabled; + enum ebpf_threads_status enabled; void *(*start_routine)(void *); int update_every; int global_charts; @@ -271,6 +292,10 @@ typedef struct ebpf_module { struct bpf_link **probe_links; struct bpf_object *objects; struct netdata_static_thread *thread; + + // charts + char memory_usage[NETDATA_EBPF_CHART_MEM_LENGTH]; + char memory_allocations[NETDATA_EBPF_CHART_MEM_LENGTH]; } ebpf_module_t; int ebpf_get_kernel_version(); @@ -368,4 +393,9 @@ struct btf *ebpf_load_btf_file(char *path, char *filename); int ebpf_is_function_inside_btf(struct btf *file, char *function); #endif +void ebpf_update_kernel_memory_with_vector(ebpf_plugin_stats_t *report, ebpf_local_maps_t *maps); +void ebpf_update_kernel_memory(ebpf_plugin_stats_t *report, ebpf_local_maps_t *map, ebpf_stats_action_t action); +void ebpf_statistic_create_aral_chart(char *name, ebpf_module_t *em); +void ebpf_send_data_aral_chart(ARAL *memory, ebpf_module_t *em); + #endif /* NETDATA_EBPF_H */ diff --git a/libnetdata/health/health.c b/libnetdata/health/health.c index c44ba082..d5403cef 100644 --- a/libnetdata/health/health.c +++ b/libnetdata/health/health.c @@ -81,19 +81,19 @@ SILENCER *health_silencers_addparam(SILENCER *silencer, char *key, char *value) if (hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) { silencer->alarms = strdupz(value); - silencer->alarms_pattern = simple_pattern_create(silencer->alarms, NULL, SIMPLE_PATTERN_EXACT); + silencer->alarms_pattern = simple_pattern_create(silencer->alarms, NULL, SIMPLE_PATTERN_EXACT, true); } else if (hash == hash_chart && !strcasecmp(key, HEALTH_CHART_KEY)) { silencer->charts = strdupz(value); - silencer->charts_pattern = simple_pattern_create(silencer->charts, NULL, SIMPLE_PATTERN_EXACT); + silencer->charts_pattern = simple_pattern_create(silencer->charts, NULL, SIMPLE_PATTERN_EXACT, true); } else if (hash == hash_context && !strcasecmp(key, HEALTH_CONTEXT_KEY)) { silencer->contexts = strdupz(value); - silencer->contexts_pattern = simple_pattern_create(silencer->contexts, NULL, SIMPLE_PATTERN_EXACT); + silencer->contexts_pattern = simple_pattern_create(silencer->contexts, NULL, SIMPLE_PATTERN_EXACT, true); } else if (hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) { silencer->hosts = strdupz(value); - silencer->hosts_pattern = simple_pattern_create(silencer->hosts, NULL, SIMPLE_PATTERN_EXACT); + silencer->hosts_pattern = simple_pattern_create(silencer->hosts, NULL, SIMPLE_PATTERN_EXACT, true); } else if (hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY)) { silencer->families = strdupz(value); - silencer->families_pattern = simple_pattern_create(silencer->families, NULL, SIMPLE_PATTERN_EXACT); + silencer->families_pattern = simple_pattern_create(silencer->families, NULL, SIMPLE_PATTERN_EXACT, true); } return silencer; diff --git a/libnetdata/inlined.h b/libnetdata/inlined.h index aa7f3c21..2697b9a0 100644 --- a/libnetdata/inlined.h +++ b/libnetdata/inlined.h @@ -7,147 +7,391 @@ #ifdef KERNEL_32BIT typedef uint32_t kernel_uint_t; -#define str2kernel_uint_t(string) str2uint32_t(string) +#define str2kernel_uint_t(string) str2uint32_t(string, NULL) #define KERNEL_UINT_FORMAT "%u" #else typedef uint64_t kernel_uint_t; -#define str2kernel_uint_t(string) str2uint64_t(string) +#define str2kernel_uint_t(string) str2uint64_t(string, NULL) #define KERNEL_UINT_FORMAT "%" PRIu64 #endif -#define str2pid_t(string) str2uint32_t(string) +#define str2pid_t(string) str2uint32_t(string, NULL) // for faster execution, allow the compiler to inline // these functions that are called thousands of times per second -static inline uint32_t simple_hash(const char *name) { +static inline uint32_t djb2_hash32(const char* name) { unsigned char *s = (unsigned char *) name; - uint32_t hval = 0x811c9dc5; + uint32_t hash = 5381; + while (*s) + hash = ((hash << 5) + hash) + (uint32_t) *s++; // hash * 33 + char + return hash; +} + +static inline uint32_t pluginsd_parser_hash32(const char *name) { + unsigned char *s = (unsigned char *) name; + uint32_t hash = 0; while (*s) { - hval *= 16777619; - hval ^= (uint32_t) *s++; + hash <<= 5; + hash += *s++ - ' '; } - return hval; + return hash; } -static inline uint32_t simple_uhash(const char *name) { +// https://stackoverflow.com/a/107657 +static inline uint32_t larson_hash32(const char *name) { unsigned char *s = (unsigned char *) name; - uint32_t hval = 0x811c9dc5, c; + uint32_t hash = 0; + while (*s) + hash = hash * 101 + (uint32_t) *s++; + return hash; +} + +// http://isthe.com/chongo/tech/comp/fnv/ +static inline uint32_t fnv1_hash32(const char *name) { + unsigned char *s = (unsigned char *) name; + uint32_t hash = 0x811c9dc5; + while (*s) { + hash *= 0x01000193; // 16777619 + hash ^= (uint32_t) *s++; + } + return hash; +} + +// http://isthe.com/chongo/tech/comp/fnv/ +static inline uint32_t fnv1a_hash32(const char *name) { + unsigned char *s = (unsigned char *) name; + uint32_t hash = 0x811c9dc5; + while (*s) { + hash ^= (uint32_t) *s++; + hash *= 0x01000193; // 16777619 + } + return hash; +} + +static inline uint32_t fnv1a_uhash32(const char *name) { + unsigned char *s = (unsigned char *) name; + uint32_t hash = 0x811c9dc5, c; while ((c = *s++)) { if (unlikely(c >= 'A' && c <= 'Z')) c += 'a' - 'A'; - hval *= 16777619; - hval ^= c; + hash ^= c; + hash *= 0x01000193; // 16777619 } - return hval; + return hash; } -static inline int str2i(const char *s) { - int n = 0; - char c, negative = (char)(*s == '-'); - const char *e = s + 30; // max number of character to iterate +#define simple_hash(s) fnv1a_hash32(s) +#define simple_uhash(s) fnv1a_uhash32(s) - for(c = (char)((negative)?*(++s):*s); c >= '0' && c <= '9' && s < e ; c = *(++s)) { - n *= 10; - n += c - '0'; - } +static uint32_t murmur32(uint32_t k) __attribute__((const)); +static inline uint32_t murmur32(uint32_t k) { + k ^= k >> 16; + k *= 0x85ebca6b; + k ^= k >> 13; + k *= 0xc2b2ae35; + k ^= k >> 16; - if(unlikely(negative)) - return -n; + return k; +} - return n; +static uint64_t murmur64(uint64_t k) __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 long str2l(const char *s) { - long n = 0; - char c, negative = (*s == '-'); - const char *e = &s[30]; // max number of character to iterate +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) { +#ifdef ENV64BIT + uint64_t hash = murmur64(ptr); + return hash % modulo; +#else + uint32_t hash = murmur32(ptr); + return hash % modulo; +#endif +} + +static inline unsigned int str2u(const char *s) { + unsigned int n = 0; - for(c = (negative)?*(++s):*s; c >= '0' && c <= '9' && s < e ; c = *(++s)) { - n *= 10; - n += c - '0'; + while(*s >= '0' && *s <= '9') + n = n * 10 + (*s++ - '0'); + + return n; +} + +static inline int str2i(const char *s) { + if(unlikely(*s == '-')) { + s++; + return -(int) str2u(s); + } + else { + if(unlikely(*s == '+')) s++; + return (int) str2u(s); } +} - if(unlikely(negative)) - return -n; +static inline unsigned long str2ul(const char *s) { + unsigned long n = 0; + + while(*s >= '0' && *s <= '9') + n = n * 10 + (*s++ - '0'); return n; } -static inline uint32_t str2uint32_t(const char *s) { +static inline long str2l(const char *s) { + if(unlikely(*s == '-')) { + s++; + return -(long) str2ul(s); + } + else { + if(unlikely(*s == '+')) s++; + return (long) str2ul(s); + } +} + +static inline uint32_t str2uint32_t(const char *s, char **endptr) { uint32_t n = 0; - char c; - const char *e = &s[30]; // max number of character to iterate - for(c = *s; c >= '0' && c <= '9' && s < e ; c = *(++s)) { - n *= 10; - n += c - '0'; - } + while(*s >= '0' && *s <= '9') + n = n * 10 + (*s++ - '0'); + + if(unlikely(endptr)) + *endptr = (char *)s; + return n; } -static inline uint64_t str2uint64_t(const char *s) { +static inline uint64_t str2uint64_t(const char *s, char **endptr) { uint64_t n = 0; - char c; - const char *e = &s[30]; // max number of character to iterate - for(c = *s; c >= '0' && c <= '9' && s < e ; c = *(++s)) { - n *= 10; - n += c - '0'; - } +#ifdef ENV32BIT + unsigned long n32 = 0; + while (*s >= '0' && *s <= '9' && n32 < (ULONG_MAX / 10)) + n32 = n32 * 10 + (*s++ - '0'); + + n = n32; +#endif + + while(*s >= '0' && *s <= '9') + n = n * 10 + (*s++ - '0'); + + if(unlikely(endptr)) + *endptr = (char *)s; + return n; } -static inline unsigned long str2ul(const char *s) { - unsigned long n = 0; - char c; - const char *e = &s[30]; // max number of character to iterate +static inline unsigned long long int str2ull(const char *s, char **endptr) { + return str2uint64_t(s, endptr); +} - for(c = *s; c >= '0' && c <= '9' && s < e ; c = *(++s)) { - n *= 10; - n += c - '0'; +static inline long long str2ll(const char *s, char **endptr) { + if(unlikely(*s == '-')) { + s++; + return -(long long) str2uint64_t(s, endptr); } - return n; + else { + if(unlikely(*s == '+')) s++; + return (long long) str2uint64_t(s, endptr); + } +} + +static inline uint64_t str2uint64_hex(const char *src, char **endptr) { + uint64_t num = 0; + const unsigned char *s = (const unsigned char *)src; + unsigned char c; + + while ((c = hex_value_from_ascii[*s]) != 255) { + num = (num << 4) | c; + s++; + } + + if(endptr) + *endptr = (char *)s; + + return num; } -static inline unsigned long long str2ull(const char *s) { - unsigned long long n = 0; - char c; - const char *e = &s[30]; // max number of character to iterate +static inline uint64_t str2uint64_base64(const char *src, char **endptr) { + uint64_t num = 0; + const unsigned char *s = (const unsigned char *)src; + unsigned char c; + + while ((c = base64_value_from_ascii[*s]) != 255) { + num = (num << 6) | c; + s++; + } + + if(endptr) + *endptr = (char *)s; + + return num; +} + +static inline NETDATA_DOUBLE str2ndd_parse_double_decimal_digits_internal(const char *src, int *digits) { + const char *s = src; + NETDATA_DOUBLE n = 0.0; + + while(*s >= '0' && *s <= '9') { + + // this works for both 32-bit and 64-bit systems + unsigned long ni = 0; + unsigned exponent = 0; + while (*s >= '0' && *s <= '9' && ni < (ULONG_MAX / 10)) { + ni = (ni * 10) + (*s++ - '0'); + exponent++; + } - for(c = *s; c >= '0' && c <= '9' && s < e ; c = *(++s)) { - n *= 10; - n += c - '0'; + n = n * powndd(10.0, exponent) + (NETDATA_DOUBLE)ni; } + + *digits = (int)(s - src); return n; } -static inline long long str2ll(const char *s, char **endptr) { - int negative = 0; +static inline NETDATA_DOUBLE str2ndd(const char *src, char **endptr) { + const char *s = src; + + NETDATA_DOUBLE sign = 1.0; + NETDATA_DOUBLE result; + int integral_digits = 0; + + NETDATA_DOUBLE fractional = 0.0; + int fractional_digits = 0; + + NETDATA_DOUBLE exponent = 0.0; + int exponent_digits = 0; + + switch(*s) { + case '-': + s++; + sign = -1.0; + break; + + case '+': + s++; + break; + + case 'n': + if(s[1] == 'a' && s[2] == 'n') { + if(endptr) *endptr = (char *)&s[3]; + return NAN; + } + if(s[1] == 'u' && s[2] == 'l' && s[3] == 'l') { + if(endptr) *endptr = (char *)&s[3]; + return NAN; + } + break; + + case 'i': + if(s[1] == 'n' && s[2] == 'f') { + if(endptr) *endptr = (char *)&s[3]; + return INFINITY; + } + break; + + default: + break; + } - if(unlikely(*s == '-')) { + result = str2ndd_parse_double_decimal_digits_internal(s, &integral_digits); + s += integral_digits; + + if(unlikely(*s == '.')) { s++; - negative = 1; + fractional = str2ndd_parse_double_decimal_digits_internal(s, &fractional_digits); + s += fractional_digits; } - else if(unlikely(*s == '+')) + + if (unlikely(*s == 'e' || *s == 'E')) { + const char *e_ptr = s; s++; - long long n = 0; - char c; - const char *e = &s[30]; // max number of character to iterate + int exponent_sign = 1; + if (*s == '-') { + exponent_sign = -1; + s++; + } + else if(*s == '+') + s++; - for(c = *s; c >= '0' && c <= '9' && s < e ; c = *(++s)) { - n *= 10; - n += c - '0'; + exponent = str2ndd_parse_double_decimal_digits_internal(s, &exponent_digits); + if(unlikely(!exponent_digits)) { + exponent = 0; + s = e_ptr; + } + else { + s += exponent_digits; + exponent *= exponent_sign; + } } if(unlikely(endptr)) *endptr = (char *)s; - if(unlikely(negative)) - return -n; + if (unlikely(exponent_digits)) + result *= powndd(10.0, exponent); + + if (unlikely(fractional_digits)) + result += fractional / powndd(10.0, fractional_digits) * (exponent_digits ? powndd(10.0, exponent) : 1.0); + + return sign * result; +} + +static inline unsigned long long str2ull_encoded(const char *s) { + if(*s == IEEE754_UINT64_B64_PREFIX[0]) + return str2uint64_base64(s + sizeof(IEEE754_UINT64_B64_PREFIX) - 1, NULL); + + if(s[0] == HEX_PREFIX[0] && s[1] == HEX_PREFIX[1]) + return str2uint64_hex(s + 2, NULL); + + return str2uint64_t(s, NULL); +} + +static inline long long str2ll_encoded(const char *s) { + if(*s == '-') + return -(long long) str2ull_encoded(&s[1]); else - return n; + return (long long) str2ull_encoded(s); +} + +static inline NETDATA_DOUBLE str2ndd_encoded(const char *src, char **endptr) { + if (*src == IEEE754_DOUBLE_B64_PREFIX[0]) { + // double parsing from base64 + uint64_t n = str2uint64_base64(src + sizeof(IEEE754_DOUBLE_B64_PREFIX) - 1, endptr); + NETDATA_DOUBLE *ptr = (NETDATA_DOUBLE *) (&n); + return *ptr; + } + + if (*src == IEEE754_DOUBLE_HEX_PREFIX[0]) { + // double parsing from hex + uint64_t n = str2uint64_hex(src + sizeof(IEEE754_DOUBLE_HEX_PREFIX) - 1, endptr); + NETDATA_DOUBLE *ptr = (NETDATA_DOUBLE *) (&n); + return *ptr; + } + + double sign = 1.0; + + if(*src == '-') { + sign = -1.0; + src++; + } + + if(unlikely(*src == IEEE754_UINT64_B64_PREFIX[0])) + return (NETDATA_DOUBLE) str2uint64_base64(src + sizeof(IEEE754_UINT64_B64_PREFIX) - 1, endptr) * sign; + + if(unlikely(*src == HEX_PREFIX[0] && src[1] == HEX_PREFIX[1])) + return (NETDATA_DOUBLE) str2uint64_hex(src + sizeof(HEX_PREFIX) - 1, endptr) * sign; + + return str2ndd(src, endptr) * sign; } static inline char *strncpyz(char *dst, const char *src, size_t n) { @@ -248,7 +492,7 @@ static inline int read_single_number_file(const char *filename, unsigned long lo } buffer[30] = '\0'; - *result = str2ull(buffer); + *result = str2ull(buffer, NULL); return 0; } @@ -266,4 +510,66 @@ static inline int read_single_signed_number_file(const char *filename, long long return 0; } +static inline int uuid_memcmp(const uuid_t *uu1, const uuid_t *uu2) { + return memcmp(uu1, uu2, sizeof(uuid_t)); +} + +static inline char *strsep_skip_consecutive_separators(char **ptr, char *s) { + char *p = (char *)""; + while (p && !p[0] && *ptr) p = strsep(ptr, s); + return (p); +} + +// remove leading and trailing spaces; may return NULL +static inline char *trim(char *s) { + // skip leading spaces + while (*s && isspace(*s)) s++; + if (!*s) return NULL; + + // skip tailing spaces + // this way is way faster. Writes only one NUL char. + ssize_t l = (ssize_t)strlen(s); + if (--l >= 0) { + char *p = s + l; + while (p > s && isspace(*p)) p--; + *++p = '\0'; + } + + if (!*s) return NULL; + + return s; +} + +// like trim(), but also remove duplicate spaces inside the string; may return NULL +static inline char *trim_all(char *buffer) { + char *d = buffer, *s = buffer; + + // skip spaces + while(isspace(*s)) s++; + + while(*s) { + // copy the non-space part + while(*s && !isspace(*s)) *d++ = *s++; + + // add a space if we have to + if(*s && isspace(*s)) { + *d++ = ' '; + s++; + } + + // skip spaces + while(isspace(*s)) s++; + } + + *d = '\0'; + + if(d > buffer) { + d--; + if(isspace(*d)) *d = '\0'; + } + + if(!buffer[0]) return NULL; + return buffer; +} + #endif //NETDATA_INLINED_H diff --git a/libnetdata/json/README.md b/libnetdata/json/README.md index e772f114..1c49739b 100644 --- a/libnetdata/json/README.md +++ b/libnetdata/json/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/json/ sidebar_label: "json" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # json diff --git a/libnetdata/july/README.md b/libnetdata/july/README.md index df2a3d38..99584912 100644 --- a/libnetdata/july/README.md +++ b/libnetdata/july/README.md @@ -3,7 +3,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/july/ sidebar_label: "July interface" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> diff --git a/libnetdata/libjudy/src/JudyL/JudyLTablesGen.c b/libnetdata/libjudy/src/JudyL/JudyLTablesGen.c index cb8b13ff..ce4b3715 100644 --- a/libnetdata/libjudy/src/JudyL/JudyLTablesGen.c +++ b/libnetdata/libjudy/src/JudyL/JudyLTablesGen.c @@ -15,7 +15,7 @@ // Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // _________________ -// @(#) $Revision: 4.37 $ $Source: /judy/src/JudyCommon/JudyTables.c $ +// @(#) $Revision: 4.37.1-netdata $ $Source: JudyTables.c $ #ifndef JU_WIN #include // unavailable on win_*. @@ -30,7 +30,12 @@ #define TERMINATOR 999 // terminator for Alloc tables -#define BPW sizeof(Word_t) // define bytes per word +// define bytes per word +#ifdef JU_64BIT +#define BPW 8UL +#else +#define BPW 4UL +#endif #ifdef JUDY1 #include "Judy1.h" @@ -199,7 +204,7 @@ FUNCTION int main() } - fprintf(fd,"// @(#) From generation tool: $Revision: 4.37 $ $Source: /judy/src/JudyCommon/JudyTables.c $\n"); + fprintf(fd,"// @(#) From generation tool: $Revision: 4.37.1-netdata $ $Source: JudyTables.c $\n"); fprintf(fd,"//\n\n"); diff --git a/libnetdata/libnetdata.c b/libnetdata/libnetdata.c index 666344a9..a8f26c33 100644 --- a/libnetdata/libnetdata.c +++ b/libnetdata/libnetdata.c @@ -225,10 +225,6 @@ void posix_memfree(void *ptr) { libc_free(ptr); } -#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) - struct malloc_header_signature { uint32_t magic; uint32_t size; @@ -1046,171 +1042,6 @@ void netdata_fix_chart_id(char *s) { while ((*s = netdata_map_chart_ids[(unsigned char) *s])) s++; } -/* -// http://stackoverflow.com/questions/7666509/hash-function-for-string -uint32_t simple_hash(const char *name) -{ - const char *s = name; - uint32_t hash = 5381; - int i; - - while((i = *s++)) hash = ((hash << 5) + hash) + i; - - // fprintf(stderr, "HASH: %lu %s\n", hash, name); - - return hash; -} -*/ - -/* -// http://isthe.com/chongo/tech/comp/fnv/#FNV-1a -uint32_t simple_hash(const char *name) { - unsigned char *s = (unsigned char *) name; - uint32_t hval = 0x811c9dc5; - - // FNV-1a algorithm - while (*s) { - // multiply by the 32 bit FNV magic prime mod 2^32 - // NOTE: No need to optimize with left shifts. - // GCC will use imul instruction anyway. - // Tested with 'gcc -O3 -S' - //hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); - hval *= 16777619; - - // xor the bottom with the current octet - hval ^= (uint32_t) *s++; - } - - // fprintf(stderr, "HASH: %u = %s\n", hval, name); - return hval; -} - -uint32_t simple_uhash(const char *name) { - unsigned char *s = (unsigned char *) name; - uint32_t hval = 0x811c9dc5, c; - - // FNV-1a algorithm - while ((c = *s++)) { - if (unlikely(c >= 'A' && c <= 'Z')) c += 'a' - 'A'; - hval *= 16777619; - hval ^= c; - } - return hval; -} -*/ - -/* -// http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx -// one at a time hash -uint32_t simple_hash(const char *name) { - unsigned char *s = (unsigned char *)name; - uint32_t h = 0; - - while(*s) { - h += *s++; - h += (h << 10); - h ^= (h >> 6); - } - - h += (h << 3); - h ^= (h >> 11); - h += (h << 15); - - // fprintf(stderr, "HASH: %u = %s\n", h, name); - - return h; -} -*/ - -void strreverse(char *begin, char *end) { - while (end > begin) { - // clearer code. - char aux = *end; - *end-- = *begin; - *begin++ = aux; - } -} - -char *strsep_on_1char(char **ptr, char c) { - if(unlikely(!ptr || !*ptr)) - return NULL; - - // remember the position we started - char *s = *ptr; - - // skip separators in front - while(*s == c) s++; - char *ret = s; - - // find the next separator - while(*s++) { - if(unlikely(*s == c)) { - *s++ = '\0'; - *ptr = s; - return ret; - } - } - - *ptr = NULL; - return ret; -} - -char *mystrsep(char **ptr, char *s) { - char *p = ""; - while (p && !p[0] && *ptr) p = strsep(ptr, s); - return (p); -} - -char *trim(char *s) { - // skip leading spaces - while (*s && isspace(*s)) s++; - if (!*s) return NULL; - - // skip tailing spaces - // this way is way faster. Writes only one NUL char. - ssize_t l = strlen(s); - if (--l >= 0) { - char *p = s + l; - while (p > s && isspace(*p)) p--; - *++p = '\0'; - } - - if (!*s) return NULL; - - return s; -} - -inline char *trim_all(char *buffer) { - char *d = buffer, *s = buffer; - - // skip spaces - while(isspace(*s)) s++; - - while(*s) { - // copy the non-space part - while(*s && !isspace(*s)) *d++ = *s++; - - // add a space if we have to - if(*s && isspace(*s)) { - *d++ = ' '; - s++; - } - - // skip spaces - while(isspace(*s)) s++; - } - - *d = '\0'; - - if(d > buffer) { - d--; - if(isspace(*d)) *d = '\0'; - } - - if(!buffer[0]) return NULL; - return buffer; -} - static int memory_file_open(const char *filename, size_t size) { // info("memory_file_open('%s', %zu", filename, size); @@ -1883,17 +1714,20 @@ inline int config_isspace(char c) } // split a text into words, respecting quotes -inline size_t quoted_strings_splitter(char *str, char **words, size_t max_words, int (*custom_isspace)(char), char *recover_input, char **recover_location, int max_recover) +inline size_t quoted_strings_splitter(char *str, char **words, size_t max_words, int (*custom_isspace)(char)) { char *s = str, quote = 0; size_t i = 0; - int rec = 0; - char *recover = recover_input; // skip all white space while (unlikely(custom_isspace(*s))) s++; + if(unlikely(!*s)) { + words[i] = NULL; + return 0; + } + // check for quote if (unlikely(*s == '\'' || *s == '"')) { quote = *s; // remember the quote @@ -1905,19 +1739,15 @@ inline size_t quoted_strings_splitter(char *str, char **words, size_t max_words, // while we have something while (likely(*s)) { - // if it is escape + // if it is an escape if (unlikely(*s == '\\' && s[1])) { s += 2; continue; } - // if it is quote + // if it is a quote else if (unlikely(*s == quote)) { quote = 0; - if (recover && rec < max_recover) { - recover_location[rec++] = s; - *recover++ = *s; - } *s = ' '; continue; } @@ -1925,19 +1755,13 @@ inline size_t quoted_strings_splitter(char *str, char **words, size_t max_words, // if it is a space else if (unlikely(quote == 0 && custom_isspace(*s))) { // terminate the word - if (recover && rec < max_recover) { - if (!rec || recover_location[rec-1] != s) { - recover_location[rec++] = s; - *recover++ = *s; - } - } *s++ = '\0'; // skip all white space while (likely(custom_isspace(*s))) s++; - // check for quote + // check for a quote if (unlikely(*s == '\'' || *s == '"')) { quote = *s; // remember the quote s++; // skip the quote @@ -1965,9 +1789,9 @@ inline size_t quoted_strings_splitter(char *str, char **words, size_t max_words, return i; } -inline size_t pluginsd_split_words(char *str, char **words, size_t max_words, char *recover_input, char **recover_location, int max_recover) +inline size_t pluginsd_split_words(char *str, char **words, size_t max_words) { - return quoted_strings_splitter(str, words, max_words, pluginsd_space, recover_input, recover_location, max_recover); + return quoted_strings_splitter(str, words, max_words, pluginsd_space); } bool bitmap256_get_bit(BITMAP256 *ptr, uint8_t idx) { @@ -2072,3 +1896,141 @@ void for_each_open_fd(OPEN_FD_ACTION action, OPEN_FD_EXCLUDE excluded_fds){ closedir(dir); } } + +struct timing_steps { + const char *name; + usec_t time; + size_t count; +} timing_steps[TIMING_STEP_MAX + 1] = { + [TIMING_STEP_INTERNAL] = { .name = "internal", .time = 0, }, + + [TIMING_STEP_BEGIN2_PREPARE] = { .name = "BEGIN2 prepare", .time = 0, }, + [TIMING_STEP_BEGIN2_FIND_CHART] = { .name = "BEGIN2 find chart", .time = 0, }, + [TIMING_STEP_BEGIN2_PARSE] = { .name = "BEGIN2 parse", .time = 0, }, + [TIMING_STEP_BEGIN2_ML] = { .name = "BEGIN2 ml", .time = 0, }, + [TIMING_STEP_BEGIN2_PROPAGATE] = { .name = "BEGIN2 propagate", .time = 0, }, + [TIMING_STEP_BEGIN2_STORE] = { .name = "BEGIN2 store", .time = 0, }, + + [TIMING_STEP_SET2_PREPARE] = { .name = "SET2 prepare", .time = 0, }, + [TIMING_STEP_SET2_LOOKUP_DIMENSION] = { .name = "SET2 find dimension", .time = 0, }, + [TIMING_STEP_SET2_PARSE] = { .name = "SET2 parse", .time = 0, }, + [TIMING_STEP_SET2_ML] = { .name = "SET2 ml", .time = 0, }, + [TIMING_STEP_SET2_PROPAGATE] = { .name = "SET2 propagate", .time = 0, }, + [TIMING_STEP_RRDSET_STORE_METRIC] = { .name = "SET2 rrdset store", .time = 0, }, + [TIMING_STEP_DBENGINE_FIRST_CHECK] = { .name = "db 1st check", .time = 0, }, + [TIMING_STEP_DBENGINE_CHECK_DATA] = { .name = "db check data", .time = 0, }, + [TIMING_STEP_DBENGINE_PACK] = { .name = "db pack", .time = 0, }, + [TIMING_STEP_DBENGINE_PAGE_FIN] = { .name = "db page fin", .time = 0, }, + [TIMING_STEP_DBENGINE_MRG_UPDATE] = { .name = "db mrg update", .time = 0, }, + [TIMING_STEP_DBENGINE_PAGE_ALLOC] = { .name = "db page alloc", .time = 0, }, + [TIMING_STEP_DBENGINE_CREATE_NEW_PAGE] = { .name = "db new page", .time = 0, }, + [TIMING_STEP_DBENGINE_FLUSH_PAGE] = { .name = "db page flush", .time = 0, }, + [TIMING_STEP_SET2_STORE] = { .name = "SET2 store", .time = 0, }, + + [TIMING_STEP_END2_PREPARE] = { .name = "END2 prepare", .time = 0, }, + [TIMING_STEP_END2_PUSH_V1] = { .name = "END2 push v1", .time = 0, }, + [TIMING_STEP_END2_ML] = { .name = "END2 ml", .time = 0, }, + [TIMING_STEP_END2_RRDSET] = { .name = "END2 rrdset", .time = 0, }, + [TIMING_STEP_END2_PROPAGATE] = { .name = "END2 propagate", .time = 0, }, + [TIMING_STEP_END2_STORE] = { .name = "END2 store", .time = 0, }, + + // terminator + [TIMING_STEP_MAX] = { .name = NULL, .time = 0, }, +}; + +void timing_action(TIMING_ACTION action, TIMING_STEP step) { + static __thread usec_t last_action_time = 0; + static struct timing_steps timings2[TIMING_STEP_MAX + 1] = {}; + + switch(action) { + case TIMING_ACTION_INIT: + last_action_time = now_monotonic_usec(); + break; + + case TIMING_ACTION_STEP: { + if(!last_action_time) + return; + + usec_t now = now_monotonic_usec(); + __atomic_add_fetch(&timing_steps[step].time, now - last_action_time, __ATOMIC_RELAXED); + __atomic_add_fetch(&timing_steps[step].count, 1, __ATOMIC_RELAXED); + last_action_time = now; + break; + } + + case TIMING_ACTION_FINISH: { + if(!last_action_time) + return; + + usec_t expected = __atomic_load_n(&timing_steps[TIMING_STEP_INTERNAL].time, __ATOMIC_RELAXED); + if(last_action_time - expected < 10 * USEC_PER_SEC) { + last_action_time = 0; + return; + } + + if(!__atomic_compare_exchange_n(&timing_steps[TIMING_STEP_INTERNAL].time, &expected, last_action_time, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) { + last_action_time = 0; + return; + } + + struct timing_steps timings3[TIMING_STEP_MAX + 1]; + memcpy(timings3, timing_steps, sizeof(timings3)); + + size_t total_reqs = 0; + usec_t total_usec = 0; + for(size_t t = 1; t < TIMING_STEP_MAX ; t++) { + total_usec += timings3[t].time - timings2[t].time; + total_reqs += timings3[t].count - timings2[t].count; + } + + BUFFER *wb = buffer_create(1024, NULL); + + for(size_t t = 1; t < TIMING_STEP_MAX ; t++) { + size_t requests = timings3[t].count - timings2[t].count; + if(!requests) continue; + + buffer_sprintf(wb, "TIMINGS REPORT: [%3zu. %-20s]: # %10zu, t %11.2f ms (%6.2f %%), avg %6.2f usec/run\n", + t, + timing_steps[t].name ? timing_steps[t].name : "x", + requests, + (double) (timings3[t].time - timings2[t].time) / (double)USEC_PER_MS, + (double) (timings3[t].time - timings2[t].time) * 100.0 / (double) total_usec, + (double) (timings3[t].time - timings2[t].time) / (double)requests + ); + } + + info("TIMINGS REPORT:\n%sTIMINGS REPORT: total # %10zu, t %11.2f ms", + buffer_tostring(wb), total_reqs, (double)total_usec / USEC_PER_MS); + + memcpy(timings2, timings3, sizeof(timings2)); + + last_action_time = 0; + buffer_free(wb); + } + } +} + +int hash256_string(const unsigned char *string, size_t size, char *hash) { + EVP_MD_CTX *ctx; + ctx = EVP_MD_CTX_create(); + + if (!ctx) + return 0; + + if (!EVP_DigestInit(ctx, EVP_sha256())) { + EVP_MD_CTX_destroy(ctx); + return 0; + } + + if (!EVP_DigestUpdate(ctx, string, size)) { + EVP_MD_CTX_destroy(ctx); + return 0; + } + + if (!EVP_DigestFinal(ctx, (unsigned char *)hash, NULL)) { + EVP_MD_CTX_destroy(ctx); + return 0; + } + + return 1; +} diff --git a/libnetdata/libnetdata.h b/libnetdata/libnetdata.h index c504bd4b..c2449493 100644 --- a/libnetdata/libnetdata.h +++ b/libnetdata/libnetdata.h @@ -32,6 +32,9 @@ extern "C" { #define OS_FREEBSD 2 #define OS_MACOS 3 +#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) // ---------------------------------------------------------------------------- // system include files for all netdata C programs @@ -363,15 +366,131 @@ size_t judy_aral_structures(void); // --------------------------------------------------------------------------------------------- +#include "storage_number/storage_number.h" + +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 + + uint32_t count; // the number of original points aggregated + uint32_t 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_s = 0; \ + (x).end_time_s = 0; \ + } while(0) + +#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_s = start_s; \ + (x).end_time_s = end_s; \ + } while(0) + +#define STORAGE_POINT_UNSET (STORAGE_POINT){ .min = NAN, .max = NAN, .sum = NAN, .count = 0, .anomaly_count = 0, .flags = SN_FLAG_NONE, .start_time_s = 0, .end_time_s = 0 } + +#define storage_point_is_unset(x) (!(x).count) +#define storage_point_is_gap(x) (!netdata_double_isnumber((x).sum)) +#define storage_point_is_zero(x) (!(x).count || (netdata_double_is_zero((x).min) && netdata_double_is_zero((x).max) && netdata_double_is_zero((x).sum) && (x).anomaly_count == 0)) + +#define storage_point_merge_to(dst, src) do { \ + if(storage_point_is_unset(dst)) \ + (dst) = (src); \ + \ + else if(!storage_point_is_unset(src) && \ + !storage_point_is_gap(src)) { \ + \ + if((src).start_time_s < (dst).start_time_s) \ + (dst).start_time_s = (src).start_time_s;\ + \ + if((src).end_time_s > (dst).end_time_s) \ + (dst).end_time_s = (src).end_time_s; \ + \ + if((src).min < (dst).min) \ + (dst).min = (src).min; \ + \ + if((src).max > (dst).max) \ + (dst).max = (src).max; \ + \ + (dst).sum += (src).sum; \ + \ + (dst).count += (src).count; \ + (dst).anomaly_count += (src).anomaly_count; \ + \ + (dst).flags |= (src).flags & SN_FLAG_RESET; \ + } \ +} while(0) + +#define storage_point_add_to(dst, src) do { \ + if(storage_point_is_unset(dst)) \ + (dst) = (src); \ + \ + else if(!storage_point_is_unset(src) && \ + !storage_point_is_gap(src)) { \ + \ + if((src).start_time_s < (dst).start_time_s) \ + (dst).start_time_s = (src).start_time_s;\ + \ + if((src).end_time_s > (dst).end_time_s) \ + (dst).end_time_s = (src).end_time_s; \ + \ + (dst).min += (src).min; \ + (dst).max += (src).max; \ + (dst).sum += (src).sum; \ + \ + (dst).count += (src).count; \ + (dst).anomaly_count += (src).anomaly_count; \ + \ + (dst).flags |= (src).flags & SN_FLAG_RESET; \ + } \ +} while(0) + +#define storage_point_make_positive(sp) do { \ + if(!storage_point_is_unset(sp) && \ + !storage_point_is_gap(sp)) { \ + \ + if(unlikely(signbit((sp).sum))) \ + (sp).sum = -(sp).sum; \ + \ + if(unlikely(signbit((sp).min))) \ + (sp).min = -(sp).min; \ + \ + if(unlikely(signbit((sp).max))) \ + (sp).max = -(sp).max; \ + \ + if(unlikely((sp).min > (sp).max)) { \ + NETDATA_DOUBLE t = (sp).min; \ + (sp).min = (sp).max; \ + (sp).max = t; \ + } \ + } \ +} while(0) + +#define storage_point_anomaly_rate(sp) \ + (NETDATA_DOUBLE)(storage_point_is_unset(sp) ? 0.0 : (NETDATA_DOUBLE)((sp).anomaly_count) * 100.0 / (NETDATA_DOUBLE)((sp).count)) + +#define storage_point_average_value(sp) \ + ((sp).count ? (sp).sum / (NETDATA_DOUBLE)((sp).count) : 0.0) + +// --------------------------------------------------------------------------------------------- void netdata_fix_chart_id(char *s); void netdata_fix_chart_name(char *s); -void strreverse(char* begin, char* end); -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); @@ -485,8 +604,8 @@ void bitmap256_set_bit(BITMAP256 *ptr, uint8_t idx, bool value); int config_isspace(char c); int pluginsd_space(char c); -size_t quoted_strings_splitter(char *str, char **words, size_t max_words, int (*custom_isspace)(char), char *recover_input, char **recover_location, int max_recover); -size_t pluginsd_split_words(char *str, char **words, size_t max_words, char *recover_string, char **recover_location, int max_recover); +size_t quoted_strings_splitter(char *str, char **words, size_t max_words, int (*custom_isspace)(char)); +size_t pluginsd_split_words(char *str, char **words, size_t max_words); static inline char *get_word(char **words, size_t num_words, size_t index) { if (index >= num_words) @@ -514,7 +633,6 @@ 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" #include "buffer/buffer.h" #include "locks/locks.h" @@ -547,10 +665,11 @@ extern char *netdata_configured_host_prefix; #include "libnetdata/aral/aral.h" #include "onewayalloc/onewayalloc.h" #include "worker_utilization/worker_utilization.h" +#include "parser/parser.h" +#include "yaml.h" -// BEWARE: Outside of the C code this also exists in alarm-notify.sh -#define DEFAULT_CLOUD_BASE_URL "https://api.netdata.cloud" -#define DEFAULT_CLOUD_UI_URL "https://app.netdata.cloud" +// BEWARE: this exists in alarm-notify.sh +#define DEFAULT_CLOUD_BASE_URL "https://app.netdata.cloud" #define RRD_STORAGE_TIERS 5 @@ -609,58 +728,61 @@ static inline PPvoid_t JudyLLastThenPrev(Pcvoid_t PArray, Word_t * PIndex, bool 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; -} +typedef enum { + TIMING_STEP_INTERNAL = 0, + + TIMING_STEP_BEGIN2_PREPARE, + TIMING_STEP_BEGIN2_FIND_CHART, + TIMING_STEP_BEGIN2_PARSE, + TIMING_STEP_BEGIN2_ML, + TIMING_STEP_BEGIN2_PROPAGATE, + TIMING_STEP_BEGIN2_STORE, + + TIMING_STEP_SET2_PREPARE, + TIMING_STEP_SET2_LOOKUP_DIMENSION, + TIMING_STEP_SET2_PARSE, + TIMING_STEP_SET2_ML, + TIMING_STEP_SET2_PROPAGATE, + TIMING_STEP_RRDSET_STORE_METRIC, + TIMING_STEP_DBENGINE_FIRST_CHECK, + TIMING_STEP_DBENGINE_CHECK_DATA, + TIMING_STEP_DBENGINE_PACK, + TIMING_STEP_DBENGINE_PAGE_FIN, + TIMING_STEP_DBENGINE_MRG_UPDATE, + TIMING_STEP_DBENGINE_PAGE_ALLOC, + TIMING_STEP_DBENGINE_CREATE_NEW_PAGE, + TIMING_STEP_DBENGINE_FLUSH_PAGE, + TIMING_STEP_SET2_STORE, + + TIMING_STEP_END2_PREPARE, + TIMING_STEP_END2_PUSH_V1, + TIMING_STEP_END2_ML, + TIMING_STEP_END2_RRDSET, + TIMING_STEP_END2_PROPAGATE, + TIMING_STEP_END2_STORE, + + // terminator + TIMING_STEP_MAX, +} TIMING_STEP; -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; - } -} +typedef enum { + TIMING_ACTION_INIT, + TIMING_ACTION_STEP, + TIMING_ACTION_FINISH, +} TIMING_ACTION; + +#ifdef NETDATA_TIMING_REPORT +#define timing_init() timing_action(TIMING_ACTION_INIT, TIMING_STEP_INTERNAL) +#define timing_step(step) timing_action(TIMING_ACTION_STEP, step) +#define timing_report() timing_action(TIMING_ACTION_FINISH, TIMING_STEP_INTERNAL) +#else +#define timing_init() debug_dummy() +#define timing_step(step) debug_dummy() +#define timing_report() debug_dummy() +#endif +void timing_action(TIMING_ACTION action, TIMING_STEP step); +int hash256_string(const unsigned char *string, size_t size, char *hash); # ifdef __cplusplus } # endif diff --git a/libnetdata/locks/README.md b/libnetdata/locks/README.md index 8810e3d1..5560832b 100644 --- a/libnetdata/locks/README.md +++ b/libnetdata/locks/README.md @@ -4,9 +4,11 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/locks sidebar_label: "Locks" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> +# Locks + ## How to trace netdata locks To enable tracing rwlocks in netdata, compile netdata by setting `CFLAGS="-DNETDATA_TRACE_RWLOCKS=1"`, like this: diff --git a/libnetdata/log/README.md b/libnetdata/log/README.md index 5f9e5bc7..3684abd6 100644 --- a/libnetdata/log/README.md +++ b/libnetdata/log/README.md @@ -1,10 +1,10 @@ # Log diff --git a/libnetdata/log/log.c b/libnetdata/log/log.c index 1dcdba9c..6832d628 100644 --- a/libnetdata/log/log.c +++ b/libnetdata/log/log.c @@ -582,9 +582,11 @@ void reopen_all_log_files() { 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(); + // Netdata starts using stderr and if it has success to open file it redirects + FILE *fp = open_log_file(stdcollector_fd, stderror, stderr_filename, + &error_log_syslog, 1, &stdcollector_fd); + if (fp) + stderror = fp; } #ifdef ENABLE_ACLK @@ -606,9 +608,10 @@ void open_all_log_files() { open_log_file(STDOUT_FILENO, stdout, stdout_filename, &output_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(); + // Netdata starts using stderr and if it has success to open file it redirects + FILE *fp = open_log_file(stdcollector_fd, NULL, stderr_filename, &error_log_syslog, 1, &stdcollector_fd); + if (fp) + stderror = fp; #ifdef ENABLE_ACLK if(aclklog_enabled) @@ -631,7 +634,7 @@ int error_log_limit(int reset) { static time_t start = 0; static unsigned long counter = 0, prevented = 0; - FILE *fp = (!stderror) ? stderr : stderror; + FILE *fp = 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); @@ -778,7 +781,7 @@ void debug_int( const char *file, const char *function, const unsigned long line 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; + FILE *fp = (is_collector) ? stderr : stderror; log_lock(); @@ -838,7 +841,7 @@ 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; + FILE *fp = stderror; if(erl->sleep_ut) sleep_usec(erl->sleep_ut); @@ -907,7 +910,7 @@ void error_limit_int(ERROR_LIMIT *erl, const char *prefix, const char *file __ma 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; + FILE *fp = (is_collector) ? stderr : stderror; va_list args; @@ -972,7 +975,7 @@ static void print_call_stack(void) { #endif void fatal_int( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) { - FILE *fp = (!stderror) ? stderr : stderror; + FILE *fp = stderror; // save a copy of errno - just in case this function generates a new error int __errno = errno; diff --git a/libnetdata/onewayalloc/README.md b/libnetdata/onewayalloc/README.md index 3fa0d9fd..38d92cea 100644 --- a/libnetdata/onewayalloc/README.md +++ b/libnetdata/onewayalloc/README.md @@ -1,10 +1,10 @@ # One Way Allocator diff --git a/libnetdata/onewayalloc/onewayalloc.c b/libnetdata/onewayalloc/onewayalloc.c index 2f007b18..489ce73d 100644 --- a/libnetdata/onewayalloc/onewayalloc.c +++ b/libnetdata/onewayalloc/onewayalloc.c @@ -97,6 +97,10 @@ ONEWAYALLOC *onewayalloc_create(size_t size_hint) { } void *onewayalloc_mallocz(ONEWAYALLOC *owa, size_t size) { +#ifdef FSANITIZE_ADDRESS + return mallocz(size); +#endif + OWA_PAGE *head = (OWA_PAGE *)owa; OWA_PAGE *page = head->last; @@ -142,6 +146,11 @@ void *onewayalloc_memdupz(ONEWAYALLOC *owa, const void *src, size_t size) { } void onewayalloc_freez(ONEWAYALLOC *owa __maybe_unused, const void *ptr __maybe_unused) { +#ifdef FSANITIZE_ADDRESS + freez((void *)ptr); + return; +#endif + #ifdef NETDATA_INTERNAL_CHECKS // allow the caller to call us for a mallocz() allocation // so try to find it in our memory and if it is not there diff --git a/libnetdata/parser/Makefile.am b/libnetdata/parser/Makefile.am new file mode 100644 index 00000000..02fe3a31 --- /dev/null +++ b/libnetdata/parser/Makefile.am @@ -0,0 +1,9 @@ +# 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/parser/README.md b/libnetdata/parser/README.md new file mode 100644 index 00000000..136c23c6 --- /dev/null +++ b/libnetdata/parser/README.md @@ -0,0 +1,28 @@ + + +# Parser + +## Introduction + +Generic parser that is used to register keywords and a corresponding function that will be executed when that +keyword is encountered in the command stream (either from plugins or via streaming) + +To use a parser do the following: + +1. Define a structure that will be used to share user state across calls (user defined `void *user`) +2. Initialize the parser using `parser_init` +3. Register keywords with their associated callback function using `parser_add_keyword` +4. Start a loop for as long there is input (or parser_action returns error) + 1. Fetch the next line using `parser_next` (if needed) + 2. Process the line using `parser_action` +5. Release the parser using `parser_destroy` +6. Release the user structure + +See examples in receiver.c / pluginsd_parser.c diff --git a/libnetdata/parser/parser.c b/libnetdata/parser/parser.c new file mode 100644 index 00000000..c3eebcd1 --- /dev/null +++ b/libnetdata/parser/parser.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "parser.h" +#include "collectors/plugins.d/pluginsd_parser.h" + +static inline int find_first_keyword(const char *src, char *dst, int dst_size, int (*custom_isspace)(char)) { + const char *s = src, *keyword_start; + + while (unlikely(custom_isspace(*s))) s++; + keyword_start = s; + + while (likely(*s && !custom_isspace(*s)) && dst_size > 1) { + *dst++ = *s++; + dst_size--; + } + *dst = '\0'; + return dst_size == 0 ? 0 : (int) (s - keyword_start); +} + +/* + * Initialize a parser + * user : as defined by the user, will be shared across calls + * input : main input stream (auto detect stream -- file, socket, pipe) + * buffer : This is the buffer to be used (if null a buffer of size will be allocated) + * size : buffer size either passed or will be allocated + * If the buffer is auto allocated, it will auto freed when the parser is destroyed + * + * + */ + +PARSER *parser_init(void *user, FILE *fp_input, FILE *fp_output, int fd, + PARSER_INPUT_TYPE flags, void *ssl __maybe_unused) +{ + PARSER *parser; + + parser = callocz(1, sizeof(*parser)); + parser->user = user; + parser->fd = fd; + parser->fp_input = fp_input; + parser->fp_output = fp_output; +#ifdef ENABLE_HTTPS + parser->ssl_output = ssl; +#endif + parser->flags = flags; + parser->worker_job_next_id = WORKER_PARSER_FIRST_JOB; + + return parser; +} + + +static inline PARSER_KEYWORD *parser_find_keyword(PARSER *parser, const char *command) { + uint32_t hash = parser_hash_function(command); + uint32_t slot = hash % PARSER_KEYWORDS_HASHTABLE_SIZE; + PARSER_KEYWORD *t = parser->keywords.hashtable[slot]; + + if(likely(t && strcmp(t->keyword, command) == 0)) + return t; + + return NULL; +} + +/* + * Add a keyword and the corresponding function that will be called + * Multiple functions may be added + * Input : keyword + * : callback function + * : flags + * Output: > 0 registered function number + * : 0 Error + */ + +void parser_add_keyword(PARSER *parser, char *keyword, keyword_function func) { + if(unlikely(!parser || !keyword || !*keyword || !func)) + fatal("PARSER: invalid parameters"); + + PARSER_KEYWORD *t = callocz(1, sizeof(*t)); + t->worker_job_id = parser->worker_job_next_id++; + t->keyword = strdupz(keyword); + t->func = func; + + uint32_t hash = parser_hash_function(keyword); + uint32_t slot = hash % PARSER_KEYWORDS_HASHTABLE_SIZE; + + if(unlikely(parser->keywords.hashtable[slot])) + fatal("PARSER: hashtable collision between keyword '%s' and '%s' on slot %u. " + "Change the hashtable size and / or the hashing function. " + "Run the unit test to find the optimal values.", + parser->keywords.hashtable[slot]->keyword, + t->keyword, + slot + ); + + parser->keywords.hashtable[slot] = t; + + worker_register_job_name(t->worker_job_id, t->keyword); +} + +/* + * Cleanup a previously allocated parser + */ + +void parser_destroy(PARSER *parser) +{ + if (unlikely(!parser)) + return; + + dictionary_destroy(parser->inflight.functions); + + // Remove keywords + for(size_t i = 0 ; i < PARSER_KEYWORDS_HASHTABLE_SIZE; i++) { + PARSER_KEYWORD *t = parser->keywords.hashtable[i]; + if (t) { + freez(t->keyword); + freez(t); + } + } + + freez(parser); +} + + +/* + * Fetch the next line to process + * + */ + +int parser_next(PARSER *parser, char *buffer, size_t buffer_size) +{ + char *tmp = fgets(buffer, (int)buffer_size, (FILE *)parser->fp_input); + + if (unlikely(!tmp)) { + if (feof((FILE *)parser->fp_input)) + error("PARSER: read failed: end of file"); + + else if (ferror((FILE *)parser->fp_input)) + error("PARSER: read failed: input error"); + + else + error("PARSER: read failed: unknown error"); + + return 1; + } + + return 0; +} + + +/* +* Takes an initialized parser object that has an unprocessed entry (by calling parser_next) +* and if it contains a valid keyword, it will execute all the callbacks +* +*/ + +inline int parser_action(PARSER *parser, char *input) +{ + parser->line++; + + if(unlikely(parser->flags & PARSER_DEFER_UNTIL_KEYWORD)) { + char command[PLUGINSD_LINE_MAX + 1]; + bool has_keyword = find_first_keyword(input, command, PLUGINSD_LINE_MAX, pluginsd_space); + + if(!has_keyword || strcmp(command, parser->defer.end_keyword) != 0) { + if(parser->defer.response) { + buffer_strcat(parser->defer.response, input); + if(buffer_strlen(parser->defer.response) > 10 * 1024 * 1024) { + // more than 10MB of data + // a bad plugin that did not send the end_keyword + internal_error(true, "PLUGINSD: deferred response is too big (%zu bytes). Stopping this plugin.", buffer_strlen(parser->defer.response)); + return 1; + } + } + return 0; + } + else { + // call the action + parser->defer.action(parser, parser->defer.action_data); + + // empty everything + parser->defer.action = NULL; + parser->defer.action_data = NULL; + parser->defer.end_keyword = NULL; + parser->defer.response = NULL; + parser->flags &= ~PARSER_DEFER_UNTIL_KEYWORD; + } + return 0; + } + + char *words[PLUGINSD_MAX_WORDS]; + size_t num_words = pluginsd_split_words(input, words, PLUGINSD_MAX_WORDS); + const char *command = get_word(words, num_words, 0); + + if(unlikely(!command)) + return 0; + + PARSER_RC rc; + PARSER_KEYWORD *t = parser_find_keyword(parser, command); + if(likely(t)) { + worker_is_busy(t->worker_job_id); + rc = (*t->func)(words, num_words, parser->user); + worker_is_idle(); + } + else + rc = PARSER_RC_ERROR; + +#ifdef NETDATA_INTERNAL_CHECKS + if(rc == PARSER_RC_ERROR) { + BUFFER *wb = buffer_create(PLUGINSD_LINE_MAX, NULL); + for(size_t i = 0; i < num_words ;i++) { + if(i) buffer_fast_strcat(wb, " ", 1); + + buffer_fast_strcat(wb, "\"", 1); + const char *s = get_word(words, num_words, i); + buffer_strcat(wb, s?s:""); + buffer_fast_strcat(wb, "\"", 1); + } + + internal_error(true, "PLUGINSD: parser_action('%s') failed on line %zu: { %s } (quotes added to show parsing)", + command, parser->line, buffer_tostring(wb)); + + buffer_free(wb); + } +#endif + + return (rc == PARSER_RC_ERROR || rc == PARSER_RC_STOP); +} diff --git a/libnetdata/parser/parser.h b/libnetdata/parser/parser.h new file mode 100644 index 00000000..9e0d3480 --- /dev/null +++ b/libnetdata/parser/parser.h @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_INCREMENTAL_PARSER_H +#define NETDATA_INCREMENTAL_PARSER_H 1 + +#include "../libnetdata.h" + +#define WORKER_PARSER_FIRST_JOB 3 + +// this has to be in-sync with the same at receiver.c +#define WORKER_RECEIVER_JOB_REPLICATION_COMPLETION (WORKER_PARSER_FIRST_JOB - 3) + +#define PARSER_KEYWORDS_HASHTABLE_SIZE 73 // unittest finds this magic number +//#define parser_hash_function(s) djb2_hash32(s) +//#define parser_hash_function(s) fnv1_hash32(s) +//#define parser_hash_function(s) fnv1a_hash32(s) +//#define parser_hash_function(s) larson_hash32(s) +#define parser_hash_function(s) pluginsd_parser_hash32(s) + +// PARSER return codes +typedef enum __attribute__ ((__packed__)) parser_rc { + PARSER_RC_OK, // Callback was successful, go on + PARSER_RC_STOP, // Callback says STOP + PARSER_RC_ERROR // Callback failed (abort rest of callbacks) +} PARSER_RC; + +typedef enum __attribute__ ((__packed__)) parser_input_type { + PARSER_INPUT_SPLIT = (1 << 1), + PARSER_DEFER_UNTIL_KEYWORD = (1 << 2), +} PARSER_INPUT_TYPE; + +typedef PARSER_RC (*keyword_function)(char **words, size_t num_words, void *user_data); + +typedef struct parser_keyword { + size_t worker_job_id; + char *keyword; + keyword_function func; +} PARSER_KEYWORD; + +typedef struct parser { + size_t worker_job_next_id; + uint8_t version; // Parser version + int fd; // Socket + FILE *fp_input; // Input source e.g. stream + FILE *fp_output; // Stream to send commands to plugin +#ifdef ENABLE_HTTPS + struct netdata_ssl *ssl_output; +#endif + void *user; // User defined structure to hold extra state between calls + uint32_t flags; + size_t line; + + struct { + PARSER_KEYWORD *hashtable[PARSER_KEYWORDS_HASHTABLE_SIZE]; + } keywords; + + struct { + const char *end_keyword; + BUFFER *response; + void (*action)(struct parser *parser, void *action_data); + void *action_data; + } defer; + + struct { + DICTIONARY *functions; + usec_t smaller_timeout; + } inflight; +} PARSER; + +PARSER *parser_init(void *user, FILE *fp_input, FILE *fp_output, int fd, PARSER_INPUT_TYPE flags, void *ssl); +void parser_add_keyword(PARSER *working_parser, char *keyword, keyword_function func); +int parser_next(PARSER *working_parser, char *buffer, size_t buffer_size); +int parser_action(PARSER *working_parser, char *input); +void parser_destroy(PARSER *working_parser); + +PARSER_RC pluginsd_set(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_begin(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_end(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_chart(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_chart_definition_end(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_dimension(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_variable(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_flush(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_disable(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_label(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_overwrite(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_clabel_commit(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_clabel(char **words, size_t num_words, void *user); + +PARSER_RC pluginsd_replay_begin(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_replay_rrddim_collection_state(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_replay_rrdset_collection_state(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_replay_set(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_replay_end(char **words, size_t num_words, void *user); + +PARSER_RC pluginsd_begin_v2(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_set_v2(char **words, size_t num_words, void *user); +PARSER_RC pluginsd_end_v2(char **words, size_t num_words, void *user); +void pluginsd_cleanup_v2(void *user); + +#endif diff --git a/libnetdata/popen/README.md b/libnetdata/popen/README.md index 804690d1..7bd2bd3d 100644 --- a/libnetdata/popen/README.md +++ b/libnetdata/popen/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/popen sidebar_label: "popen" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # popen diff --git a/libnetdata/procfile/README.md b/libnetdata/procfile/README.md index 8610e77e..37138bd1 100644 --- a/libnetdata/procfile/README.md +++ b/libnetdata/procfile/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/procf sidebar_label: "Procfile" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # PROCFILE diff --git a/libnetdata/simple_pattern/README.md b/libnetdata/simple_pattern/README.md index a0a7cf68..e00006d3 100644 --- a/libnetdata/simple_pattern/README.md +++ b/libnetdata/simple_pattern/README.md @@ -5,7 +5,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/simpl sidebar_label: "Simple patterns" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # Simple patterns diff --git a/libnetdata/simple_pattern/simple_pattern.c b/libnetdata/simple_pattern/simple_pattern.c index 81c2ed0b..a26ae4f9 100644 --- a/libnetdata/simple_pattern/simple_pattern.c +++ b/libnetdata/simple_pattern/simple_pattern.c @@ -4,17 +4,20 @@ struct simple_pattern { const char *match; - size_t len; + uint32_t len; SIMPLE_PREFIX_MODE mode; - char negative; + bool negative; + bool case_sensitive; struct simple_pattern *child; - struct simple_pattern *next; }; -static inline struct simple_pattern *parse_pattern(char *str, SIMPLE_PREFIX_MODE default_mode) { +static struct simple_pattern *parse_pattern(char *str, SIMPLE_PREFIX_MODE default_mode, size_t count) { + if(unlikely(count >= 1000)) + return NULL; + // fprintf(stderr, "PARSING PATTERN: '%s'\n", str); SIMPLE_PREFIX_MODE mode; @@ -31,7 +34,7 @@ static inline struct simple_pattern *parse_pattern(char *str, SIMPLE_PREFIX_MODE // do we have an asterisk in the middle? if(*c == '*' && c[1] != '\0') { // yes, we have - child = parse_pattern(c, default_mode); + child = parse_pattern(c, default_mode, count + 1); c[1] = '\0'; } @@ -70,12 +73,12 @@ static inline struct simple_pattern *parse_pattern(char *str, SIMPLE_PREFIX_MODE return m; } -SIMPLE_PATTERN *simple_pattern_create(const char *list, const char *separators, SIMPLE_PREFIX_MODE default_mode) { +SIMPLE_PATTERN *simple_pattern_create(const char *list, const char *separators, SIMPLE_PREFIX_MODE default_mode, bool case_sensitive) { struct simple_pattern *root = NULL, *last = NULL; if(unlikely(!list || !*list)) return root; - int isseparator[256] = { + char isseparator[256] = { [' '] = 1 // space , ['\t'] = 1 // tab , ['\r'] = 1 // carriage return @@ -96,14 +99,14 @@ SIMPLE_PATTERN *simple_pattern_create(const char *list, const char *separators, buf[0] = '\0'; char *c = buf; - char negative = 0; + bool negative = false; // skip all spaces while(isseparator[(unsigned char)*s]) s++; if(*s == '!') { - negative = 1; + negative = true; s++; } @@ -137,8 +140,9 @@ SIMPLE_PATTERN *simple_pattern_create(const char *list, const char *separators, continue; // fprintf(stderr, "FOUND PATTERN: '%s'\n", buf); - struct simple_pattern *m = parse_pattern(buf, default_mode); + struct simple_pattern *m = parse_pattern(buf, default_mode, 0); m->negative = negative; + m->case_sensitive = case_sensitive; // link it at the end if(unlikely(!root)) @@ -174,76 +178,128 @@ static inline char *add_wildcarded(const char *matched, size_t matched_size, cha return wildcarded; } -static inline int match_pattern(struct simple_pattern *m, const char *str, size_t len, char *wildcarded, size_t *wildcarded_size) { +static inline int sp_strcmp(const char *s1, const char *s2, bool case_sensitive) { + if(case_sensitive) + return strcmp(s1, s2); + + return strcasecmp(s1, s2); +} + +static inline int sp_strncmp(const char *s1, const char *s2, size_t n, bool case_sensitive) { + if(case_sensitive) + return strncmp(s1, s2, n); + + return strncasecmp(s1, s2, n); +} + +static inline char *sp_strstr(const char *haystack, const char *needle, bool case_sensitive) { + if(case_sensitive) + return strstr(haystack, needle); + + return strcasestr(haystack, needle); +} + +static inline bool match_pattern(struct simple_pattern *m, const char *str, size_t len, char *wildcarded, size_t *wildcarded_size) { char *s; - if(m->len <= len) { + bool loop = true; + while(loop && m->len <= len) { + loop = false; + switch(m->mode) { + default: + case SIMPLE_PATTERN_EXACT: + if(unlikely(sp_strcmp(str, m->match, m->case_sensitive) == 0)) { + if(!m->child) return true; + return false; + } + break; + case SIMPLE_PATTERN_SUBSTRING: - if(!m->len) return 1; - if((s = strstr(str, m->match))) { + if(!m->len) return true; + if((s = sp_strstr(str, m->match, m->case_sensitive))) { wildcarded = add_wildcarded(str, s - str, wildcarded, wildcarded_size); if(!m->child) { - wildcarded = add_wildcarded(&s[m->len], len - (&s[m->len] - str), wildcarded, wildcarded_size); - return 1; + add_wildcarded(&s[m->len], len - (&s[m->len] - str), wildcarded, wildcarded_size); + return true; + } + + // instead of recursion + { + len = len - (s - str) - m->len; + str = &s[m->len]; + m = m->child; + loop = true; + // return match_pattern(m->child, &s[m->len], len - (s - str) - m->len, wildcarded, wildcarded_size); } - return match_pattern(m->child, &s[m->len], len - (s - str) - m->len, wildcarded, wildcarded_size); } break; case SIMPLE_PATTERN_PREFIX: - if(unlikely(strncmp(str, m->match, m->len) == 0)) { + if(unlikely(sp_strncmp(str, m->match, m->len, m->case_sensitive) == 0)) { if(!m->child) { - wildcarded = add_wildcarded(&str[m->len], len - m->len, wildcarded, wildcarded_size); - return 1; + add_wildcarded(&str[m->len], len - m->len, wildcarded, wildcarded_size); + return true; + } + // instead of recursion + { + len = len - m->len; + str = &str[m->len]; + m = m->child; + loop = true; + // return match_pattern(m->child, &str[m->len], len - m->len, wildcarded, wildcarded_size); } - return match_pattern(m->child, &str[m->len], len - m->len, wildcarded, wildcarded_size); } break; case SIMPLE_PATTERN_SUFFIX: - if(unlikely(strcmp(&str[len - m->len], m->match) == 0)) { - wildcarded = add_wildcarded(str, len - m->len, wildcarded, wildcarded_size); - if(!m->child) return 1; - return 0; - } - break; - - case SIMPLE_PATTERN_EXACT: - default: - if(unlikely(strcmp(str, m->match) == 0)) { - if(!m->child) return 1; - return 0; + if(unlikely(sp_strcmp(&str[len - m->len], m->match, m->case_sensitive) == 0)) { + add_wildcarded(str, len - m->len, wildcarded, wildcarded_size); + if(!m->child) return true; + return false; } break; } } - return 0; + return false; } -int simple_pattern_matches_extract(SIMPLE_PATTERN *list, const char *str, char *wildcarded, size_t wildcarded_size) { +static inline SIMPLE_PATTERN_RESULT simple_pattern_matches_extract_with_length(SIMPLE_PATTERN *list, const char *str, size_t len, char *wildcarded, size_t wildcarded_size) { struct simple_pattern *m, *root = (struct simple_pattern *)list; - if(unlikely(!root || !str || !*str)) return 0; - - size_t len = strlen(str); for(m = root; m ; m = m->next) { char *ws = wildcarded; size_t wss = wildcarded_size; if(unlikely(ws)) *ws = '\0'; if (match_pattern(m, str, len, ws, &wss)) { - - //if(ws && wss) - // fprintf(stderr, "FINAL WILDCARDED '%s' of length %zu\n", ws, strlen(ws)); - - if (m->negative) return 0; - return 1; + if (m->negative) return SP_MATCHED_NEGATIVE; + return SP_MATCHED_POSITIVE; } } - return 0; + return SP_NOT_MATCHED; +} + +SIMPLE_PATTERN_RESULT simple_pattern_matches_buffer_extract(SIMPLE_PATTERN *list, BUFFER *str, char *wildcarded, size_t wildcarded_size) { + if(!list || !str || buffer_strlen(str)) return SP_NOT_MATCHED; + return simple_pattern_matches_extract_with_length(list, buffer_tostring(str), buffer_strlen(str), wildcarded, wildcarded_size); +} + +SIMPLE_PATTERN_RESULT simple_pattern_matches_string_extract(SIMPLE_PATTERN *list, STRING *str, char *wildcarded, size_t wildcarded_size) { + if(!list || !str) return SP_NOT_MATCHED; + return simple_pattern_matches_extract_with_length(list, string2str(str), string_strlen(str), wildcarded, wildcarded_size); +} + +SIMPLE_PATTERN_RESULT simple_pattern_matches_extract(SIMPLE_PATTERN *list, const char *str, char *wildcarded, size_t wildcarded_size) { + if(!list || !str || !*str) return SP_NOT_MATCHED; + return simple_pattern_matches_extract_with_length(list, str, strlen(str), wildcarded, wildcarded_size); +} + +SIMPLE_PATTERN_RESULT simple_pattern_matches_length_extract(SIMPLE_PATTERN *list, const char *str, size_t len, char *wildcarded, size_t wildcarded_size) { + if(!list || !str || !*str || !len) return SP_NOT_MATCHED; + return simple_pattern_matches_extract_with_length(list, str, len, wildcarded, wildcarded_size); } static inline void free_pattern(struct simple_pattern *m) { diff --git a/libnetdata/simple_pattern/simple_pattern.h b/libnetdata/simple_pattern/simple_pattern.h index 7282053e..1a8d8f7d 100644 --- a/libnetdata/simple_pattern/simple_pattern.h +++ b/libnetdata/simple_pattern/simple_pattern.h @@ -6,25 +6,38 @@ #include "../libnetdata.h" -typedef enum { +typedef enum __attribute__ ((__packed__)) { SIMPLE_PATTERN_EXACT, SIMPLE_PATTERN_PREFIX, SIMPLE_PATTERN_SUFFIX, SIMPLE_PATTERN_SUBSTRING } SIMPLE_PREFIX_MODE; +typedef enum __attribute__ ((__packed__)) { + SP_NOT_MATCHED, + SP_MATCHED_NEGATIVE, + SP_MATCHED_POSITIVE, +} SIMPLE_PATTERN_RESULT; + typedef void SIMPLE_PATTERN; // create a simple_pattern from the string given // default_mode is used in cases where EXACT matches, without an asterisk, // should be considered PREFIX matches. -SIMPLE_PATTERN *simple_pattern_create(const char *list, const char *separators, SIMPLE_PREFIX_MODE default_mode); +SIMPLE_PATTERN *simple_pattern_create(const char *list, const char *separators, SIMPLE_PREFIX_MODE default_mode, bool case_sensitive); + +struct netdata_string; // test if string str is matched from the pattern and fill 'wildcarded' with the parts matched by '*' -int simple_pattern_matches_extract(SIMPLE_PATTERN *list, const char *str, char *wildcarded, size_t wildcarded_size); +SIMPLE_PATTERN_RESULT simple_pattern_matches_extract(SIMPLE_PATTERN *list, const char *str, char *wildcarded, size_t wildcarded_size); +SIMPLE_PATTERN_RESULT simple_pattern_matches_string_extract(SIMPLE_PATTERN *list, struct netdata_string *str, char *wildcarded, size_t wildcarded_size); +SIMPLE_PATTERN_RESULT simple_pattern_matches_buffer_extract(SIMPLE_PATTERN *list, BUFFER *str, char *wildcarded, size_t wildcarded_size); +SIMPLE_PATTERN_RESULT simple_pattern_matches_length_extract(SIMPLE_PATTERN *list, const char *str, size_t len, char *wildcarded, size_t wildcarded_size); // test if string str is matched from the pattern -#define simple_pattern_matches(list, str) simple_pattern_matches_extract(list, str, NULL, 0) +#define simple_pattern_matches(list, str) (simple_pattern_matches_extract(list, str, NULL, 0) == SP_MATCHED_POSITIVE) +#define simple_pattern_matches_string(list, str) (simple_pattern_matches_string_extract(list, str, NULL, 0) == SP_MATCHED_POSITIVE) +#define simple_pattern_matches_buffer(list, str) (simple_pattern_matches_buffer_extract(list, str, NULL, 0) == SP_MATCHED_POSITIVE) // free a simple_pattern that was created with simple_pattern_create() // list can be NULL, in which case, this does nothing. @@ -37,6 +50,11 @@ char *simple_pattern_iterate(SIMPLE_PATTERN **p); // Auxiliary function to create a pattern char *simple_pattern_trim_around_equal(char *src); +#define SIMPLE_PATTERN_DEFAULT_WEB_SEPARATORS ",|\t\r\n\f\v" + #define is_valid_sp(x) ((x) && *(x) && !((x)[0] == '*' && (x)[1] == '\0')) +#define string_to_simple_pattern(str) (is_valid_sp(str) ? simple_pattern_create(str, SIMPLE_PATTERN_DEFAULT_WEB_SEPARATORS, SIMPLE_PATTERN_EXACT, true) : NULL) +#define string_to_simple_pattern_nocase(str) (is_valid_sp(str) ? simple_pattern_create(str, SIMPLE_PATTERN_DEFAULT_WEB_SEPARATORS, SIMPLE_PATTERN_EXACT, false) : NULL) + #endif //NETDATA_SIMPLE_PATTERN_H diff --git a/libnetdata/socket/README.md b/libnetdata/socket/README.md index 70bfd344..e339a071 100644 --- a/libnetdata/socket/README.md +++ b/libnetdata/socket/README.md @@ -1,5 +1,8 @@ - - diff --git a/libnetdata/socket/socket.c b/libnetdata/socket/socket.c index 69124b94..7eb212b3 100644 --- a/libnetdata/socket/socket.c +++ b/libnetdata/socket/socket.c @@ -1,5 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later +#ifndef _GNU_SOURCE +#define _GNU_SOURCE // for POLLRDHUP +#endif + +#ifndef __BSD_VISIBLE +#define __BSD_VISIBLE // for POLLRDHUP +#endif + #include "../libnetdata.h" // -------------------------------------------------------------------------------------------------------------------- @@ -11,6 +19,46 @@ #define LARGE_SOCK_SIZE 4096 #endif +bool fd_is_socket(int fd) { + int type; + socklen_t len = sizeof(type); + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &len) == -1) + return false; + + return true; +} + +bool sock_has_output_error(int fd) { + if(fd < 0) { + //internal_error(true, "invalid socket %d", fd); + return false; + } + +// if(!fd_is_socket(fd)) { +// //internal_error(true, "fd %d is not a socket", fd); +// return false; +// } + + short int errors = POLLERR | POLLHUP | POLLNVAL; + +#ifdef POLLRDHUP + errors |= POLLRDHUP; +#endif + + struct pollfd pfd = { + .fd = fd, + .events = POLLOUT | errors, + .revents = 0, + }; + + if(poll(&pfd, 1, 0) == -1) { + //internal_error(true, "poll() failed"); + return false; + } + + return ((pfd.revents & errors) || !(pfd.revents & POLLOUT)); +} + int sock_setnonblock(int fd) { int flags; @@ -923,53 +971,36 @@ int connect_to_one_of_urls(const char *destination, int default_port, struct tim ssize_t netdata_ssl_read(SSL *ssl, void *buf, size_t num) { error_limit_static_thread_var(erl, 1, 0); - int bytes, err, retries = 0; + int bytes, err; - //do { 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)) { 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); + error_limit(&erl, "SSL_write() returned %d bytes, SSL error %d", bytes, err); } - if(retries > 1) - error_limit(&erl, "SSL_read() retried %d times", retries); - return bytes; } ssize_t netdata_ssl_write(SSL *ssl, const void *buf, size_t num) { error_limit_static_thread_var(erl, 1, 0); - int bytes, err, retries = 0; - size_t total = 0; + int bytes, err; - //do { - bytes = SSL_write(ssl, (uint8_t *)buf + total, (int)(num - total)); + bytes = SSL_write(ssl, (uint8_t *)buf, (int)num); err = SSL_get_error(ssl, bytes); - retries++; - - if(bytes > 0) - total += bytes; - - //} while ((bytes <= 0 && (err == SSL_ERROR_WANT_WRITE)) || (bytes > 0 && total < num)); 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); + error_limit(&erl, "SSL_write() returned %d bytes, SSL error %d", bytes, err); } - if(retries > 1) - error_limit(&erl, "SSL_write() retried %d times", retries); - return bytes; } #endif @@ -1118,8 +1149,8 @@ int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags) { * update the client_host if uninitialized - ensure the hostsize is the number * of *writable* bytes (i.e. be aware of the strdup used to compact the pollinfo). */ -extern int connection_allowed(int fd, char *client_ip, char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list, - const char *patname, int allow_dns) +int connection_allowed(int fd, char *client_ip, char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list, + const char *patname, int allow_dns) { debug(D_LISTENER,"checking %s... (allow_dns=%d)", patname, allow_dns); if (!access_list) diff --git a/libnetdata/socket/socket.h b/libnetdata/socket/socket.h index 9577453d..11006301 100644 --- a/libnetdata/socket/socket.h +++ b/libnetdata/socket/socket.h @@ -22,8 +22,11 @@ typedef enum web_client_acl { WEB_CLIENT_ACL_SSL_FORCE = (1 << 7), WEB_CLIENT_ACL_SSL_DEFAULT = (1 << 8), WEB_CLIENT_ACL_ACLK = (1 << 9), + WEB_CLIENT_ACL_WEBRTC = (1 << 10), } WEB_CLIENT_ACL; +#define WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC (WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK | WEB_CLIENT_ACL_WEBRTC) + #define WEB_CLIENT_ACL_ALL 0xFFFF #define web_client_can_access_dashboard(w) ((w)->acl & WEB_CLIENT_ACL_DASHBOARD) @@ -74,6 +77,9 @@ ssize_t recv_timeout(int sockfd, void *buf, size_t len, int flags, int timeout); ssize_t send_timeout(int sockfd, void *buf, size_t len, int flags, int timeout); #endif +bool fd_is_socket(int fd); +bool sock_has_output_error(int fd); + int sock_setnonblock(int fd); int sock_delnonblock(int fd); int sock_setreuse(int fd, int reuse); diff --git a/libnetdata/statistical/README.md b/libnetdata/statistical/README.md index 8fa101f0..937c26ac 100644 --- a/libnetdata/statistical/README.md +++ b/libnetdata/statistical/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/stati sidebar_label: "Statistical functions" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # Statistical functions diff --git a/libnetdata/storage_number/README.md b/libnetdata/storage_number/README.md index da2c3ccf..2dd6df6c 100644 --- a/libnetdata/storage_number/README.md +++ b/libnetdata/storage_number/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/stora sidebar_label: "Storage number" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # Netdata storage number diff --git a/libnetdata/storage_number/storage_number.c b/libnetdata/storage_number/storage_number.c index 7511f3a7..ebae71d8 100644 --- a/libnetdata/storage_number/storage_number.c +++ b/libnetdata/storage_number/storage_number.c @@ -2,6 +2,78 @@ #include "../libnetdata.h" +bool is_system_ieee754_double(void) { + static bool logged = false; + + struct { + NETDATA_DOUBLE original; + + union { + uint64_t i; + NETDATA_DOUBLE d; + }; + } tests[] = { + { .original = 1.25, .i = 0x3FF4000000000000 }, + { .original = 1.0, .i = 0x3FF0000000000000 }, + { .original = 2.0, .i = 0x4000000000000000 }, + { .original = 4.0, .i = 0x4010000000000000 }, + { .original = 8.8, .i = 0x402199999999999A }, + { .original = 16.16, .i = 0x403028F5C28F5C29 }, + { .original = 32.32, .i = 0x404028F5C28F5C29 }, + { .original = 64.64, .i = 0x405028F5C28F5C29 }, + { .original = 128.128, .i = 0x406004189374BC6A }, + { .original = 32768.32768, .i = 0x40E0000A7C5AC472 }, + { .original = 65536.65536, .i = 0x40F0000A7C5AC472 }, + { .original = -65536.65536, .i = 0xC0F0000A7C5AC472 }, + { .original = 65535.65535, .i = 0x40EFFFF4F8A0902E }, + { .original = -65535.65535, .i = 0xC0EFFFF4F8A0902E }, + { .original = 4.503599627e15, .i = 0x432FFFFFFFF4B180 }, + { .original = -4.503599627e15, .i = 0xC32FFFFFFFF4B180 }, + { .original = 1.25e25, .i = 0x4524ADF4B7320335 }, + { .original = 1.25e307, .i = 0x7FB1CCF385EBC8A0 }, + { .original = 1.25e-25, .i = 0x3AC357C299A88EA7 }, + { .original = 1.25e-100, .i = 0x2B317F7D4ED8C33E }, + { .original = NAN, .i = 0x7FF8000000000000 }, + { .original = -INFINITY, .i = 0xFFF0000000000000 }, + { .original = INFINITY, .i = 0x7FF0000000000000 }, + { .original = 1.25e-132, .i = 0x248C6463225AB7EC }, + { .original = 0.0, .i = 0x0000000000000000 }, + { .original = -0.0, .i = 0x8000000000000000 }, + { .original = DBL_MIN, .i = 0x0010000000000000 }, + { .original = DBL_MAX, .i = 0x7FEFFFFFFFFFFFFF }, + { .original = -DBL_MIN, .i = 0x8010000000000000 }, + { .original = -DBL_MAX, .i = 0xFFEFFFFFFFFFFFFF }, + }; + + size_t errors = 0; + size_t elements = sizeof(tests) / sizeof(tests[0]); + for(size_t i = 0; i < elements ; i++) { + uint64_t *ptr = (uint64_t *)&tests[i].original; + + if(*ptr != tests[i].i && (tests[i].original == tests[i].d || (isnan(tests[i].original) && isnan(tests[i].d)))) { + if(!logged) + info("IEEE754: test #%zu, value " NETDATA_DOUBLE_FORMAT_G " is represented in this system as %lX, but it was expected as %lX", + i+1, tests[i].original, *ptr, tests[i].i); + errors++; + } + } + + if(!errors && sizeof(NETDATA_DOUBLE) == sizeof(uint64_t)) { + if(!logged) + info("IEEE754: system is using IEEE754 DOUBLE PRECISION values"); + + logged = true; + return true; + } + else { + if(!logged) + info("IEEE754: system is NOT compatible with IEEE754 DOUBLE PRECISION values"); + + logged = true; + return false; + } +} + storage_number pack_storage_number(NETDATA_DOUBLE value, SN_FLAGS flags) { // bit 32 = sign 0:positive, 1:negative // bit 31 = 0:divide, 1:multiply @@ -159,73 +231,3 @@ int print_netdata_double(char *str, NETDATA_DOUBLE value) return (int) ((wstr - str) + 2 + decimal ); } */ - -int print_netdata_double(char *str, NETDATA_DOUBLE value) { - // info("printing number " NETDATA_DOUBLE_FORMAT, value); - char integral_str[50], fractional_str[50]; - - char *wstr = str; - - if(unlikely(value < 0)) { - *wstr++ = '-'; - value = -value; - } - - NETDATA_DOUBLE integral, fractional; - -#ifdef STORAGE_WITH_MATH - fractional = modfndd(value, &integral) * 10000000.0; -#else - integral = (NETDATA_DOUBLE)((unsigned long long)(value * 10000000ULL) / 10000000ULL); - fractional = (NETDATA_DOUBLE)((unsigned long long)(value * 10000000ULL) % 10000000ULL); -#endif - - unsigned long long integral_int = (unsigned long long)integral; - unsigned long long fractional_int = (unsigned long long)llrintndd(fractional); - if(unlikely(fractional_int >= 10000000)) { - integral_int += 1; - fractional_int -= 10000000; - } - - // info("integral " NETDATA_DOUBLE_FORMAT " (%llu), fractional " NETDATA_DOUBLE_FORMAT " (%llu)", integral, integral_int, fractional, fractional_int); - - char *istre; - if(unlikely(integral_int == 0)) { - integral_str[0] = '0'; - istre = &integral_str[1]; - } - else - // convert the integral part to string (reversed) - istre = print_number_llu_r_smart(integral_str, integral_int); - - // copy reversed the integral string - istre--; - while( istre >= integral_str ) *wstr++ = *istre--; - - if(likely(fractional_int != 0)) { - // add a dot - *wstr++ = '.'; - - // convert the fractional part to string (reversed) - char *fstre = print_number_llu_r_smart(fractional_str, fractional_int); - - // prepend zeros to reach 7 digits length - int decimal = 7; - int len = (int)(fstre - fractional_str); - while(len < decimal) { - *wstr++ = '0'; - len++; - } - - char *begin = fractional_str; - while(begin < fstre && *begin == '0') begin++; - - // copy reversed the fractional string - fstre--; - while( fstre >= begin ) *wstr++ = *fstre--; - } - - *wstr = '\0'; - // info("printed number '%s'", str); - return (int)(wstr - str); -} diff --git a/libnetdata/storage_number/storage_number.h b/libnetdata/storage_number/storage_number.h index faea4775..82c870d6 100644 --- a/libnetdata/storage_number/storage_number.h +++ b/libnetdata/storage_number/storage_number.h @@ -13,6 +13,7 @@ typedef long double NETDATA_DOUBLE; #define NETDATA_DOUBLE_FORMAT_ZERO "%0.0Lf" #define NETDATA_DOUBLE_FORMAT_AUTO "%Lf" #define NETDATA_DOUBLE_MODIFIER "Lf" +#define NETDATA_DOUBLE_FORMAT_G "%0.19Le" #define NETDATA_DOUBLE_MAX LDBL_MAX @@ -24,6 +25,9 @@ typedef long double NETDATA_DOUBLE; #define copysignndd(x, y) copysignl(x, y) #define modfndd(x, y) modfl(x, y) #define fabsndd(x) fabsl(x) +#define floorndd(x) floorl(x) +#define ceilndd(x) ceill(x) +#define log10ndd(x) log10l(x) #else // NETDATA_WITH_LONG_DOUBLE @@ -32,6 +36,7 @@ typedef double NETDATA_DOUBLE; #define NETDATA_DOUBLE_FORMAT_ZERO "%0.0f" #define NETDATA_DOUBLE_FORMAT_AUTO "%f" #define NETDATA_DOUBLE_MODIFIER "f" +#define NETDATA_DOUBLE_FORMAT_G "%0.19e" #define NETDATA_DOUBLE_MAX DBL_MAX @@ -43,6 +48,9 @@ typedef double NETDATA_DOUBLE; #define copysignndd(x, y) copysign(x, y) #define modfndd(x, y) modf(x, y) #define fabsndd(x) fabs(x) +#define floorndd(x) floor(x) +#define ceilndd(x) ceil(x) +#define log10ndd(x) log10(x) #endif // NETDATA_WITH_LONG_DOUBLE @@ -62,6 +70,9 @@ typedef long long collected_number; #define netdata_double_isnumber(a) (fpclassify(a) != FP_NAN && fpclassify(a) != FP_INFINITE) #endif +#define netdata_double_is_zero(a) (!netdata_double_isnumber(a) || considered_equal_ndd(a, 0.0)) +#define netdata_double_is_nonzero(a) (!netdata_double_is_zero(a)) + typedef uint32_t storage_number; typedef struct storage_number_tier1 { @@ -104,8 +115,6 @@ typedef enum { storage_number pack_storage_number(NETDATA_DOUBLE value, SN_FLAGS flags) __attribute__((const)); static inline NETDATA_DOUBLE unpack_storage_number(storage_number value) __attribute__((const)); -int print_netdata_double(char *str, NETDATA_DOUBLE value); - // sign div/mul <--- multiplier / divider ---> 10/100 RESET EXISTS VALUE #define STORAGE_NUMBER_POSITIVE_MAX_RAW (storage_number)( (0 << 31) | (1 << 30) | (1 << 29) | (1 << 28) | (1 << 27) | (1 << 26) | (0 << 25) | (1 << 24) | 0x00ffffff ) #define STORAGE_NUMBER_POSITIVE_MIN_RAW (storage_number)( (0 << 31) | (0 << 30) | (1 << 29) | (1 << 28) | (1 << 27) | (0 << 26) | (0 << 25) | (1 << 24) | 0x00000001 ) @@ -158,75 +167,12 @@ static inline NETDATA_DOUBLE unpack_storage_number(storage_number value) { return sign * unpack_storage_number_lut10x[(factor * 16) + (exp * 8) + mul] * n; } -static inline NETDATA_DOUBLE str2ndd(const char *s, char **endptr) { - int negative = 0; - const char *start = s; - unsigned long long integer_part = 0; - unsigned long decimal_part = 0; - size_t decimal_digits = 0; - - switch(*s) { - case '-': - s++; - negative = 1; - break; - - case '+': - s++; - break; - - case 'n': - if(s[1] == 'a' && s[2] == 'n') { - if(endptr) *endptr = (char *)&s[3]; - return NAN; - } - break; - - case 'i': - if(s[1] == 'n' && s[2] == 'f') { - if(endptr) *endptr = (char *)&s[3]; - return INFINITY; - } - break; - - default: - break; - } - - while (*s >= '0' && *s <= '9') { - integer_part = (integer_part * 10) + (*s - '0'); - s++; - } - - if(unlikely(*s == '.')) { - decimal_part = 0; - s++; - - while (*s >= '0' && *s <= '9') { - decimal_part = (decimal_part * 10) + (*s - '0'); - s++; - decimal_digits++; - } - } - - if(unlikely(*s == 'e' || *s == 'E')) - return strtondd(start, endptr); - - if(unlikely(endptr)) - *endptr = (char *)s; - - if(unlikely(negative)) { - if(unlikely(decimal_digits)) - return -((NETDATA_DOUBLE)integer_part + (NETDATA_DOUBLE)decimal_part / powndd(10.0, decimal_digits)); - else - return -((NETDATA_DOUBLE)integer_part); - } - else { - if(unlikely(decimal_digits)) - return (NETDATA_DOUBLE)integer_part + (NETDATA_DOUBLE)decimal_part / powndd(10.0, decimal_digits); - else - return (NETDATA_DOUBLE)integer_part; - } -} +// all these prefixes should use characters that are not allowed in the numbers they represent +#define HEX_PREFIX "0x" // we check 2 characters when parsing +#define IEEE754_UINT64_B64_PREFIX "#" // we check the 1st character during parsing +#define IEEE754_DOUBLE_B64_PREFIX "@" // we check the 1st character during parsing +#define IEEE754_DOUBLE_HEX_PREFIX "%" // we check the 1st character during parsing + +bool is_system_ieee754_double(void); #endif /* NETDATA_STORAGE_NUMBER_H */ diff --git a/libnetdata/string/README.md b/libnetdata/string/README.md index b1c6e61c..4fd44250 100644 --- a/libnetdata/string/README.md +++ b/libnetdata/string/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/strin sidebar_label: "String" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # STRING diff --git a/libnetdata/string/string.c b/libnetdata/string/string.c index 4e232523..9385aa6e 100644 --- a/libnetdata/string/string.c +++ b/libnetdata/string/string.c @@ -300,16 +300,20 @@ void string_freez(STRING *string) { string_stats_atomic_increment(releases); } -size_t string_strlen(STRING *string) { +inline size_t string_strlen(STRING *string) { if(unlikely(!string)) return 0; return string->length - 1; } -const char *string2str(STRING *string) { +inline const char *string2str(STRING *string) { if(unlikely(!string)) return ""; return string->str; } +int string_strcmp(STRING *string, const char *s) { + return strcmp(string2str(string), s); +} + STRING *string_2way_merge(STRING *a, STRING *b) { static STRING *X = NULL; diff --git a/libnetdata/string/string.h b/libnetdata/string/string.h index cec44ebd..70840ee9 100644 --- a/libnetdata/string/string.h +++ b/libnetdata/string/string.h @@ -13,6 +13,7 @@ STRING *string_dup(STRING *string); void string_freez(STRING *string); size_t string_strlen(STRING *string); const char *string2str(STRING *string) NEVERNULL; +int string_strcmp(STRING *string, const char *s); // keep common prefix/suffix and replace everything else with [x] STRING *string_2way_merge(STRING *a, STRING *b); diff --git a/libnetdata/string/utf8.h b/libnetdata/string/utf8.h index 133ec710..3e6c8c28 100644 --- a/libnetdata/string/utf8.h +++ b/libnetdata/string/utf8.h @@ -3,7 +3,7 @@ #ifndef NETDATA_STRING_UTF8_H #define NETDATA_STRING_UTF8_H 1 -#define IS_UTF8_BYTE(x) (x & 0x80) -#define IS_UTF8_STARTBYTE(x) (IS_UTF8_BYTE(x)&&(x & 0x40)) +#define IS_UTF8_BYTE(x) ((x) & 0x80) +#define IS_UTF8_STARTBYTE(x) (IS_UTF8_BYTE(x)&&((x) & 0x40)) #endif /* NETDATA_STRING_UTF8_H */ diff --git a/libnetdata/threads/README.md b/libnetdata/threads/README.md index 71979fea..7e9e493f 100644 --- a/libnetdata/threads/README.md +++ b/libnetdata/threads/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/threa sidebar_label: "Threads" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # Threads diff --git a/libnetdata/threads/threads.c b/libnetdata/threads/threads.c index 16de45fd..a4591d5a 100644 --- a/libnetdata/threads/threads.c +++ b/libnetdata/threads/threads.c @@ -10,7 +10,7 @@ static pthread_attr_t *netdata_threads_attr = NULL; typedef struct { void *arg; pthread_t *thread; - const char *tag; + char tag[NETDATA_THREAD_NAME_MAX + 1]; void *(*start_routine) (void *); NETDATA_THREAD_OPTIONS options; } NETDATA_THREAD; @@ -18,11 +18,66 @@ typedef struct { static __thread NETDATA_THREAD *netdata_thread = NULL; inline int netdata_thread_tag_exists(void) { - return (netdata_thread && netdata_thread->tag && *netdata_thread->tag); + return (netdata_thread && *netdata_thread->tag); +} + +static const char *thread_name_get(bool recheck) { + static __thread char threadname[NETDATA_THREAD_NAME_MAX + 1] = ""; + + if(netdata_thread_tag_exists()) + strncpyz(threadname, netdata_thread->tag, NETDATA_THREAD_NAME_MAX); + else { + if(!recheck && threadname[0]) + return threadname; + +#if defined(__FreeBSD__) + pthread_get_name_np(pthread_self(), threadname, NETDATA_THREAD_NAME_MAX + 1); + if(strcmp(threadname, "netdata") == 0) + strncpyz(threadname, "MAIN", NETDATA_THREAD_NAME_MAX); +#elif defined(__APPLE__) + strncpyz(threadname, "MAIN", NETDATA_THREAD_NAME_MAX); +#elif defined(HAVE_PTHREAD_GETNAME_NP) + pthread_getname_np(pthread_self(), threadname, NETDATA_THREAD_NAME_MAX + 1); + if(strcmp(threadname, "netdata") == 0) + strncpyz(threadname, "MAIN", NETDATA_THREAD_NAME_MAX); +#else + strncpyz(threadname, "MAIN", NETDATA_THREAD_NAME_MAX); +#endif + } + + return threadname; } const char *netdata_thread_tag(void) { - return (netdata_thread_tag_exists() ? netdata_thread->tag : "MAIN"); + return thread_name_get(false); +} + +static size_t webrtc_id = 0; +static __thread bool webrtc_name_set = false; +void webrtc_set_thread_name(void) { + if(!netdata_thread && !webrtc_name_set) { + webrtc_name_set = true; + char threadname[NETDATA_THREAD_NAME_MAX + 1]; + +#if defined(__FreeBSD__) + snprintfz(threadname, NETDATA_THREAD_NAME_MAX, "WEBRTC[%zu]", __atomic_fetch_add(&webrtc_id, 1, __ATOMIC_RELAXED)); + pthread_set_name_np(pthread_self(), threadname); +#elif defined(__APPLE__) + snprintfz(threadname, NETDATA_THREAD_NAME_MAX, "WEBRTC[%zu]", __atomic_fetch_add(&webrtc_id, 1, __ATOMIC_RELAXED)); + pthread_setname_np(threadname); +#elif defined(HAVE_PTHREAD_GETNAME_NP) + pthread_getname_np(pthread_self(), threadname, NETDATA_THREAD_NAME_MAX+1); + if(strcmp(threadname, "netdata") == 0) { + snprintfz(threadname, NETDATA_THREAD_NAME_MAX, "WEBRTC[%zu]", __atomic_fetch_add(&webrtc_id, 1, __ATOMIC_RELAXED)); + pthread_setname_np(pthread_self(), threadname); + } +#else + snprintfz(threadname, NETDATA_THREAD_NAME_MAX, "WEBRTC[%zu]", __atomic_fetch_add(&webrtc_id, 1, __ATOMIC_RELAXED)); + pthread_setname_np(pthread_self(), threadname); +#endif + + thread_name_get(true); + } } // ---------------------------------------------------------------------------- @@ -127,8 +182,7 @@ static void thread_cleanup(void *ptr) { service_exits(); worker_unregister(); - freez((void *)netdata_thread->tag); - netdata_thread->tag = NULL; + netdata_thread->tag[0] = '\0'; freez(netdata_thread); netdata_thread = NULL; @@ -136,7 +190,7 @@ static void thread_cleanup(void *ptr) { static void thread_set_name_np(NETDATA_THREAD *nt) { - if (nt->tag) { + if (nt && nt->tag[0]) { int ret = 0; char threadname[NETDATA_THREAD_NAME_MAX+1]; @@ -173,6 +227,8 @@ void uv_thread_set_name_np(uv_thread_t ut, const char* name) { ret = pthread_setname_np(ut, threadname); #endif + thread_name_get(true); + if (ret) info("cannot set libuv thread name to %s. Err: %d", threadname, ret); } @@ -187,7 +243,7 @@ void os_thread_get_current_name_np(char threadname[NETDATA_THREAD_NAME_MAX + 1]) #endif } -static void *thread_start(void *ptr) { +static void *netdata_thread_init(void *ptr) { netdata_thread = (NETDATA_THREAD *)ptr; if(!(netdata_thread->options & NETDATA_THREAD_OPTION_DONT_LOG_STARTUP)) @@ -213,11 +269,11 @@ int netdata_thread_create(netdata_thread_t *thread, const char *tag, NETDATA_THR NETDATA_THREAD *info = mallocz(sizeof(NETDATA_THREAD)); info->arg = arg; info->thread = thread; - info->tag = strdupz(tag); info->start_routine = start_routine; info->options = options; + strncpyz(info->tag, tag, NETDATA_THREAD_NAME_MAX); - int ret = pthread_create(thread, netdata_threads_attr, thread_start, info); + int ret = pthread_create(thread, netdata_threads_attr, netdata_thread_init, info); if(ret != 0) error("failed to create new thread for %s. pthread_create() failed with code %d", tag, ret); diff --git a/libnetdata/threads/threads.h b/libnetdata/threads/threads.h index ccc18aff..ad31b881 100644 --- a/libnetdata/threads/threads.h +++ b/libnetdata/threads/threads.h @@ -43,6 +43,8 @@ int netdata_thread_detach(pthread_t thread); void uv_thread_set_name_np(uv_thread_t ut, const char* name); void os_thread_get_current_name_np(char threadname[NETDATA_THREAD_NAME_MAX + 1]); +void webrtc_set_thread_name(void); + #define netdata_thread_self pthread_self #define netdata_thread_testcancel pthread_testcancel diff --git a/libnetdata/url/README.md b/libnetdata/url/README.md index cca6f873..1ba51e2e 100644 --- a/libnetdata/url/README.md +++ b/libnetdata/url/README.md @@ -4,7 +4,7 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/url/R sidebar_label: "URL" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata libraries" +learn_rel_path: "Developers/libnetdata" --> # URL diff --git a/libnetdata/url/url.c b/libnetdata/url/url.c index f90b3d58..7a671946 100644 --- a/libnetdata/url/url.c +++ b/libnetdata/url/url.c @@ -17,7 +17,7 @@ char to_hex(char code) { return hex[code & 15]; } -/* Returns a url-encoded version of str */ +/* Returns an url-encoded version of str */ /* IMPORTANT: be sure to free() the returned string after use */ char *url_encode(char *str) { char *buf, *pbuf; @@ -33,8 +33,8 @@ char *url_encode(char *str) { else{ *pbuf++ = '%'; - *pbuf++ = to_hex(*str >> 4); - *pbuf++ = to_hex(*str & 15); + *pbuf++ = to_hex((char)(*str >> 4)); + *pbuf++ = to_hex((char)(*str & 15)); } str++; @@ -55,9 +55,9 @@ char *url_encode(char *str) { * * @return The character decoded on success and 0 otherwise */ -char url_percent_escape_decode(char *s) { +char url_percent_escape_decode(const char *s) { if(likely(s[1] && s[2])) - return from_hex(s[1]) << 4 | from_hex(s[2]); + return (char)(from_hex(s[1]) << 4 | from_hex(s[2])); return 0; } @@ -98,7 +98,7 @@ char url_utf8_get_byte_length(char c) { * * @return count of bytes written to *d */ -char url_decode_multibyte_utf8(char *s, char *d, char *d_end) { +char url_decode_multibyte_utf8(const char *s, char *d, const char *d_end) { char first_byte = url_percent_escape_decode(s); if(unlikely(!first_byte || !IS_UTF8_STARTBYTE(first_byte))) @@ -189,9 +189,9 @@ unsigned char *utf8_check(unsigned char *s) return NULL; } -char *url_decode_r(char *to, char *url, size_t size) { - char *s = url, // source - *d = to, // destination +char *url_decode_r(char *to, const char *url, size_t size) { + const char *s = url; // source + char *d = to, // destination *e = &to[size - 1]; // destination end while(*s && d < e) { @@ -236,31 +236,45 @@ fail_cleanup: return NULL; } -/** - * Is request complete? - * - * Check whether the request is complete. - * This function cannot check all the requests METHODS, for example, case you are working with POST, it will fail. - * - * @param begin is the first character of the sequence to analyse. - * @param end is the last character of the sequence - * @param length is the length of the total of bytes read, it is not the difference between end and begin. - * - * @return It returns 1 when the request is complete and 0 otherwise. - */ -inline int url_is_request_complete(char *begin, char *end, size_t length) { +inline bool url_is_request_complete(char *begin, char *end, size_t length, char **post_payload, size_t *post_payload_size) { + if (begin == end || length < 4) + return false; - if ( begin == end) { - //Message cannot be complete when first and last address are the same - return 0; + if(likely(strncmp(begin, "GET ", 4)) == 0) { + return strstr(end - 4, "\r\n\r\n"); } + else if(unlikely(strncmp(begin, "POST ", 5) == 0)) { + char *cl = strstr(begin, "Content-Length: "); + if(!cl) return false; + cl = &cl[16]; - //This math to verify the last is valid, because we are discarding the POST - if (length > 4) { - begin = end - 4; - } + size_t content_length = str2ul(cl); + + char *payload = strstr(cl, "\r\n\r\n"); + if(!payload) return false; + payload += 4; + + size_t payload_length = length - (payload - begin); + + if(payload_length == content_length) { + if(post_payload && post_payload_size) { + if (*post_payload) + freez(*post_payload); + + *post_payload = mallocz(payload_length + 1); + memcpy(*post_payload, payload, payload_length); + (*post_payload)[payload_length] = '\0'; + + *post_payload_size = payload_length; + } + return true; + } - return (strstr(begin, "\r\n\r\n"))?1:0; + return false; + } + else { + return strstr(end - 4, "\r\n\r\n"); + } } /** @@ -283,109 +297,3 @@ inline char *url_find_protocol(char *s) { return s; } - -/** - * Map query string - * - * Map the query string fields that will be decoded. - * This functions must be called after to check the presence of query strings, - * here we are assuming that you already tested this. - * - * @param out the pointer to pointers that will be used to map - * @param url the input url that we are decoding. - * - * @return It returns the number of total variables in the query string. - */ -int url_map_query_string(char **out, char *url) { - (void)out; - (void)url; - int count = 0; - - //First we try to parse considering that there was not URL encode process - char *moveme = url; - char *ptr; - - //We always we have at least one here, so I can set this. - out[count++] = moveme; - while(moveme) { - ptr = strchr((moveme+1), '&'); - if(ptr) { - out[count++] = ptr; - } - - moveme = ptr; - } - - //I could not find any '&', so I am assuming now it is like '%26' - if (count == 1) { - moveme = url; - while(moveme) { - ptr = strchr((moveme+1), '%'); - if(ptr) { - char *test = (ptr+1); - if (!strncmp(test, "3f", 2) || !strncmp(test, "3F", 2)) { - out[count++] = ptr; - } - } - moveme = ptr; - } - } - - return count; -} - -/** - * Parse query string - * - * Parse the query string mapped and store it inside output. - * - * @param output is a vector where I will store the string. - * @param max is the maximum length of the output - * @param map the map done by the function url_map_query_string. - * @param total the total number of variables inside map - * - * @return It returns 0 on success and -1 otherwise - */ -int url_parse_query_string(char *output, size_t max, char **map, int total) { - if(!total) { - return 0; - } - - int counter, next; - size_t length; - char *end; - char *begin = map[0]; - char save; - size_t copied = 0; - for(counter = 0, next=1 ; next <= total ; ++counter, ++next) { - if (next != total) { - end = map[next]; - length = (size_t) (end - begin); - save = *end; - *end = 0x00; - } else { - length = strlen(begin); - end = NULL; - } - length++; - - if (length > (max - copied)) { - error("Parsing query string: we cannot parse a query string so big"); - break; - } - - if(!url_decode_r(output, begin, length)) { - return -1; - } - length = strlen(output); - copied += length; - output += length; - - begin = end; - if (begin) { - *begin = save; - } - } - - return 0; -} diff --git a/libnetdata/url/url.h b/libnetdata/url/url.h index da0f69ac..9db018f0 100644 --- a/libnetdata/url/url.h +++ b/libnetdata/url/url.h @@ -23,13 +23,9 @@ char *url_encode(char *str); /* IMPORTANT: be sure to free() the returned string after use */ char *url_decode(char *str); -char *url_decode_r(char *to, char *url, size_t size); +char *url_decode_r(char *to, const char *url, size_t size); -#define WEB_FIELDS_MAX 400 -int url_map_query_string(char **out, char *url); -int url_parse_query_string(char *output, size_t max, char **map, int total); - -int url_is_request_complete(char *begin,char *end,size_t length); +bool url_is_request_complete(char *begin, char *end, size_t length, char **post_payload, size_t *post_payload_length); char *url_find_protocol(char *s); #endif /* NETDATA_URL_H */ diff --git a/libnetdata/worker_utilization/README.md b/libnetdata/worker_utilization/README.md index 35f30b40..04dfb621 100644 --- a/libnetdata/worker_utilization/README.md +++ b/libnetdata/worker_utilization/README.md @@ -1,6 +1,10 @@ # Worker Utilization diff --git a/libnetdata/worker_utilization/worker_utilization.c b/libnetdata/worker_utilization/worker_utilization.c index 8028e3a2..d47d81c4 100644 --- a/libnetdata/worker_utilization/worker_utilization.c +++ b/libnetdata/worker_utilization/worker_utilization.c @@ -61,6 +61,14 @@ static struct workers_globals { static __thread struct worker *worker = NULL; // the current thread worker +static inline usec_t worker_now_monotonic_usec(void) { +#ifdef NETDATA_WITHOUT_WORKERS_LATENCY + return 0; +#else + return now_monotonic_usec(); +#endif +} + size_t workers_allocated_memory(void) { netdata_spinlock_lock(&workers_globals.spinlock); size_t memory = workers_globals.memory; @@ -77,7 +85,7 @@ void worker_register(const char *name) { worker->tag = strdupz(netdata_thread_tag()); worker->workname = strdupz(name); - usec_t now = now_monotonic_usec(); + usec_t now = worker_now_monotonic_usec(); worker->statistics_last_checkpoint = now; worker->last_action_timestamp = now; worker->last_action = WORKER_IDLE; @@ -181,14 +189,14 @@ static inline void worker_is_idle_with_time(usec_t now) { void worker_is_idle(void) { if(unlikely(!worker || worker->last_action != WORKER_BUSY)) return; - worker_is_idle_with_time(now_monotonic_usec()); + worker_is_idle_with_time(worker_now_monotonic_usec()); } void worker_is_busy(size_t job_id) { if(unlikely(!worker || job_id >= WORKER_UTILIZATION_MAX_JOB_TYPES)) return; - usec_t now = now_monotonic_usec(); + usec_t now = worker_now_monotonic_usec(); if(worker->last_action == WORKER_BUSY) worker_is_idle_with_time(now); @@ -260,7 +268,7 @@ void workers_foreach(const char *name, void (*callback)( struct worker *p; DOUBLE_LINKED_LIST_FOREACH_FORWARD(workname->base, p, prev, next) { - usec_t now = now_monotonic_usec(); + usec_t now = worker_now_monotonic_usec(); // find per job type statistics STRING *per_job_type_name[WORKER_UTILIZATION_MAX_JOB_TYPES]; diff --git a/ml/ADCharts.cc b/ml/ADCharts.cc deleted file mode 100644 index cbb13f5d..00000000 --- a/ml/ADCharts.cc +++ /dev/null @@ -1,518 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "ADCharts.h" -#include "Config.h" - -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); - } - - /* - * 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); - } - - /* - * 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); - } - - /* - * 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); - } - -} - -void ml::updateHostAndDetectionRateCharts(RRDHOST *RH, collected_number AnomalyRate) { - static thread_local RRDSET *HostRateRS = nullptr; - static thread_local RRDDIM *AnomalyRateRD = nullptr; - - if (!HostRateRS) { - std::stringstream IdSS, NameSS; - - IdSS << "anomaly_rate_on_" << localhost->machine_guid; - NameSS << "anomaly_rate_on_" << rrdhost_hostname(localhost); - - HostRateRS = rrdset_create( - 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_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 - ); - rrdset_flag_set(HostRateRS, RRDSET_FLAG_ANOMALY_DETECTION); - - AnomalyRateRD = rrddim_add(HostRateRS, "anomaly_rate", NULL, - 1, 100, RRD_ALGORITHM_ABSOLUTE); - } - - rrddim_set_by_pointer(HostRateRS, AnomalyRateRD, AnomalyRate); - rrdset_done(HostRateRS); - - static thread_local RRDSET *AnomalyDetectionRS = nullptr; - static thread_local RRDDIM *AboveThresholdRD = nullptr; - static thread_local RRDDIM *NewAnomalyEventRD = nullptr; - - if (!AnomalyDetectionRS) { - std::stringstream IdSS, NameSS; - - IdSS << "anomaly_detection_on_" << localhost->machine_guid; - NameSS << "anomaly_detection_on_" << rrdhost_hostname(localhost); - - AnomalyDetectionRS = rrdset_create( - 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_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 - ); - rrdset_flag_set(AnomalyDetectionRS, RRDSET_FLAG_ANOMALY_DETECTION); - - AboveThresholdRD = rrddim_add(AnomalyDetectionRS, "above_threshold", NULL, - 1, 1, RRD_ALGORITHM_ABSOLUTE); - NewAnomalyEventRD = rrddim_add(AnomalyDetectionRS, "new_anomaly_event", NULL, - 1, 1, RRD_ALGORITHM_ABSOLUTE); - } - - /* - * Compute the values of the dimensions based on the host rate chart - */ - ONEWAYALLOC *OWA = onewayalloc_create(0); - time_t Now = now_realtime_sec(); - time_t Before = Now - RH->rrd_update_every; - time_t After = Before - Cfg.AnomalyDetectionQueryDuration; - RRDR_OPTIONS Options = static_cast(0x00000000); - - RRDR *R = rrd2rrdr_legacy( - OWA, HostRateRS, - 1 /* points wanted */, - After, - Before, - Cfg.AnomalyDetectionGroupingMethod, - 0 /* resampling time */, - Options, "anomaly_rate", - NULL /* group options */, - 0, /* timeout */ - 0, /* tier */ - QUERY_SOURCE_ML, - STORAGE_PRIORITY_BEST_EFFORT - ); - - 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); - } - - rrdr_free(OWA, R); - } - - onewayalloc_destroy(OWA); -} - -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); - } - - /* - * 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::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); - } - - /* - * 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); - } - - /* - * 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 deleted file mode 100644 index ee09669e..00000000 --- a/ml/ADCharts.h +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef ML_ADCHARTS_H -#define ML_ADCHARTS_H - -#include "Stats.h" -#include "ml-private.h" - -namespace ml { - -void updateDimensionsChart(RRDHOST *RH, const MachineLearningStats &MLS); - -void updateHostAndDetectionRateCharts(RRDHOST *RH, collected_number AnomalyRate); - -void updateResourceUsageCharts(RRDHOST *RH, const struct rusage &PredictionRU, const struct rusage &TrainingRU); - -void updateTrainingStatisticsChart(RRDHOST *RH, const TrainingStats &TS); - -} // namespace ml - -#endif /* ML_ADCHARTS_H */ diff --git a/ml/Chart.cc b/ml/Chart.cc deleted file mode 100644 index e69de29b..00000000 diff --git a/ml/Chart.h b/ml/Chart.h deleted file mode 100644 index dbd6a910..00000000 --- a/ml/Chart.h +++ /dev/null @@ -1,128 +0,0 @@ -// 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 L(M); - Dimensions[D->getRD()] = D; - } - - void removeDimension(Dimension *D) { - std::lock_guard L(M); - Dimensions.erase(D->getRD()); - } - - void getModelsAsJson(nlohmann::json &Json) { - std::lock_guard 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 L(M); - return MLS; - } - -private: - RRDSET *RS; - MachineLearningStats MLS; - - Mutex M; - std::unordered_map Dimensions; -}; - -} // namespace ml - -#endif /* ML_CHART_H */ diff --git a/ml/Config.cc b/ml/Config.cc index ba3a6144..d451c602 100644 --- a/ml/Config.cc +++ b/ml/Config.cc @@ -1,15 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include "Config.h" #include "ml-private.h" -using namespace ml; - /* * Global configuration instance to be shared between training and * prediction threads. */ -Config ml::Cfg; +ml_config_t Cfg; template static T clamp(const T& Value, const T& Min, const T& Max) { @@ -19,96 +16,110 @@ static T clamp(const T& Value, const T& Min, const T& Max) { /* * Initialize global configuration variable. */ -void Config::readMLConfig(void) { - const char *ConfigSectionML = CONFIG_SECTION_ML; +void ml_config_load(ml_config_t *cfg) { + const char *config_section_ml = CONFIG_SECTION_ML; - bool EnableAnomalyDetection = config_get_boolean(ConfigSectionML, "enabled", true); + bool enable_anomaly_detection = config_get_boolean(config_section_ml, "enabled", true); /* * Read values */ - 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); + unsigned max_train_samples = config_get_number(config_section_ml, "maximum num samples to train", 4 * 3600); + unsigned min_train_samples = config_get_number(config_section_ml, "minimum num samples to train", 1 * 900); + unsigned train_every = config_get_number(config_section_ml, "train every", 1 * 3600); + unsigned num_models_to_use = config_get_number(config_section_ml, "number of models per dimension", 1); + + unsigned diff_n = config_get_number(config_section_ml, "num samples to diff", 1); + unsigned smooth_n = config_get_number(config_section_ml, "num samples to smooth", 3); + unsigned lag_n = config_get_number(config_section_ml, "num samples to lag", 5); + + double random_sampling_ratio = config_get_float(config_section_ml, "random sampling ratio", 1.0 / 5.0 /* default lag_n */); + unsigned max_kmeans_iters = config_get_number(config_section_ml, "maximum number of k-means iterations", 1000); - unsigned DiffN = config_get_number(ConfigSectionML, "num samples to diff", 1); - unsigned SmoothN = config_get_number(ConfigSectionML, "num samples to smooth", 3); - unsigned LagN = config_get_number(ConfigSectionML, "num samples to lag", 5); + double dimension_anomaly_rate_threshold = config_get_float(config_section_ml, "dimension anomaly score threshold", 0.99); - double RandomSamplingRatio = config_get_float(ConfigSectionML, "random sampling ratio", 1.0 / LagN); - unsigned MaxKMeansIters = config_get_number(ConfigSectionML, "maximum number of k-means iterations", 1000); + double host_anomaly_rate_threshold = config_get_float(config_section_ml, "host anomaly rate threshold", 1.0); + std::string anomaly_detection_grouping_method = config_get(config_section_ml, "anomaly detection grouping method", "average"); + time_t anomaly_detection_query_duration = config_get_number(config_section_ml, "anomaly detection grouping duration", 5 * 60); - double DimensionAnomalyScoreThreshold = config_get_float(ConfigSectionML, "dimension anomaly score threshold", 0.99); + size_t num_training_threads = config_get_number(config_section_ml, "num training threads", 4); + size_t flush_models_batch_size = config_get_number(config_section_ml, "flush models batch size", 128); - double HostAnomalyRateThreshold = config_get_float(ConfigSectionML, "host anomaly rate threshold", 1.0); - std::string AnomalyDetectionGroupingMethod = config_get(ConfigSectionML, "anomaly detection grouping method", "average"); - time_t AnomalyDetectionQueryDuration = config_get_number(ConfigSectionML, "anomaly detection grouping duration", 5 * 60); + bool enable_statistics_charts = config_get_boolean(config_section_ml, "enable statistics charts", true); /* * Clamp */ - MaxTrainSamples = clamp(MaxTrainSamples, 1 * 3600, 24 * 3600); - MinTrainSamples = clamp(MinTrainSamples, 1 * 900, 6 * 3600); - TrainEvery = clamp(TrainEvery, 1 * 3600, 6 * 3600); - NumModelsToUse = clamp(NumModelsToUse, 1, 7 * 24); + max_train_samples = clamp(max_train_samples, 1 * 3600, 24 * 3600); + min_train_samples = clamp(min_train_samples, 1 * 900, 6 * 3600); + train_every = clamp(train_every, 1 * 3600, 6 * 3600); + num_models_to_use = clamp(num_models_to_use, 1, 7 * 24); - DiffN = clamp(DiffN, 0u, 1u); - SmoothN = clamp(SmoothN, 0u, 5u); - LagN = clamp(LagN, 1u, 5u); + diff_n = clamp(diff_n, 0u, 1u); + smooth_n = clamp(smooth_n, 0u, 5u); + lag_n = clamp(lag_n, 1u, 5u); - RandomSamplingRatio = clamp(RandomSamplingRatio, 0.2, 1.0); - MaxKMeansIters = clamp(MaxKMeansIters, 500u, 1000u); + random_sampling_ratio = clamp(random_sampling_ratio, 0.2, 1.0); + max_kmeans_iters = clamp(max_kmeans_iters, 500u, 1000u); - DimensionAnomalyScoreThreshold = clamp(DimensionAnomalyScoreThreshold, 0.01, 5.00); + dimension_anomaly_rate_threshold = clamp(dimension_anomaly_rate_threshold, 0.01, 5.00); - HostAnomalyRateThreshold = clamp(HostAnomalyRateThreshold, 0.1, 10.0); - AnomalyDetectionQueryDuration = clamp(AnomalyDetectionQueryDuration, 60, 15 * 60); + host_anomaly_rate_threshold = clamp(host_anomaly_rate_threshold, 0.1, 10.0); + anomaly_detection_query_duration = clamp(anomaly_detection_query_duration, 60, 15 * 60); + + num_training_threads = clamp(num_training_threads, 1, 128); + flush_models_batch_size = clamp(flush_models_batch_size, 8, 512); /* * Validate */ - if (MinTrainSamples >= MaxTrainSamples) { - error("invalid min/max train samples found (%u >= %u)", MinTrainSamples, MaxTrainSamples); + if (min_train_samples >= max_train_samples) { + error("invalid min/max train samples found (%u >= %u)", min_train_samples, max_train_samples); - MinTrainSamples = 1 * 3600; - MaxTrainSamples = 4 * 3600; + min_train_samples = 1 * 3600; + max_train_samples = 4 * 3600; } /* * Assign to config instance */ - Cfg.EnableAnomalyDetection = EnableAnomalyDetection; + cfg->enable_anomaly_detection = enable_anomaly_detection; - Cfg.MaxTrainSamples = MaxTrainSamples; - Cfg.MinTrainSamples = MinTrainSamples; - Cfg.TrainEvery = TrainEvery; - Cfg.NumModelsToUse = NumModelsToUse; + cfg->max_train_samples = max_train_samples; + cfg->min_train_samples = min_train_samples; + cfg->train_every = train_every; - Cfg.DiffN = DiffN; - Cfg.SmoothN = SmoothN; - Cfg.LagN = LagN; + cfg->num_models_to_use = num_models_to_use; - Cfg.RandomSamplingRatio = RandomSamplingRatio; - Cfg.MaxKMeansIters = MaxKMeansIters; + cfg->diff_n = diff_n; + cfg->smooth_n = smooth_n; + cfg->lag_n = lag_n; - Cfg.DimensionAnomalyScoreThreshold = DimensionAnomalyScoreThreshold; + cfg->random_sampling_ratio = random_sampling_ratio; + cfg->max_kmeans_iters = max_kmeans_iters; - Cfg.HostAnomalyRateThreshold = HostAnomalyRateThreshold; - Cfg.AnomalyDetectionGroupingMethod = web_client_api_request_v1_data_group(AnomalyDetectionGroupingMethod.c_str(), RRDR_GROUPING_AVERAGE); - Cfg.AnomalyDetectionQueryDuration = AnomalyDetectionQueryDuration; + cfg->host_anomaly_rate_threshold = host_anomaly_rate_threshold; + cfg->anomaly_detection_grouping_method = + time_grouping_parse(anomaly_detection_grouping_method.c_str(), RRDR_GROUPING_AVERAGE); + cfg->anomaly_detection_query_duration = anomaly_detection_query_duration; + cfg->dimension_anomaly_score_threshold = dimension_anomaly_rate_threshold; - Cfg.HostsToSkip = config_get(ConfigSectionML, "hosts to skip from training", "!*"); - Cfg.SP_HostsToSkip = simple_pattern_create(Cfg.HostsToSkip.c_str(), NULL, SIMPLE_PATTERN_EXACT); + cfg->hosts_to_skip = config_get(config_section_ml, "hosts to skip from training", "!*"); + cfg->sp_host_to_skip = simple_pattern_create(cfg->hosts_to_skip.c_str(), NULL, SIMPLE_PATTERN_EXACT, true); // 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(Cfg.ChartsToSkip.c_str(), NULL, SIMPLE_PATTERN_EXACT); + cfg->charts_to_skip = "anomaly_detection.* "; + cfg->charts_to_skip += config_get(config_section_ml, "charts to skip from training", "netdata.*"); + cfg->sp_charts_to_skip = simple_pattern_create(cfg->charts_to_skip.c_str(), NULL, SIMPLE_PATTERN_EXACT, true); + + cfg->stream_anomaly_detection_charts = config_get_boolean(config_section_ml, "stream anomaly detection charts", true); + + cfg->num_training_threads = num_training_threads; + cfg->flush_models_batch_size = flush_models_batch_size; - Cfg.StreamADCharts = config_get_boolean(ConfigSectionML, "stream anomaly detection charts", true); + cfg->enable_statistics_charts = enable_statistics_charts; } diff --git a/ml/Config.h b/ml/Config.h deleted file mode 100644 index f10e1149..00000000 --- a/ml/Config.h +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef ML_CONFIG_H -#define ML_CONFIG_H - -#include "ml-private.h" - -namespace ml { - -class Config { -public: - bool EnableAnomalyDetection; - - unsigned MaxTrainSamples; - unsigned MinTrainSamples; - unsigned TrainEvery; - - unsigned NumModelsToUse; - - unsigned DBEngineAnomalyRateEvery; - - unsigned DiffN; - unsigned SmoothN; - unsigned LagN; - - double RandomSamplingRatio; - unsigned MaxKMeansIters; - - double DimensionAnomalyScoreThreshold; - - double HostAnomalyRateThreshold; - RRDR_GROUPING AnomalyDetectionGroupingMethod; - time_t AnomalyDetectionQueryDuration; - - bool StreamADCharts; - - std::string HostsToSkip; - SIMPLE_PATTERN *SP_HostsToSkip; - - std::string ChartsToSkip; - SIMPLE_PATTERN *SP_ChartsToSkip; - - std::vector RandomNums; - - void readMLConfig(); -}; - -extern Config Cfg; - -} // namespace ml - -#endif /* ML_CONFIG_H */ diff --git a/ml/Dimension.cc b/ml/Dimension.cc deleted file mode 100644 index db925689..00000000 --- a/ml/Dimension.cc +++ /dev/null @@ -1,346 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "Config.h" -#include "Dimension.h" -#include "Query.h" -#include "Host.h" - -using namespace ml; - -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"; - } -} - -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 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. - TrainingResp.QueryBeforeT = TrainingResp.LastEntryOnResponse; - TrainingResp.QueryAfterT = std::max( - TrainingResp.QueryBeforeT - static_cast((MaxN - 1) * updateEvery()), - TrainingResp.FirstEntryOnResponse - ); - - if (TrainingResp.QueryAfterT >= TrainingResp.QueryBeforeT) { - TrainingResp.Result = TrainingResult::InvalidQueryTimeRange; - return { nullptr, TrainingResp }; - } - - if (rrdset_is_replicating(RD->rrdset)) { - TrainingResp.Result = TrainingResult::ChartUnderReplication; - return { nullptr, TrainingResp }; - } - - CalculatedNumber *CNs = new CalculatedNumber[MaxN * (Cfg.LagN + 1)](); - - // Start the query. - size_t Idx = 0; - - CalculatedNumber LastValue = std::numeric_limits::quiet_NaN(); - Query Q = Query(getRD()); - - 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]; - TrainingResp.CollectedValues++; - } else - CNs[Idx] = LastValue; - - Idx++; - } - TrainingResp.TotalValues = Idx; - - if (TrainingResp.CollectedValues < MinN) { - TrainingResp.Result = TrainingResult::NotEnoughCollectedValues; - - delete[] CNs; - return { nullptr, TrainingResp }; - } - - // Find first non-NaN value. - for (Idx = 0; std::isnan(CNs[Idx]); Idx++, TrainingResp.TotalValues--) { } - - // Overwrite NaN values. - if (Idx != 0) - memmove(CNs, &CNs[Idx], sizeof(CalculatedNumber) * TrainingResp.TotalValues); - - TrainingResp.Result = TrainingResult::Ok; - return { CNs, TrainingResp }; -} - -TrainingResult Dimension::trainModel(const TrainingRequest &TrainingReq) { - auto P = getCalculatedNumbers(TrainingReq); - CalculatedNumber *CNs = P.first; - TrainingResponse TrainingResp = P.second; - - if (TrainingResp.Result != TrainingResult::Ok) { - std::lock_guard 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; - - LastTrainingTime = TrainingResp.LastEntryOnResponse; - return TrainingResp.Result; - } - - unsigned N = TrainingResp.TotalValues; - unsigned TargetNumSamples = Cfg.MaxTrainSamples * Cfg.RandomSamplingRatio; - double SamplingRatio = std::min(static_cast(TargetNumSamples) / N, 1.0); - - SamplesBuffer SB = SamplesBuffer(CNs, N, 1, Cfg.DiffN, Cfg.SmoothN, Cfg.LagN, - SamplingRatio, Cfg.RandomNums); - std::vector Samples; - SB.preprocess(Samples); - - KMeans KM; - KM.train(Samples, Cfg.MaxKMeansIters); - - { - std::lock_guard L(M); - - 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 TrainingResp.Result; -} - -void Dimension::scheduleForTraining(time_t CurrT) { - switch (MT) { - case MetricType::Constant: { - return; - } default: - break; - } - - switch (TS) { - case TrainingStatus::PendingWithModel: - case TrainingStatus::PendingWithoutModel: - break; - case TrainingStatus::Untrained: { - Host *H = reinterpret_cast(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(RD->rrdset->rrdhost->ml_host); - TS = TrainingStatus::PendingWithModel; - H->scheduleForTraining(getTrainingRequest(CurrT)); - } - break; - } - } -} - -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(); - 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); - 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) - SameValue = false; - CNs[N - 1] = Value; - - // 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); - SB.preprocess(Feature); - - /* - * Lock to predict and possibly schedule the dimension for training - */ - - std::unique_lock 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) { - ModelsConsulted++; - - double AnomalyScore = KM.anomalyScore(Feature); - if (AnomalyScore == std::numeric_limits::quiet_NaN()) - continue; - - if (AnomalyScore < (100 * Cfg.DimensionAnomalyScoreThreshold)) { - global_statistics_ml_models_consulted(ModelsConsulted); - return false; - } - - Sum += 1; - } - - global_statistics_ml_models_consulted(ModelsConsulted); - return Sum; -} - -std::vector Dimension::getModels() { - std::unique_lock 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 deleted file mode 100644 index 2b1adfff..00000000 --- a/ml/Dimension.h +++ /dev/null @@ -1,198 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef ML_DIMENSION_H -#define ML_DIMENSION_H - -#include "Mutex.h" -#include "Stats.h" -#include "Query.h" -#include "Config.h" - -#include "ml-private.h" - -namespace ml { - -static inline std::string getMLDimensionID(RRDDIM *RD) { - RRDSET *RS = RD->rrdset; - - std::stringstream SS; - SS << rrdset_context(RS) << "|" << rrdset_id(RS) << "|" << rrddim_name(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), - 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; - } - - unsigned updateEvery() const { - return RD->update_every; - } - - MetricType getMT() const { - return MT; - } - - TrainingStatus getTS() const { - return TS; - } - - MachineLearningStatus getMLS() const { - return MLS; - } - - TrainingResult trainModel(const TrainingRequest &TR); - - void scheduleForTraining(time_t CurrT); - - bool predict(time_t CurrT, CalculatedNumber Value, bool Exists); - - std::vector getModels(); - - void dump() const; - -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 getCalculatedNumbers(const TrainingRequest &TrainingReq); - -public: - RRDDIM *RD; - MetricType MT; - TrainingStatus TS; - TrainingResponse TR; - - time_t LastTrainingTime; - - MachineLearningStatus MLS; - - std::vector CNs; - DSample Feature; - std::vector Models; - Mutex M; -}; - -} // namespace ml - -#endif /* ML_DIMENSION_H */ diff --git a/ml/Host.cc b/ml/Host.cc deleted file mode 100644 index a5f276a8..00000000 --- a/ml/Host.cc +++ /dev/null @@ -1,387 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "Config.h" -#include "Host.h" -#include "Queue.h" -#include "ADCharts.h" - -#include "json/single_include/nlohmann/json.hpp" - -using namespace ml; - -void Host::addChart(Chart *C) { - std::lock_guard L(M); - Charts[C->getRS()] = C; -} - -void Host::removeChart(Chart *C) { - std::lock_guard L(M); - Charts.erase(C->getRS()); -} - -void Host::getConfigAsJson(nlohmann::json &Json) const { - Json["version"] = 1; - - Json["enabled"] = Cfg.EnableAnomalyDetection; - - Json["min-train-samples"] = Cfg.MinTrainSamples; - Json["max-train-samples"] = Cfg.MaxTrainSamples; - Json["train-every"] = Cfg.TrainEvery; - - Json["diff-n"] = Cfg.DiffN; - Json["smooth-n"] = Cfg.SmoothN; - Json["lag-n"] = Cfg.LagN; - - Json["random-sampling-ratio"] = Cfg.RandomSamplingRatio; - Json["max-kmeans-iters"] = Cfg.MaxKMeansIters; - - Json["dimension-anomaly-score-threshold"] = Cfg.DimensionAnomalyScoreThreshold; - - Json["host-anomaly-rate-threshold"] = Cfg.HostAnomalyRateThreshold; - Json["anomaly-detection-grouping-method"] = group_method2string(Cfg.AnomalyDetectionGroupingMethod); - Json["anomaly-detection-query-duration"] = Cfg.AnomalyDetectionQueryDuration; - - Json["hosts-to-skip"] = Cfg.HostsToSkip; - Json["charts-to-skip"] = Cfg.ChartsToSkip; -} - -void Host::getModelsAsJson(nlohmann::json &Json) { - std::lock_guard L(M); - - for (auto &CP : Charts) { - Chart *C = CP.second; - C->getModelsAsJson(Json); - } -} - -#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 - -void Host::detectOnce() { - worker_is_busy(WORKER_JOB_DETECTION_PREP); - - MLS = {}; - MachineLearningStats MLSCopy = {}; - TrainingStats TSCopy = {}; - - { - std::lock_guard L(M); - - /* - * prediction/detection stats - */ - for (auto &CP : Charts) { - Chart *C = CP.second; - - if (!C->isAvailableForML()) - continue; - - MachineLearningStats ChartMLS = C->getMLS(); - - MLS.NumMachineLearningStatusEnabled += ChartMLS.NumMachineLearningStatusEnabled; - MLS.NumMachineLearningStatusDisabledUE += ChartMLS.NumMachineLearningStatusDisabledUE; - MLS.NumMachineLearningStatusDisabledSP += ChartMLS.NumMachineLearningStatusDisabledSP; - - MLS.NumMetricTypeConstant += ChartMLS.NumMetricTypeConstant; - MLS.NumMetricTypeVariable += ChartMLS.NumMetricTypeVariable; - - MLS.NumTrainingStatusUntrained += ChartMLS.NumTrainingStatusUntrained; - MLS.NumTrainingStatusPendingWithoutModel += ChartMLS.NumTrainingStatusPendingWithoutModel; - MLS.NumTrainingStatusTrained += ChartMLS.NumTrainingStatusTrained; - MLS.NumTrainingStatusPendingWithModel += ChartMLS.NumTrainingStatusPendingWithModel; - - MLS.NumAnomalousDimensions += ChartMLS.NumAnomalousDimensions; - MLS.NumNormalDimensions += ChartMLS.NumNormalDimensions; - } - - HostAnomalyRate = 0.0; - size_t NumActiveDimensions = MLS.NumAnomalousDimensions + MLS.NumNormalDimensions; - if (NumActiveDimensions) - HostAnomalyRate = static_cast(MLS.NumAnomalousDimensions) / NumActiveDimensions; - - MLSCopy = MLS; - - /* - * training stats - */ - TSCopy = TS; - - TS.QueueSize = 0; - TS.NumPoppedItems = 0; - - TS.AllottedUT = 0; - TS.ConsumedUT = 0; - TS.RemainingUT = 0; - - TS.TrainingResultOk = 0; - TS.TrainingResultInvalidQueryTimeRange = 0; - TS.TrainingResultNotEnoughCollectedValues = 0; - TS.TrainingResultNullAcquiredDimension = 0; - TS.TrainingResultChartUnderReplication = 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; - } - - if(!RH) - return; - - 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 - - worker_is_busy(WORKER_JOB_DETECTION_STATS); - updateTrainingStatisticsChart(RH, TSCopy); -} - -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(RD->ml_dimension); - } - } - - return AcquiredDimension(AcqRD, D); - } - -private: - AcquiredDimension(RRDDIM_ACQUIRED *AcqRD, Dimension *D) : AcqRD(AcqRD), D(D) {} - -public: - TrainingResult train(const TrainingRequest &TR) { - if (!D) - return TrainingResult::NullAcquiredDimension; - - return D->trainModel(TR); - } - - ~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; - } - - usec_t AllottedUT = (Cfg.TrainEvery * RH->rrd_update_every * USEC_PER_SEC) / Size; - if (AllottedUT > USEC_PER_SEC) - AllottedUT = USEC_PER_SEC; - - usec_t StartUT = now_monotonic_usec(); - TrainingResult TrainingRes; - { - worker_is_busy(WORKER_JOB_TRAINING_FIND); - AcquiredDimension AcqDim = AcquiredDimension::find(RH, TrainingReq.ChartId, TrainingReq.DimensionId); - - worker_is_busy(WORKER_JOB_TRAINING_TRAIN); - TrainingRes = AcqDim.train(TrainingReq); - - 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 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; - } - } - - worker_is_idle(); - std::this_thread::sleep_for(std::chrono::microseconds{RemainingUT}); - worker_is_busy(0); - } -} - -void Host::detect() { - worker_register("MLDETECT"); - 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"); - - service_register(SERVICE_THREAD_TYPE_NETDATA, NULL, (force_quit_t )ml_cancel_anomaly_detection_threads, RH, true); - - heartbeat_t HB; - heartbeat_init(&HB); - - while (service_running((SERVICE_TYPE)(SERVICE_ML_PREDICTION | SERVICE_COLLECTORS))) { - worker_is_idle(); - heartbeat_next(&HB, (RH ? RH->rrd_update_every : default_rrd_update_every) * USEC_PER_SEC); - detectOnce(); - } -} - -void Host::getDetectionInfoAsJson(nlohmann::json &Json) const { - Json["version"] = 1; - 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(Arg); - H->train(); - return nullptr; -} - -void *detect_main(void *Arg) { - Host *H = reinterpret_cast(Arg); - H->detect(); - return nullptr; -} - -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 - - snprintfz(Tag, NETDATA_THREAD_TAG_MAX, "MLTR[%s]", rrdhost_hostname(RH)); - netdata_thread_create(&TrainingThread, Tag, NETDATA_THREAD_OPTION_JOINABLE, train_main, static_cast(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(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 deleted file mode 100644 index 289cb5ab..00000000 --- a/ml/Host.h +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#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 -{ - -class Host { - -friend void* train_main(void *); -friend void *detect_main(void *); - -public: - 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); - void getDetectionInfoAsJson(nlohmann::json &Json) const; - - void startAnomalyDetectionThreads(); - void stopAnomalyDetectionThreads(bool join); - - void scheduleForTraining(TrainingRequest TR); - void train(); - - void detect(); - void detectOnce(); - -private: - RRDHOST *RH; - MachineLearningStats MLS; - TrainingStats TS; - CalculatedNumber HostAnomalyRate{0.0}; - std::atomic ThreadsRunning; - std::atomic ThreadsCancelled; - std::atomic ThreadsJoined; - - Queue TrainingQueue; - - Mutex M; - std::unordered_map Charts; - - netdata_thread_t TrainingThread; - netdata_thread_t DetectionThread; -}; - -} // namespace ml - -#endif /* ML_HOST_H */ diff --git a/ml/KMeans.cc b/ml/KMeans.cc deleted file mode 100644 index edc2ef49..00000000 --- a/ml/KMeans.cc +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "KMeans.h" -#include - -void KMeans::train(const std::vector &Samples, size_t MaxIterations) { - MinDist = std::numeric_limits::max(); - MaxDist = std::numeric_limits::min(); - - ClusterCenters.clear(); - - dlib::pick_initial_centers(NumClusters, ClusterCenters, Samples); - dlib::find_clusters_using_kmeans(Samples, ClusterCenters, MaxIterations); - - for (const auto &S : Samples) { - CalculatedNumber MeanDist = 0.0; - - for (const auto &KMCenter : ClusterCenters) - MeanDist += dlib::length(KMCenter - S); - - MeanDist /= NumClusters; - - if (MeanDist < MinDist) - MinDist = MeanDist; - - if (MeanDist > MaxDist) - MaxDist = MeanDist; - } -} - -CalculatedNumber KMeans::anomalyScore(const DSample &Sample) const { - CalculatedNumber MeanDist = 0.0; - for (const auto &CC: ClusterCenters) - MeanDist += dlib::length(CC - Sample); - - MeanDist /= NumClusters; - - if (MaxDist == MinDist) - return 0.0; - - CalculatedNumber AnomalyScore = 100.0 * std::abs((MeanDist - MinDist) / (MaxDist - MinDist)); - return (AnomalyScore > 100.0) ? 100.0 : AnomalyScore; -} diff --git a/ml/KMeans.h b/ml/KMeans.h deleted file mode 100644 index 0398eeb8..00000000 --- a/ml/KMeans.h +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef KMEANS_H -#define KMEANS_H - -#include -#include -#include -#include - -#include "SamplesBuffer.h" -#include "json/single_include/nlohmann/json.hpp" - -class KMeans { -public: - KMeans(size_t NumClusters = 2) : NumClusters(NumClusters) { - MinDist = std::numeric_limits::max(); - MaxDist = std::numeric_limits::min(); - }; - - void train(const std::vector &Samples, size_t MaxIterations); - CalculatedNumber anomalyScore(const DSample &Sample) const; - - void toJson(nlohmann::json &J) const { - J = nlohmann::json{ - {"CCs", ClusterCenters}, - {"MinDist", MinDist}, - {"MaxDist", MaxDist} - }; - } - -private: - size_t NumClusters; - - std::vector ClusterCenters; - - CalculatedNumber MinDist; - CalculatedNumber MaxDist; -}; - -#endif /* KMEANS_H */ diff --git a/ml/Mutex.h b/ml/Mutex.h deleted file mode 100644 index fcdb7531..00000000 --- a/ml/Mutex.h +++ /dev/null @@ -1,36 +0,0 @@ -#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 deleted file mode 100644 index 42a96e85..00000000 --- a/ml/Query.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef QUERY_H -#define QUERY_H - -#include "ml-private.h" - -namespace ml { - -class Query { -public: - Query(RRDDIM *RD) : RD(RD), Initialized(false) { - Ops = RD->tiers[0].query_ops; - } - - time_t latestTime() { - return Ops->latest_time_s(RD->tiers[0].db_metric_handle); - } - - time_t oldestTime() { - 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, STORAGE_PRIORITY_BEST_EFFORT); - Initialized = true; - points_read = 0; - } - - bool isFinished() { - return Ops->is_finished(&Handle); - } - - ~Query() { - if (Initialized) { - Ops->finalize(&Handle); - global_statistics_ml_query_completed(points_read); - points_read = 0; - } - } - - std::pair nextMetric() { - points_read++; - STORAGE_POINT sp = Ops->next_metric(&Handle); - return {sp.end_time_s, sp.sum / sp.count }; - } - -private: - RRDDIM *RD; - bool Initialized; - size_t points_read; - - struct storage_engine_query_ops *Ops; - struct storage_engine_query_handle Handle; -}; - -} // namespace ml - -#endif /* QUERY_H */ diff --git a/ml/Queue.h b/ml/Queue.h deleted file mode 100644 index 37a74bd0..00000000 --- a/ml/Queue.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef QUEUE_H -#define QUEUE_H - -#include "ml-private.h" -#include "Mutex.h" -#include -#include -#include - -template -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 L(M); - - Q.push(t); - pthread_cond_signal(&CV); - } - - std::pair pop(void) { - std::lock_guard 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 L(M); - Exit = true; - pthread_cond_signal(&CV); - } - -private: - std::queue Q; - Mutex M; - pthread_cond_t CV; - std::atomic Exit; -}; - -#endif /* QUEUE_H */ diff --git a/ml/README.md b/ml/README.md index 7f3ed276..ac7c7c01 100644 --- a/ml/README.md +++ b/ml/README.md @@ -5,18 +5,22 @@ description: "This is an in-depth look at how Netdata uses ML to detect anomalie sidebar_label: "Configure machine learning (ML) powered anomaly detection" learn_status: "Published" learn_topic_type: "Tasks" -learn_rel_path: "Setup" +learn_rel_path: "Configuration" --> # Machine learning (ML) powered anomaly detection ## Overview +Machine learning is a subfield of artificial intelligence that enables computers to learn and improve from experience without being explicitly programmed. In monitoring, machine learning can be used to detect patterns and anomalies in large datasets, enabling users to identify potential issues before they become critical. The importance of machine learning in monitoring lies in its ability to analyze vast amounts of data in real-time and provide actionable insights that can help optimize system performance and prevent downtime. Machine learning can also improve the efficiency and scalability of monitoring systems, enabling organizations to monitor complex infrastructures and applications with ease. + +The primary goal of implementing machine learning features in Netdata is to enable users to detect and alert on anomalies in their systems with advanced anomaly detection capabilities. Netdata's machine learning features are designed to be highly customizable and scalable, so users can tailor the ML models and training process to their specific requirements and monitor systems of any size or complexity. + 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. -Once ML is enabled, Netdata will begin training a model for each dimension. By default this model is a [k-means clustering](https://en.wikipedia.org/wiki/K-means_clustering) model trained on the most recent 4 hours of data. Rather than just using the most recent value of each raw metric, the model works on a preprocessed ["feature vector"](#feature-vector) of recent smoothed and differenced values. This should enable the model to detect a wider range of potentially anomalous patterns in recent observations as opposed to just point anomalies like big spikes or drops. ([This infographic](https://user-images.githubusercontent.com/2178292/144414415-275a3477-5b47-43d6-8959-509eb48ebb20.png) shows some different types of anomalies.) +Once ML is enabled, Netdata will begin training a model for each dimension. By default this model is a [k-means clustering](https://en.wikipedia.org/wiki/K-means_clustering) model trained on the most recent 4 hours of data. Rather than just using the most recent value of each raw metric, the model works on a preprocessed ["feature vector"](#feature-vector) of recent smoothed and differenced values. This enables the model to detect a wider range of potentially anomalous patterns in recent observations as opposed to just point anomalies like big spikes or drops. ([This infographic](https://user-images.githubusercontent.com/2178292/144414415-275a3477-5b47-43d6-8959-509eb48ebb20.png) shows some different types of anomalies). The sections below will introduce some of the main concepts: - anomaly bit @@ -114,7 +118,9 @@ 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://github.com/netdata/netdata/blob/master/docs/guides/step-by-step/step-04.md). +> 📑 Note +> +> If you would like to learn more about configuring Netdata please see the [Configuration section](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md) of our documentation. Below is a list of all the available configuration params and their default values. @@ -266,3 +272,4 @@ The anomaly rate across all dimensions of a node. - 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://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. +- Check out our ML related blog posts over at [https://blog.netdata.cloud](https://blog.netdata.cloud/tags/machine-learning) diff --git a/ml/SamplesBuffer.cc b/ml/SamplesBuffer.cc deleted file mode 100644 index 359b60c2..00000000 --- a/ml/SamplesBuffer.cc +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// -#include "SamplesBuffer.h" - -#include -#include -#include - -void Sample::print(std::ostream &OS) const { - for (size_t Idx = 0; Idx != NumDims - 1; Idx++) - OS << CNs[Idx] << ", "; - - OS << CNs[NumDims - 1]; -} - -void SamplesBuffer::print(std::ostream &OS) const { - for (size_t Idx = Preprocessed ? (DiffN + (SmoothN - 1) + (LagN)) : 0; - Idx != NumSamples; Idx++) { - Sample S = Preprocessed ? getPreprocessedSample(Idx) : getSample(Idx); - OS << S << std::endl; - } -} - -std::vector SamplesBuffer::getPreprocessedSamples() const { - std::vector V; - - for (size_t Idx = Preprocessed ? (DiffN + (SmoothN - 1) + (LagN)) : 0; - Idx != NumSamples; Idx++) { - Sample S = Preprocessed ? getPreprocessedSample(Idx) : getSample(Idx); - V.push_back(S); - } - - return V; -} - -void SamplesBuffer::diffSamples() { - // Panda's DataFrame default behaviour is to subtract each element from - // itself. For us `DiffN = 0` means "disable diff-ing" when preprocessing - // the samples buffer. This deviation will make it easier for us to test - // the KMeans implementation. - if (DiffN == 0) - return; - - for (size_t Idx = 0; Idx != (NumSamples - DiffN); Idx++) { - size_t High = (NumSamples - 1) - Idx; - size_t Low = High - DiffN; - - Sample LHS = getSample(High); - Sample RHS = getSample(Low); - - LHS.diff(RHS); - } -} - -void SamplesBuffer::smoothSamples() { - // Holds the mean value of each window - CalculatedNumber AccCNs[1] = { 0 }; - Sample Acc(AccCNs, 1); - - // Used to avoid clobbering the accumulator when moving the window - CalculatedNumber TmpCNs[1] = { 0 }; - Sample Tmp(TmpCNs, 1); - - CalculatedNumber Factor = (CalculatedNumber) 1 / SmoothN; - - // Calculate the value of the 1st window - for (size_t Idx = 0; Idx != std::min(SmoothN, NumSamples); Idx++) { - Tmp.add(getSample(NumSamples - (Idx + 1))); - } - - Acc.add(Tmp); - Acc.scale(Factor); - - // Move the window and update the samples - for (size_t Idx = NumSamples; Idx != (DiffN + SmoothN - 1); Idx--) { - Sample S = getSample(Idx - 1); - - // Tmp <- Next window (if any) - if (Idx >= (SmoothN + 1)) { - Tmp.diff(S); - Tmp.add(getSample(Idx - (SmoothN + 1))); - } - - // S <- Acc - S.copy(Acc); - - // Acc <- Tmp - Acc.copy(Tmp); - Acc.scale(Factor); - } -} - -void SamplesBuffer::lagSamples() { - if (LagN == 0) - return; - - for (size_t Idx = NumSamples; Idx != LagN; Idx--) { - Sample PS = getPreprocessedSample(Idx - 1); - PS.lag(getSample(Idx - 1), LagN); - } -} - -void SamplesBuffer::preprocess(std::vector &Samples) { - assert(Preprocessed == false); - - size_t OutN = NumSamples; - - // Diff - if (DiffN >= OutN) - return; - OutN -= DiffN; - diffSamples(); - - // Smooth - if (SmoothN == 0 || SmoothN > OutN) - return; - OutN -= (SmoothN - 1); - smoothSamples(); - - // Lag - if (LagN >= OutN) - return; - OutN -= LagN; - lagSamples(); - - Samples.reserve(OutN); - Preprocessed = true; - - uint32_t MaxMT = std::numeric_limits::max(); - uint32_t CutOff = static_cast(MaxMT) * SamplingRatio; - - for (size_t Idx = NumSamples - OutN; Idx != NumSamples; Idx++) { - if (RandNums[Idx] > CutOff) - continue; - - DSample DS; - DS.set_size(NumDimsPerSample * (LagN + 1)); - - const Sample PS = getPreprocessedSample(Idx); - PS.initDSample(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(); - - // 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::max(); - uint32_t CutOff = static_cast(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 deleted file mode 100644 index ca60f4b9..00000000 --- a/ml/SamplesBuffer.h +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef SAMPLES_BUFFER_H -#define SAMPLES_BUFFER_H - -#include -#include - -#include -#include -#include - -#include - -typedef double CalculatedNumber; -typedef dlib::matrix DSample; - -class Sample { -public: - Sample(CalculatedNumber *Buf, size_t N) : CNs(Buf), NumDims(N) {} - - void initDSample(DSample &DS) const { - for (size_t Idx = 0; Idx != NumDims; Idx++) { - DS(Idx) = std::abs(CNs[Idx]); - } - } - - void add(const Sample &RHS) const { - assert(NumDims == RHS.NumDims); - - for (size_t Idx = 0; Idx != NumDims; Idx++) - CNs[Idx] += RHS.CNs[Idx]; - }; - - void diff(const Sample &RHS) const { - assert(NumDims == RHS.NumDims); - - for (size_t Idx = 0; Idx != NumDims; Idx++) - CNs[Idx] -= RHS.CNs[Idx]; - }; - - void copy(const Sample &RHS) const { - assert(NumDims == RHS.NumDims); - - std::memcpy(CNs, RHS.CNs, NumDims * sizeof(CalculatedNumber)); - } - - void scale(CalculatedNumber Factor) { - for (size_t Idx = 0; Idx != NumDims; Idx++) - CNs[Idx] *= Factor; - } - - void lag(const Sample &S, size_t LagN) { - size_t N = S.NumDims; - - for (size_t Idx = 0; Idx != (LagN + 1); Idx++) { - Sample Src(S.CNs - (Idx * N), N); - Sample Dst(CNs + (Idx * N), N); - Dst.copy(Src); - } - } - - const CalculatedNumber *getCalculatedNumbers() const { - return CNs; - }; - - void print(std::ostream &OS) const; - -private: - CalculatedNumber *CNs; - size_t NumDims; -}; - -inline std::ostream& operator<<(std::ostream &OS, const Sample &S) { - S.print(OS); - return OS; -} - -class SamplesBuffer { -public: - SamplesBuffer(CalculatedNumber *CNs, - size_t NumSamples, size_t NumDimsPerSample, - size_t DiffN, size_t SmoothN, size_t LagN, - double SamplingRatio, std::vector &RandNums) : - CNs(CNs), NumSamples(NumSamples), NumDimsPerSample(NumDimsPerSample), - DiffN(DiffN), SmoothN(SmoothN), LagN(LagN), - SamplingRatio(SamplingRatio), RandNums(RandNums), - BytesPerSample(NumDimsPerSample * sizeof(CalculatedNumber)), - Preprocessed(false) { - assert(NumDimsPerSample == 1 && "SamplesBuffer supports only one dimension per sample"); - }; - - void preprocess(std::vector &Samples); - void preprocess(DSample &Feature); - std::vector getPreprocessedSamples() const; - - size_t capacity() const { return NumSamples; } - void print(std::ostream &OS) const; - -private: - size_t getSampleOffset(size_t Index) const { - assert(Index < NumSamples); - return Index * NumDimsPerSample; - } - - size_t getPreprocessedSampleOffset(size_t Index) const { - assert(Index < NumSamples); - return getSampleOffset(Index) * (LagN + 1); - } - - void setSample(size_t Index, const Sample &S) const { - size_t Offset = getSampleOffset(Index); - std::memcpy(&CNs[Offset], S.getCalculatedNumbers(), BytesPerSample); - } - - const Sample getSample(size_t Index) const { - size_t Offset = getSampleOffset(Index); - return Sample(&CNs[Offset], NumDimsPerSample); - }; - - const Sample getPreprocessedSample(size_t Index) const { - size_t Offset = getPreprocessedSampleOffset(Index); - return Sample(&CNs[Offset], NumDimsPerSample * (LagN + 1)); - }; - - void diffSamples(); - void smoothSamples(); - void lagSamples(); - -private: - CalculatedNumber *CNs; - size_t NumSamples; - size_t NumDimsPerSample; - size_t DiffN; - size_t SmoothN; - size_t LagN; - double SamplingRatio; - std::vector &RandNums; - - size_t BytesPerSample; - bool Preprocessed; -}; - -inline std::ostream& operator<<(std::ostream& OS, const SamplesBuffer &SB) { - SB.print(OS); - return OS; -} - -#endif /* SAMPLES_BUFFER_H */ diff --git a/ml/Stats.h b/ml/Stats.h deleted file mode 100644 index b99bc39d..00000000 --- a/ml/Stats.h +++ /dev/null @@ -1,46 +0,0 @@ -// 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/ad_charts.cc b/ml/ad_charts.cc new file mode 100644 index 00000000..086cd5aa --- /dev/null +++ b/ml/ad_charts.cc @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ad_charts.h" + +void ml_update_dimensions_chart(ml_host_t *host, const ml_machine_learning_stats_t &mls) { + /* + * Machine learning status + */ + if (Cfg.enable_statistics_charts) { + if (!host->machine_learning_status_rs) { + char id_buf[1024]; + char name_buf[1024]; + + snprintfz(id_buf, 1024, "machine_learning_status_on_%s", localhost->machine_guid); + snprintfz(name_buf, 1024, "machine_learning_status_on_%s", rrdhost_hostname(localhost)); + + host->machine_learning_status_rs = rrdset_create( + host->rh, + "netdata", // type + id_buf, + name_buf, // 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 + localhost->rrd_update_every, // update_every + RRDSET_TYPE_LINE // chart_type + ); + rrdset_flag_set(host->machine_learning_status_rs , RRDSET_FLAG_ANOMALY_DETECTION); + + host->machine_learning_status_enabled_rd = + rrddim_add(host->machine_learning_status_rs, "enabled", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + host->machine_learning_status_disabled_sp_rd = + rrddim_add(host->machine_learning_status_rs, "disabled-sp", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(host->machine_learning_status_rs, + host->machine_learning_status_enabled_rd, mls.num_machine_learning_status_enabled); + rrddim_set_by_pointer(host->machine_learning_status_rs, + host->machine_learning_status_disabled_sp_rd, mls.num_machine_learning_status_disabled_sp); + + rrdset_done(host->machine_learning_status_rs); + } + + /* + * Metric type + */ + if (Cfg.enable_statistics_charts) { + if (!host->metric_type_rs) { + char id_buf[1024]; + char name_buf[1024]; + + snprintfz(id_buf, 1024, "metric_types_on_%s", localhost->machine_guid); + snprintfz(name_buf, 1024, "metric_types_on_%s", rrdhost_hostname(localhost)); + + host->metric_type_rs = rrdset_create( + host->rh, + "netdata", // type + id_buf, // id + name_buf, // 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 + localhost->rrd_update_every, // update_every + RRDSET_TYPE_LINE // chart_type + ); + rrdset_flag_set(host->metric_type_rs, RRDSET_FLAG_ANOMALY_DETECTION); + + host->metric_type_constant_rd = + rrddim_add(host->metric_type_rs, "constant", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + host->metric_type_variable_rd = + rrddim_add(host->metric_type_rs, "variable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(host->metric_type_rs, + host->metric_type_constant_rd, mls.num_metric_type_constant); + rrddim_set_by_pointer(host->metric_type_rs, + host->metric_type_variable_rd, mls.num_metric_type_variable); + + rrdset_done(host->metric_type_rs); + } + + /* + * Training status + */ + if (Cfg.enable_statistics_charts) { + if (!host->training_status_rs) { + char id_buf[1024]; + char name_buf[1024]; + + snprintfz(id_buf, 1024, "training_status_on_%s", localhost->machine_guid); + snprintfz(name_buf, 1024, "training_status_on_%s", rrdhost_hostname(localhost)); + + host->training_status_rs = rrdset_create( + host->rh, + "netdata", // type + id_buf, // id + name_buf, // 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 + localhost->rrd_update_every, // update_every + RRDSET_TYPE_LINE // chart_type + ); + + rrdset_flag_set(host->training_status_rs, RRDSET_FLAG_ANOMALY_DETECTION); + + host->training_status_untrained_rd = + rrddim_add(host->training_status_rs, "untrained", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + host->training_status_pending_without_model_rd = + rrddim_add(host->training_status_rs, "pending-without-model", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + host->training_status_trained_rd = + rrddim_add(host->training_status_rs, "trained", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + host->training_status_pending_with_model_rd = + rrddim_add(host->training_status_rs, "pending-with-model", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(host->training_status_rs, + host->training_status_untrained_rd, mls.num_training_status_untrained); + rrddim_set_by_pointer(host->training_status_rs, + host->training_status_pending_without_model_rd, mls.num_training_status_pending_without_model); + rrddim_set_by_pointer(host->training_status_rs, + host->training_status_trained_rd, mls.num_training_status_trained); + rrddim_set_by_pointer(host->training_status_rs, + host->training_status_pending_with_model_rd, mls.num_training_status_pending_with_model); + + rrdset_done(host->training_status_rs); + } + + /* + * Prediction status + */ + { + if (!host->dimensions_rs) { + char id_buf[1024]; + char name_buf[1024]; + + snprintfz(id_buf, 1024, "dimensions_on_%s", localhost->machine_guid); + snprintfz(name_buf, 1024, "dimensions_on_%s", rrdhost_hostname(localhost)); + + host->dimensions_rs = rrdset_create( + host->rh, + "anomaly_detection", // type + id_buf, // id + name_buf, // 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 + localhost->rrd_update_every, // update_every + RRDSET_TYPE_LINE // chart_type + ); + rrdset_flag_set(host->dimensions_rs, RRDSET_FLAG_ANOMALY_DETECTION); + + host->dimensions_anomalous_rd = + rrddim_add(host->dimensions_rs, "anomalous", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + host->dimensions_normal_rd = + rrddim_add(host->dimensions_rs, "normal", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(host->dimensions_rs, + host->dimensions_anomalous_rd, mls.num_anomalous_dimensions); + rrddim_set_by_pointer(host->dimensions_rs, + host->dimensions_normal_rd, mls.num_normal_dimensions); + + rrdset_done(host->dimensions_rs); + } +} + +void ml_update_host_and_detection_rate_charts(ml_host_t *host, collected_number AnomalyRate) { + /* + * Anomaly rate + */ + { + if (!host->anomaly_rate_rs) { + char id_buf[1024]; + char name_buf[1024]; + + snprintfz(id_buf, 1024, "anomaly_rate_on_%s", localhost->machine_guid); + snprintfz(name_buf, 1024, "anomaly_rate_on_%s", rrdhost_hostname(localhost)); + + host->anomaly_rate_rs = rrdset_create( + host->rh, + "anomaly_detection", // type + id_buf, // id + name_buf, // name + "anomaly_rate", // family + "anomaly_detection.anomaly_rate", // ctx + "Percentage of anomalous dimensions", // title + "percentage", // units + NETDATA_ML_PLUGIN, // plugin + NETDATA_ML_MODULE_DETECTION, // module + ML_CHART_PRIO_ANOMALY_RATE, // priority + localhost->rrd_update_every, // update_every + RRDSET_TYPE_LINE // chart_type + ); + rrdset_flag_set(host->anomaly_rate_rs, RRDSET_FLAG_ANOMALY_DETECTION); + + host->anomaly_rate_rd = + rrddim_add(host->anomaly_rate_rs, "anomaly_rate", NULL, 1, 100, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(host->anomaly_rate_rs, host->anomaly_rate_rd, AnomalyRate); + + rrdset_done(host->anomaly_rate_rs); + } + + /* + * Detector Events + */ + { + if (!host->detector_events_rs) { + char id_buf[1024]; + char name_buf[1024]; + + snprintfz(id_buf, 1024, "anomaly_detection_on_%s", localhost->machine_guid); + snprintfz(name_buf, 1024, "anomaly_detection_on_%s", rrdhost_hostname(localhost)); + + host->detector_events_rs = rrdset_create( + host->rh, + "anomaly_detection", // type + id_buf, // id + name_buf, // name + "anomaly_detection", // family + "anomaly_detection.detector_events", // ctx + "Anomaly detection events", // title + "percentage", // units + NETDATA_ML_PLUGIN, // plugin + NETDATA_ML_MODULE_DETECTION, // module + ML_CHART_PRIO_DETECTOR_EVENTS, // priority + localhost->rrd_update_every, // update_every + RRDSET_TYPE_LINE // chart_type + ); + rrdset_flag_set(host->detector_events_rs, RRDSET_FLAG_ANOMALY_DETECTION); + + host->detector_events_above_threshold_rd = + rrddim_add(host->detector_events_rs, "above_threshold", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + host->detector_events_new_anomaly_event_rd = + rrddim_add(host->detector_events_rs, "new_anomaly_event", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + /* + * Compute the values of the dimensions based on the host rate chart + */ + ONEWAYALLOC *OWA = onewayalloc_create(0); + time_t Now = now_realtime_sec(); + time_t Before = Now - host->rh->rrd_update_every; + time_t After = Before - Cfg.anomaly_detection_query_duration; + RRDR_OPTIONS Options = static_cast(0x00000000); + + RRDR *R = rrd2rrdr_legacy( + OWA, + host->anomaly_rate_rs, + 1 /* points wanted */, + After, + Before, + Cfg.anomaly_detection_grouping_method, + 0 /* resampling time */, + Options, "anomaly_rate", + NULL /* group options */, + 0, /* timeout */ + 0, /* tier */ + QUERY_SOURCE_ML, + STORAGE_PRIORITY_SYNCHRONOUS + ); + + if (R) { + if (R->d == 1 && R->n == 1 && R->rows == 1) { + static thread_local bool prev_above_threshold = false; + bool above_threshold = R->v[0] >= Cfg.host_anomaly_rate_threshold; + bool new_anomaly_event = above_threshold && !prev_above_threshold; + prev_above_threshold = above_threshold; + + rrddim_set_by_pointer(host->detector_events_rs, + host->detector_events_above_threshold_rd, above_threshold); + rrddim_set_by_pointer(host->detector_events_rs, + host->detector_events_new_anomaly_event_rd, new_anomaly_event); + + rrdset_done(host->detector_events_rs); + } + + rrdr_free(OWA, R); + } + + onewayalloc_destroy(OWA); + } +} + +void ml_update_training_statistics_chart(ml_training_thread_t *training_thread, const ml_training_stats_t &ts) { + /* + * queue stats + */ + { + if (!training_thread->queue_stats_rs) { + char id_buf[1024]; + char name_buf[1024]; + + snprintfz(id_buf, 1024, "training_queue_%zu_stats", training_thread->id); + snprintfz(name_buf, 1024, "training_queue_%zu_stats", training_thread->id); + + training_thread->queue_stats_rs = rrdset_create( + localhost, + "netdata", // type + id_buf, // id + name_buf, // 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 + localhost->rrd_update_every, // update_every + RRDSET_TYPE_LINE// chart_type + ); + rrdset_flag_set(training_thread->queue_stats_rs, RRDSET_FLAG_ANOMALY_DETECTION); + + training_thread->queue_stats_queue_size_rd = + rrddim_add(training_thread->queue_stats_rs, "queue_size", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + training_thread->queue_stats_popped_items_rd = + rrddim_add(training_thread->queue_stats_rs, "popped_items", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(training_thread->queue_stats_rs, + training_thread->queue_stats_queue_size_rd, ts.queue_size); + rrddim_set_by_pointer(training_thread->queue_stats_rs, + training_thread->queue_stats_popped_items_rd, ts.num_popped_items); + + rrdset_done(training_thread->queue_stats_rs); + } + + /* + * training stats + */ + { + if (!training_thread->training_time_stats_rs) { + char id_buf[1024]; + char name_buf[1024]; + + snprintfz(id_buf, 1024, "training_queue_%zu_time_stats", training_thread->id); + snprintfz(name_buf, 1024, "training_queue_%zu_time_stats", training_thread->id); + + training_thread->training_time_stats_rs = rrdset_create( + localhost, + "netdata", // type + id_buf, // id + name_buf, // 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 + localhost->rrd_update_every, // update_every + RRDSET_TYPE_LINE// chart_type + ); + rrdset_flag_set(training_thread->training_time_stats_rs, RRDSET_FLAG_ANOMALY_DETECTION); + + training_thread->training_time_stats_allotted_rd = + rrddim_add(training_thread->training_time_stats_rs, "allotted", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); + training_thread->training_time_stats_consumed_rd = + rrddim_add(training_thread->training_time_stats_rs, "consumed", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); + training_thread->training_time_stats_remaining_rd = + rrddim_add(training_thread->training_time_stats_rs, "remaining", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(training_thread->training_time_stats_rs, + training_thread->training_time_stats_allotted_rd, ts.allotted_ut); + rrddim_set_by_pointer(training_thread->training_time_stats_rs, + training_thread->training_time_stats_consumed_rd, ts.consumed_ut); + rrddim_set_by_pointer(training_thread->training_time_stats_rs, + training_thread->training_time_stats_remaining_rd, ts.remaining_ut); + + rrdset_done(training_thread->training_time_stats_rs); + } + + /* + * training result stats + */ + { + if (!training_thread->training_results_rs) { + char id_buf[1024]; + char name_buf[1024]; + + snprintfz(id_buf, 1024, "training_queue_%zu_results", training_thread->id); + snprintfz(name_buf, 1024, "training_queue_%zu_results", training_thread->id); + + training_thread->training_results_rs = rrdset_create( + localhost, + "netdata", // type + id_buf, // id + name_buf, // 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 + localhost->rrd_update_every, // update_every + RRDSET_TYPE_LINE// chart_type + ); + rrdset_flag_set(training_thread->training_results_rs, RRDSET_FLAG_ANOMALY_DETECTION); + + training_thread->training_results_ok_rd = + rrddim_add(training_thread->training_results_rs, "ok", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + training_thread->training_results_invalid_query_time_range_rd = + rrddim_add(training_thread->training_results_rs, "invalid-queries", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + training_thread->training_results_not_enough_collected_values_rd = + rrddim_add(training_thread->training_results_rs, "not-enough-values", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + training_thread->training_results_null_acquired_dimension_rd = + rrddim_add(training_thread->training_results_rs, "null-acquired-dimensions", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + training_thread->training_results_chart_under_replication_rd = + rrddim_add(training_thread->training_results_rs, "chart-under-replication", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); + } + + rrddim_set_by_pointer(training_thread->training_results_rs, + training_thread->training_results_ok_rd, ts.training_result_ok); + rrddim_set_by_pointer(training_thread->training_results_rs, + training_thread->training_results_invalid_query_time_range_rd, ts.training_result_invalid_query_time_range); + rrddim_set_by_pointer(training_thread->training_results_rs, + training_thread->training_results_not_enough_collected_values_rd, ts.training_result_not_enough_collected_values); + rrddim_set_by_pointer(training_thread->training_results_rs, + training_thread->training_results_null_acquired_dimension_rd, ts.training_result_null_acquired_dimension); + rrddim_set_by_pointer(training_thread->training_results_rs, + training_thread->training_results_chart_under_replication_rd, ts.training_result_chart_under_replication); + + rrdset_done(training_thread->training_results_rs); + } +} + +void ml_update_global_statistics_charts(uint64_t models_consulted) { + if (Cfg.enable_statistics_charts) { + 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) models_consulted); + + rrdset_done(st); + } +} diff --git a/ml/ad_charts.h b/ml/ad_charts.h new file mode 100644 index 00000000..349b369a --- /dev/null +++ b/ml/ad_charts.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef ML_ADCHARTS_H +#define ML_ADCHARTS_H + +#include "ml-private.h" + +void ml_update_dimensions_chart(ml_host_t *host, const ml_machine_learning_stats_t &mls); + +void ml_update_host_and_detection_rate_charts(ml_host_t *host, collected_number anomaly_rate); + +void ml_update_training_statistics_chart(ml_training_thread_t *training_thread, const ml_training_stats_t &ts); + +#endif /* ML_ADCHARTS_H */ diff --git a/ml/ml-dummy.c b/ml/ml-dummy.c index 17801889..708ab68e 100644 --- a/ml/ml-dummy.c +++ b/ml/ml-dummy.c @@ -8,78 +8,97 @@ bool ml_capable() { return false; } -bool ml_enabled(RRDHOST *RH) { - (void) RH; +bool ml_enabled(RRDHOST *rh) { + UNUSED(rh); + return false; +} + +bool ml_streaming_enabled() { return false; } void ml_init(void) {} -void ml_host_new(RRDHOST *RH) { - UNUSED(RH); +void ml_fini(void) {} + +void ml_start_threads(void) {} + +void ml_stop_threads(void) {} + +void ml_host_new(RRDHOST *rh) { + UNUSED(rh); } -void ml_host_delete(RRDHOST *RH) { - UNUSED(RH); +void ml_host_delete(RRDHOST *rh) { + UNUSED(rh); } -void ml_chart_new(RRDSET *RS) { - UNUSED(RS); +void ml_host_start_training_thread(RRDHOST *rh) { + UNUSED(rh); } -void ml_chart_delete(RRDSET *RS) { - UNUSED(RS); +void ml_host_stop_training_thread(RRDHOST *rh) { + UNUSED(rh); } -void ml_dimension_new(RRDDIM *RD) { - UNUSED(RD); +void ml_host_cancel_training_thread(RRDHOST *rh) { + UNUSED(rh); } -void ml_dimension_delete(RRDDIM *RD) { - UNUSED(RD); +void ml_host_get_info(RRDHOST *rh, BUFFER *wb) { + UNUSED(rh); + UNUSED(wb); } -void ml_start_anomaly_detection_threads(RRDHOST *RH) { - UNUSED(RH); +void ml_host_get_models(RRDHOST *rh, BUFFER *wb) { + UNUSED(rh); + UNUSED(wb); } -void ml_stop_anomaly_detection_threads(RRDHOST *RH) { - UNUSED(RH); +void ml_host_get_runtime_info(RRDHOST *rh) { + UNUSED(rh); } -char *ml_get_host_info(RRDHOST *RH) { - (void) RH; - return NULL; +void ml_chart_new(RRDSET *rs) { + UNUSED(rs); } -char *ml_get_host_runtime_info(RRDHOST *RH) { - (void) RH; - return NULL; +void ml_chart_delete(RRDSET *rs) { + UNUSED(rs); } -void ml_chart_update_begin(RRDSET *RS) { - (void) RS; +bool ml_chart_update_begin(RRDSET *rs) { + UNUSED(rs); + return false; } -void ml_chart_update_end(RRDSET *RS) { - (void) RS; +void ml_chart_update_end(RRDSET *rs) { + UNUSED(rs); } -char *ml_get_host_models(RRDHOST *RH) { - (void) RH; - return NULL; +void ml_dimension_new(RRDDIM *rd) { + UNUSED(rd); } -bool ml_is_anomalous(RRDDIM *RD, time_t CurrT, double Value, bool Exists) { - (void) RD; - (void) CurrT; - (void) Value; - (void) Exists; - return false; +void ml_dimension_delete(RRDDIM *rd) { + UNUSED(rd); } -bool ml_streaming_enabled() { +bool ml_dimension_is_anomalous(RRDDIM *rd, time_t curr_time, double value, bool exists) { + UNUSED(rd); + UNUSED(curr_time); + UNUSED(value); + UNUSED(exists); return false; } +int ml_dimension_load_models(RRDDIM *rd) { + UNUSED(rd); + return 0; +} + +void ml_update_global_statistics_charts(uint64_t models_consulted) { + UNUSED(models_consulted); +} + #endif diff --git a/ml/ml-private.h b/ml/ml-private.h index e479f235..327cc59a 100644 --- a/ml/ml-private.h +++ b/ml/ml-private.h @@ -1,13 +1,335 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef ML_PRIVATE_H -#define ML_PRIVATE_H +#ifndef NETDATA_ML_PRIVATE_H +#define NETDATA_ML_PRIVATE_H -#include "KMeans.h" +#include "dlib/matrix.h" #include "ml/ml.h" -#include -#include -#include +#include +#include -#endif /* ML_PRIVATE_H */ +typedef double calculated_number_t; +typedef dlib::matrix DSample; + +/* + * Features + */ + +typedef struct { + size_t diff_n; + size_t smooth_n; + size_t lag_n; + + calculated_number_t *dst; + size_t dst_n; + + calculated_number_t *src; + size_t src_n; + + std::vector &preprocessed_features; +} ml_features_t; + +/* + * KMeans + */ + +typedef struct { + std::vector cluster_centers; + + calculated_number_t min_dist; + calculated_number_t max_dist; + + uint32_t after; + uint32_t before; +} ml_kmeans_t; + +typedef struct machine_learning_stats_t { + size_t num_machine_learning_status_enabled; + size_t num_machine_learning_status_disabled_sp; + + size_t num_metric_type_constant; + size_t num_metric_type_variable; + + size_t num_training_status_untrained; + size_t num_training_status_pending_without_model; + size_t num_training_status_trained; + size_t num_training_status_pending_with_model; + + size_t num_anomalous_dimensions; + size_t num_normal_dimensions; +} ml_machine_learning_stats_t; + +typedef struct training_stats_t { + size_t queue_size; + size_t num_popped_items; + + usec_t allotted_ut; + usec_t consumed_ut; + usec_t remaining_ut; + + size_t training_result_ok; + size_t training_result_invalid_query_time_range; + size_t training_result_not_enough_collected_values; + size_t training_result_null_acquired_dimension; + size_t training_result_chart_under_replication; +} ml_training_stats_t; + +enum ml_metric_type { + // The dimension has constant values, no need to train + METRIC_TYPE_CONSTANT, + + // The dimension's values fluctuate, we need to generate a model + METRIC_TYPE_VARIABLE, +}; + +enum ml_machine_learning_status { + // Enable training/prediction + MACHINE_LEARNING_STATUS_ENABLED, + + // Disable because configuration pattern matches the chart's id + MACHINE_LEARNING_STATUS_DISABLED_DUE_TO_EXCLUDED_CHART, +}; + +enum ml_training_status { + // We don't have a model for this dimension + TRAINING_STATUS_UNTRAINED, + + // Request for training sent, but we don't have any models yet + TRAINING_STATUS_PENDING_WITHOUT_MODEL, + + // Request to update existing models sent + TRAINING_STATUS_PENDING_WITH_MODEL, + + // Have a valid, up-to-date model + TRAINING_STATUS_TRAINED, +}; + +enum ml_training_result { + // We managed to create a KMeans model + TRAINING_RESULT_OK, + + // Could not query DB with a correct time range + TRAINING_RESULT_INVALID_QUERY_TIME_RANGE, + + // Did not gather enough data from DB to run KMeans + TRAINING_RESULT_NOT_ENOUGH_COLLECTED_VALUES, + + // Acquired a null dimension + TRAINING_RESULT_NULL_ACQUIRED_DIMENSION, + + // Chart is under replication + TRAINING_RESULT_CHART_UNDER_REPLICATION, +}; + +typedef struct { + // Chart/dimension we want to train + char machine_guid[GUID_LEN + 1]; + STRING *chart_id; + STRING *dimension_id; + + // Creation time of request + time_t request_time; + + // First/last entry of this dimension in DB + // at the point the request was made + time_t first_entry_on_request; + time_t last_entry_on_request; +} ml_training_request_t; + +typedef struct { + // Time when the request for this response was made + time_t request_time; + + // First/last entry of the dimension in DB when generating the request + time_t first_entry_on_request; + time_t last_entry_on_request; + + // First/last entry of the dimension in DB when generating the response + time_t first_entry_on_response; + time_t last_entry_on_response; + + // After/Before timestamps of our DB query + time_t query_after_t; + time_t query_before_t; + + // Actual after/before returned by the DB query ops + time_t db_after_t; + time_t db_before_t; + + // Number of doubles returned by the DB query + size_t collected_values; + + // Number of values we return to the caller + size_t total_values; + + // Result of training response + enum ml_training_result result; +} ml_training_response_t; + +/* + * Queue +*/ + +typedef struct { + std::queue internal; + netdata_mutex_t mutex; + pthread_cond_t cond_var; + std::atomic exit; +} ml_queue_t; + +typedef struct { + RRDDIM *rd; + + enum ml_metric_type mt; + enum ml_training_status ts; + enum ml_machine_learning_status mls; + + ml_training_response_t tr; + time_t last_training_time; + + std::vector cns; + + std::vector km_contexts; + netdata_mutex_t mutex; + ml_kmeans_t kmeans; + std::vector feature; +} ml_dimension_t; + +typedef struct { + RRDSET *rs; + ml_machine_learning_stats_t mls; + + netdata_mutex_t mutex; +} ml_chart_t; + +void ml_chart_update_dimension(ml_chart_t *chart, ml_dimension_t *dim, bool is_anomalous); + +typedef struct { + RRDHOST *rh; + + ml_machine_learning_stats_t mls; + + calculated_number_t host_anomaly_rate; + + netdata_mutex_t mutex; + + ml_queue_t *training_queue; + + /* + * bookkeeping for anomaly detection charts + */ + + RRDSET *machine_learning_status_rs; + RRDDIM *machine_learning_status_enabled_rd; + RRDDIM *machine_learning_status_disabled_sp_rd; + + RRDSET *metric_type_rs; + RRDDIM *metric_type_constant_rd; + RRDDIM *metric_type_variable_rd; + + RRDSET *training_status_rs; + RRDDIM *training_status_untrained_rd; + RRDDIM *training_status_pending_without_model_rd; + RRDDIM *training_status_trained_rd; + RRDDIM *training_status_pending_with_model_rd; + + RRDSET *dimensions_rs; + RRDDIM *dimensions_anomalous_rd; + RRDDIM *dimensions_normal_rd; + + RRDSET *anomaly_rate_rs; + RRDDIM *anomaly_rate_rd; + + RRDSET *detector_events_rs; + RRDDIM *detector_events_above_threshold_rd; + RRDDIM *detector_events_new_anomaly_event_rd; +} ml_host_t; + +typedef struct { + uuid_t metric_uuid; + ml_kmeans_t kmeans; +} ml_model_info_t; + +typedef struct { + size_t id; + netdata_thread_t nd_thread; + netdata_mutex_t nd_mutex; + + ml_queue_t *training_queue; + ml_training_stats_t training_stats; + + calculated_number_t *training_cns; + calculated_number_t *scratch_training_cns; + std::vector training_samples; + + std::vector pending_model_info; + + RRDSET *queue_stats_rs; + RRDDIM *queue_stats_queue_size_rd; + RRDDIM *queue_stats_popped_items_rd; + + RRDSET *training_time_stats_rs; + RRDDIM *training_time_stats_allotted_rd; + RRDDIM *training_time_stats_consumed_rd; + RRDDIM *training_time_stats_remaining_rd; + + RRDSET *training_results_rs; + RRDDIM *training_results_ok_rd; + RRDDIM *training_results_invalid_query_time_range_rd; + RRDDIM *training_results_not_enough_collected_values_rd; + RRDDIM *training_results_null_acquired_dimension_rd; + RRDDIM *training_results_chart_under_replication_rd; +} ml_training_thread_t; + +typedef struct { + bool enable_anomaly_detection; + + unsigned max_train_samples; + unsigned min_train_samples; + unsigned train_every; + + unsigned num_models_to_use; + + unsigned db_engine_anomaly_rate_every; + + unsigned diff_n; + unsigned smooth_n; + unsigned lag_n; + + double random_sampling_ratio; + unsigned max_kmeans_iters; + + double dimension_anomaly_score_threshold; + + double host_anomaly_rate_threshold; + RRDR_TIME_GROUPING anomaly_detection_grouping_method; + time_t anomaly_detection_query_duration; + + bool stream_anomaly_detection_charts; + + std::string hosts_to_skip; + SIMPLE_PATTERN *sp_host_to_skip; + + std::string charts_to_skip; + SIMPLE_PATTERN *sp_charts_to_skip; + + std::vector random_nums; + + netdata_thread_t detection_thread; + std::atomic detection_stop; + + size_t num_training_threads; + size_t flush_models_batch_size; + + std::vector training_threads; + std::atomic training_stop; + + bool enable_statistics_charts; +} ml_config_t; + +void ml_config_load(ml_config_t *cfg); + +extern ml_config_t Cfg; + +#endif /* NETDATA_ML_PRIVATE_H */ diff --git a/ml/ml.cc b/ml/ml.cc index 461c83ba..1f49f4bf 100644 --- a/ml/ml.cc +++ b/ml/ml.cc @@ -1,202 +1,1621 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include "Config.h" -#include "Dimension.h" -#include "Chart.h" -#include "Host.h" +#include + +#include "ml-private.h" #include -using namespace ml; +#include "ad_charts.h" +#include "database/sqlite/sqlite3.h" + +#define WORKER_TRAIN_QUEUE_POP 0 +#define WORKER_TRAIN_ACQUIRE_DIMENSION 1 +#define WORKER_TRAIN_QUERY 2 +#define WORKER_TRAIN_KMEANS 3 +#define WORKER_TRAIN_UPDATE_MODELS 4 +#define WORKER_TRAIN_RELEASE_DIMENSION 5 +#define WORKER_TRAIN_UPDATE_HOST 6 +#define WORKER_TRAIN_FLUSH_MODELS 7 + +static sqlite3 *db = NULL; +static netdata_mutex_t db_mutex = NETDATA_MUTEX_INITIALIZER; + +/* + * Functions to convert enums to strings +*/ + +__attribute__((unused)) static const char * +ml_machine_learning_status_to_string(enum ml_machine_learning_status mls) +{ + switch (mls) { + case MACHINE_LEARNING_STATUS_ENABLED: + return "enabled"; + case MACHINE_LEARNING_STATUS_DISABLED_DUE_TO_EXCLUDED_CHART: + return "disabled-sp"; + default: + return "unknown"; + } +} + +__attribute__((unused)) static const char * +ml_metric_type_to_string(enum ml_metric_type mt) +{ + switch (mt) { + case METRIC_TYPE_CONSTANT: + return "constant"; + case METRIC_TYPE_VARIABLE: + return "variable"; + default: + return "unknown"; + } +} + +__attribute__((unused)) static const char * +ml_training_status_to_string(enum ml_training_status ts) +{ + switch (ts) { + case TRAINING_STATUS_PENDING_WITH_MODEL: + return "pending-with-model"; + case TRAINING_STATUS_PENDING_WITHOUT_MODEL: + return "pending-without-model"; + case TRAINING_STATUS_TRAINED: + return "trained"; + case TRAINING_STATUS_UNTRAINED: + return "untrained"; + default: + return "unknown"; + } +} + +__attribute__((unused)) static const char * +ml_training_result_to_string(enum ml_training_result tr) +{ + switch (tr) { + case TRAINING_RESULT_OK: + return "ok"; + case TRAINING_RESULT_INVALID_QUERY_TIME_RANGE: + return "invalid-query"; + case TRAINING_RESULT_NOT_ENOUGH_COLLECTED_VALUES: + return "missing-values"; + case TRAINING_RESULT_NULL_ACQUIRED_DIMENSION: + return "null-acquired-dim"; + case TRAINING_RESULT_CHART_UNDER_REPLICATION: + return "chart-under-replication"; + default: + return "unknown"; + } +} + +/* + * Features +*/ + +// subtract elements that are `diff_n` positions apart +static void +ml_features_diff(ml_features_t *features) +{ + if (features->diff_n == 0) + return; + + for (size_t idx = 0; idx != (features->src_n - features->diff_n); idx++) { + size_t high = (features->src_n - 1) - idx; + size_t low = high - features->diff_n; + + features->dst[low] = features->src[high] - features->src[low]; + } + + size_t n = features->src_n - features->diff_n; + memcpy(features->src, features->dst, n * sizeof(calculated_number_t)); + + for (size_t idx = features->src_n - features->diff_n; idx != features->src_n; idx++) + features->src[idx] = 0.0; +} + +// a function that computes the window average of an array inplace +static void +ml_features_smooth(ml_features_t *features) +{ + calculated_number_t sum = 0.0; + + size_t idx = 0; + for (; idx != features->smooth_n - 1; idx++) + sum += features->src[idx]; + + for (; idx != (features->src_n - features->diff_n); idx++) { + sum += features->src[idx]; + calculated_number_t prev_cn = features->src[idx - (features->smooth_n - 1)]; + features->src[idx - (features->smooth_n - 1)] = sum / features->smooth_n; + sum -= prev_cn; + } + + for (idx = 0; idx != features->smooth_n; idx++) + features->src[(features->src_n - 1) - idx] = 0.0; +} + +// create lag'd vectors out of the preprocessed buffer +static void +ml_features_lag(ml_features_t *features) +{ + size_t n = features->src_n - features->diff_n - features->smooth_n + 1 - features->lag_n; + features->preprocessed_features.resize(n); + + unsigned target_num_samples = Cfg.max_train_samples * Cfg.random_sampling_ratio; + double sampling_ratio = std::min(static_cast(target_num_samples) / n, 1.0); + + uint32_t max_mt = std::numeric_limits::max(); + uint32_t cutoff = static_cast(max_mt) * sampling_ratio; + + size_t sample_idx = 0; + + for (size_t idx = 0; idx != n; idx++) { + DSample &DS = features->preprocessed_features[sample_idx++]; + DS.set_size(features->lag_n); + + if (Cfg.random_nums[idx] > cutoff) { + sample_idx--; + continue; + } + + for (size_t feature_idx = 0; feature_idx != features->lag_n + 1; feature_idx++) + DS(feature_idx) = features->src[idx + feature_idx]; + } + + features->preprocessed_features.resize(sample_idx); +} + +static void +ml_features_preprocess(ml_features_t *features) +{ + ml_features_diff(features); + ml_features_smooth(features); + ml_features_lag(features); +} + +/* + * KMeans +*/ + +static void +ml_kmeans_init(ml_kmeans_t *kmeans) +{ + kmeans->cluster_centers.reserve(2); + kmeans->min_dist = std::numeric_limits::max(); + kmeans->max_dist = std::numeric_limits::min(); +} + +static void +ml_kmeans_train(ml_kmeans_t *kmeans, const ml_features_t *features, time_t after, time_t before) +{ + kmeans->after = (uint32_t) after; + kmeans->before = (uint32_t) before; + + kmeans->min_dist = std::numeric_limits::max(); + kmeans->max_dist = std::numeric_limits::min(); + + kmeans->cluster_centers.clear(); + + dlib::pick_initial_centers(2, kmeans->cluster_centers, features->preprocessed_features); + dlib::find_clusters_using_kmeans(features->preprocessed_features, kmeans->cluster_centers, Cfg.max_kmeans_iters); + + for (const auto &preprocessed_feature : features->preprocessed_features) { + calculated_number_t mean_dist = 0.0; + + for (const auto &cluster_center : kmeans->cluster_centers) { + mean_dist += dlib::length(cluster_center - preprocessed_feature); + } + + mean_dist /= kmeans->cluster_centers.size(); + + if (mean_dist < kmeans->min_dist) + kmeans->min_dist = mean_dist; + + if (mean_dist > kmeans->max_dist) + kmeans->max_dist = mean_dist; + } +} + +static calculated_number_t +ml_kmeans_anomaly_score(const ml_kmeans_t *kmeans, const DSample &DS) +{ + calculated_number_t mean_dist = 0.0; + for (const auto &CC: kmeans->cluster_centers) + mean_dist += dlib::length(CC - DS); + + mean_dist /= kmeans->cluster_centers.size(); + + if (kmeans->max_dist == kmeans->min_dist) + return 0.0; + + calculated_number_t anomaly_score = 100.0 * std::abs((mean_dist - kmeans->min_dist) / (kmeans->max_dist - kmeans->min_dist)); + return (anomaly_score > 100.0) ? 100.0 : anomaly_score; +} + +/* + * Queue +*/ + +static ml_queue_t * +ml_queue_init() +{ + ml_queue_t *q = new ml_queue_t(); + + netdata_mutex_init(&q->mutex); + pthread_cond_init(&q->cond_var, NULL); + q->exit = false; + return q; +} + +static void +ml_queue_destroy(ml_queue_t *q) +{ + netdata_mutex_destroy(&q->mutex); + pthread_cond_destroy(&q->cond_var); + delete q; +} + +static void +ml_queue_push(ml_queue_t *q, const ml_training_request_t req) +{ + netdata_mutex_lock(&q->mutex); + q->internal.push(req); + pthread_cond_signal(&q->cond_var); + netdata_mutex_unlock(&q->mutex); +} + +static ml_training_request_t +ml_queue_pop(ml_queue_t *q) +{ + netdata_mutex_lock(&q->mutex); + + ml_training_request_t req = { + {'\0'}, // machine_guid + NULL, // chart id + NULL, // dimension id + 0, // current time + 0, // first entry + 0 // last entry + }; + + while (q->internal.empty()) { + pthread_cond_wait(&q->cond_var, &q->mutex); + + if (q->exit) { + netdata_mutex_unlock(&q->mutex); + + // We return a dummy request because the queue has been signaled + return req; + } + } + + req = q->internal.front(); + q->internal.pop(); + + netdata_mutex_unlock(&q->mutex); + return req; +} + +static size_t +ml_queue_size(ml_queue_t *q) +{ + netdata_mutex_lock(&q->mutex); + size_t size = q->internal.size(); + netdata_mutex_unlock(&q->mutex); + return size; +} + +static void +ml_queue_signal(ml_queue_t *q) +{ + netdata_mutex_lock(&q->mutex); + q->exit = true; + pthread_cond_signal(&q->cond_var); + netdata_mutex_unlock(&q->mutex); +} + +/* + * Dimension +*/ + +static std::pair +ml_dimension_calculated_numbers(ml_training_thread_t *training_thread, ml_dimension_t *dim, const ml_training_request_t &training_request) +{ + ml_training_response_t training_response = {}; + + training_response.request_time = training_request.request_time; + training_response.first_entry_on_request = training_request.first_entry_on_request; + training_response.last_entry_on_request = training_request.last_entry_on_request; + + training_response.first_entry_on_response = rrddim_first_entry_s_of_tier(dim->rd, 0); + training_response.last_entry_on_response = rrddim_last_entry_s_of_tier(dim->rd, 0); + + size_t min_n = Cfg.min_train_samples; + size_t max_n = Cfg.max_train_samples; + + // Figure out what our time window should be. + training_response.query_before_t = training_response.last_entry_on_response; + training_response.query_after_t = std::max( + training_response.query_before_t - static_cast((max_n - 1) * dim->rd->update_every), + training_response.first_entry_on_response + ); + + if (training_response.query_after_t >= training_response.query_before_t) { + training_response.result = TRAINING_RESULT_INVALID_QUERY_TIME_RANGE; + return { NULL, training_response }; + } + + if (rrdset_is_replicating(dim->rd->rrdset)) { + training_response.result = TRAINING_RESULT_CHART_UNDER_REPLICATION; + return { NULL, training_response }; + } + + /* + * Execute the query + */ + struct storage_engine_query_handle handle; + + storage_engine_query_init(dim->rd->tiers[0].backend, dim->rd->tiers[0].db_metric_handle, &handle, + training_response.query_after_t, training_response.query_before_t, + STORAGE_PRIORITY_BEST_EFFORT); + + size_t idx = 0; + memset(training_thread->training_cns, 0, sizeof(calculated_number_t) * max_n * (Cfg.lag_n + 1)); + calculated_number_t last_value = std::numeric_limits::quiet_NaN(); + + while (!storage_engine_query_is_finished(&handle)) { + if (idx == max_n) + break; + + STORAGE_POINT sp = storage_engine_query_next_metric(&handle); + + time_t timestamp = sp.end_time_s; + calculated_number_t value = sp.sum / sp.count; + + if (netdata_double_isnumber(value)) { + if (!training_response.db_after_t) + training_response.db_after_t = timestamp; + training_response.db_before_t = timestamp; + + training_thread->training_cns[idx] = value; + last_value = training_thread->training_cns[idx]; + training_response.collected_values++; + } else + training_thread->training_cns[idx] = last_value; + + idx++; + } + storage_engine_query_finalize(&handle); + + global_statistics_ml_query_completed(/* points_read */ idx); + + training_response.total_values = idx; + if (training_response.collected_values < min_n) { + training_response.result = TRAINING_RESULT_NOT_ENOUGH_COLLECTED_VALUES; + return { NULL, training_response }; + } + + // Find first non-NaN value. + for (idx = 0; std::isnan(training_thread->training_cns[idx]); idx++, training_response.total_values--) { } + + // Overwrite NaN values. + if (idx != 0) + memmove(training_thread->training_cns, &training_thread->training_cns[idx], sizeof(calculated_number_t) * training_response.total_values); + + training_response.result = TRAINING_RESULT_OK; + return { training_thread->training_cns, training_response }; +} + +const char *db_models_create_table = + "CREATE TABLE IF NOT EXISTS models(" + " dim_id BLOB, after INT, before INT," + " min_dist REAL, max_dist REAL," + " c00 REAL, c01 REAL, c02 REAL, c03 REAL, c04 REAL, c05 REAL," + " c10 REAL, c11 REAL, c12 REAL, c13 REAL, c14 REAL, c15 REAL," + " PRIMARY KEY(dim_id, after)" + ");"; + +const char *db_models_add_model = + "INSERT OR REPLACE INTO models(" + " dim_id, after, before," + " min_dist, max_dist," + " c00, c01, c02, c03, c04, c05," + " c10, c11, c12, c13, c14, c15)" + "VALUES(" + " @dim_id, @after, @before," + " @min_dist, @max_dist," + " @c00, @c01, @c02, @c03, @c04, @c05," + " @c10, @c11, @c12, @c13, @c14, @c15);"; + +const char *db_models_load = + "SELECT * FROM models " + "WHERE dim_id = @dim_id AND after >= @after ORDER BY before ASC;"; + +const char *db_models_delete = + "DELETE FROM models " + "WHERE dim_id = @dim_id AND before < @before;"; + +static int +ml_dimension_add_model(const uuid_t *metric_uuid, const ml_kmeans_t *km) +{ + static __thread sqlite3_stmt *res = NULL; + int param = 0; + int rc = 0; + + if (unlikely(!db)) { + error_report("Database has not been initialized"); + return 1; + } + + if (unlikely(!res)) { + rc = prepare_statement(db, db_models_add_model, &res); + if (unlikely(rc != SQLITE_OK)) { + error_report("Failed to prepare statement to store model, rc = %d", rc); + return 1; + } + } + + rc = sqlite3_bind_blob(res, ++param, metric_uuid, sizeof(*metric_uuid), SQLITE_STATIC); + if (unlikely(rc != SQLITE_OK)) + goto bind_fail; + + rc = sqlite3_bind_int(res, ++param, (int) km->after); + if (unlikely(rc != SQLITE_OK)) + goto bind_fail; + + rc = sqlite3_bind_int(res, ++param, (int) km->before); + if (unlikely(rc != SQLITE_OK)) + goto bind_fail; + + rc = sqlite3_bind_double(res, ++param, km->min_dist); + if (unlikely(rc != SQLITE_OK)) + goto bind_fail; + + rc = sqlite3_bind_double(res, ++param, km->max_dist); + if (unlikely(rc != SQLITE_OK)) + goto bind_fail; + + if (km->cluster_centers.size() != 2) + fatal("Expected 2 cluster centers, got %zu", km->cluster_centers.size()); + + for (const DSample &ds : km->cluster_centers) { + if (ds.size() != 6) + fatal("Expected dsample with 6 dimensions, got %ld", ds.size()); + + for (long idx = 0; idx != ds.size(); idx++) { + calculated_number_t cn = ds(idx); + int rc = sqlite3_bind_double(res, ++param, cn); + if (unlikely(rc != SQLITE_OK)) + goto bind_fail; + } + } + + rc = execute_insert(res); + if (unlikely(rc != SQLITE_DONE)) + error_report("Failed to store model, rc = %d", rc); + + rc = sqlite3_reset(res); + if (unlikely(rc != SQLITE_OK)) + error_report("Failed to reset statement when storing model, rc = %d", rc); + + return 0; + +bind_fail: + error_report("Failed to bind parameter %d to store model, rc = %d", param, rc); + rc = sqlite3_reset(res); + if (unlikely(rc != SQLITE_OK)) + error_report("Failed to reset statement to store model, rc = %d", rc); + return 1; +} + +static int +ml_dimension_delete_models(const uuid_t *metric_uuid, time_t before) +{ + static __thread sqlite3_stmt *res = NULL; + int rc = 0; + int param = 0; + + if (unlikely(!db)) { + error_report("Database has not been initialized"); + return 1; + } + + if (unlikely(!res)) { + rc = prepare_statement(db, db_models_delete, &res); + if (unlikely(rc != SQLITE_OK)) { + error_report("Failed to prepare statement to delete models, rc = %d", rc); + return 1; + } + } + + rc = sqlite3_bind_blob(res, ++param, metric_uuid, sizeof(*metric_uuid), SQLITE_STATIC); + if (unlikely(rc != SQLITE_OK)) + goto bind_fail; + + rc = sqlite3_bind_int(res, ++param, (int) before); + if (unlikely(rc != SQLITE_OK)) + goto bind_fail; + + rc = execute_insert(res); + if (unlikely(rc != SQLITE_DONE)) + error_report("Failed to delete models, rc = %d", rc); + + rc = sqlite3_reset(res); + if (unlikely(rc != SQLITE_OK)) + error_report("Failed to reset statement when deleting models, rc = %d", rc); + + return 0; + +bind_fail: + error_report("Failed to bind parameter %d to delete models, rc = %d", param, rc); + rc = sqlite3_reset(res); + if (unlikely(rc != SQLITE_OK)) + error_report("Failed to reset statement to delete models, rc = %d", rc); + return 1; +} + +int ml_dimension_load_models(RRDDIM *rd) { + ml_dimension_t *dim = (ml_dimension_t *) rd->ml_dimension; + if (!dim) + return 0; + + netdata_mutex_lock(&dim->mutex); + bool is_empty = dim->km_contexts.empty(); + netdata_mutex_unlock(&dim->mutex); + + if (!is_empty) + return 0; + + std::vector V; + + static __thread sqlite3_stmt *res = NULL; + int rc = 0; + int param = 0; + + if (unlikely(!db)) { + error_report("Database has not been initialized"); + return 1; + } + + if (unlikely(!res)) { + rc = prepare_statement(db, db_models_load, &res); + if (unlikely(rc != SQLITE_OK)) { + error_report("Failed to prepare statement to load models, rc = %d", rc); + return 1; + } + } + + rc = sqlite3_bind_blob(res, ++param, &dim->rd->metric_uuid, sizeof(dim->rd->metric_uuid), SQLITE_STATIC); + if (unlikely(rc != SQLITE_OK)) + goto bind_fail; + + rc = sqlite3_bind_int(res, ++param, now_realtime_usec() - (Cfg.num_models_to_use * Cfg.max_train_samples)); + if (unlikely(rc != SQLITE_OK)) + goto bind_fail; + + netdata_mutex_lock(&dim->mutex); + + dim->km_contexts.reserve(Cfg.num_models_to_use); + while ((rc = sqlite3_step_monitored(res)) == SQLITE_ROW) { + ml_kmeans_t km; + + km.after = sqlite3_column_int(res, 2); + km.before = sqlite3_column_int(res, 3); + + km.min_dist = sqlite3_column_int(res, 4); + km.max_dist = sqlite3_column_int(res, 5); + + km.cluster_centers.resize(2); + + km.cluster_centers[0].set_size(Cfg.lag_n + 1); + km.cluster_centers[0](0) = sqlite3_column_double(res, 6); + km.cluster_centers[0](1) = sqlite3_column_double(res, 7); + km.cluster_centers[0](2) = sqlite3_column_double(res, 8); + km.cluster_centers[0](3) = sqlite3_column_double(res, 9); + km.cluster_centers[0](4) = sqlite3_column_double(res, 10); + km.cluster_centers[0](5) = sqlite3_column_double(res, 11); + + km.cluster_centers[1].set_size(Cfg.lag_n + 1); + km.cluster_centers[1](0) = sqlite3_column_double(res, 12); + km.cluster_centers[1](1) = sqlite3_column_double(res, 13); + km.cluster_centers[1](2) = sqlite3_column_double(res, 14); + km.cluster_centers[1](3) = sqlite3_column_double(res, 15); + km.cluster_centers[1](4) = sqlite3_column_double(res, 16); + km.cluster_centers[1](5) = sqlite3_column_double(res, 17); + + dim->km_contexts.push_back(km); + } + + if (!dim->km_contexts.empty()) { + dim->ts = TRAINING_STATUS_TRAINED; + } + + netdata_mutex_unlock(&dim->mutex); + + if (unlikely(rc != SQLITE_DONE)) + error_report("Failed to load models, rc = %d", rc); + + rc = sqlite3_reset(res); + if (unlikely(rc != SQLITE_OK)) + error_report("Failed to reset statement when loading models, rc = %d", rc); + + return 0; + +bind_fail: + error_report("Failed to bind parameter %d to load models, rc = %d", param, rc); + rc = sqlite3_reset(res); + if (unlikely(rc != SQLITE_OK)) + error_report("Failed to reset statement to load models, rc = %d", rc); + return 1; +} + +static enum ml_training_result +ml_dimension_train_model(ml_training_thread_t *training_thread, ml_dimension_t *dim, const ml_training_request_t &training_request) +{ + worker_is_busy(WORKER_TRAIN_QUERY); + auto P = ml_dimension_calculated_numbers(training_thread, dim, training_request); + ml_training_response_t training_response = P.second; + + if (training_response.result != TRAINING_RESULT_OK) { + netdata_mutex_lock(&dim->mutex); + + dim->mt = METRIC_TYPE_CONSTANT; + + switch (dim->ts) { + case TRAINING_STATUS_PENDING_WITH_MODEL: + dim->ts = TRAINING_STATUS_TRAINED; + break; + case TRAINING_STATUS_PENDING_WITHOUT_MODEL: + dim->ts = TRAINING_STATUS_UNTRAINED; + break; + default: + break; + } + + dim->tr = training_response; + + dim->last_training_time = training_response.last_entry_on_response; + enum ml_training_result result = training_response.result; + netdata_mutex_unlock(&dim->mutex); + + return result; + } + + // compute kmeans + worker_is_busy(WORKER_TRAIN_KMEANS); + { + memcpy(training_thread->scratch_training_cns, training_thread->training_cns, + training_response.total_values * sizeof(calculated_number_t)); + + ml_features_t features = { + Cfg.diff_n, Cfg.smooth_n, Cfg.lag_n, + training_thread->scratch_training_cns, training_response.total_values, + training_thread->training_cns, training_response.total_values, + training_thread->training_samples + }; + ml_features_preprocess(&features); + + ml_kmeans_init(&dim->kmeans); + ml_kmeans_train(&dim->kmeans, &features, training_response.query_after_t, training_response.query_before_t); + } + + // update models + worker_is_busy(WORKER_TRAIN_UPDATE_MODELS); + { + netdata_mutex_lock(&dim->mutex); + + if (dim->km_contexts.size() < Cfg.num_models_to_use) { + dim->km_contexts.push_back(std::move(dim->kmeans)); + } else { + bool can_drop_middle_km = false; + + if (Cfg.num_models_to_use > 2) { + const ml_kmeans_t *old_km = &dim->km_contexts[dim->km_contexts.size() - 1]; + const ml_kmeans_t *middle_km = &dim->km_contexts[dim->km_contexts.size() - 2]; + const ml_kmeans_t *new_km = &dim->kmeans; + + can_drop_middle_km = (middle_km->after < old_km->before) && + (middle_km->before > new_km->after); + } + + if (can_drop_middle_km) { + dim->km_contexts.back() = dim->kmeans; + } else { + std::rotate(std::begin(dim->km_contexts), std::begin(dim->km_contexts) + 1, std::end(dim->km_contexts)); + dim->km_contexts[dim->km_contexts.size() - 1] = std::move(dim->kmeans); + } + } + + dim->mt = METRIC_TYPE_CONSTANT; + dim->ts = TRAINING_STATUS_TRAINED; + dim->tr = training_response; + dim->last_training_time = rrddim_last_entry_s(dim->rd); + + // Add the newly generated model to the list of pending models to flush + ml_model_info_t model_info; + uuid_copy(model_info.metric_uuid, dim->rd->metric_uuid); + model_info.kmeans = dim->km_contexts.back(); + training_thread->pending_model_info.push_back(model_info); + + netdata_mutex_unlock(&dim->mutex); + } + + return training_response.result; +} + +static void +ml_dimension_schedule_for_training(ml_dimension_t *dim, time_t curr_time) +{ + switch (dim->mt) { + case METRIC_TYPE_CONSTANT: + return; + default: + break; + } + + bool schedule_for_training = false; + + switch (dim->ts) { + case TRAINING_STATUS_PENDING_WITH_MODEL: + case TRAINING_STATUS_PENDING_WITHOUT_MODEL: + schedule_for_training = false; + break; + case TRAINING_STATUS_UNTRAINED: + schedule_for_training = true; + dim->ts = TRAINING_STATUS_PENDING_WITHOUT_MODEL; + break; + case TRAINING_STATUS_TRAINED: + if ((dim->last_training_time + (Cfg.train_every * dim->rd->update_every)) < curr_time) { + schedule_for_training = true; + dim->ts = TRAINING_STATUS_PENDING_WITH_MODEL; + } + break; + } + + if (schedule_for_training) { + ml_training_request_t req; + + memcpy(req.machine_guid, dim->rd->rrdset->rrdhost->machine_guid, GUID_LEN + 1); + req.chart_id = string_dup(dim->rd->rrdset->id); + req.dimension_id = string_dup(dim->rd->id); + req.request_time = curr_time; + req.first_entry_on_request = rrddim_first_entry_s(dim->rd); + req.last_entry_on_request = rrddim_last_entry_s(dim->rd); + + ml_host_t *host = (ml_host_t *) dim->rd->rrdset->rrdhost->ml_host; + ml_queue_push(host->training_queue, req); + } +} + +static bool +ml_dimension_predict(ml_dimension_t *dim, time_t curr_time, calculated_number_t value, bool exists) +{ + // Nothing to do if ML is disabled for this dimension + if (dim->mls != MACHINE_LEARNING_STATUS_ENABLED) + return false; + + // Don't treat values that don't exist as anomalous + if (!exists) { + dim->cns.clear(); + return false; + } + + // Save the value and return if we don't have enough values for a sample + unsigned n = Cfg.diff_n + Cfg.smooth_n + Cfg.lag_n; + if (dim->cns.size() < n) { + dim->cns.push_back(value); + return false; + } + + // Push the value and check if it's different from the last one + bool same_value = true; + std::rotate(std::begin(dim->cns), std::begin(dim->cns) + 1, std::end(dim->cns)); + if (dim->cns[n - 1] != value) + same_value = false; + dim->cns[n - 1] = value; + + // Create the sample + assert((n * (Cfg.lag_n + 1) <= 128) && + "Static buffers too small to perform prediction. " + "This should not be possible with the default clamping of feature extraction options"); + calculated_number_t src_cns[128]; + calculated_number_t dst_cns[128]; + + memset(src_cns, 0, n * (Cfg.lag_n + 1) * sizeof(calculated_number_t)); + memcpy(src_cns, dim->cns.data(), n * sizeof(calculated_number_t)); + memcpy(dst_cns, dim->cns.data(), n * sizeof(calculated_number_t)); + + ml_features_t features = { + Cfg.diff_n, Cfg.smooth_n, Cfg.lag_n, + dst_cns, n, src_cns, n, + dim->feature + }; + ml_features_preprocess(&features); + + /* + * Lock to predict and possibly schedule the dimension for training + */ + if (netdata_mutex_trylock(&dim->mutex) != 0) + return false; + + // Mark the metric time as variable if we received different values + if (!same_value) + dim->mt = METRIC_TYPE_VARIABLE; + + // Decide if the dimension needs to be scheduled for training + ml_dimension_schedule_for_training(dim, curr_time); + + // Nothing to do if we don't have a model + switch (dim->ts) { + case TRAINING_STATUS_UNTRAINED: + case TRAINING_STATUS_PENDING_WITHOUT_MODEL: { + netdata_mutex_unlock(&dim->mutex); + return false; + } + default: + break; + } + + /* + * Use the KMeans models to check if the value is anomalous + */ + + size_t sum = 0; + size_t models_consulted = 0; + + for (const auto &km_ctx : dim->km_contexts) { + models_consulted++; + + calculated_number_t anomaly_score = ml_kmeans_anomaly_score(&km_ctx, features.preprocessed_features[0]); + if (anomaly_score == std::numeric_limits::quiet_NaN()) + continue; + + if (anomaly_score < (100 * Cfg.dimension_anomaly_score_threshold)) { + global_statistics_ml_models_consulted(models_consulted); + netdata_mutex_unlock(&dim->mutex); + return false; + } + + sum += 1; + } + + netdata_mutex_unlock(&dim->mutex); + + global_statistics_ml_models_consulted(models_consulted); + return sum; +} + +/* + * Chart +*/ + +static bool +ml_chart_is_available_for_ml(ml_chart_t *chart) +{ + return rrdset_is_available_for_exporting_and_alarms(chart->rs); +} + +void +ml_chart_update_dimension(ml_chart_t *chart, ml_dimension_t *dim, bool is_anomalous) +{ + switch (dim->mls) { + case MACHINE_LEARNING_STATUS_DISABLED_DUE_TO_EXCLUDED_CHART: + chart->mls.num_machine_learning_status_disabled_sp++; + return; + case MACHINE_LEARNING_STATUS_ENABLED: { + chart->mls.num_machine_learning_status_enabled++; + + switch (dim->mt) { + case METRIC_TYPE_CONSTANT: + chart->mls.num_metric_type_constant++; + chart->mls.num_training_status_trained++; + chart->mls.num_normal_dimensions++; + return; + case METRIC_TYPE_VARIABLE: + chart->mls.num_metric_type_variable++; + break; + } + + switch (dim->ts) { + case TRAINING_STATUS_UNTRAINED: + chart->mls.num_training_status_untrained++; + return; + case TRAINING_STATUS_PENDING_WITHOUT_MODEL: + chart->mls.num_training_status_pending_without_model++; + return; + case TRAINING_STATUS_TRAINED: + chart->mls.num_training_status_trained++; + + chart->mls.num_anomalous_dimensions += is_anomalous; + chart->mls.num_normal_dimensions += !is_anomalous; + return; + case TRAINING_STATUS_PENDING_WITH_MODEL: + chart->mls.num_training_status_pending_with_model++; + + chart->mls.num_anomalous_dimensions += is_anomalous; + chart->mls.num_normal_dimensions += !is_anomalous; + return; + } + + return; + } + } +} + +/* + * Host detection & training functions +*/ + +#define WORKER_JOB_DETECTION_COLLECT_STATS 0 +#define WORKER_JOB_DETECTION_DIM_CHART 1 +#define WORKER_JOB_DETECTION_HOST_CHART 2 +#define WORKER_JOB_DETECTION_STATS 3 + +static void +ml_host_detect_once(ml_host_t *host) +{ + worker_is_busy(WORKER_JOB_DETECTION_COLLECT_STATS); + + host->mls = {}; + ml_machine_learning_stats_t mls_copy = {}; + + { + netdata_mutex_lock(&host->mutex); + + /* + * prediction/detection stats + */ + void *rsp = NULL; + rrdset_foreach_read(rsp, host->rh) { + RRDSET *rs = static_cast(rsp); -bool ml_capable() { + ml_chart_t *chart = (ml_chart_t *) rs->ml_chart; + if (!chart) + continue; + + if (!ml_chart_is_available_for_ml(chart)) + continue; + + ml_machine_learning_stats_t chart_mls = chart->mls; + + host->mls.num_machine_learning_status_enabled += chart_mls.num_machine_learning_status_enabled; + host->mls.num_machine_learning_status_disabled_sp += chart_mls.num_machine_learning_status_disabled_sp; + + host->mls.num_metric_type_constant += chart_mls.num_metric_type_constant; + host->mls.num_metric_type_variable += chart_mls.num_metric_type_variable; + + host->mls.num_training_status_untrained += chart_mls.num_training_status_untrained; + host->mls.num_training_status_pending_without_model += chart_mls.num_training_status_pending_without_model; + host->mls.num_training_status_trained += chart_mls.num_training_status_trained; + host->mls.num_training_status_pending_with_model += chart_mls.num_training_status_pending_with_model; + + host->mls.num_anomalous_dimensions += chart_mls.num_anomalous_dimensions; + host->mls.num_normal_dimensions += chart_mls.num_normal_dimensions; + } + rrdset_foreach_done(rsp); + + host->host_anomaly_rate = 0.0; + size_t NumActiveDimensions = host->mls.num_anomalous_dimensions + host->mls.num_normal_dimensions; + if (NumActiveDimensions) + host->host_anomaly_rate = static_cast(host->mls.num_anomalous_dimensions) / NumActiveDimensions; + + mls_copy = host->mls; + + netdata_mutex_unlock(&host->mutex); + } + + worker_is_busy(WORKER_JOB_DETECTION_DIM_CHART); + ml_update_dimensions_chart(host, mls_copy); + + worker_is_busy(WORKER_JOB_DETECTION_HOST_CHART); + ml_update_host_and_detection_rate_charts(host, host->host_anomaly_rate * 10000.0); +} + +typedef struct { + RRDHOST_ACQUIRED *acq_rh; + RRDSET_ACQUIRED *acq_rs; + RRDDIM_ACQUIRED *acq_rd; + ml_dimension_t *dim; +} ml_acquired_dimension_t; + +static ml_acquired_dimension_t +ml_acquired_dimension_get(char *machine_guid, STRING *chart_id, STRING *dimension_id) +{ + RRDHOST_ACQUIRED *acq_rh = NULL; + RRDSET_ACQUIRED *acq_rs = NULL; + RRDDIM_ACQUIRED *acq_rd = NULL; + ml_dimension_t *dim = NULL; + + rrd_rdlock(); + + acq_rh = rrdhost_find_and_acquire(machine_guid); + if (acq_rh) { + RRDHOST *rh = rrdhost_acquired_to_rrdhost(acq_rh); + if (rh && !rrdhost_flag_check(rh, RRDHOST_FLAG_ORPHAN | RRDHOST_FLAG_ARCHIVED)) { + acq_rs = rrdset_find_and_acquire(rh, string2str(chart_id)); + if (acq_rs) { + RRDSET *rs = rrdset_acquired_to_rrdset(acq_rs); + if (rs && !rrdset_flag_check(rs, RRDSET_FLAG_ARCHIVED | RRDSET_FLAG_OBSOLETE)) { + acq_rd = rrddim_find_and_acquire(rs, string2str(dimension_id)); + if (acq_rd) { + RRDDIM *rd = rrddim_acquired_to_rrddim(acq_rd); + if (rd) + dim = (ml_dimension_t *) rd->ml_dimension; + } + } + } + } + } + + rrd_unlock(); + + ml_acquired_dimension_t acq_dim = { + acq_rh, acq_rs, acq_rd, dim + }; + + return acq_dim; +} + +static void +ml_acquired_dimension_release(ml_acquired_dimension_t acq_dim) +{ + if (acq_dim.acq_rd) + rrddim_acquired_release(acq_dim.acq_rd); + + if (acq_dim.acq_rs) + rrdset_acquired_release(acq_dim.acq_rs); + + if (acq_dim.acq_rh) + rrdhost_acquired_release(acq_dim.acq_rh); +} + +static enum ml_training_result +ml_acquired_dimension_train(ml_training_thread_t *training_thread, ml_acquired_dimension_t acq_dim, const ml_training_request_t &tr) +{ + if (!acq_dim.dim) + return TRAINING_RESULT_NULL_ACQUIRED_DIMENSION; + + return ml_dimension_train_model(training_thread, acq_dim.dim, tr); +} + +static void * +ml_detect_main(void *arg) +{ + UNUSED(arg); + + worker_register("MLDETECT"); + worker_register_job_name(WORKER_JOB_DETECTION_COLLECT_STATS, "collect stats"); + 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, "training stats"); + + heartbeat_t hb; + heartbeat_init(&hb); + + while (!Cfg.detection_stop) { + worker_is_idle(); + heartbeat_next(&hb, USEC_PER_SEC); + + RRDHOST *rh; + rrd_rdlock(); + rrdhost_foreach_read(rh) { + if (!rh->ml_host) + continue; + + ml_host_detect_once((ml_host_t *) rh->ml_host); + } + rrd_unlock(); + + if (Cfg.enable_statistics_charts) { + // collect and update training thread stats + for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { + ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; + + netdata_mutex_lock(&training_thread->nd_mutex); + ml_training_stats_t training_stats = training_thread->training_stats; + training_thread->training_stats = {}; + netdata_mutex_unlock(&training_thread->nd_mutex); + + // calc the avg values + if (training_stats.num_popped_items) { + training_stats.queue_size /= training_stats.num_popped_items; + training_stats.allotted_ut /= training_stats.num_popped_items; + training_stats.consumed_ut /= training_stats.num_popped_items; + training_stats.remaining_ut /= training_stats.num_popped_items; + } else { + training_stats.queue_size = ml_queue_size(training_thread->training_queue); + training_stats.consumed_ut = 0; + training_stats.remaining_ut = training_stats.allotted_ut; + + training_stats.training_result_ok = 0; + training_stats.training_result_invalid_query_time_range = 0; + training_stats.training_result_not_enough_collected_values = 0; + training_stats.training_result_null_acquired_dimension = 0; + training_stats.training_result_chart_under_replication = 0; + } + + ml_update_training_statistics_chart(training_thread, training_stats); + } + } + } + + return NULL; +} + +/* + * Public API +*/ + +bool ml_capable() +{ return true; } -bool ml_enabled(RRDHOST *RH) { - if (!Cfg.EnableAnomalyDetection) +bool ml_enabled(RRDHOST *rh) +{ + if (!rh) + return false; + + if (!Cfg.enable_anomaly_detection) return false; - if (simple_pattern_matches(Cfg.SP_HostsToSkip, rrdhost_hostname(RH))) + if (simple_pattern_matches(Cfg.sp_host_to_skip, rrdhost_hostname(rh))) return false; return true; } -/* - * Assumptions: - * 1) hosts outlive their sets, and sets outlive their dimensions, - * 2) dimensions always have a set that has a host. - */ +bool ml_streaming_enabled() +{ + return Cfg.stream_anomaly_detection_charts; +} -void ml_init(void) { - // Read config values - Cfg.readMLConfig(); +void ml_host_new(RRDHOST *rh) +{ + if (!ml_enabled(rh)) + return; + + ml_host_t *host = new ml_host_t(); - if (!Cfg.EnableAnomalyDetection) + host->rh = rh; + host->mls = ml_machine_learning_stats_t(); + //host->ts = ml_training_stats_t(); + + static std::atomic times_called(0); + host->training_queue = Cfg.training_threads[times_called++ % Cfg.num_training_threads].training_queue; + + host->host_anomaly_rate = 0.0; + + netdata_mutex_init(&host->mutex); + + rh->ml_host = (rrd_ml_host_t *) host; +} + +void ml_host_delete(RRDHOST *rh) +{ + ml_host_t *host = (ml_host_t *) rh->ml_host; + if (!host) return; - // Generate random numbers to efficiently sample the features we need - // for KMeans clustering. - std::random_device RD; - std::mt19937 Gen(RD()); + netdata_mutex_destroy(&host->mutex); - Cfg.RandomNums.reserve(Cfg.MaxTrainSamples); - for (size_t Idx = 0; Idx != Cfg.MaxTrainSamples; Idx++) - Cfg.RandomNums.push_back(Gen()); + delete host; + rh->ml_host = NULL; } -void ml_host_new(RRDHOST *RH) { - if (!ml_enabled(RH)) +void ml_host_get_info(RRDHOST *rh, BUFFER *wb) +{ + ml_host_t *host = (ml_host_t *) rh->ml_host; + if (!host) { + buffer_json_member_add_boolean(wb, "enabled", false); return; + } + + buffer_json_member_add_uint64(wb, "version", 1); + + buffer_json_member_add_boolean(wb, "enabled", Cfg.enable_anomaly_detection); - Host *H = new Host(RH); - RH->ml_host = reinterpret_cast(H); + buffer_json_member_add_uint64(wb, "min-train-samples", Cfg.min_train_samples); + buffer_json_member_add_uint64(wb, "max-train-samples", Cfg.max_train_samples); + buffer_json_member_add_uint64(wb, "train-every", Cfg.train_every); + + buffer_json_member_add_uint64(wb, "diff-n", Cfg.diff_n); + buffer_json_member_add_uint64(wb, "smooth-n", Cfg.smooth_n); + buffer_json_member_add_uint64(wb, "lag-n", Cfg.lag_n); + + buffer_json_member_add_double(wb, "random-sampling-ratio", Cfg.random_sampling_ratio); + buffer_json_member_add_uint64(wb, "max-kmeans-iters", Cfg.random_sampling_ratio); + + buffer_json_member_add_double(wb, "dimension-anomaly-score-threshold", Cfg.dimension_anomaly_score_threshold); + + buffer_json_member_add_string(wb, "anomaly-detection-grouping-method", + time_grouping_method2string(Cfg.anomaly_detection_grouping_method)); + + buffer_json_member_add_int64(wb, "anomaly-detection-query-duration", Cfg.anomaly_detection_query_duration); + + buffer_json_member_add_string(wb, "hosts-to-skip", Cfg.hosts_to_skip.c_str()); + buffer_json_member_add_string(wb, "charts-to-skip", Cfg.charts_to_skip.c_str()); } -void ml_host_delete(RRDHOST *RH) { - Host *H = reinterpret_cast(RH->ml_host); - if (!H) +void ml_host_get_detection_info(RRDHOST *rh, BUFFER *wb) +{ + ml_host_t *host = (ml_host_t *) rh->ml_host; + if (!host) return; - delete H; - RH->ml_host = nullptr; + netdata_mutex_lock(&host->mutex); + + buffer_json_member_add_uint64(wb, "version", 1); + buffer_json_member_add_uint64(wb, "anomalous-dimensions", host->mls.num_anomalous_dimensions); + buffer_json_member_add_uint64(wb, "normal-dimensions", host->mls.num_normal_dimensions); + buffer_json_member_add_uint64(wb, "total-dimensions", host->mls.num_anomalous_dimensions + + host->mls.num_normal_dimensions); + buffer_json_member_add_uint64(wb, "trained-dimensions", host->mls.num_training_status_trained + + host->mls.num_training_status_pending_with_model); + netdata_mutex_unlock(&host->mutex); } -void ml_chart_new(RRDSET *RS) { - Host *H = reinterpret_cast(RS->rrdhost->ml_host); - if (!H) +void ml_host_get_models(RRDHOST *rh, BUFFER *wb) +{ + UNUSED(rh); + UNUSED(wb); + + // TODO: To be implemented + error("Fetching KMeans models is not supported yet"); +} + +void ml_chart_new(RRDSET *rs) +{ + ml_host_t *host = (ml_host_t *) rs->rrdhost->ml_host; + if (!host) return; - Chart *C = new Chart(RS); - RS->ml_chart = reinterpret_cast(C); + ml_chart_t *chart = new ml_chart_t(); - H->addChart(C); + chart->rs = rs; + chart->mls = ml_machine_learning_stats_t(); + + netdata_mutex_init(&chart->mutex); + + rs->ml_chart = (rrd_ml_chart_t *) chart; } -void ml_chart_delete(RRDSET *RS) { - Host *H = reinterpret_cast(RS->rrdhost->ml_host); - if (!H) +void ml_chart_delete(RRDSET *rs) +{ + ml_host_t *host = (ml_host_t *) rs->rrdhost->ml_host; + if (!host) return; - Chart *C = reinterpret_cast(RS->ml_chart); - H->removeChart(C); + ml_chart_t *chart = (ml_chart_t *) rs->ml_chart; + + netdata_mutex_destroy(&chart->mutex); - delete C; - RS->ml_chart = nullptr; + delete chart; + rs->ml_chart = NULL; } -void ml_dimension_new(RRDDIM *RD) { - Chart *C = reinterpret_cast(RD->rrdset->ml_chart); - if (!C) +bool ml_chart_update_begin(RRDSET *rs) +{ + ml_chart_t *chart = (ml_chart_t *) rs->ml_chart; + if (!chart) + return false; + + netdata_mutex_lock(&chart->mutex); + chart->mls = {}; + return true; +} + +void ml_chart_update_end(RRDSET *rs) +{ + ml_chart_t *chart = (ml_chart_t *) rs->ml_chart; + if (!chart) return; - Dimension *D = new Dimension(RD); - RD->ml_dimension = reinterpret_cast(D); - C->addDimension(D); + netdata_mutex_unlock(&chart->mutex); } -void ml_dimension_delete(RRDDIM *RD) { - Dimension *D = reinterpret_cast(RD->ml_dimension); - if (!D) +void ml_dimension_new(RRDDIM *rd) +{ + ml_chart_t *chart = (ml_chart_t *) rd->rrdset->ml_chart; + if (!chart) return; - Chart *C = reinterpret_cast(RD->rrdset->ml_chart); - C->removeDimension(D); + ml_dimension_t *dim = new ml_dimension_t(); + + dim->rd = rd; + + dim->mt = METRIC_TYPE_CONSTANT; + dim->ts = TRAINING_STATUS_UNTRAINED; + + dim->last_training_time = 0; + + ml_kmeans_init(&dim->kmeans); + + if (simple_pattern_matches(Cfg.sp_charts_to_skip, rrdset_name(rd->rrdset))) + dim->mls = MACHINE_LEARNING_STATUS_DISABLED_DUE_TO_EXCLUDED_CHART; + else + dim->mls = MACHINE_LEARNING_STATUS_ENABLED; + + netdata_mutex_init(&dim->mutex); + + dim->km_contexts.reserve(Cfg.num_models_to_use); + + rd->ml_dimension = (rrd_ml_dimension_t *) dim; - delete D; - RD->ml_dimension = nullptr; + metaqueue_ml_load_models(rd); } -char *ml_get_host_info(RRDHOST *RH) { - nlohmann::json ConfigJson; +void ml_dimension_delete(RRDDIM *rd) +{ + ml_dimension_t *dim = (ml_dimension_t *) rd->ml_dimension; + if (!dim) + return; - if (RH && RH->ml_host) { - Host *H = reinterpret_cast(RH->ml_host); - H->getConfigAsJson(ConfigJson); - } else { - ConfigJson["enabled"] = false; - } + netdata_mutex_destroy(&dim->mutex); - return strdupz(ConfigJson.dump(2, '\t').c_str()); + delete dim; + rd->ml_dimension = NULL; } -char *ml_get_host_runtime_info(RRDHOST *RH) { - nlohmann::json ConfigJson; +bool ml_dimension_is_anomalous(RRDDIM *rd, time_t curr_time, double value, bool exists) +{ + ml_dimension_t *dim = (ml_dimension_t *) rd->ml_dimension; + if (!dim) + return false; - if (RH && RH->ml_host) { - Host *H = reinterpret_cast(RH->ml_host); - H->getDetectionInfoAsJson(ConfigJson); - } else { - return nullptr; - } + ml_chart_t *chart = (ml_chart_t *) rd->rrdset->ml_chart; - return strdup(ConfigJson.dump(1, '\t').c_str()); + bool is_anomalous = ml_dimension_predict(dim, curr_time, value, exists); + ml_chart_update_dimension(chart, dim, is_anomalous); + + return is_anomalous; } -char *ml_get_host_models(RRDHOST *RH) { - nlohmann::json ModelsJson; +static int ml_flush_pending_models(ml_training_thread_t *training_thread) { + (void) db_execute(db, "BEGIN TRANSACTION;"); + + for (const auto &pending_model: training_thread->pending_model_info) { + int rc = ml_dimension_add_model(&pending_model.metric_uuid, &pending_model.kmeans); + if (rc) + return rc; - if (RH && RH->ml_host) { - Host *H = reinterpret_cast(RH->ml_host); - H->getModelsAsJson(ModelsJson); - return strdup(ModelsJson.dump(2, '\t').c_str()); + rc = ml_dimension_delete_models(&pending_model.metric_uuid, pending_model.kmeans.before - (Cfg.num_models_to_use * Cfg.train_every)); + if (rc) + return rc; } - return nullptr; + (void) db_execute(db, "COMMIT TRANSACTION;"); + + training_thread->pending_model_info.clear(); + return 0; } -void ml_start_anomaly_detection_threads(RRDHOST *RH) { - if (RH && RH->ml_host) { - Host *H = reinterpret_cast(RH->ml_host); - H->startAnomalyDetectionThreads(); +static void *ml_train_main(void *arg) { + ml_training_thread_t *training_thread = (ml_training_thread_t *) arg; + + char worker_name[1024]; + snprintfz(worker_name, 1024, "training_thread_%zu", training_thread->id); + worker_register("MLTRAIN"); + + worker_register_job_name(WORKER_TRAIN_QUEUE_POP, "pop queue"); + worker_register_job_name(WORKER_TRAIN_ACQUIRE_DIMENSION, "acquire"); + worker_register_job_name(WORKER_TRAIN_QUERY, "query"); + worker_register_job_name(WORKER_TRAIN_KMEANS, "kmeans"); + worker_register_job_name(WORKER_TRAIN_UPDATE_MODELS, "update models"); + worker_register_job_name(WORKER_TRAIN_RELEASE_DIMENSION, "release"); + worker_register_job_name(WORKER_TRAIN_UPDATE_HOST, "update host"); + worker_register_job_name(WORKER_TRAIN_FLUSH_MODELS, "flush models"); + + while (!Cfg.training_stop) { + worker_is_busy(WORKER_TRAIN_QUEUE_POP); + + ml_training_request_t training_req = ml_queue_pop(training_thread->training_queue); + + // we know this thread has been cancelled, when the queue starts + // returning "null" requests without blocking on queue's pop(). + if (training_req.chart_id == NULL) + break; + + size_t queue_size = ml_queue_size(training_thread->training_queue) + 1; + + usec_t allotted_ut = (Cfg.train_every * USEC_PER_SEC) / queue_size; + if (allotted_ut > USEC_PER_SEC) + allotted_ut = USEC_PER_SEC; + + usec_t start_ut = now_monotonic_usec(); + + enum ml_training_result training_res; + { + worker_is_busy(WORKER_TRAIN_ACQUIRE_DIMENSION); + ml_acquired_dimension_t acq_dim = ml_acquired_dimension_get( + training_req.machine_guid, + training_req.chart_id, + training_req.dimension_id); + + training_res = ml_acquired_dimension_train(training_thread, acq_dim, training_req); + + string_freez(training_req.chart_id); + string_freez(training_req.dimension_id); + + worker_is_busy(WORKER_TRAIN_RELEASE_DIMENSION); + ml_acquired_dimension_release(acq_dim); + } + + usec_t consumed_ut = now_monotonic_usec() - start_ut; + + usec_t remaining_ut = 0; + if (consumed_ut < allotted_ut) + remaining_ut = allotted_ut - consumed_ut; + + if (Cfg.enable_statistics_charts) { + worker_is_busy(WORKER_TRAIN_UPDATE_HOST); + + netdata_mutex_lock(&training_thread->nd_mutex); + + training_thread->training_stats.queue_size += queue_size; + training_thread->training_stats.num_popped_items += 1; + + training_thread->training_stats.allotted_ut += allotted_ut; + training_thread->training_stats.consumed_ut += consumed_ut; + training_thread->training_stats.remaining_ut += remaining_ut; + + switch (training_res) { + case TRAINING_RESULT_OK: + training_thread->training_stats.training_result_ok += 1; + break; + case TRAINING_RESULT_INVALID_QUERY_TIME_RANGE: + training_thread->training_stats.training_result_invalid_query_time_range += 1; + break; + case TRAINING_RESULT_NOT_ENOUGH_COLLECTED_VALUES: + training_thread->training_stats.training_result_not_enough_collected_values += 1; + break; + case TRAINING_RESULT_NULL_ACQUIRED_DIMENSION: + training_thread->training_stats.training_result_null_acquired_dimension += 1; + break; + case TRAINING_RESULT_CHART_UNDER_REPLICATION: + training_thread->training_stats.training_result_chart_under_replication += 1; + break; + } + + netdata_mutex_unlock(&training_thread->nd_mutex); + } + + if (training_thread->pending_model_info.size() >= Cfg.flush_models_batch_size) { + worker_is_busy(WORKER_TRAIN_FLUSH_MODELS); + netdata_mutex_lock(&db_mutex); + ml_flush_pending_models(training_thread); + netdata_mutex_unlock(&db_mutex); + continue; + } + + worker_is_idle(); + std::this_thread::sleep_for(std::chrono::microseconds{remaining_ut}); } + + return NULL; } -void ml_stop_anomaly_detection_threads(RRDHOST *RH) { - if (RH && RH->ml_host) { - Host *H = reinterpret_cast(RH->ml_host); - H->stopAnomalyDetectionThreads(true); +void ml_init() +{ + // Read config values + ml_config_load(&Cfg); + + if (!Cfg.enable_anomaly_detection) + return; + + // Generate random numbers to efficiently sample the features we need + // for KMeans clustering. + std::random_device RD; + std::mt19937 Gen(RD()); + + Cfg.random_nums.reserve(Cfg.max_train_samples); + for (size_t Idx = 0; Idx != Cfg.max_train_samples; Idx++) + Cfg.random_nums.push_back(Gen()); + + // init training thread-specific data + Cfg.training_threads.resize(Cfg.num_training_threads); + for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { + ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; + + size_t max_elements_needed_for_training = Cfg.max_train_samples * (Cfg.lag_n + 1); + training_thread->training_cns = new calculated_number_t[max_elements_needed_for_training](); + training_thread->scratch_training_cns = new calculated_number_t[max_elements_needed_for_training](); + + training_thread->id = idx; + training_thread->training_queue = ml_queue_init(); + training_thread->pending_model_info.reserve(Cfg.flush_models_batch_size); + netdata_mutex_init(&training_thread->nd_mutex); } -} -void ml_cancel_anomaly_detection_threads(RRDHOST *RH) { - if (RH && RH->ml_host) { - Host *H = reinterpret_cast(RH->ml_host); - H->stopAnomalyDetectionThreads(false); + // open sqlite db + char path[FILENAME_MAX]; + snprintfz(path, FILENAME_MAX - 1, "%s/%s", netdata_configured_cache_dir, "ml.db"); + int rc = sqlite3_open(path, &db); + if (rc != SQLITE_OK) { + error_report("Failed to initialize database at %s, due to \"%s\"", path, sqlite3_errstr(rc)); + sqlite3_close(db); + db = NULL; + } + + if (db) { + char *err = NULL; + int rc = sqlite3_exec(db, db_models_create_table, NULL, NULL, &err); + if (rc != SQLITE_OK) { + error_report("Failed to create models table (%s, %s)", sqlite3_errstr(rc), err ? err : ""); + sqlite3_close(db); + sqlite3_free(err); + db = NULL; + } } } -void ml_chart_update_begin(RRDSET *RS) { - Chart *C = reinterpret_cast(RS->ml_chart); - if (!C) +void ml_fini() { + if (!Cfg.enable_anomaly_detection) return; - C->updateBegin(); + int rc = sqlite3_close_v2(db); + if (unlikely(rc != SQLITE_OK)) + error_report("Error %d while closing the SQLite database, %s", rc, sqlite3_errstr(rc)); } -void ml_chart_update_end(RRDSET *RS) { - Chart *C = reinterpret_cast(RS->ml_chart); - if (!C) +void ml_start_threads() { + if (!Cfg.enable_anomaly_detection) return; - C->updateEnd(); -} + // start detection & training threads + Cfg.detection_stop = false; + Cfg.training_stop = false; -bool ml_is_anomalous(RRDDIM *RD, time_t CurrT, double Value, bool Exists) { - Dimension *D = reinterpret_cast(RD->ml_dimension); - if (!D) - return false; + char tag[NETDATA_THREAD_TAG_MAX + 1]; - Chart *C = reinterpret_cast(RD->rrdset->ml_chart); + snprintfz(tag, NETDATA_THREAD_TAG_MAX, "%s", "PREDICT"); + netdata_thread_create(&Cfg.detection_thread, tag, NETDATA_THREAD_OPTION_JOINABLE, ml_detect_main, NULL); - bool IsAnomalous = D->predict(CurrT, Value, Exists); - C->updateDimension(D, IsAnomalous); - return IsAnomalous; + for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { + ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; + snprintfz(tag, NETDATA_THREAD_TAG_MAX, "TRAIN[%zu]", training_thread->id); + netdata_thread_create(&training_thread->nd_thread, tag, NETDATA_THREAD_OPTION_JOINABLE, ml_train_main, training_thread); + } } -bool ml_streaming_enabled() { - return Cfg.StreamADCharts; -} +void ml_stop_threads() +{ + if (!Cfg.enable_anomaly_detection) + return; -#include "ml-private.h" + Cfg.detection_stop = true; + Cfg.training_stop = true; + + netdata_thread_cancel(Cfg.detection_thread); + netdata_thread_join(Cfg.detection_thread, NULL); + + // signal the training queue of each thread + for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { + ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; + + ml_queue_signal(training_thread->training_queue); + } + + // cancel training threads + for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { + ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; + + netdata_thread_cancel(training_thread->nd_thread); + } + + // join training threads + for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { + ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; + + netdata_thread_join(training_thread->nd_thread, NULL); + } + + // clear training thread data + for (size_t idx = 0; idx != Cfg.num_training_threads; idx++) { + ml_training_thread_t *training_thread = &Cfg.training_threads[idx]; + + delete[] training_thread->training_cns; + delete[] training_thread->scratch_training_cns; + ml_queue_destroy(training_thread->training_queue); + netdata_mutex_destroy(&training_thread->nd_mutex); + } +} diff --git a/ml/ml.h b/ml/ml.h index 8bed627f..964dd082 100644 --- a/ml/ml.h +++ b/ml/ml.h @@ -10,39 +10,35 @@ extern "C" { #include "daemon/common.h" #include "web/api/queries/rrdr.h" -// This is a DBEngine function redeclared here so that we can free -// the anomaly rate dimension, whenever its backing dimension is freed. -void rrddim_free(RRDSET *st, RRDDIM *rd); - bool ml_capable(); - -bool ml_enabled(RRDHOST *RH); +bool ml_enabled(RRDHOST *rh); +bool ml_streaming_enabled(); void ml_init(void); +void ml_fini(void); -void ml_host_new(RRDHOST *RH); -void ml_host_delete(RRDHOST *RH); +void ml_start_threads(void); +void ml_stop_threads(void); -void ml_chart_new(RRDSET *RS); -void ml_chart_delete(RRDSET *RS); +void ml_host_new(RRDHOST *rh); +void ml_host_delete(RRDHOST *rh); -void ml_dimension_new(RRDDIM *RD); -void ml_dimension_delete(RRDDIM *RD); +void ml_host_get_info(RRDHOST *RH, BUFFER *wb); +void ml_host_get_detection_info(RRDHOST *RH, BUFFER *wb); +void ml_host_get_models(RRDHOST *RH, BUFFER *wb); -void ml_start_anomaly_detection_threads(RRDHOST *RH); -void ml_stop_anomaly_detection_threads(RRDHOST *RH); -void ml_cancel_anomaly_detection_threads(RRDHOST *RH); +void ml_chart_new(RRDSET *rs); +void ml_chart_delete(RRDSET *rs); +bool ml_chart_update_begin(RRDSET *rs); +void ml_chart_update_end(RRDSET *rs); -char *ml_get_host_info(RRDHOST *RH); -char *ml_get_host_runtime_info(RRDHOST *RH); -char *ml_get_host_models(RRDHOST *RH); +void ml_dimension_new(RRDDIM *rd); +void ml_dimension_delete(RRDDIM *rd); +bool ml_dimension_is_anomalous(RRDDIM *rd, time_t curr_time, double value, bool exists); -void ml_chart_update_begin(RRDSET *RS); -void ml_chart_update_end(RRDSET *RS); +int ml_dimension_load_models(RRDDIM *rd); -bool ml_is_anomalous(RRDDIM *RD, time_t curr_t, double value, bool exists); - -bool ml_streaming_enabled(); +void ml_update_global_statistics_charts(uint64_t models_consulted); #ifdef __cplusplus }; diff --git a/netdata-installer.sh b/netdata-installer.sh index e45eead1..c145c280 100755 --- a/netdata-installer.sh +++ b/netdata-installer.sh @@ -709,6 +709,70 @@ bundle_jsonc() { bundle_jsonc +# ----------------------------------------------------------------------------- +build_yaml() { + env_cmd='' + + if [ -z "${DONT_SCRUB_CFLAGS_EVEN_THOUGH_IT_MAY_BREAK_THINGS}" ]; then + env_cmd="env CFLAGS='-fPIC -pipe -Wno-unused-value' CXXFLAGS='-fPIC -pipe' LDFLAGS=" + fi + + cd "${1}" > /dev/null || return 1 + run eval "${env_cmd} ./configure --disable-shared --disable-dependency-tracking --with-pic" + run eval "${env_cmd} ${make} ${MAKEOPTS}" + cd - > /dev/null || return 1 +} + +copy_yaml() { + target_dir="${PWD}/externaldeps/libyaml" + + run mkdir -p "${target_dir}" || return 1 + + run cp "${1}/src/.libs/libyaml.a" "${target_dir}/libyaml.a" || return 1 + run cp "${1}/include/yaml.h" "${target_dir}/" || return 1 +} + +bundle_yaml() { + if pkg-config yaml-0.1; then + return 0 + fi + + if [ -z "$(command -v cmake)" ]; then + run_failed "Could not find cmake, which is required to build YAML. Critical error." + return 0 + fi + + [ -n "${GITHUB_ACTIONS}" ] && echo "::group::Bundling YAML." + + progress "Prepare YAML" + + YAML_PACKAGE_VERSION="$(cat packaging/yaml.version)" + + tmp="$(mktemp -d -t netdata-yaml-XXXXXX)" + YAML_PACKAGE_BASENAME="yaml-${YAML_PACKAGE_VERSION}.tar.gz" + + if fetch_and_verify "yaml" \ + "https://github.com/yaml/libyaml/releases/download/${YAML_PACKAGE_VERSION}/${YAML_PACKAGE_BASENAME}" \ + "${YAML_PACKAGE_BASENAME}" \ + "${tmp}" \ + "${NETDATA_LOCAL_TARBALL_OVERRIDE_YAML}"; then + if run tar --no-same-owner -xf "${tmp}/${YAML_PACKAGE_BASENAME}" -C "${tmp}" && + build_yaml "${tmp}/yaml-${YAML_PACKAGE_VERSION}" && + copy_yaml "${tmp}/yaml-${YAML_PACKAGE_VERSION}" && + rm -rf "${tmp}"; then + run_ok "YAML built and prepared." + else + run_failed "Failed to build YAML, critical error." + fi + else + run_failed "Unable to fetch sources for YAML, critical error." + fi + + [ -n "${GITHUB_ACTIONS}" ] && echo "::endgroup::" +} + +bundle_yaml + # ----------------------------------------------------------------------------- get_kernel_version() { @@ -788,7 +852,7 @@ copy_libbpf() { } bundle_libbpf() { - if { [ -n "${NETDATA_DISABLE_EBPF}" ] && [ ${NETDATA_DISABLE_EBPF} = 1 ]; } || [ "$(uname -s)" != Linux ]; then + if { [ -n "${NETDATA_DISABLE_EBPF}" ] && [ "${NETDATA_DISABLE_EBPF}" = 1 ]; } || [ "$(uname -s)" != Linux ]; then return 0 fi @@ -822,14 +886,14 @@ bundle_libbpf() { rm -rf "${tmp}"; then run_ok "libbpf built and prepared." else - if [ -n "${NETDATA_DISABLE_EBPF}" ] && [ ${NETDATA_DISABLE_EBPF} = 0 ]; then + if [ -n "${NETDATA_DISABLE_EBPF}" ] && [ "${NETDATA_DISABLE_EBPF}" = 0 ]; then fatal "failed to build libbpf." I0005 else run_failed "Failed to build libbpf. eBPF support will be disabled" fi fi else - if [ -n "${NETDATA_DISABLE_EBPF}" ] && [ ${NETDATA_DISABLE_EBPF} = 0 ]; then + if [ -n "${NETDATA_DISABLE_EBPF}" ] && [ "${NETDATA_DISABLE_EBPF}" = 0 ]; then fatal "Failed to fetch sources for libbpf." I0006 else run_failed "Unable to fetch sources for libbpf. eBPF support will be disabled" @@ -846,7 +910,7 @@ copy_co_re() { } bundle_ebpf_co_re() { - if { [ -n "${NETDATA_DISABLE_EBPF}" ] && [ ${NETDATA_DISABLE_EBPF} = 1 ]; } || [ "$(uname -s)" != Linux ]; then + if { [ -n "${NETDATA_DISABLE_EBPF}" ] && [ "${NETDATA_DISABLE_EBPF}" = 1 ]; } || [ "$(uname -s)" != Linux ]; then return 0 fi @@ -869,7 +933,7 @@ bundle_ebpf_co_re() { rm -rf "${tmp}"; then run_ok "libbpf built and prepared." else - if [ -n "${NETDATA_DISABLE_EBPF}" ] && [ ${NETDATA_DISABLE_EBPF} = 0 ]; then + if [ -n "${NETDATA_DISABLE_EBPF}" ] && [ "${NETDATA_DISABLE_EBPF}" = 0 ]; then fatal "Failed to get eBPF CO-RE files." I0007 else run_failed "Failed to get eBPF CO-RE files. eBPF support will be disabled" @@ -878,7 +942,7 @@ bundle_ebpf_co_re() { fi fi else - if [ -n "${NETDATA_DISABLE_EBPF}" ] && [ ${NETDATA_DISABLE_EBPF} = 0 ]; then + if [ -n "${NETDATA_DISABLE_EBPF}" ] && [ "${NETDATA_DISABLE_EBPF}" = 0 ]; then fatal "Failed to fetch eBPF CO-RE files." I0008 else run_failed "Failed to fetch eBPF CO-RE files. eBPF support will be disabled" @@ -1018,13 +1082,14 @@ if [ ! -f "${NETDATA_PREFIX}/etc/netdata/.installer-cleanup-of-stock-configs-don (find -L "${NETDATA_PREFIX}/etc/netdata" -type f -not -path '*/\.*' -not -path "${NETDATA_PREFIX}/etc/netdata/orig/*" \( -name '*.conf.old' -o -name '*.conf' -o -name '*.conf.orig' -o -name '*.conf.installer_backup.*' \)) | while IFS= read -r x; do if [ -f "${x}" ]; then # find it relative filename - f=$("$x" | sed "${NETDATA_PREFIX}/etc/netdata/") + p="$(echo "${NETDATA_PREFIX}/etc/netdata" | sed -e 's/\//\\\//')" + f="$(echo "${x}" | sed -e "s/${p}//")" # find the stock filename - t=$("${f}" | sed ".conf.installer_backup.*/.conf") - t=$("${t}" | sed ".conf.old/.conf") - t=$("${t}" | sed ".conf.orig/.conf") - t=$("${t}" | sed "orig//") + t="$(echo "${f}" | sed -e 's/\.conf\.installer_backup\..*/\.conf/')" + t="$(echo "${t}" | sed -e 's/\.conf\.old/\.conf/')" + t="$(echo "${t}" | sed -e 's/\.conf\.orig/\.conf/')" + t="$(echo "${t}" | sed -e 's/orig//')" if [ -z "${md5sum}" ] || [ ! -x "${md5sum}" ]; then # we don't have md5sum - keep it @@ -1059,7 +1124,7 @@ fi # ----------------------------------------------------------------------------- progress "Fix generated files permissions" -run find ./system/ -type f -a \! -name \*.in -a \! -name Makefile\* -a \! -name \*.conf -a \! -name \*.service -a \! -name \*.timer -a \! -name \*.logrotate -a \! -name \.install-type -exec chmod 755 {} \; +run chmod 755 ./system/*/init.d/netdata ./system/*/rc.d/netdata ./system/runit/run ./system/install-service.sh # ----------------------------------------------------------------------------- progress "Creating standard user and groups for netdata" @@ -1434,9 +1499,6 @@ install_go() { run chown "root:${NETDATA_GROUP}" "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/go.d.plugin" fi run chmod 0750 "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/go.d.plugin" - if command -v setcap 1>/dev/null 2>&1; then - run setcap "cap_net_admin+epi cap_net_raw=eip" "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/go.d.plugin" - fi rm -rf "${tmp}" [ -n "${GITHUB_ACTIONS}" ] && echo "::endgroup::" @@ -1444,6 +1506,12 @@ install_go() { install_go +if [ -f "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/go.d.plugin" ]; then + if command -v setcap 1>/dev/null 2>&1; then + run setcap "cap_net_admin+epi cap_net_raw=eip" "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/go.d.plugin" + fi +fi + should_install_ebpf() { if [ "${NETDATA_DISABLE_EBPF:=0}" -eq 1 ]; then run_failed "eBPF has been explicitly disabled, it will not be available in this install." @@ -1496,6 +1564,11 @@ remove_old_ebpf() { rm -f "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/pnetdata_ebpf"*.?.*.o fi + # Remove old eBPF programs that did not have "rhf" suffix + if [ ! -f "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/ebpf.d/pnetdata_ebpf_process.3.10.rhf.o" ]; then + rm -f "${NETDATA_PREFIX}/usr/libexec/netdata/plugins.d/ebpf.d/"*.o + fi + # Remove old reject list from previous directory if [ -f "${NETDATA_PREFIX}/usr/lib/netdata/conf.d/ebpf_kernel_reject_list.txt" ]; then echo >&2 "Removing old ebpf_kernel_reject_list.txt." diff --git a/netdata.spec.in b/netdata.spec.in index 1c6aa61d..c2fa7dca 100644 --- a/netdata.spec.in +++ b/netdata.spec.in @@ -27,6 +27,20 @@ AutoReqProv: yes %global _have_ebpf 0 %endif +# Disable FreeIPMI on Amazon Linux +%if 0%{?amzn} +%global _have_freeipmi 0 +%else +%global _have_freeipmi 1 +%endif + +# Disable the NFACCT plugin on Amazon Linux +%if 0%{?amzn} +%global _have_nfacct 0 +%else +%global _have_nfacct 1 +%endif + # Mitigate the cross-distro mayhem by strictly defining the libexec destination %define _prefix /usr %define _sysconfdir /etc @@ -34,73 +48,21 @@ AutoReqProv: yes %define _libexecdir /usr/libexec %define _libdir /usr/lib +# Fedora doesn’t define this, but other distros do +%{!?_presetdir:%global _presetdir %{_libdir}/systemd/system-preset} + # Redefine centos_ver to standardize on a single macro %{?rhel:%global centos_ver %rhel} # # Conditional build: -%bcond_without systemd # systemd %bcond_without netns # build with netns support (cgroup-network) %if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version} >= 1140 %else -%undefine with_systemd %undefine with_netns %endif -%if %{with systemd} -%if 0%{?suse_version} -%global netdata_initd_buildrequires \ -BuildRequires: systemd-rpm-macros \ -%{nil} -%global netdata_initd_requires \ -%{?systemd_requires} \ -%{nil} -%global netdata_init_post %service_add_post netdata.service \ -/sbin/service netdata restart > /dev/null 2>&1 \ -%{nil} -%global netdata_init_preun %service_del_preun netdata.service \ -/sbin/service netdata stop > /dev/null 2>&1 \ -%{nil} -%global netdata_init_postun %service_del_postun netdata.service -%else -%global netdata_initd_buildrequires \ -BuildRequires: systemd -%global netdata_initd_requires \ -Requires(preun): systemd-units \ -Requires(postun): systemd-units \ -Requires(post): systemd-units \ -%{nil} -%global netdata_init_post %systemd_post netdata.service \ -/usr/bin/systemctl enable netdata.service \ -/usr/bin/systemctl daemon-reload \ -/usr/bin/systemctl restart netdata.service \ -%{nil} -%global netdata_init_preun %systemd_preun netdata.service -%global netdata_init_postun %systemd_postun_with_restart netdata.service -%endif -%else -%global netdata_initd_buildrequires %{nil} -%global netdata_initd_requires \ -Requires(post): chkconfig \ -%{nil} -%global netdata_init_post \ -/sbin/chkconfig --add netdata \ -/sbin/service netdata restart > /dev/null 2>&1 \ -%{nil} -%global netdata_init_preun %{nil} \ -if [ $1 = 0 ]; then \ - /sbin/service netdata stop > /dev/null 2>&1 \ - /sbin/chkconfig --del netdata \ -fi \ -%{nil} -%global netdata_init_postun %{nil} \ -if [ $1 != 0 ]; then \ - /sbin/service netdata condrestart 2>&1 > /dev/null \ -fi \ -%{nil} -%endif - Summary: Real-time performance monitoring, done right! Name: netdata Version: %{version} @@ -127,7 +89,7 @@ BuildRequires: git-core BuildRequires: autoconf %if 0%{?fedora} || 0%{?rhel} >= 7 || 0%{?suse_version} >= 1140 BuildRequires: autoconf-archive -%if 0%{?rhel} <= 8 +%if 0%{?rhel} <= 8 && 0%{?amzn} < 2023 BuildRequires: autogen %endif %endif @@ -145,12 +107,14 @@ BuildRequires: protobuf-devel BuildRequires: libprotobuf-c-devel BuildRequires: liblz4-devel BuildRequires: libjson-c-devel +BuildRequires: libyaml-devel %else %if 0%{?fedora} BuildRequires: protobuf-devel BuildRequires: protobuf-c-devel BuildRequires: lz4-devel BuildRequires: json-c-devel +BuildRequires: libyaml-devel %else %if 0%{?centos_ver} >= 8 BuildRequires: protobuf-devel @@ -158,11 +122,16 @@ BuildRequires: protobuf-c-devel %endif BuildRequires: lz4-devel BuildRequires: json-c-devel +BuildRequires: libyaml-devel %endif %endif # Core build requirements for service install -%{netdata_initd_buildrequires} +%if 0%{?suse_version} +BuildRequires: systemd-rpm-macros +%else +BuildRequires: systemd +%endif # Runtime dependencies # @@ -180,24 +149,36 @@ Requires: python3 Requires(pre): /usr/sbin/groupadd Requires(pre): /usr/sbin/useradd -%{netdata_initd_requires} - # ##################################################################### # Functionality-dependent package dependencies # ##################################################################### -# Note: Some or all of the Packages may be found in the EPEL repo, +# Note: Some or all of the Packages may be found in the EPEL repo, # rather than the standard ones +# epbf dependencies +%if 0%{?_have_ebpf} +%if 0%{?suse_version} +BuildRequires: libelf-devel +%else +BuildRequires: elfutils-libelf-devel +%endif +%endif +# end - ebpf dependencies + # nfacct plugin dependencies +%if %{_have_nfacct} BuildRequires: libmnl-devel %if 0%{?fedora} || 0%{?suse_version} >= 1140 BuildRequires: libnetfilter_acct-devel %endif +%endif # end nfacct plugin dependencies # freeipmi plugin dependencies +%if %{_have_freeipmi} BuildRequires: freeipmi-devel +%endif # end - freeipmi plugin dependencies # CUPS plugin dependencies @@ -287,11 +268,13 @@ install -m 755 -p packaging/installer/netdata-updater.sh "${RPM_BUILD_ROOT}%{_li # ########################################################### # logrotate settings install -m 755 -d "${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d" -install -m 644 -p system/netdata.logrotate "${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}" +install -m 644 -p system/logrotate/netdata "${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}" # ########################################################### # Install freeipmi +%if %{_have_freeipmi} install -m 4750 -p freeipmi.plugin "${RPM_BUILD_ROOT}%{_libexecdir}/%{name}/plugins.d/freeipmi.plugin" +%endif # ########################################################### # Install apps.plugin @@ -328,15 +311,10 @@ install -m 755 -d "${RPM_BUILD_ROOT}%{_localstatedir}/lib/%{name}/registry" # ########################################################### # Install netdata service -%if %{with systemd} install -m 755 -d "${RPM_BUILD_ROOT}%{_unitdir}" -install -m 644 -p system/netdata.service "${RPM_BUILD_ROOT}%{_unitdir}/netdata.service" -%else -# install SYSV init stuff -install -d "${RPM_BUILD_ROOT}/etc/rc.d/init.d" -install -m 755 system/netdata-init-d \ - "${RPM_BUILD_ROOT}/etc/rc.d/init.d/netdata" -%endif +install -m 644 -p system/systemd/netdata.service "${RPM_BUILD_ROOT}%{_unitdir}/netdata.service" +install -m 755 -d "${RPM_BUILD_ROOT}%{_presetdir}" +install -m 644 -p system/systemd/50-netdata.preset "${RPM_BUILD_ROOT}%{_presetdir}/50-netdata.preset" # ############################################################ # Package Go within netdata (TBD: Package it separately) @@ -454,13 +432,25 @@ for item in docker nginx varnish haproxy adm nsd proxy squid ceph nobody I2C; do done %post -%{netdata_init_post} +%if 0%{?suse_version} +%service_add_post netdata.service +%else +%systemd_post netdata.service +%endif %preun -%{netdata_init_preun} +%if 0%{?suse_version} +%service_del_preun netdata.service +%else +%systemd_preun netdata.service +%endif %postun -%{netdata_init_postun} +%if 0%{?suse_version} +%service_del_postun netdata.service +%else +%systemd_postun_with_restart netdata.service +%endif %clean rm -rf "${RPM_BUILD_ROOT}" @@ -479,11 +469,8 @@ rm -rf "${RPM_BUILD_ROOT}" %{_sbindir}/netdatacli %{_sbindir}/netdata-claim.sh -%if %{with systemd} %{_unitdir}/netdata.service -%else -%{_sysconfdir}/rc.d/init.d/netdata -%endif +%{_presetdir}/50-netdata.preset %defattr(0750,root,netdata,0750) @@ -517,9 +504,6 @@ rm -rf "${RPM_BUILD_ROOT}" # perf plugin %caps(cap_dac_read_search=ep) %attr(0750,root,netdata) %{_libexecdir}/%{name}/plugins.d/slabinfo.plugin -# freeipmi files -%attr(4750,root,netdata) %{_libexecdir}/%{name}/plugins.d/freeipmi.plugin - # go.d.plugin (the capability required for wireguard module) %caps(cap_net_admin,cap_net_raw=eip) %{_libexecdir}/%{name}/plugins.d/go.d.plugin @@ -537,7 +521,9 @@ rm -rf "${RPM_BUILD_ROOT}" %attr(0770,netdata,netdata) %dir %{_localstatedir}/lib/%{name}/registry # Free IPMI belongs to a different sub-package +%if %{_have_freeipmi} %exclude %{_libexecdir}/%{name}/plugins.d/freeipmi.plugin +%endif # CUPS belongs to a different sub package %if 0%{?centos_ver} != 6 && 0%{?centos_ver} != 7 @@ -557,6 +543,7 @@ Use this plugin to enable metrics collection from cupsd, the daemon running when %attr(0750,root,netdata) %{_libexecdir}/%{name}/plugins.d/cups.plugin %endif +%if %{_have_freeipmi} %package plugin-freeipmi Summary: FreeIPMI - The Intelligent Platform Management System Group: Applications/System @@ -565,14 +552,20 @@ Requires: netdata = %{version} %description plugin-freeipmi The IPMI specification defines a set of interfaces for platform management. -It is implemented by a number vendors for system management. The features of IPMI that most users will be interested in +It is implemented by a number vendors for system management. The features of IPMI that most users will be interested in are sensor monitoring, system event monitoring, power control, and serial-over-LAN (SOL). %files plugin-freeipmi %attr(4750,root,netdata) %{_libexecdir}/%{name}/plugins.d/freeipmi.plugin +%endif %changelog -* Wed Feb 03 2022 Austin Hemmelgarn 0.0.0-16 +* Tue Mar 21 2023 Austin Hemmelgarn 0.0.0-18 +- Fix systemd handling to follow BCP. +- Drop pre-systemd init support. +* Thu Feb 16 2023 Konstantin Shalygin 0.0.0-17 +- Added eBPF build dependency +* Fri Feb 03 2022 Austin Hemmelgarn 0.0.0-16 - Bundle updater script in native packages. * Mon Oct 11 2021 Austin Hemmelgarn 0.0.0-15 - Remove support code for legacy ACLK implementation. @@ -609,9 +602,8 @@ First draft refactor on package dependencies section * Wed Jan 02 2019 Pawel Krupa - 0.0.0-3 - Temporary set version statically - Fix changelog ordering -- Comment-out node.d configuration directory +- Comment-out node.d configuration directory * Wed Jan 02 2019 Pawel Krupa - 0.0.0-2 - Fix permissions for log files * Sun Nov 15 2015 Alon Bar-Lev - 0.0.0-1 - Initial add. - diff --git a/packaging/PLATFORM_SUPPORT.md b/packaging/PLATFORM_SUPPORT.md index 62e208c7..0de0c3b1 100644 --- a/packaging/PLATFORM_SUPPORT.md +++ b/packaging/PLATFORM_SUPPORT.md @@ -1,15 +1,15 @@ -# Netdata platform support policy +# Platform support policy Netdata defines three tiers of official support: @@ -63,8 +63,10 @@ to work on these platforms with minimal user effort. | 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 | 12.x | x86\_64, i386, ARMv7, AArch64 | | | Debian | 11.x | x86\_64, i386, ARMv7, AArch64 | | | Debian | 10.x | x86\_64, i386, ARMv7, AArch64 | | +| Fedora | 38 | x86\_64, AArch64 | | | Fedora | 37 | x86\_64, AArch64 | | | Fedora | 36 | x86\_64, AArch64 | | | openSUSE | Leap 15.4 | x86\_64, AArch64 | | @@ -73,10 +75,11 @@ to work on these platforms with minimal user effort. | 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 | 23.04 | x86\_64, AArch64, ARMv7 | | | 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 @@ -87,13 +90,14 @@ 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 | | +| Amazon Linux | 2023 | x86\_64, AArch64 | Scheduled for promotion to Core tier at some point after the release of v1.39.0 of the Netdata Agent | +| Amazon Linux | 2 | x86\_64, AArch64 | Scheduled for promotion to Core tier at some point after the release of v1.39.0 of the Netdata Agent | +| 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 @@ -152,6 +156,7 @@ This is a list of platforms that we have supported in the recent past but no lon | Platform | Version | Notes | |--------------|-----------|----------------------| +| Alpine Linux | 3.14 | EOL as of 2023-05-01 | | 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 | @@ -160,6 +165,7 @@ This is a list of platforms that we have supported in the recent past but no lon | 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 | +| Ubuntu | 18.04 | EOL as of 2023-04-02 | ## Static builds diff --git a/packaging/building-native-packages-locally.md b/packaging/building-native-packages-locally.md index 84a0fb4d..d3b47377 100644 --- a/packaging/building-native-packages-locally.md +++ b/packaging/building-native-packages-locally.md @@ -1,10 +1,6 @@ - +# Build native (DEB/RPM) packages for testing -# How to build native (DEB/RPM) packages locally for testing +This document provides instructions for developers who need to build native packages locally for testing. ## Requirements diff --git a/packaging/check-kernel-config.sh b/packaging/check-kernel-config.sh index ded32257..515259c1 100755 --- a/packaging/check-kernel-config.sh +++ b/packaging/check-kernel-config.sh @@ -51,15 +51,18 @@ fi if [ -n "${CONFIG_PATH}" ]; then GREP='grep' + CAT='cat' if echo "${CONFIG_PATH}" | grep -q '.gz'; then - GREP='zgrep' + CAT='zcat' fi REQUIRED_CONFIG="KPROBES KPROBES_ON_FTRACE HAVE_KPROBES BPF BPF_SYSCALL BPF_JIT" for required_config in ${REQUIRED_CONFIG}; do - if ! "${GREP}" -q "CONFIG_${required_config}=y" "${CONFIG_PATH}"; then + # Fix issue https://github.com/netdata/netdata/issues/14668 + # if ! "${GREP}" -q "CONFIG_${required_config}=y" "${CONFIG_PATH}"; then + if ! { "${CAT}" "${CONFIG_PATH}" | "${GREP}" -q "CONFIG_${required_config}=y" >&2 >/dev/null; } ;then echo >&2 " Missing Kernel Config: ${required_config}" exit 1 fi diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile index ce5a0b93..fcd9432b 100644 --- a/packaging/docker/Dockerfile +++ b/packaging/docker/Dockerfile @@ -51,7 +51,9 @@ RUN mkdir -p /app/usr/sbin/ \ mv /usr/sbin/netdatacli /app/usr/sbin/ && \ mv packaging/docker/run.sh /app/usr/sbin/ && \ mv packaging/docker/health.sh /app/usr/sbin/ && \ - cp -rp /deps/* /app/usr/local/ && \ + mkdir -p /deps/etc && \ + cp -rp /deps/etc /app/usr/local/etc && \ + chmod -R o+rX /app && \ chmod +x /app/usr/sbin/run.sh ##################################################################### @@ -106,6 +108,9 @@ RUN chown -R root:root \ if [ -f /usr/libexec/netdata/plugins.d/freeipmi.plugin ]; then \ chmod 4755 /usr/libexec/netdata/plugins.d/freeipmi.plugin; \ fi && \ + if [ -f /usr/libexec/netdata/plugins.d/go.d.plugin ] && command -v setcap 1>/dev/null 2>&1; then \ + setcap "cap_net_raw=eip" /usr/libexec/netdata/plugins.d/go.d.plugin 2>/dev/null; \ + fi && \ # 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 {} \; && \ @@ -114,6 +119,8 @@ RUN chown -R root:root \ ENV NETDATA_LISTENER_PORT 19999 EXPOSE $NETDATA_LISTENER_PORT +ENV NETDATA_EXTRA_APK_PACKAGES="" + ENTRYPOINT ["/usr/sbin/run.sh"] HEALTHCHECK --interval=60s --timeout=10s --retries=3 CMD /usr/sbin/health.sh diff --git a/packaging/docker/README.md b/packaging/docker/README.md index aec5723e..ef7dd6de 100644 --- a/packaging/docker/README.md +++ b/packaging/docker/README.md @@ -1,26 +1,16 @@ -# Install the Netdata Agent with Docker +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; -Running the Netdata Agent in a container works best for an internal network or to quickly analyze a host. Docker helps -you get set up quickly, and doesn't install anything permanent on the system, which makes uninstalling the Agent easy. - -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](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 -_actively_ contributing to Netdata's future. +# Install Netdata with Docker ## Limitations running the Agent in Docker @@ -41,22 +31,7 @@ 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. - -
-What are these "some distros"? - -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 -``` -
- -You can create a new Agent container using either `docker run` or Docker Compose. After using either method, you can +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`. Both methods create a [bind mount](https://docs.docker.com/storage/bind-mounts/) for Netdata's configuration files @@ -64,7 +39,12 @@ _within the container_ at `/etc/netdata`. See the [configuration section](#confi you want to access the configuration files from your _host_ machine, see [host-editable configuration](#host-editable-configuration). -**`docker run`**: Use the `docker run` command, along with the following options, to start a new container. + + + +

Using the docker run command

+ +Run the following command along with the following options on your terminal, to start a new container. ```bash docker run -d --name=netdata \ @@ -80,48 +60,65 @@ docker run -d --name=netdata \ --restart unless-stopped \ --cap-add SYS_PTRACE \ --security-opt apparmor=unconfined \ - --ulimit nofile=4096 \ netdata/netdata ``` -**Docker Compose**: Copy the following code and paste into a new file called `docker-compose.yml`, then run -`docker-compose up -d` in the same directory as the `docker-compose.yml` file to start the container. - -```yaml -version: '3' -services: - netdata: - image: netdata/netdata - container_name: netdata - hostname: example.com # set to fqdn of host - ports: - - 19999:19999 - restart: unless-stopped - cap_add: - - SYS_PTRACE - security_opt: - - apparmor:unconfined - ulimits: - nofile: - soft: 4096 - volumes: - - netdataconfig:/etc/netdata - - netdatalib:/var/lib/netdata - - netdatacache:/var/cache/netdata - - /etc/passwd:/host/etc/passwd:ro - - /etc/group:/host/etc/group:ro - - /proc:/host/proc:ro - - /sys:/host/sys:ro - - /etc/os-release:/host/etc/os-release:ro - -volumes: - netdataconfig: - netdatalib: - netdatacache: -``` +> ### Note +> +> If you plan to Claim the node to Netdata Cloud, you can find the command with the right parameters by clicking the "Add Nodes" button in your Space's Nodes tab. + +
+ + +

Using the docker-compose command

+ +#### Steps + +1. Copy the following code and paste into a new file called `docker-compose.yml` + + ```yaml + version: '3' + services: + netdata: + image: netdata/netdata + container_name: netdata + hostname: example.com # set to fqdn of host + ports: + - 19999:19999 + restart: unless-stopped + cap_add: + - SYS_PTRACE + security_opt: + - apparmor:unconfined + volumes: + - netdataconfig:/etc/netdata + - netdatalib:/var/lib/netdata + - netdatacache:/var/cache/netdata + - /etc/passwd:/host/etc/passwd:ro + - /etc/group:/host/etc/group:ro + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /etc/os-release:/host/etc/os-release:ro + + volumes: + netdataconfig: + netdatalib: + netdatacache: + ``` + +2. Run `docker-compose up -d` in the same directory as the `docker-compose.yml` file to start the container. + +> :bookmark_tabs: Note +> +> If you plan to Claim the node to Netdata Cloud, you can find the command with the right parameters by clicking the "Add Nodes" button in your Space's "Nodes" view. + +
+
## Docker tags +See our full list of Docker images at [Docker Hub](https://hub.docker.com/r/netdata/netdata). + The official `netdata/netdata` Docker image provides the following named tags: * `stable`: The `stable` tag will always point to the most recently published stable build. @@ -136,6 +133,20 @@ Additionally, for each stable release, three tags are pushed, one with the full that would match that tag (for example, if `v1.30.1` were to be published, the `v1.30` tag would be updated to point to that instead of `v1.30.0`). +## Adding extra packages at runtime + +By default, the official Netdata container images do not include a number of optional runtime dependencies. You +can add these dependencies, or any other APK packages, at runtime by listing them in the environment variable +`NETDATA_EXTRA_APK_PACKAGES`. + +Commonly useful packages include: + +- `apcupsd`: For monitoring APC UPS devices. +- `libvirt-daemon`: For resolving cgroup names for libvirt domains. +- `lm-sensors`: For monitoring hardware sensors. +- `msmtp`: For email alert support. +- `netcat-openbsd`: For IRC alert support. + ## Health Checks Our Docker image provides integrated support for health checks through the standard Docker interfaces. @@ -176,7 +187,9 @@ to restart the container: `docker restart netdata`. ### Host-editable configuration -> **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 +> :warning: Warning +> +> The [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 @@ -208,7 +221,6 @@ docker run -d --name=netdata \ --restart unless-stopped \ --cap-add SYS_PTRACE \ --security-opt apparmor=unconfined \ - --ulimit nofile=4096 \ netdata/netdata ``` @@ -230,9 +242,6 @@ services: - SYS_PTRACE security_opt: - apparmor:unconfined - ulimits: - nofile: - soft: 4096 volumes: - ./netdataconfig/netdata:/etc/netdata:ro - netdatalib:/var/lib/netdata @@ -322,17 +331,17 @@ 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](https://github.com/netdata/netdata/blob/master/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) or +[CetusGuard](https://github.com/hectorm/cetusguard) 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 network. Access to the proxy container is limited to only within the network. -Below is [an example repository (and image)](https://github.com/Tecnativa/docker-socket-proxy) that provides a proxy to -the socket. +Here are two examples, the first using [a Docker image based on HAProxy](https://github.com/Tecnativa/docker-socket-proxy) +and the second using [CetusGuard](https://github.com/hectorm/cetusguard). -You run the Docker Socket Proxy in its own Docker Compose file and leave it on a private network that you can add to -other services that require access. +##### Docker Socket Proxy (HAProxy) ```yaml version: '3' @@ -347,17 +356,46 @@ services: proxy: image: tecnativa/docker-socket-proxy volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro + - /var/run/docker.sock:/var/run/docker.sock:ro environment: - CONTAINERS=1 - ``` **Note:** Replace `2375` with the port of your proxy. +##### CetusGuard + +```yaml +version: '3' +services: + netdata: + image: netdata/netdata + # ... rest of your config ... + ports: + - 19999:19999 + environment: + - DOCKER_HOST=cetusguard:2375 + cetusguard: + image: hectorm/cetusguard:v1 + read_only: true + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + environment: + CETUSGUARD_BACKEND_ADDR: unix:///var/run/docker.sock + CETUSGUARD_FRONTEND_ADDR: tcp://:2375 + CETUSGUARD_RULES: | + ! Inspect a container + GET %API_PREFIX_CONTAINERS%/%CONTAINER_ID_OR_NAME%/json +``` + +You can run the socket proxy in its own Docker Compose file and leave it on a private network that you can add to +other services that require access. + #### Giving group access to the Docker socket (less safe) +> :warning: Caution +> > You should seriously consider the necessity of activating this option, as it grants to the `netdata` -user access to the privileged socket connection of docker service and therefore your whole machine. +> user access to the privileged socket connection of docker service and therefore your whole machine. If you want to have your container names resolved by Netdata, make the `netdata` user be part of the group that owns the socket. @@ -386,6 +424,8 @@ grep docker /etc/group | cut -d ':' -f 3 #### Running as root (unsafe) +> :warning: Caution +> > You should seriously consider the necessity of activating this option, as it grants to the `netdata` user access to > the privileged socket connection of docker service, and therefore your whole machine. @@ -495,9 +535,6 @@ services: - SYS_PTRACE security_opt: - apparmor:unconfined - ulimits: - nofile: - soft: 4096 volumes: - netdatalib:/var/lib/netdata - netdatacache:/var/cache/netdata @@ -520,4 +557,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 GitHub Acions infrastructure. +You may either use the command line tools available or take advantage of our GitHub Actions infrastructure. diff --git a/packaging/docker/run.sh b/packaging/docker/run.sh index 9029e22b..ed77c394 100755 --- a/packaging/docker/run.sh +++ b/packaging/docker/run.sh @@ -21,6 +21,8 @@ if [ ! "${DISABLE_TELEMETRY:-0}" -eq 0 ] || touch /etc/netdata/.opt-out-from-anonymous-statistics fi +chmod o+rX / # Needed to fix permissions issues in some cases. + BALENA_PGID=$(stat -c %g /var/run/balena.sock 2>/dev/null || true) DOCKER_PGID=$(stat -c %g /var/run/docker.sock 2>/dev/null || true) @@ -67,4 +69,17 @@ if [ -n "${NETDATA_CLAIM_URL}" ] && [ -n "${NETDATA_CLAIM_TOKEN}" ] && [ ! -f /v -daemon-not-running fi +if [ -n "${NETDATA_EXTRA_APK_PACKAGES}" ]; then + echo "Fetching APK repository metadata." + if ! apk update; then + echo "Failed to fetch APK repository metadata." + else + echo "Installing supplementary packages." + # shellcheck disable=SC2086 + if ! apk add --no-cache ${NETDATA_EXTRA_APK_PACKAGES}; then + echo "Failed to install supplementary packages." + fi + fi +fi + exec /usr/sbin/netdata -u "${DOCKER_USR}" -D -s /host -p "${NETDATA_LISTENER_PORT}" "$@" diff --git a/packaging/ebpf-co-re.checksums b/packaging/ebpf-co-re.checksums index 7f08d732..813e421c 100644 --- a/packaging/ebpf-co-re.checksums +++ b/packaging/ebpf-co-re.checksums @@ -1 +1 @@ -d1864cd736d236aa3738152d86096529830822a26405a62fe164779949bb3658 netdata-ebpf-co-re-glibc-v1.1.0.tar.xz +a50e649635cc2fe86c21a08334ee73451f08591ebbda8b5d0012c3b8fad2cc1e netdata-ebpf-co-re-glibc-v1.1.2.tar.xz diff --git a/packaging/ebpf-co-re.version b/packaging/ebpf-co-re.version index 795460fc..0f1acbd5 100644 --- a/packaging/ebpf-co-re.version +++ b/packaging/ebpf-co-re.version @@ -1 +1 @@ -v1.1.0 +v1.1.2 diff --git a/packaging/ebpf.checksums b/packaging/ebpf.checksums index e7434911..0d0be4ea 100644 --- a/packaging/ebpf.checksums +++ b/packaging/ebpf.checksums @@ -1,3 +1,3 @@ -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 +597a20895bbedcf87528b08fa9057426bd3c7638aa1ffac94f8987a90634513d ./netdata-kernel-collector-glibc-v1.1.2.tar.xz +25db2232b75bdb7fc6e10db870c3a3290f52ecfcdcf546d0e51947f2a4c17ccf ./netdata-kernel-collector-musl-v1.1.2.tar.xz +1d60425f5e8c6e30b3be86028dfc62c16022d8fe561e4c21c84cf6e8b998cd7d ./netdata-kernel-collector-static-v1.1.2.tar.xz diff --git a/packaging/ebpf.version b/packaging/ebpf.version index 795460fc..0f1acbd5 100644 --- a/packaging/ebpf.version +++ b/packaging/ebpf.version @@ -1 +1 @@ -v1.1.0 +v1.1.2 diff --git a/packaging/go.d.checksums b/packaging/go.d.checksums index 3e70b04f..6f338464 100644 --- a/packaging/go.d.checksums +++ b/packaging/go.d.checksums @@ -1,17 +1,17 @@ -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 +dc6cf312bf8211236c141a67aa8571ac58e98f9705dfcb5dc1a3103732a053a0 *config.tar.gz +2c6d0cee9207d00fe3f7e0845193cd511d40239ce94edcbaeb7319674ec86245 *go.d.plugin-v0.52.2.darwin-amd64.tar.gz +75bf5ac062bec27856890b12e6d5e5be0ecb931d25e2d0cba8f0e3c72f1255fd *go.d.plugin-v0.52.2.darwin-arm64.tar.gz +4a8a55c2bed0674019acd280aebc9f02ba958fa4e6f78ac3e88ffddd68254a36 *go.d.plugin-v0.52.2.freebsd-386.tar.gz +117316e1f9d945cfb6c9a8b7ee4576cf5dd27b9237fd21bae9fbbddc80aa0dc5 *go.d.plugin-v0.52.2.freebsd-amd64.tar.gz +ecb4ee060f153fb711a112e61eb126f893adb64badbb2dbc8e19c72230fa24b4 *go.d.plugin-v0.52.2.freebsd-arm.tar.gz +ae3057d396ab133ff19880644897ff9e4a1b34b85262422df3e9e079b72507f8 *go.d.plugin-v0.52.2.freebsd-arm64.tar.gz +446bbb62858db60b15e50710091186ba00de728b3e349d9f7db77f1475a8891f *go.d.plugin-v0.52.2.linux-386.tar.gz +fff928e244f87dd0b07734aaad87240957f5ee571e4f4196f4d50300d67ff8ec *go.d.plugin-v0.52.2.linux-amd64.tar.gz +2beb004ecc2820c76d8eb82a5da5251e21cc93675b0dd6575e393f4762f60d28 *go.d.plugin-v0.52.2.linux-arm.tar.gz +234cc81cbb7e104a8882aeff03ecd56214cb0aeb923db60c42aa0b6131f34bab *go.d.plugin-v0.52.2.linux-arm64.tar.gz +1a4c1106c82439a3e488d7a3b42432cefa27577e2425daf73871af7431d14ae8 *go.d.plugin-v0.52.2.linux-mips.tar.gz +29ebc77c995d4428018cf6f014023639e123f88dcebd026a3e476413261a4981 *go.d.plugin-v0.52.2.linux-mips64.tar.gz +9e6262de77b2e5f0ba2d0882097eaf68f727a0af0fd9d31bd547a8fa55bdeb04 *go.d.plugin-v0.52.2.linux-mips64le.tar.gz +05b7bbad67a36aa42f7a2933f0f429689ae7f5d23c9ce54bc26b9b386ddfca4b *go.d.plugin-v0.52.2.linux-mipsle.tar.gz +9b18de7731d02fd2fc48fe250ea071f4d797b9af26b51d562a4bc39cf5d7f34c *go.d.plugin-v0.52.2.linux-ppc64.tar.gz +357350165a42aa2c7fc03694a9176608943f6c3e4ce0e40ebad5bd5304b024e6 *go.d.plugin-v0.52.2.linux-ppc64le.tar.gz diff --git a/packaging/go.d.version b/packaging/go.d.version index f6460f6b..e831eb73 100644 --- a/packaging/go.d.version +++ b/packaging/go.d.version @@ -1 +1 @@ -v0.50.0 +v0.52.2 diff --git a/packaging/installer/README.md b/packaging/installer/README.md index 90d3b8de..869684da 100644 --- a/packaging/installer/README.md +++ b/packaging/installer/README.md @@ -1,116 +1,147 @@ - +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'; -import { Install, InstallBox } from '@site/src/components/Install/' +# Install Netdata -import { OneLineInstallWget, OneLineInstallCurl } from '@site/src/components/OneLineInstall/' +This document will guide you through installing the open-source Netdata monitoring Agent on Linux, Docker, Kubernetes, and many others, often with one command. -# Installation guide +## Get started -Netdata is a monitoring agent designed to run on all your systems: physical and virtual servers, containers, even -IoT/edge devices. Netdata runs on Linux, FreeBSD, macOS, Kubernetes, Docker, and all their derivatives. +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. -The best way to install Netdata is with our [**automatic one-line installation -script**](#automatic-one-line-installation-script), which works with all Linux distributions and most macOS environments. +Together with [Netdata Cloud](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md), you can monitor your entire infrastructure in +real time and troubleshoot problems that threaten the health of your nodes. -If you want to install Netdata with Docker, on a Kubernetes cluster, or a different operating system, see [Have a -different operating system, or want to try another -method?](#have-a-different-operating-system-or-want-to-try-another-method) +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. -Some third parties, such as the packaging teams at various Linux distributions, distribute old, broken, or altered -packages. We recommend you install Netdata using one of the methods listed below to guarantee you get the latest -checksum-verified packages. +To install Netdata in minutes on your platform: -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](https://github.com/netdata/netdata/blob/master/docs/anonymous-statistics.md) -page. +1. Sign up to +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 -The usage statistics are _vital_ for us, as we use them to discover bugs and prioritize new features. We thank you for -_actively_ contributing to Netdata's future. +Upon installation completing successfully, you should be able to see the node live in your Netdata Space and live charts +in the Overview tab. [Read more about the cloud features](https://github.com/netdata/netdata/blob/master/docs/quickstart/infrastructure.md). -## Automatic one-line installation script +Where you go from here is based on your use case, immediate needs, and experience with monitoring and troubleshooting, +but we have some hints on what you might want to do next. -![](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) +### What's next? -This method is fully automatic on all Linux distributions, including Ubuntu, Debian, Fedora, CentOS, and others, as well as on mac OS environments. +Explore our [general advanced installation options and troubleshooting](#advanced-installation-options-and-troubleshooting), specific options +for the [single line installer](#install-on-linux-with-one-line-installer), or [other installation methods](#other-installation-methods). -To install Netdata, including all dependencies required to connect to Netdata Cloud, and get _automatic nightly -updates_, run the following as your normal user: +#### Agent user interface - +To access the UI provided by the locally installed agent, open a browser and navigate to `http://NODE:19999`, replacing `NODE` with either `localhost` or +the hostname/IP address of the remote node. You can also read more about +[the agent dashboard](https://github.com/netdata/netdata/blob/master/web/gui/README.md). -Or, if you have cURL but not wget (such as on macOS): +#### Configuration - +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](https://github.com/netdata/netdata/blob/master/docs/store/change-metrics-storage.md). -This script will preferentially use native DEB/RPM packages if we provide them for your platform. +#### Data collection -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](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md). +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](https://github.com/netdata/netdata/blob/master/collectors/README.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/collectors/REFERENCE.md) +or read about its requirements to configure your endpoint to publish metrics in the correct format and endpoint. -Scroll down for details about [automatic updates](#automatic-updates) or [nightly vs. stable -releases](#nightly-vs-stable-releases). +#### Alarms & notifications -### Post-installation +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](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md) or +[enable notifications](https://github.com/netdata/netdata/blob/master/docs/monitor/enable-notifications.md) to customize your Netdata experience. -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. +#### Make your deployment production ready -Or, skip straight to [configuring the Netdata Agent](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md). +Go through our [deployment strategies](https://github.com/netdata/netdata/edit/master/docs/category-overview-pages/deployment-strategies.md), +for suggested configuration changes for production deployments. -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. +## Install on Linux with one-line installer -## Have a different operating system, or want to try another method? +The **recommended** way to install Netdata on a Linux node (physical, virtual, container, IoT) is our one-line +[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. -Netdata works on many different platforms. To see all supported platforms, check out our [platform support -policy](https://github.com/netdata/netdata/blob/master/packaging/PLATFORM_SUPPORT.md). +To install, copy the script, paste it into your node's terminal, and hit `Enter` to begin the installation process. -Below, you can find a few additional installation methods, followed by separate instructions for a variety of unique -operating systems. + + wget> -### Alternative methods + - - - - + curl> + + + + + + +> ### 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 claiming to. +> You can leave the room id blank to have your node claimed to the default "All nodes" room. + +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 methods + + + - - - + - - - + + +- [Native DEB/RPM packages](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/packages.md) +- [Run with Docker](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md) +- [Deploy on Kubernetes](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kubernetes.md) +- [Install on macOS](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/macos.md) +- [Linux from Git](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/manual.md) +- [Linux from source](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/source.md) +- [Linux for offline nodes](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/offline.md) + +The full list of all installation methods for various systems is available in [Netdata Learn](https://learn.netdata.cloud), +under [Installation](https://github.com/netdata/netdata/blob/master/docs/category-overview-pages/installation-overview.md). -## Automatic updates +## Advanced installation options and troubleshooting + +### Automatic updates By default, Netdata's installation scripts enable automatic updates for both nightly and stable release channels. @@ -125,7 +156,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](https://github.com/netdata/netdata/blob/master/packaging/installer/UPDATE.md). -### Network usage of Netdata’s automatic updater +#### Network usage of Netdata’s automatic updater The auto-update functionality set up by the installation scripts requires working internet access to function correctly. In particular, it currently requires access to GitHub (to check if a newer version of the updater script @@ -136,7 +167,7 @@ Note that the auto-update functionality will check for updates to itself indepen and will try to use the latest version of the updater script whenever possible. This is intended to reduce the amount of effort required by users to get updates working again in the event of a bug in the updater code. -## Nightly vs. stable releases +### Nightly vs. stable releases The Netdata team maintains two releases of the Netdata agent: **nightly** and **stable**. By default, Netdata's installation scripts will give you **automatic, nightly** updates, as that is our recommended configuration. @@ -153,22 +184,29 @@ the community helps fix any bugs that might have been introduced in previous rel **Pros of using nightly releases:** -- Get the latest features and bug fixes as soon as they're available -- Receive security-related fixes immediately -- Use stable, fully-tested code that's always improving -- Leverage the same Netdata experience our community is using +- Get the latest features and bug fixes as soon as they're available +- Receive security-related fixes immediately +- Use stable, fully-tested code that's always improving +- Leverage the same Netdata experience our community is using **Pros of using stable releases:** -- Protect yourself from the rare instance when major bugs slip through our testing and negatively affect a Netdata +- Protect yourself from the rare instance when major bugs slip through our testing and negatively affect a Netdata installation -- Retain more control over the Netdata version you use +- Retain more control over the Netdata version you use + +### Anonymous statistics -## Troubleshooting and known issues +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](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 +_actively_ contributing to Netdata's future. + +### Troubleshooting and known issues We are tracking a few issues related to installation and packaging. -### Older distributions (Ubuntu 14.04, Debian 8, CentOS 6) and OpenSSL +#### Older distributions (Ubuntu 14.04, Debian 8, CentOS 6) and OpenSSL If you're running an older Linux distribution or one that has reached EOL, such as Ubuntu 14.04 LTS, Debian 8, or CentOS 6, your Agent may not be able to securely connect to Netdata Cloud due to an outdated version of OpenSSL. These old @@ -179,13 +217,13 @@ If you choose to continue using the outdated version of OpenSSL, your node will with hostname verification disabled. Without verification, your Netdata Cloud connection could be vulnerable to man-in-the-middle attacks. -### CentOS 6 and CentOS 8 +#### 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](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 +#### Access to file is not permitted If you see an error similar to `Access to file is not permitted: /usr/share/netdata/web//index.html` when you try to visit the Agent dashboard at `http://NODE:19999`, you need to update Netdata's permissions to match those of your @@ -207,16 +245,14 @@ These files need to have the same user and group used to install your netdata. S # chown -R netdata.netdata /usr/share/netdata/web ``` -### Multiple versions of OpenSSL +#### Multiple versions of OpenSSL We've received reports from the community about issues with running the `kickstart.sh` script on systems that have both a distribution-installed version of OpenSSL and a manually-installed local version. The Agent's installer cannot handle both. -### Clang compiler on Linux +#### Clang compiler on Linux 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](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 c24fdee8..82cea498 100644 --- a/packaging/installer/REINSTALL.md +++ b/packaging/installer/REINSTALL.md @@ -1,14 +1,4 @@ - - -# Reinstall the Netdata Agent +# Reinstall Netdata In certain situations, such as needing to enable a feature or troubleshoot an issue, you may need to reinstall the Netdata Agent on your node. diff --git a/packaging/installer/UNINSTALL.md b/packaging/installer/UNINSTALL.md index 2ff22f5c..a66bd7a2 100644 --- a/packaging/installer/UNINSTALL.md +++ b/packaging/installer/UNINSTALL.md @@ -1,18 +1,9 @@ - - # 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](https://github.com/netdata/netdata/blob/master/packaging/installer/REINSTALL.md) instead of removing the Netdata Agent entirely. +> ### Note +> +> 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 [reinstalling Netdata](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: @@ -27,9 +18,7 @@ curl https://my-netdata.io/kickstart.sh > /tmp/netdata-kickstart.sh && sh /tmp/n ``` This will work in most cases without you needing to do anything more other than accepting removal of configuration -and data files. You can confirm whether this approach will work for you by adding `--dry-run` to the list of -options. If that produces a line with a message like `Would attempt to uninstall existing install`, then this -method will work on your system. +and data files. If you used a non-standard installation prefix, you may need to specify that prefix using the `--old-install-prefix` option when uninstalling this way. diff --git a/packaging/installer/UPDATE.md b/packaging/installer/UPDATE.md index 9d4289f8..3df84023 100644 --- a/packaging/installer/UPDATE.md +++ b/packaging/installer/UPDATE.md @@ -1,14 +1,4 @@ - - -# Update the Netdata Agent +# Update Netdata By default, the Netdata Agent automatically updates with the latest nightly or stable version depending on which you installed. If you opted out of automatic updates, you need to update your Netdata Agent to the latest nightly diff --git a/packaging/installer/dependencies/alpine.sh b/packaging/installer/dependencies/alpine.sh index 65999dc3..321d5770 100755 --- a/packaging/installer/dependencies/alpine.sh +++ b/packaging/installer/dependencies/alpine.sh @@ -31,6 +31,7 @@ package_tree=" util-linux-dev libmnl-dev json-c-dev + yaml-dev " usage() { diff --git a/packaging/installer/dependencies/arch.sh b/packaging/installer/dependencies/arch.sh index cdda5273..c0890d92 100755 --- a/packaging/installer/dependencies/arch.sh +++ b/packaging/installer/dependencies/arch.sh @@ -20,6 +20,7 @@ declare -a package_tree=( util-linux libmnl json-c + libyaml libuv lz4 openssl diff --git a/packaging/installer/dependencies/centos.sh b/packaging/installer/dependencies/centos.sh index a05bce8f..845f1113 100755 --- a/packaging/installer/dependencies/centos.sh +++ b/packaging/installer/dependencies/centos.sh @@ -19,6 +19,7 @@ declare -a package_tree=( libuuid-devel libmnl-devel json-c-devel + libyaml-devel libuv-devel lz4-devel openssl-devel @@ -96,7 +97,7 @@ check_flags() { validate_tree_centos() { local opts= - export local package_manager= + package_manager= if [[ "${NON_INTERACTIVE}" == "1" ]]; then echo >&2 "Running in non-interactive mode" opts="-y" diff --git a/packaging/installer/dependencies/clearlinux.sh b/packaging/installer/dependencies/clearlinux.sh index 832dac55..f6f616d6 100755 --- a/packaging/installer/dependencies/clearlinux.sh +++ b/packaging/installer/dependencies/clearlinux.sh @@ -15,6 +15,7 @@ declare -a package_tree=( devpkg-util-linux devpkg-libmnl devpkg-json-c + yaml-dev devpkg-libuv devpkg-lz4 devpkg-openssl diff --git a/packaging/installer/dependencies/debian.sh b/packaging/installer/dependencies/debian.sh index a2c421a9..8186940e 100755 --- a/packaging/installer/dependencies/debian.sh +++ b/packaging/installer/dependencies/debian.sh @@ -31,6 +31,7 @@ package_tree=" liblz4-dev libssl-dev libelf-dev + libyaml-dev python python3 " diff --git a/packaging/installer/dependencies/fedora.sh b/packaging/installer/dependencies/fedora.sh index a1c3a1df..58912288 100755 --- a/packaging/installer/dependencies/fedora.sh +++ b/packaging/installer/dependencies/fedora.sh @@ -39,6 +39,7 @@ declare -a package_tree=( libuuid-devel libmnl-devel json-c-devel + libyaml-devel libuv-devel lz4-devel openssl-devel diff --git a/packaging/installer/dependencies/freebsd.sh b/packaging/installer/dependencies/freebsd.sh index 91451356..69a650a6 100755 --- a/packaging/installer/dependencies/freebsd.sh +++ b/packaging/installer/dependencies/freebsd.sh @@ -21,6 +21,7 @@ package_tree=" lzlib e2fsprogs-libuuid json-c + libyaml libuv liblz4 openssl diff --git a/packaging/installer/dependencies/gentoo.sh b/packaging/installer/dependencies/gentoo.sh index e7ed6445..cbe8c8e5 100755 --- a/packaging/installer/dependencies/gentoo.sh +++ b/packaging/installer/dependencies/gentoo.sh @@ -24,6 +24,7 @@ package_tree=" sys-apps/util-linux net-libs/libmnl dev-libs/json-c + dev-libs/libyaml dev-libs/libuv app-arch/lz4 dev-libs/openssl diff --git a/packaging/installer/dependencies/ol.sh b/packaging/installer/dependencies/ol.sh index 0f1f90e6..2166bcc5 100755 --- a/packaging/installer/dependencies/ol.sh +++ b/packaging/installer/dependencies/ol.sh @@ -24,6 +24,7 @@ declare -a package_tree=( libuuid-devel libmnl-devel json-c-devel + libyaml-devel libuv-devel lz4-devel openssl-devel diff --git a/packaging/installer/dependencies/opensuse.sh b/packaging/installer/dependencies/opensuse.sh index b1f0c218..81291ef7 100755 --- a/packaging/installer/dependencies/opensuse.sh +++ b/packaging/installer/dependencies/opensuse.sh @@ -25,6 +25,7 @@ declare -a package_tree=( libuuid-devel libmnl-devel libjson-c-devel + libyaml-devel libuv-devel liblz4-devel libopenssl-devel diff --git a/packaging/installer/dependencies/rockylinux.sh b/packaging/installer/dependencies/rockylinux.sh index 63981df4..7ac98f5e 100755 --- a/packaging/installer/dependencies/rockylinux.sh +++ b/packaging/installer/dependencies/rockylinux.sh @@ -23,6 +23,7 @@ declare -a package_tree=( libuuid-devel libmnl-devel json-c-devel + libyaml-devel libuv-devel lz4-devel openssl-devel diff --git a/packaging/installer/dependencies/ubuntu.sh b/packaging/installer/dependencies/ubuntu.sh index 295dbf01..e3d734c6 100755 --- a/packaging/installer/dependencies/ubuntu.sh +++ b/packaging/installer/dependencies/ubuntu.sh @@ -27,6 +27,7 @@ package_tree=" uuid-dev libmnl-dev libjson-c-dev + libyaml-dev libuv1-dev liblz4-dev libssl-dev diff --git a/packaging/installer/functions.sh b/packaging/installer/functions.sh index ebb4aab7..b12a9a58 100644 --- a/packaging/installer/functions.sh +++ b/packaging/installer/functions.sh @@ -95,10 +95,19 @@ progress() { echo >&2 " --- ${TPUT_DIM}${TPUT_BOLD}${*}${TPUT_RESET} --- " } +check_for_curl() { + if [ -z "${curl}" ]; then + curl="$(PATH="${PATH}:/opt/netdata/bin" command -v curl 2>/dev/null && true)" + fi +} + get() { url="${1}" - if command -v curl > /dev/null 2>&1; then - curl -q -o - -sSL --connect-timeout 10 --retry 3 "${url}" + + check_for_curl + + if [ -n "${curl}" ]; then + "${curl}" -q -o - -sSL --connect-timeout 10 --retry 3 "${url}" elif command -v wget > /dev/null 2>&1; then wget -T 15 -O - "${url}" else @@ -112,8 +121,10 @@ download_file() { name="${3}" opt="${4}" - if command -v curl > /dev/null 2>&1; then - run curl -q -sSL --connect-timeout 10 --retry 3 --output "${dest}" "${url}" + check_for_curl + + if [ -n "${curl}" ]; then + run "${curl}" -q -sSL --connect-timeout 10 --retry 3 --output "${dest}" "${url}" elif command -v wget > /dev/null 2>&1; then run wget -T 15 -O "${dest}" "${url}" else @@ -400,7 +411,7 @@ get_group(){ if command -v getent > /dev/null 2>&1; then getent group "${1:-""}" else - cat /etc/group | grep "^${1}:" + grep "^${1}:" /etc/group fi } @@ -459,21 +470,21 @@ install_non_systemd_init() { if [ -d /etc/init.d ] && [ ! -f /etc/init.d/netdata ]; then if expr "${key}" : "^(gentoo|alpine).*"; then echo >&2 "Installing OpenRC init file..." - run cp system/netdata-openrc /etc/init.d/netdata && + run cp system/openrc/init.d/netdata /etc/init.d/netdata && run chmod 755 /etc/init.d/netdata && run rc-update add netdata default && return 0 elif expr "${key}" : "^devuan*" || [ "${key}" = "debian-7" ] || [ "${key}" = "ubuntu-12.04" ] || [ "${key}" = "ubuntu-14.04" ]; then echo >&2 "Installing LSB init file..." - run cp system/netdata-lsb /etc/init.d/netdata && + run cp system/lsb/init.d/netdata /etc/init.d/netdata && run chmod 755 /etc/init.d/netdata && run update-rc.d netdata defaults && run update-rc.d netdata enable && return 0 elif expr "${key}" : "^(amzn-201[5678]|ol|CentOS release 6|Red Hat Enterprise Linux Server release 6|Scientific Linux CERN SLC release 6|CloudLinux Server release 6).*"; then echo >&2 "Installing init.d file..." - run cp system/netdata-init-d /etc/init.d/netdata && + run cp system/initd/init.d/netdata /etc/init.d/netdata && run chmod 755 /etc/init.d/netdata && run chkconfig netdata on && return 0 @@ -571,7 +582,7 @@ install_netdata_service() { echo >&2 "Installing MacOS X plist file..." # This is used by netdata-installer.sh # shellcheck disable=SC2034 - run cp system/netdata.plist /Library/LaunchDaemons/com.github.netdata.plist && + run cp system/launchd/netdata.plist /Library/LaunchDaemons/com.github.netdata.plist && run launchctl load /Library/LaunchDaemons/com.github.netdata.plist && NETDATA_START_CMD="launchctl start com.github.netdata" && NETDATA_STOP_CMD="launchctl stop com.github.netdata" @@ -581,7 +592,7 @@ install_netdata_service() { elif [ "${uname}" = "FreeBSD" ]; then # This is used by netdata-installer.sh # shellcheck disable=SC2034 - run cp system/netdata-freebsd /etc/rc.d/netdata && NETDATA_START_CMD="service netdata start" && + run cp system/freebsd/rc.d/netdata /etc/rc.d/netdata && NETDATA_START_CMD="service netdata start" && NETDATA_STOP_CMD="service netdata stop" && NETDATA_INSTALLER_START_CMD="service netdata onestart" && myret=$? @@ -589,7 +600,7 @@ install_netdata_service() { echo >&2 "Note: To explicitly enable netdata automatic start, set 'netdata_enable' to 'YES' in /etc/rc.conf" echo >&2 "" - return ${myret} + return "${myret}" elif issystemd; then # systemd is running on this system @@ -610,7 +621,7 @@ install_netdata_service() { fi echo >&2 "Installing systemd service..." - run cp system/netdata.service "${SYSTEMD_DIRECTORY}/netdata.service" && + run cp system/systemd/netdata.service "${SYSTEMD_DIRECTORY}/netdata.service" && run systemctl daemon-reload && ${ENABLE_NETDATA_IF_PREVIOUSLY_ENABLED} && return 0 @@ -834,10 +845,12 @@ restart_netdata() { # install netdata logrotate install_netdata_logrotate() { + src="${NETDATA_PREFIX}/usr/lib/netdata/system/logrotate/netdata" + if [ "${UID}" -eq 0 ]; then if [ -d /etc/logrotate.d ]; then if [ ! -f /etc/logrotate.d/netdata ]; then - run cp system/netdata.logrotate /etc/logrotate.d/netdata + run cp "${src}" /etc/logrotate.d/netdata fi if [ -f /etc/logrotate.d/netdata ]; then @@ -873,8 +886,10 @@ create_netdata_conf() { export http_proxy= export https_proxy= - if command -v curl 1> /dev/null 2>&1; then - run curl -sSL --connect-timeout 10 --retry 3 "${url}" > "${path}.new" + check_for_curl + + if [ -n "${curl}" ]; then + run "${curl}" -sSL --connect-timeout 10 --retry 3 "${url}" > "${path}.new" elif command -v wget 1> /dev/null 2>&1; then run wget -T 15 -O - "${url}" > "${path}.new" fi @@ -903,32 +918,29 @@ portable_add_user() { [ -z "${homedir}" ] && homedir="/tmp" # Check if user exists - if cut -d ':' -f 1 < /etc/passwd | grep "^${username}$" 1> /dev/null 2>&1; then - echo >&2 "User '${username}' already exists." - return 0 + if command -v getent > /dev/null 2>&1; then + if getent passwd "${username}" > /dev/null 2>&1; then + echo >&2 "User '${username}' already exists." + return 0 + fi + else + if cut -d ':' -f 1 < /etc/passwd | grep "^${username}$" 1> /dev/null 2>&1; then + echo >&2 "User '${username}' already exists." + return 0 + fi fi echo >&2 "Adding ${username} user account with home ${homedir} ..." nologin="$(command -v nologin || echo '/bin/false')" - # Linux if command -v useradd 1> /dev/null 2>&1; then run useradd -r -g "${username}" -c "${username}" -s "${nologin}" --no-create-home -d "${homedir}" "${username}" && return 0 - fi - - # FreeBSD - if command -v pw 1> /dev/null 2>&1; then + elif command -v pw 1> /dev/null 2>&1; then run pw useradd "${username}" -d "${homedir}" -g "${username}" -s "${nologin}" && return 0 - fi - - # BusyBox - if command -v adduser 1> /dev/null 2>&1; then + elif command -v adduser 1> /dev/null 2>&1; then run adduser -h "${homedir}" -s "${nologin}" -D -G "${username}" "${username}" && return 0 - fi - - # mac OS - if command -v sysadminctl 1> /dev/null 2>&1; then + elif command -v sysadminctl 1> /dev/null 2>&1; then run sysadminctl -addUser "${username}" && return 0 fi @@ -951,20 +963,11 @@ portable_add_group() { # Linux if command -v groupadd 1> /dev/null 2>&1; then run groupadd -r "${groupname}" && return 0 - fi - - # FreeBSD - if command -v pw 1> /dev/null 2>&1; then + elif command -v pw 1> /dev/null 2>&1; then run pw groupadd "${groupname}" && return 0 - fi - - # BusyBox - if command -v addgroup 1> /dev/null 2>&1; then + elif command -v addgroup 1> /dev/null 2>&1; then run addgroup "${groupname}" && return 0 - fi - - # mac OS - if command -v dseditgroup 1> /dev/null 2>&1; then + elif command -v dseditgroup 1> /dev/null 2>&1; then dseditgroup -o create "${groupname}" && return 0 fi @@ -995,20 +998,11 @@ portable_add_user_to_group() { # Linux if command -v usermod 1> /dev/null 2>&1; then run usermod -a -G "${groupname}" "${username}" && return 0 - fi - - # FreeBSD - if command -v pw 1> /dev/null 2>&1; then + elif command -v pw 1> /dev/null 2>&1; then run pw groupmod "${groupname}" -m "${username}" && return 0 - fi - - # BusyBox - if command -v addgroup 1> /dev/null 2>&1; then + elif command -v addgroup 1> /dev/null 2>&1; then run addgroup "${username}" "${groupname}" && return 0 - fi - - # mac OS - if command -v dseditgroup 1> /dev/null 2>&1; then + elif command -v dseditgroup 1> /dev/null 2>&1; then dseditgroup -u "${username}" "${groupname}" && return 0 fi @@ -1063,8 +1057,8 @@ install_netdata_updater() { fi if issystemd && [ -n "$(get_systemd_service_dir)" ]; then - cat "${NETDATA_SOURCE_DIR}/system/netdata-updater.timer" > "$(get_systemd_service_dir)/netdata-updater.timer" - cat "${NETDATA_SOURCE_DIR}/system/netdata-updater.service" > "$(get_systemd_service_dir)/netdata-updater.service" + cat "${NETDATA_SOURCE_DIR}/system/systemd/netdata-updater.timer" > "$(get_systemd_service_dir)/netdata-updater.timer" + cat "${NETDATA_SOURCE_DIR}/system/systemd/netdata-updater.service" > "$(get_systemd_service_dir)/netdata-updater.service" fi sed -i -e "s|THIS_SHOULD_BE_REPLACED_BY_INSTALLER_SCRIPT|${NETDATA_USER_CONFIG_DIR}/.environment|" "${NETDATA_PREFIX}/usr/libexec/netdata/netdata-updater.sh" || return 1 diff --git a/packaging/installer/install-required-packages.sh b/packaging/installer/install-required-packages.sh index c906cce3..9b1f6518 100755 --- a/packaging/installer/install-required-packages.sh +++ b/packaging/installer/install-required-packages.sh @@ -676,6 +676,20 @@ declare -A pkg_json_c_dev=( ['default']="json-c-devel" ) +#TODO:: clearlinux ? +declare -A pkg_libyaml_dev=( + ['alpine']="yaml-dev" + ['arch']="libyaml" + ['clearlinux']="yaml-dev" + ['debian']="libyaml-dev" + ['gentoo']="dev-libs/libyaml" + ['sabayon']="dev-libs/libyaml" + ['suse']="libyaml-devel" + ['freebsd']="libyaml" + ['macos']="libyaml" + ['default']="libyaml-devel" +) + declare -A pkg_libatomic=( ['arch']="NOTREQUIRED" ['clearlinux']="NOTREQUIRED" @@ -1227,6 +1241,7 @@ packages() { suitable_package libuuid-dev suitable_package libmnl-dev suitable_package json-c-dev + suitable_package libyaml-dev fi # ------------------------------------------------------------------------- @@ -1376,6 +1391,7 @@ validate_tree_freebsd() { echo >&2 " > Checking for gmake ..." if ! pkg query %n-%v | grep -q gmake; then if prompt "gmake is required to build on FreeBSD and is not installed. Shall I install it?"; then + # shellcheck disable=2086 run ${sudo} pkg install ${opts} gmake fi fi @@ -1425,13 +1441,16 @@ validate_tree_centos() { echo >&2 " > Checking for config-manager ..." if ! run ${sudo} dnf config-manager --help; then if prompt "config-manager not found, shall I install it?"; then + # shellcheck disable=2086 run ${sudo} dnf ${opts} install 'dnf-command(config-manager)' fi fi echo >&2 " > Checking for CRB ..." + # shellcheck disable=2086 if ! run dnf ${sudo} repolist | grep CRB; then if prompt "CRB not found, shall I install it?"; then + # shellcheck disable=2086 run ${sudo} dnf ${opts} config-manager --set-enabled crb fi fi @@ -1439,24 +1458,29 @@ validate_tree_centos() { echo >&2 " > Checking for config-manager ..." if ! run ${sudo} yum config-manager --help; then if prompt "config-manager not found, shall I install it?"; then + # shellcheck disable=2086 run ${sudo} yum ${opts} install 'dnf-command(config-manager)' fi fi echo >&2 " > Checking for PowerTools ..." + # shellcheck disable=2086 if ! run yum ${sudo} repolist | grep PowerTools; then if prompt "PowerTools not found, shall I install it?"; then + # shellcheck disable=2086 run ${sudo} yum ${opts} config-manager --set-enabled powertools fi fi echo >&2 " > Updating libarchive ..." + # shellcheck disable=2086 run ${sudo} yum ${opts} install libarchive elif [[ "${version}" =~ ^7(\..*)?$ ]]; then echo >&2 " > Checking for EPEL ..." if ! rpm -qa | grep epel-release > /dev/null; then if prompt "EPEL not found, shall I install it?"; then + # shellcheck disable=2086 run ${sudo} yum ${opts} install epel-release fi fi @@ -1465,6 +1489,7 @@ validate_tree_centos() { echo >&2 " > Checking for Okay ..." if ! rpm -qa | grep okay > /dev/null; then if prompt "okay not found, shall I install it?"; then + # shellcheck disable=2086 run ${sudo} yum ${opts} install http://repo.okay.com.mx/centos/6/x86_64/release/okay-release-1-3.el6.noarch.rpm fi fi @@ -1627,7 +1652,7 @@ install_equo() { PACMAN_DB_SYNCED=0 validate_install_pacman() { - if [ ${PACMAN_DB_SYNCED} -eq 0 ]; then + if [ "${PACMAN_DB_SYNCED}" -eq 0 ]; then echo >&2 " > Running pacman -Sy to sync the database" local x x=$(pacman -Sy) diff --git a/packaging/installer/kickstart.sh b/packaging/installer/kickstart.sh index 30c7b4ca..28491119 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: F050F +# Next unused error code: F0515 # ====================================================================== # Constants @@ -34,7 +34,7 @@ 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/" +TELEMETRY_URL="https://app.posthog.com/capture/" # ====================================================================== # Defaults for environment variables @@ -44,14 +44,11 @@ SELECTED_INSTALL_METHOD="none" INSTALL_TYPE="unknown" INSTALL_PREFIX="" NETDATA_AUTO_UPDATES="default" -NETDATA_CLAIM_ONLY=0 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 +NETDATA_FORCE_METHOD="" NETDATA_OFFLINE_INSTALL_SOURCE="" NETDATA_REQUIRE_CLOUD=1 NETDATA_WARNINGS="" @@ -69,7 +66,7 @@ NETDATA_TARBALL_BASEURL="${NETDATA_TARBALL_BASEURL:-https://github.com/netdata/n TELEMETRY_API_KEY="${NETDATA_POSTHOG_API_KEY:-mqkwGT0JNFqO-zX2t0mW6Tec9yooaVu7xCBlXtHnt5Y}" if echo "${0}" | grep -q 'kickstart-static64'; then - NETDATA_ONLY_STATIC=1 + NETDATA_FORCE_METHOD='static' fi if [ ! -t 1 ]; then @@ -78,6 +75,8 @@ else INTERACTIVE=1 fi +CURL="$(PATH="${PATH}:/opt/netdata/bin" command -v curl 2>/dev/null && true)" + # ====================================================================== # Shared messages used in multiple places throughout the script. @@ -103,7 +102,7 @@ main() { uninstall cleanup - ACTION= + ACTION='' INSTALL_PREFIX="${NEW_INSTALL_PREFIX}" # shellcheck disable=SC2086 main @@ -175,12 +174,9 @@ USAGE: kickstart.sh [options] --auto-update Enable automatic updates. --auto-update-type Specify a particular scheduling type for auto-updates (valid types: systemd, interval, crontab) --disable-telemetry Opt-out of anonymous statistics. - --repositories-only Only install appropriate repository configuration packages (only for native install). --native-only Only install if native binary packages are available. --static-only Only install if a static build is available. --build-only Only install using a local build. - --reinstall Explicitly reinstall instead of updating any existing install. - --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-prefix Specify an installation prefix for local builds (default: autodetect based on system type). @@ -188,13 +184,19 @@ USAGE: kickstart.sh [options] --install-version Specify the version of Netdata to install. --claim-token Use a specified token for claiming to Netdata Cloud. --claim-rooms When claiming, add the node to the specified rooms. - --claim-only If there is an existing install, only try to claim it, not update it. --claim-* Specify other options for the claiming script. --no-cleanup Don't do any cleanup steps. This is intended to help with debugging the installer. - --uninstall Uninstall an existing installation of Netdata. - --reinstall-clean Clean reinstall Netdata. --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. + +The following options are mutually exclusive and specifiy special operations other than trying to install Netdata normally or update an existing install: + + --reinstall If there is an existing install, reinstall it instead of trying to update it. If there is no existing install, install netdata normally. + --reinstall-even-if-unsafe If there is an existing install, reinstall it instead of trying to update it, even if doing so is known to potentially break things. If there is no existing install, install Netdata normally. + --reinstall-clean If there is an existing install, uninstall it before trying to install Netdata. Fails if there is no existing install. + --uninstall Uninstall an existing installation of Netdata. Fails if there is no existing install. + --claim-only If there is an existing install, only try to claim it without attempting to update it. If there is no existing install, install and claim Netdata normally. + --repositories-only Only install repository configuration packages instead of doing a full install of Netdata. Automatically sets --native-only. --prepare-offline-install-source Instead of installing the agent, prepare a directory that can be used to install on another system without needing to download anything. Additionally, this script may use the following environment variables: @@ -299,8 +301,8 @@ telemetry_event() { 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 + if [ -n "${CURL}" ]; 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 if wget --help 2>&1 | grep BusyBox > /dev/null 2>&1; then # BusyBox-compatible version of wget, there is no --no-check-certificate option @@ -329,9 +331,7 @@ trap_handler() { printf >&2 "%s\n\n" "${TPUT_BGRED}${TPUT_WHITE}${TPUT_BOLD} ERROR ${TPUT_RESET} Installer exited unexpectedly (${code}-${lineno})" case "${code}" in - 0) - printf >&2 "%s\n" "This is almost certainly the result of a bug. If you have time, please report it at ${AGENT_BUG_REPORT_URL}." - ;; + 0) printf >&2 "%s\n" "This is almost certainly the result of a bug. If you have time, please report it at ${AGENT_BUG_REPORT_URL}." ;; *) printf >&2 "%s\n" "This is probably a result of a transient issue on your system. Things should work correctly if you try again." printf >&2 "%s\n" "If you continue to experience this issue, you can reacn out to us for support on:" @@ -406,7 +406,7 @@ success_banner() { cleanup() { if [ -z "${NO_CLEANUP}" ] && [ -n "${tmpdir}" ]; then cd || true - ${ROOTCMD} rm -rf "${tmpdir}" + run_as_root rm -rf "${tmpdir}" fi } @@ -497,6 +497,16 @@ run() { return ${ret} } +run_as_root() { + confirm_root_support + + if [ "$(id -u)" -ne "0" ]; then + printf >&2 "Root privileges required to run %s\n" "${*}" + fi + + run ${ROOTCMD} "${@}" +} + run_script() { set_tmpdir @@ -571,8 +581,8 @@ check_for_remote_file() { if echo "${url}" | grep -Eq "^file:///"; then [ -e "${url#file://}" ] || return 1 - elif command -v curl > /dev/null 2>&1; then - curl --output /dev/null --silent --head --fail "${url}" || return 1 + elif [ -n "${CURL}" ]; then + "${CURL}" --output /dev/null --silent --head --fail "${url}" || return 1 elif command -v wget > /dev/null 2>&1; then wget -S --spider "${url}" 2>&1 | grep -q 'HTTP/1.1 200 OK' || return 1 else @@ -586,8 +596,8 @@ download() { if echo "${url}" | grep -Eq "^file:///"; then run cp "${url#file://}" "${dest}" || return 1 - elif command -v curl > /dev/null 2>&1; then - run curl --fail -q -sSL --connect-timeout 10 --retry 3 --output "${dest}" "${url}" || return 1 + elif [ -n "${CURL}" ]; then + run "${CURL}" --fail -q -sSL --connect-timeout 10 --retry 3 --output "${dest}" "${url}" || return 1 elif command -v wget > /dev/null 2>&1; then run wget -T 15 -O "${dest}" "${url}" || return 1 else @@ -598,8 +608,8 @@ download() { get_redirect() { url="${1}" - 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 + if [ -n "${CURL}" ]; 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 -S -O /dev/null ${url} 2>&1 | grep -m 1 Location | grep -o '[^/]*$'" || return 1 else @@ -620,75 +630,65 @@ safe_sha256sum() { } get_system_info() { + SYSARCH="$(uname -m)" + case "$(uname -s)" in Linux) SYSTYPE="Linux" - os_release_file= - if [ -s "/etc/os-release" ] && [ -r "/etc/os-release" ]; then - os_release_file="/etc/os-release" - elif [ -s "/usr/lib/os-release" ] && [ -r "/usr/lib/os-release" ]; then - os_release_file="/usr/lib/os-release" - else - warning "Cannot find usable OS release information. Native packages will not be available for this install." - fi - - if [ -n "${os_release_file}" ]; then - # shellcheck disable=SC1090 - . "${os_release_file}" - - DISTRO="${ID}" - SYSVERSION="${VERSION_ID}" - SYSCODENAME="${VERSION_CODENAME}" - SYSARCH="$(uname -m)" + if [ -z "${SKIP_DISTRO_DETECTION}" ]; then + os_release_file= + if [ -s "/etc/os-release" ] && [ -r "/etc/os-release" ]; then + os_release_file="/etc/os-release" + elif [ -s "/usr/lib/os-release" ] && [ -r "/usr/lib/os-release" ]; then + os_release_file="/usr/lib/os-release" + else + warning "Cannot find usable OS release information. Native packages will not be available for this install." + fi - supported_compat_names="debian ubuntu centos fedora opensuse ol arch" + if [ -n "${os_release_file}" ]; then + # shellcheck disable=SC1090 + . "${os_release_file}" - if str_in_list "${DISTRO}" "${supported_compat_names}"; then - DISTRO_COMPAT_NAME="${DISTRO}" + DISTRO="${ID}" + SYSVERSION="${VERSION_ID}" + SYSCODENAME="${VERSION_CODENAME}" else - case "${DISTRO}" in - opensuse-leap) - DISTRO_COMPAT_NAME="opensuse" - ;; - cloudlinux|almalinux|rocky|rhel) - DISTRO_COMPAT_NAME="centos" - ;; - artix|manjaro|obarun) - DISTRO_COMPAT_NAME="arch" - ;; - *) - DISTRO_COMPAT_NAME="unknown" - ;; - esac + DISTRO="unknown" + DISTRO_COMPAT_NAME="unknown" + SYSVERSION="unknown" + SYSCODENAME="unknown" fi + else + warning "Distribution auto-detection overridden by user. This is not guaranteed to work, and is not officially supported." + fi - case "${DISTRO_COMPAT_NAME}" in - centos|ol) - SYSVERSION=$(echo "$SYSVERSION" | cut -d'.' -f1) - ;; - esac + supported_compat_names="debian ubuntu centos fedora opensuse ol amzn arch" + + if str_in_list "${DISTRO}" "${supported_compat_names}"; then + DISTRO_COMPAT_NAME="${DISTRO}" else - DISTRO="unknown" - DISTRO_COMPAT_NAME="unknown" - SYSVERSION="unknown" - SYSCODENAME="unknown" - SYSARCH="$(uname -m)" + case "${DISTRO}" in + opensuse-leap) DISTRO_COMPAT_NAME="opensuse" ;; + cloudlinux|almalinux|rocky|rhel) DISTRO_COMPAT_NAME="centos" ;; + artix|manjaro|obarun) DISTRO_COMPAT_NAME="arch" ;; + *) DISTRO_COMPAT_NAME="unknown" ;; + esac fi + + case "${DISTRO_COMPAT_NAME}" in + centos|ol) SYSVERSION=$(echo "$SYSVERSION" | cut -d'.' -f1) ;; + esac ;; Darwin) SYSTYPE="Darwin" SYSVERSION="$(sw_vers -buildVersion)" - SYSARCH="$(uname -m)" ;; FreeBSD) SYSTYPE="FreeBSD" SYSVERSION="$(uname -K)" - SYSARCH="$(uname -m)" - ;; - *) - fatal "Unsupported system type detected. Netdata cannot be installed on this system using this script." F0200 ;; + *) fatal "Unsupported system type detected. Netdata cannot be installed on this system using this script." F0200 ;; esac } @@ -800,6 +800,7 @@ uninstall() { return 0 else progress "Found existing netdata-uninstaller. Running it.." + # shellcheck disable=SC2086 if ! run_script "${uninstaller}" ${FLAGS}; then warning "Uninstaller failed. Some parts of Netdata may still be present on the system." fi @@ -813,6 +814,7 @@ uninstall() { progress "Downloading netdata-uninstaller ..." download "${uninstaller_url}" "${tmpdir}/netdata-uninstaller.sh" chmod +x "${tmpdir}/netdata-uninstaller.sh" + # shellcheck disable=SC2086 if ! run_script "${tmpdir}/netdata-uninstaller.sh" ${FLAGS}; then warning "Uninstaller failed. Some parts of Netdata may still be present on the system." fi @@ -858,7 +860,7 @@ detect_existing_install() { if [ -n "${ndprefix}" ]; then typefile="${ndprefix}/etc/netdata/.install-type" if [ -r "${typefile}" ]; then - ${ROOTCMD} sh -c "cat \"${typefile}\" > \"${tmpdir}/install-type\"" + run_as_root sh -c "cat \"${typefile}\" > \"${tmpdir}/install-type\"" # shellcheck disable=SC1090,SC1091 . "${tmpdir}/install-type" else @@ -868,7 +870,7 @@ detect_existing_install() { envfile="${ndprefix}/etc/netdata/.environment" if [ "${INSTALL_TYPE}" = "unknown" ] || [ "${INSTALL_TYPE}" = "custom" ]; then if [ -r "${envfile}" ]; then - ${ROOTCMD} sh -c "cat \"${envfile}\" > \"${tmpdir}/environment\"" + run_as_root sh -c "cat \"${envfile}\" > \"${tmpdir}/environment\"" # shellcheck disable=SC1091 . "${tmpdir}/environment" if [ -n "${NETDATA_IS_STATIC_INSTALL}" ]; then @@ -903,15 +905,15 @@ handle_existing_install() { progress "Found an existing netdata install at ${ndprefix}, with installation type '${INSTALL_TYPE}'." fi - if [ -n "${NETDATA_REINSTALL}" ] || [ -n "${NETDATA_UNSAFE_REINSTALL}" ]; then + if [ "${ACTION}" = "reinstall" ] || [ "${ACTION}" = "unsafe-reinstall" ]; then progress "Found an existing netdata install at ${ndprefix}, but user requested reinstall, continuing." case "${INSTALL_TYPE}" in - binpkg-*) NETDATA_ONLY_NATIVE=1 ;; - *-build) NETDATA_ONLY_BUILD=1 ;; - *-static) NETDATA_ONLY_STATIC=1 ;; + binpkg-*) NETDATA_FORCE_METHOD='native' ;; + *-build) NETDATA_FORCE_METHOD='build' ;; + *-static) NETDATA_FORCE_METHOD='static' ;; *) - if [ -n "${NETDATA_UNSAFE_REINSTALL}" ]; then + if [ "${ACTION}" = "unsafe-reinstall" ]; then warning "Reinstalling over top of a ${INSTALL_TYPE} installation may be unsafe, but the user has requested we proceed." elif [ "${INTERACTIVE}" -eq 0 ]; then fatal "User requested reinstall, but we cannot safely reinstall over top of a ${INSTALL_TYPE} installation, exiting." F0104 @@ -941,9 +943,9 @@ handle_existing_install() { failmsg="We do not support trying to update or claim installations when we cannot determine the install type. You will need to uninstall the existing install using the same method you used to install it to proceed. ${claimonly_notice}" promptmsg="Attempting to update an existing install is not officially supported. It may work, but it also might break your system. ${claimonly_notice} Are you sure you want to continue?" fi - if [ "${INTERACTIVE}" -eq 0 ] && [ "${NETDATA_CLAIM_ONLY}" -eq 0 ]; then + if [ "${INTERACTIVE}" -eq 0 ] && [ "${ACTION}" != "claim" ]; then fatal "${failmsg}" F0106 - elif [ "${INTERACTIVE}" -eq 1 ] && [ "${NETDATA_CLAIM_ONLY}" -eq 0 ]; then + elif [ "${INTERACTIVE}" -eq 1 ] && [ "${ACTION}" != "claim" ]; then if confirm "${promptmsg}"; then progress "OK, continuing" else @@ -954,7 +956,7 @@ handle_existing_install() { ret=0 - if [ "${NETDATA_CLAIM_ONLY}" -eq 0 ]; then + if [ "${ACTION}" != "claim" ]; then if ! update; then warning "Failed to update existing Netdata install at ${ndprefix}." else @@ -969,7 +971,7 @@ handle_existing_install() { INSTALL_PREFIX="${ndprefix}" claim ret=$? - elif [ "${NETDATA_CLAIM_ONLY}" -eq 1 ]; then + elif [ "${ACTION}" = "claim" ]; then fatal "User asked to claim, but did not proide a claiming token." F0202 else progress "Not attempting to claim existing install at ${ndprefix} (no claiming token provided)." @@ -985,8 +987,8 @@ handle_existing_install() { fatal "This is an OCI container, use the regular container lifecycle management commands for your container tools instead of this script for managing it." F0203 ;; *) - if [ -n "${NETDATA_REINSTALL}" ] || [ -n "${NETDATA_UNSAFE_REINSTALL}" ]; then - if [ -n "${NETDATA_UNSAFE_REINSTALL}" ]; then + if [ "${ACTION}" = "reinstall" ] || [ "${ACTION}" = "unsafe-reinstall" ]; then + if [ "${ACTION}" = "unsafe-reinstall" ]; then warning "Reinstalling over top of a ${INSTALL_TYPE} installation may be unsafe, but the user has requested we proceed." elif [ "${INTERACTIVE}" -eq 0 ]; then fatal "User requested reinstall, but we cannot safely reinstall over top of a ${INSTALL_TYPE} installation, exiting." F0104 @@ -1007,7 +1009,7 @@ handle_existing_install() { cleanup trap - EXIT exit $ret - elif [ "${NETDATA_CLAIM_ONLY}" -eq 1 ]; then + elif [ "${ACTION}" = "claim" ]; then fatal "User asked to claim, but did not proide a claiming token." F0202 else fatal "Found an existing netdata install at ${ndprefix}, but the install type is '${INSTALL_TYPE}', which is not supported by this script, refusing to proceed." F0103 @@ -1022,30 +1024,30 @@ soft_disable_cloud() { cloud_prefix="${INSTALL_PREFIX}/var/lib/netdata/cloud.d" - run ${ROOTCMD} mkdir -p "${cloud_prefix}" + run_as_root mkdir -p "${cloud_prefix}" cat > "${tmpdir}/cloud.conf" << EOF [global] enabled = no EOF - run ${ROOTCMD} cp "${tmpdir}/cloud.conf" "${cloud_prefix}/cloud.conf" + run_as_root cp "${tmpdir}/cloud.conf" "${cloud_prefix}/cloud.conf" if [ -z "${NETDATA_NO_START}" ]; then case "${SYSTYPE}" in - Darwin) run ${ROOTCMD} launchctl kickstart -k com.github.netdata ;; - FreeBSD) run ${ROOTCMD} service netdata restart ;; + Darwin) run_as_root launchctl kickstart -k com.github.netdata ;; + FreeBSD) run_as_root service netdata restart ;; Linux) - initpath="$(${ROOTCMD} readlink /proc/1/exe)" + initpath="$(run_as_root readlink /proc/1/exe)" if command -v service > /dev/null 2>&1; then - run ${ROOTCMD} service netdata restart + run_as_root service netdata restart elif command -v rc-service > /dev/null 2>&1; then - run ${ROOTCMD} rc-service netdata restart + run_as_root rc-service netdata restart elif [ "$(basename "${initpath}" 2> /dev/null)" = "systemd" ]; then - run ${ROOTCMD} systemctl restart netdata + run_as_root systemctl restart netdata elif [ -f /etc/init.d/netdata ]; then - run ${ROOTCMD} /etc/init.d/netdata restart + run_as_root /etc/init.d/netdata restart fi ;; esac @@ -1053,7 +1055,7 @@ EOF } confirm_install_prefix() { - if [ -n "${INSTALL_PREFIX}" ] && [ "${NETDATA_ONLY_BUILD}" -ne 1 ]; then + if [ -n "${INSTALL_PREFIX}" ] && [ "${NETDATA_FORCE_METHOD}" != 'build' ]; then fatal "The --install-prefix option is only supported together with the --build-only option." F0204 fi @@ -1135,8 +1137,14 @@ claim() { NETDATA_CLAIM_PATH="${INSTALL_PREFIX}/netdata/usr/sbin/netdata-claim.sh" fi - if [ ! -x "${NETDATA_CLAIM_PATH}" ]; then + if [ -z "${NETDATA_CLAIM_PATH}" ]; then fatal "Unable to find usable claiming script. Reinstalling Netdata may resolve this." F050B + elif [ ! -e "${NETDATA_CLAIM_PATH}" ]; then + fatal "${NETDATA_CLAIM_PATH} does not exist." F0512 + elif [ ! -f "${NETDATA_CLAIM_PATH}" ]; then + fatal "${NETDATA_CLAIM_PATH} is not a file." F0513 + elif [ ! -x "${NETDATA_CLAIM_PATH}" ]; then + fatal "Claiming script at ${NETDATA_CLAIM_PATH} is not executable. Reinstalling Netdata may resolve this." F0514 fi if ! is_netdata_running; then @@ -1144,58 +1152,30 @@ claim() { fi # shellcheck disable=SC2086 - run ${ROOTCMD} "${NETDATA_CLAIM_PATH}" -token="${NETDATA_CLAIM_TOKEN}" -rooms="${NETDATA_CLAIM_ROOMS}" -url="${NETDATA_CLAIM_URL}" ${NETDATA_CLAIM_EXTRA} + run_as_root "${NETDATA_CLAIM_PATH}" -token="${NETDATA_CLAIM_TOKEN}" -rooms="${NETDATA_CLAIM_ROOMS}" -url="${NETDATA_CLAIM_URL}" ${NETDATA_CLAIM_EXTRA} case $? in 0) progress "Successfully claimed node" return 0 ;; - 1) - warning "Unable to claim node due to invalid claiming options. If you are seeing this message, you’ve probably found a bug and should open a bug report at ${AGENT_BUG_REPORT_URL}" - ;; - 2) - warning "Unable to claim node due to issues creating the claiming directory or preparing the local claiming key. Make sure you have a working openssl command and that ${INSTALL_PREFIX}/var/lib/netdata/cloud.d exists, then try again." - ;; - 3) - warning "Unable to claim node due to missing dependencies. Usually this means that the Netdata Agent was built without support for Netdata Cloud. If you built the agent from source, please install all needed dependencies for Cloud support. If you used the regular installation script and see this error, please file a bug report at ${AGENT_BUG_REPORT_URL}." - ;; - 4) - warning "Failed to claim node due to inability to connect to ${NETDATA_CLAIM_URL}. Usually this either means that the specified claiming URL is wrong, or that you are having networking problems." - ;; + 1) warning "Unable to claim node due to invalid claiming options. If you are seeing this message, you’ve probably found a bug and should open a bug report at ${AGENT_BUG_REPORT_URL}" ;; + 2) warning "Unable to claim node due to issues creating the claiming directory or preparing the local claiming key. Make sure you have a working openssl command and that ${INSTALL_PREFIX}/var/lib/netdata/cloud.d exists, then try again." ;; + 3) warning "Unable to claim node due to missing dependencies. Usually this means that the Netdata Agent was built without support for Netdata Cloud. If you built the agent from source, please install all needed dependencies for Cloud support. If you used the regular installation script and see this error, please file a bug report at ${AGENT_BUG_REPORT_URL}." ;; + 4) warning "Failed to claim node due to inability to connect to ${NETDATA_CLAIM_URL}. Usually this either means that the specified claiming URL is wrong, or that you are having networking problems." ;; 5) progress "Successfully claimed node, but was not able to notify the Netdata Agent. You will need to restart the Netdata service on this node before it will show up in the Cloud." return 0 ;; - 8) - warning "Failed to claim node due to an invalid agent ID. You can usually resolve this by removing ${INSTALL_PREFIX}/var/lib/netdata/registry/netdata.public.unique.id and restarting the agent. Then try to claim it again using the same options." - ;; - 9) - warning "Failed to claim node due to an invalid node name. This probably means you tried to specify a custom name for this node (for example, using the --claim-hostname option), but the hostname itself was either empty or consisted solely of whitespace. You can resolve this by specifying a valid host name and trying again." - ;; - 10) - warning "Failed to claim node due to an invalid room ID. This issue is most likely caused by a typo. Please check if the room(s) you are trying to add appear on the list of rooms provided to the --claim-rooms option ('${NETDATA_CLAIM_ROOMS}'). Then verify if the rooms are visible in Netdata Cloud and try again." - ;; - 11) - warning "Failed to claim node due to an issue with the generated RSA key pair. You can usually resolve this by removing all files in ${INSTALL_PREFIX}/var/lib/netdata/cloud.d and then trying again." - ;; - 12) - warning "Failed to claim node due to an invalid or expired claiming token. Please check that the token specified with the --claim-token option ('${NETDATA_CLAIM_TOKEN}') matches what you see in the Cloud and try again." - ;; - 13) - warning "Failed to claim node because the Cloud thinks it is already claimed. If this node was created by cloning a VM or as a container from a template, please remove the file ${INSTALL_PREFIX}/var/lib/netdata/registry/netdata.public.unique.id and restart the agent. Then try to claim it again with the same options. Otherwise, if you are certain this node has never been claimed before, you can use the --claim-id option to specify a new node ID to use for claiming, for example by using the uuidgen command like so: --claim-id \"\$(uuidgen)\"" - ;; - 14) - warning "Failed to claim node because the node is already in the process of being claimed. You should not need to do anything to resolve this, the node should show up properly in the Cloud soon. If it does not, please report a bug at ${AGENT_BUG_REPORT_URL}." - ;; - 15|16|17) - warning "Failed to claim node due to an internal server error in the Cloud. Please retry claiming this node later, and if you still see this message file a bug report at ${CLOUD_BUG_REPORT_URL}." - ;; - 18) - warning "Unable to claim node because this Netdata installation does not have a unique ID yet. Make sure the agent is running and started up correctly, and then try again." - ;; - *) - warning "Failed to claim node for an unknown reason. This usually means either networking problems or a bug. Please retry claiming later, and if you still see this message file a bug report at ${AGENT_BUG_REPORT_URL}" - ;; + 8) warning "Failed to claim node due to an invalid agent ID. You can usually resolve this by removing ${INSTALL_PREFIX}/var/lib/netdata/registry/netdata.public.unique.id and restarting the agent. Then try to claim it again using the same options." ;; + 9) warning "Failed to claim node due to an invalid node name. This probably means you tried to specify a custom name for this node (for example, using the --claim-hostname option), but the hostname itself was either empty or consisted solely of whitespace. You can resolve this by specifying a valid host name and trying again." ;; + 10) warning "Failed to claim node due to an invalid room ID. This issue is most likely caused by a typo. Please check if the room(s) you are trying to add appear on the list of rooms provided to the --claim-rooms option ('${NETDATA_CLAIM_ROOMS}'). Then verify if the rooms are visible in Netdata Cloud and try again." ;; + 11) warning "Failed to claim node due to an issue with the generated RSA key pair. You can usually resolve this by removing all files in ${INSTALL_PREFIX}/var/lib/netdata/cloud.d and then trying again." ;; + 12) warning "Failed to claim node due to an invalid or expired claiming token. Please check that the token specified with the --claim-token option ('${NETDATA_CLAIM_TOKEN}') matches what you see in the Cloud and try again." ;; + 13) warning "Failed to claim node because the Cloud thinks it is already claimed. If this node was created by cloning a VM or as a container from a template, please remove the file ${INSTALL_PREFIX}/var/lib/netdata/registry/netdata.public.unique.id and restart the agent. Then try to claim it again with the same options. Otherwise, if you are certain this node has never been claimed before, you can use the --claim-id option to specify a new node ID to use for claiming, for example by using the uuidgen command like so: --claim-id \"\$(uuidgen)\"" ;; + 14) warning "Failed to claim node because the node is already in the process of being claimed. You should not need to do anything to resolve this, the node should show up properly in the Cloud soon. If it does not, please report a bug at ${AGENT_BUG_REPORT_URL}." ;; + 15|16|17) warning "Failed to claim node due to an internal server error in the Cloud. Please retry claiming this node later, and if you still see this message file a bug report at ${CLOUD_BUG_REPORT_URL}." ;; + 18) warning "Unable to claim node because this Netdata installation does not have a unique ID yet. Make sure the agent is running and started up correctly, and then try again." ;; + *) warning "Failed to claim node for an unknown reason. This usually means either networking problems or a bug. Please retry claiming later, and if you still see this message file a bug report at ${AGENT_BUG_REPORT_URL}" ;; esac if [ -z "${NETDATA_NEW_INSTALL}" ]; then @@ -1224,16 +1204,16 @@ set_auto_updates() { if [ "${DRY_RUN}" -eq 1 ]; then progress "Would have attempted to enable automatic updates." # This first case is for catching using a new kickstart script with an old build. It can be safely removed after v1.34.0 is released. - elif ! grep -q '\-\-enable-auto-updates' ${updater}; then + elif ! grep -q '\-\-enable-auto-updates' "${updater}"; then echo - elif ! ${ROOTCMD} ${updater} --enable-auto-updates "${NETDATA_AUTO_UPDATE_TYPE}"; then + elif ! run_as_root "${updater}" --enable-auto-updates "${NETDATA_AUTO_UPDATE_TYPE}"; then warning "Failed to enable auto updates. Netdata will still work, but you will need to update manually." fi else if [ "${DRY_RUN}" -eq 1 ]; then progress "Would have attempted to disable automatic updates." else - ${ROOTCMD} ${updater} --disable-auto-updates + run_as_root "${updater}" --disable-auto-updates fi fi } @@ -1251,7 +1231,7 @@ pkg_installed() { dpkg-query --show --showformat '${Status}' "${1}" 2>&1 | cut -f 1 -d ' ' | grep -q '^install$' return $? ;; - centos|fedora|opensuse|ol) + centos|fedora|opensuse|ol|amzn) rpm -q "${1}" > /dev/null 2>&1 return $? ;; @@ -1263,9 +1243,7 @@ pkg_installed() { pacman -Qi "${1}" > /dev/null 2>&1 return $? ;; - *) - return 1 - ;; + *) return 1 ;; esac ;; Darwin) @@ -1295,7 +1273,7 @@ netdata_avail_check() { env DEBIAN_FRONTEND=noninteractive apt-cache policy netdata | grep -q repo.netdata.cloud/repos/; return $? ;; - centos|fedora|ol) + centos|fedora|ol|amzn) # shellcheck disable=SC2086 ${pm_cmd} search --nogpgcheck -v netdata | grep -qE 'Repo *: netdata(-edge)?$' return $? @@ -1304,9 +1282,7 @@ netdata_avail_check() { zypper packages -r "$(zypper repos | grep -E 'netdata |netdata-edge ' | cut -f 1 -d '|' | tr -d ' ')" | grep -E 'netdata ' return $? ;; - *) - return 1 - ;; + *) return 1 ;; esac } @@ -1323,7 +1299,7 @@ check_special_native_deps() { progress "EPEL is available, attempting to install so that required dependencies are available." # shellcheck disable=SC2086 - if ! run ${ROOTCMD} env ${env} ${pm_cmd} install ${pkg_install_opts} epel-release; then + if ! run_as_root env ${env} ${pm_cmd} install ${pkg_install_opts} epel-release; then warning "Failed to install EPEL, even though it is required to install native packages on this system." return 1 fi @@ -1334,12 +1310,39 @@ check_special_native_deps() { fi } +common_rpm_opts() { + pkg_type="rpm" + pkg_suffix=".noarch" + pkg_vsep="-" + INSTALL_TYPE="binpkg-rpm" + NATIVE_VERSION="${INSTALL_VERSION:+"-${INSTALL_VERSION}.${SYSARCH}"}" +} + +common_dnf_opts() { + if command -v dnf > /dev/null; then + pm_cmd="dnf" + repo_subcmd="makecache" + else + pm_cmd="yum" + fi + pkg_install_opts="${interactive_opts}" + repo_update_opts="${interactive_opts}" + uninstall_subcmd="remove" +} + try_package_install() { failed_refresh_msg="Failed to refresh repository metadata. ${BADNET_MSG} or by misconfiguration of one or more rpackage repositories in the system package manager configuration." - if [ -z "${DISTRO}" ] || [ "${DISTRO}" = "unknown" ]; then + if [ -z "${DISTRO_COMPAT_NAME}" ] || [ "${DISTRO_COMPAT_NAME}" = "unknown" ]; then warning "Unable to determine Linux distribution for native packages." return 2 + elif [ -z "${SYSCODENAME}" ]; then + case "${DISTRO_COMPAT_NAME}" in + debian|ubuntu) + warning "Release codename not set. Unable to check availability of native packages for this system." + return 2 + ;; + esac fi set_tmpdir @@ -1365,97 +1368,43 @@ try_package_install() { fi case "${DISTRO_COMPAT_NAME}" in - debian) - needs_early_refresh=1 - pm_cmd="apt-get" - repo_subcmd="update" - repo_prefix="debian/${SYSCODENAME}" - pkg_type="deb" - pkg_suffix="+debian${SYSVERSION}_all" - pkg_vsep="_" - pkg_install_opts="${interactive_opts}" - repo_update_opts="${interactive_opts}" - uninstall_subcmd="purge" - INSTALL_TYPE="binpkg-deb" - NATIVE_VERSION="${INSTALL_VERSION:+"=${INSTALL_VERSION}"}" - ;; - ubuntu) + debian|ubuntu) needs_early_refresh=1 pm_cmd="apt-get" repo_subcmd="update" - repo_prefix="ubuntu/${SYSCODENAME}" pkg_type="deb" - pkg_suffix="+ubuntu${SYSVERSION}_all" pkg_vsep="_" pkg_install_opts="${interactive_opts}" repo_update_opts="${interactive_opts}" uninstall_subcmd="purge" + repo_prefix="${DISTRO_COMPAT_NAME}/${SYSCODENAME}" + pkg_suffix="+${DISTRO_COMPAT_NAME}${SYSVERSION}_all" INSTALL_TYPE="binpkg-deb" NATIVE_VERSION="${INSTALL_VERSION:+"=${INSTALL_VERSION}"}" ;; centos) - if command -v dnf > /dev/null; then - pm_cmd="dnf" - repo_subcmd="makecache" - else - pm_cmd="yum" - fi + common_rpm_opts + common_dnf_opts repo_prefix="el/${SYSVERSION}" - pkg_type="rpm" - pkg_suffix=".noarch" - pkg_vsep="-" - pkg_install_opts="${interactive_opts}" - repo_update_opts="${interactive_opts}" - uninstall_subcmd="remove" - INSTALL_TYPE="binpkg-rpm" - NATIVE_VERSION="${INSTALL_VERSION:+"-${INSTALL_VERSION}.${SYSARCH}"}" ;; - fedora) - if command -v dnf > /dev/null; then - pm_cmd="dnf" - repo_subcmd="makecache" - else - pm_cmd="yum" - fi - repo_prefix="fedora/${SYSVERSION}" - pkg_type="rpm" - pkg_suffix=".noarch" - pkg_vsep="-" - pkg_install_opts="${interactive_opts}" - repo_update_opts="${interactive_opts}" - uninstall_subcmd="remove" - INSTALL_TYPE="binpkg-rpm" - NATIVE_VERSION="${INSTALL_VERSION:+"-${INSTALL_VERSION}.${SYSARCH}"}" + fedora|ol) + common_rpm_opts + common_dnf_opts + repo_prefix="${DISTRO_COMPAT_NAME}/${SYSVERSION}" ;; opensuse) + common_rpm_opts pm_cmd="zypper" repo_subcmd="--gpg-auto-import-keys refresh" repo_prefix="opensuse/${SYSVERSION}" - pkg_type="rpm" - pkg_suffix=".noarch" - pkg_vsep="-" pkg_install_opts="${interactive_opts} --allow-unsigned-rpm" repo_update_opts="" uninstall_subcmd="remove" - INSTALL_TYPE="binpkg-rpm" - NATIVE_VERSION="${INSTALL_VERSION:+"-${INSTALL_VERSION}.${SYSARCH}"}" ;; - ol) - if command -v dnf > /dev/null; then - pm_cmd="dnf" - repo_subcmd="makecache" - else - pm_cmd="yum" - fi - repo_prefix="ol/${SYSVERSION}" - pkg_type="rpm" - pkg_suffix=".noarch" - pkg_vsep="-" - pkg_install_opts="${interactive_opts}" - repo_update_opts="${interactive_opts}" - uninstall_subcmd="remove" - INSTALL_TYPE="binpkg-rpm" - NATIVE_VERSION="${INSTALL_VERSION:+"-${INSTALL_VERSION}.${SYSARCH}"}" + amzn) + common_rpm_opts + common_dnf_opts + repo_prefix="amazonlinux/${SYSVERSION}" ;; *) warning "We do not provide native packages for ${DISTRO}." @@ -1463,6 +1412,10 @@ try_package_install() { ;; esac + if [ -n "${SKIP_DISTRO_DETECTION}" ]; then + warning "Attempting to use native packages with a distro override. This is not officially supported, but may work in some cases. If your system requires a distro override to use native packages, please open an feature request at ${AGENT_BUG_REPORT_URL} about it so that we can update the installer to auto-detect this." + fi + if [ -n "${INSTALL_VERSION}" ]; then if echo "${INSTALL_VERSION}" | grep -q "nightly"; then new_release="-edge" @@ -1503,21 +1456,21 @@ try_package_install() { if [ -n "${needs_early_refresh}" ]; then # shellcheck disable=SC2086 - if ! run ${ROOTCMD} env ${env} ${pm_cmd} ${repo_subcmd} ${repo_update_opts}; then + if ! run_as_root env ${env} ${pm_cmd} ${repo_subcmd} ${repo_update_opts}; then warning "${failed_refresh_msg}" return 2 fi fi # shellcheck disable=SC2086 - if ! run ${ROOTCMD} env ${env} ${pm_cmd} install ${pkg_install_opts} "${tmpdir}/${repoconfig_file}"; then + if ! run_as_root env ${env} ${pm_cmd} install ${pkg_install_opts} "${tmpdir}/${repoconfig_file}"; then warning "Failed to install repository configuration package." return 2 fi if [ -n "${repo_subcmd}" ]; then # shellcheck disable=SC2086 - if ! run ${ROOTCMD} env ${env} ${pm_cmd} ${repo_subcmd} ${repo_update_opts}; then + if ! run_as_root env ${env} ${pm_cmd} ${repo_subcmd} ${repo_update_opts}; then fatal "${failed_refresh_msg}" F0205 fi fi @@ -1525,8 +1478,8 @@ try_package_install() { progress "Repository configuration is already present, attempting to install netdata." fi - if [ "${REPO_ACTION}" = "repositories-only" ]; then - progress "Successfully installed repository configuration package." + if [ "${ACTION}" = "repositories-only" ]; then + progress "Successfully installed repository configuraion package." deferred_warnings cleanup trap - EXIT @@ -1538,7 +1491,7 @@ try_package_install() { if [ -z "${NO_CLEANUP}" ]; then progress "Attempting to uninstall repository configuration package." # shellcheck disable=SC2086 - run ${ROOTCMD} env ${env} ${pm_cmd} ${uninstall_subcmd} ${pkg_install_opts} "${repoconfig_name}" + run_as_root env ${env} ${pm_cmd} ${uninstall_subcmd} ${pkg_install_opts} "${repoconfig_name}" fi return 2 fi @@ -1548,23 +1501,23 @@ try_package_install() { if [ -z "${NO_CLEANUP}" ]; then progress "Attempting to uninstall repository configuration package." # shellcheck disable=SC2086 - run ${ROOTCMD} env ${env} ${pm_cmd} ${uninstall_subcmd} ${pkg_install_opts} "${repoconfig_name}" + run_as_root env ${env} ${pm_cmd} ${uninstall_subcmd} ${pkg_install_opts} "${repoconfig_name}" fi return 2 fi if [ "${NETDATA_DISABLE_TELEMETRY}" -eq 1 ]; then - run ${ROOTCMD} mkdir -p "/etc/netdata" - run ${ROOTCMD} touch "/etc/netdata/.opt-out-from-anonymous-statistics" + run_as_root mkdir -p "/etc/netdata" + run_as_root touch "/etc/netdata/.opt-out-from-anonymous-statistics" fi # shellcheck disable=SC2086 - if ! run ${ROOTCMD} env ${env} ${pm_cmd} install ${pkg_install_opts} "netdata${NATIVE_VERSION}"; then + if ! run_as_root env ${env} ${pm_cmd} install ${pkg_install_opts} "netdata${NATIVE_VERSION}"; then warning "Failed to install Netdata package." if [ -z "${NO_CLEANUP}" ]; then progress "Attempting to uninstall repository configuration package." # shellcheck disable=SC2086 - run ${ROOTCMD} env ${env} ${pm_cmd} ${uninstall_subcmd} ${pkg_install_opts} "${repoconfig_name}" + run_as_root env ${env} ${pm_cmd} ${uninstall_subcmd} ${pkg_install_opts} "${repoconfig_name}" fi return 2 fi @@ -1657,7 +1610,7 @@ try_static_install() { progress "Installing netdata" # shellcheck disable=SC2086 - if ! run ${ROOTCMD} sh "${tmpdir}/${netdata_agent}" ${opts} -- ${NETDATA_INSTALLER_OPTIONS}; then + if ! run_as_root sh "${tmpdir}/${netdata_agent}" ${opts} -- ${NETDATA_INSTALLER_OPTIONS}; then warning "Failed to install static build of Netdata on ${SYSARCH}." run rm -rf /opt/netdata return 2 @@ -1666,16 +1619,16 @@ try_static_install() { if [ "${DRY_RUN}" -ne 1 ]; then install_type_file="/opt/netdata/etc/netdata/.install-type" if [ -f "${install_type_file}" ]; then - ${ROOTCMD} sh -c "cat \"${install_type_file}\" > \"${tmpdir}/install-type\"" - ${ROOTCMD} chown "$(id -u)":"$(id -g)" "${tmpdir}/install-type" + run_as_root sh -c "cat \"${install_type_file}\" > \"${tmpdir}/install-type\"" + run_as_root chown "$(id -u)":"$(id -g)" "${tmpdir}/install-type" # shellcheck disable=SC1090,SC1091 . "${tmpdir}/install-type" cat > "${tmpdir}/install-type" <<- EOF INSTALL_TYPE='kickstart-static' PREBUILT_ARCH='${PREBUILT_ARCH}' EOF - ${ROOTCMD} chown netdata:netdata "${tmpdir}/install-type" - ${ROOTCMD} cp "${tmpdir}/install-type" "${install_type_file}" + run_as_root chown netdata:netdata "${tmpdir}/install-type" + run_as_root cp "${tmpdir}/install-type" "${install_type_file}" fi fi } @@ -1728,14 +1681,8 @@ install_local_build_dependencies() { opts="--dont-wait --non-interactive" fi - if [ "${SYSTYPE}" = "Darwin" ]; then - sudo="" - else - sudo="${ROOTCMD}" - fi - # shellcheck disable=SC2086 - if ! run ${sudo} "${bash}" "${tmpdir}/install-required-packages.sh" ${opts} netdata; then + if ! run_as_root "${bash}" "${tmpdir}/install-required-packages.sh" ${opts} netdata; then warning "Failed to install all required packages, but installation might still be possible." fi } @@ -1776,9 +1723,7 @@ build_and_install() { fatal "netdata-installer.sh failed to run: Encountered an unhandled error in the installer code." I0000 fi ;; - 2) - fatal "Insufficient RAM to install netdata." F0008 - ;; + 2) fatal "Insufficient RAM to install netdata." F0008 ;; esac } @@ -1858,34 +1803,36 @@ prepare_offline_install_source() { 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" + case "${NETDATA_FORCE_METHOD}" in + static|'') + set_static_archive_urls "${SELECTED_RELEASE_CHANNEL}" "x86_64" - if check_for_remote_file "${NETDATA_STATIC_ARCHIVE_URL}"; then - for arch in ${STATIC_INSTALL_ARCHES}; do - set_static_archive_urls "${SELECTED_RELEASE_CHANNEL}" "${arch}" + if check_for_remote_file "${NETDATA_STATIC_ARCHIVE_URL}"; then + for arch in ${STATIC_INSTALL_ARCHES}; do + set_static_archive_urls "${SELECTED_RELEASE_CHANNEL}" "${arch}" - progress "Fetching ${NETDATA_STATIC_ARCHIVE_URL}" - if ! download "${NETDATA_STATIC_ARCHIVE_URL}" "netdata-${arch}-latest.gz.run"; then - warning "Failed to download static installer archive for ${arch}. ${BADNET_MSG}." - fi - done - legacy=0 - else - warning "Selected version of Netdata only provides static builds for x86_64. You will only be able to install on x86_64 systems with this offline install source." - progress "Fetching ${NETDATA_STATIC_ARCHIVE_OLD_URL}" - legacy=1 + progress "Fetching ${NETDATA_STATIC_ARCHIVE_URL}" + if ! download "${NETDATA_STATIC_ARCHIVE_URL}" "netdata-${arch}-latest.gz.run"; then + warning "Failed to download static installer archive for ${arch}. ${BADNET_MSG}." + fi + done + legacy=0 + else + warning "Selected version of Netdata only provides static builds for x86_64. You will only be able to install on x86_64 systems with this offline install source." + progress "Fetching ${NETDATA_STATIC_ARCHIVE_OLD_URL}" + legacy=1 - if ! download "${NETDATA_STATIC_ARCHIVE_OLD_URL}" "netdata-x86_64-latest.gz.run"; then - warning "Failed to download static installer archive for x86_64. ${BADNET_MSG}." + if ! download "${NETDATA_STATIC_ARCHIVE_OLD_URL}" "netdata-x86_64-latest.gz.run"; then + warning "Failed to download static installer archive for x86_64. ${BADNET_MSG}." + fi fi - fi - progress "Fetching ${NETDATA_STATIC_ARCHIVE_CHECKSUM_URL}" - if ! download "${NETDATA_STATIC_ARCHIVE_CHECKSUM_URL}" "sha256sums.txt"; then - fatal "Failed to download checksum file. ${BADNET_MSG}." F0506 - fi - fi + progress "Fetching ${NETDATA_STATIC_ARCHIVE_CHECKSUM_URL}" + if ! download "${NETDATA_STATIC_ARCHIVE_CHECKSUM_URL}" "sha256sums.txt"; then + fatal "Failed to download checksum file. ${BADNET_MSG}." F0506 + fi + ;; + esac if [ "${legacy:-0}" -eq 1 ]; then sed -e 's/netdata-latest.gz.run/netdata-x86_64-latest.gz.run' sha256sums.txt > sha256sums.tmp @@ -1935,7 +1882,7 @@ prepare_offline_install_source() { # Per system-type install logic install_on_linux() { - if [ "${NETDATA_ONLY_STATIC}" -ne 1 ] && [ "${NETDATA_ONLY_BUILD}" -ne 1 ] && [ -z "${NETDATA_OFFLINE_INSTALL_SOURCE}" ]; then + if [ "${NETDATA_FORCE_METHOD}" != 'static' ] && [ "${NETDATA_FORCE_METHOD}" != 'build' ] && [ -z "${NETDATA_OFFLINE_INSTALL_SOURCE}" ]; then SELECTED_INSTALL_METHOD="native" try_package_install @@ -1944,20 +1891,17 @@ install_on_linux() { NETDATA_INSTALL_SUCCESSFUL=1 INSTALL_PREFIX="/" ;; - 1) - fatal "Unable to install on this system." F0300 - ;; + 1) fatal "Unable to install on this system." F0300 ;; 2) - if [ "${NETDATA_ONLY_NATIVE}" -eq 1 ]; then - fatal "Could not install native binary packages." F0301 - else - warning "Could not install native binary packages, falling back to alternative installation method." - fi + case "${NETDATA_FORCE_METHOD}" in + native) fatal "Could not install native binary packages." F0301 ;; + *) warning "Could not install native binary packages, falling back to alternative installation method." ;; + esac ;; esac fi - if [ "${NETDATA_ONLY_NATIVE}" -ne 1 ] && [ "${NETDATA_ONLY_BUILD}" -ne 1 ] && [ -z "${NETDATA_INSTALL_SUCCESSFUL}" ]; then + if [ "${NETDATA_FORCE_METHOD}" != 'native' ] && [ "${NETDATA_FORCE_METHOD}" != 'build' ] && [ -z "${NETDATA_INSTALL_SUCCESSFUL}" ]; then SELECTED_INSTALL_METHOD="static" INSTALL_TYPE="kickstart-static" try_static_install @@ -1967,75 +1911,60 @@ install_on_linux() { NETDATA_INSTALL_SUCCESSFUL=1 INSTALL_PREFIX="/opt/netdata" ;; - 1) - fatal "Unable to install on this system." F0302 - ;; + 1) fatal "Unable to install on this system." F0302 ;; 2) - if [ "${NETDATA_ONLY_STATIC}" -eq 1 ]; then - fatal "Could not install static build." F0303 - else - warning "Could not install static build, falling back to alternative installation method." - fi + case "${NETDATA_FORCE_METHOD}" in + static) fatal "Could not install static build." F0303 ;; + *) warning "Could not install static build, falling back to alternative installation method." ;; + esac ;; esac fi - if [ "${NETDATA_ONLY_NATIVE}" -ne 1 ] && [ "${NETDATA_ONLY_STATIC}" -ne 1 ] && [ -z "${NETDATA_INSTALL_SUCCESSFUL}" ]; then + if [ "${NETDATA_FORCE_METHOD}" != 'native' ] && [ "${NETDATA_FORCE_METHOD}" != 'static' ] && [ -z "${NETDATA_INSTALL_SUCCESSFUL}" ]; then SELECTED_INSTALL_METHOD="build" INSTALL_TYPE="kickstart-build" try_build_install case "$?" in - 0) - NETDATA_INSTALL_SUCCESSFUL=1 - ;; - *) - fatal "Unable to install on this system." F0304 - ;; + 0) NETDATA_INSTALL_SUCCESSFUL=1 ;; + *) fatal "Unable to install on this system." F0304 ;; esac fi } install_on_macos() { - if [ "${NETDATA_ONLY_NATIVE}" -eq 1 ]; then - fatal "User requested native package, but native packages are not available for macOS. Try installing without \`--only-native\` option." F0305 - elif [ "${NETDATA_ONLY_STATIC}" -eq 1 ]; then - fatal "User requested static build, but static builds are not available for macOS. Try installing without \`--only-static\` option." F0306 - else - SELECTED_INSTALL_METHOD="build" - INSTALL_TYPE="kickstart-build" - try_build_install + case "${NETDATA_FORCE_METHOD}" in + native) fatal "User requested native package, but native packages are not available for macOS. Try installing without \`--only-native\` option." F0305 ;; + static) fatal "User requested static build, but static builds are not available for macOS. Try installing without \`--only-static\` option." F0306 ;; + *) + SELECTED_INSTALL_METHOD="build" + INSTALL_TYPE="kickstart-build" + try_build_install - case "$?" in - 0) - NETDATA_INSTALL_SUCCESSFUL=1 - ;; - *) - fatal "Unable to install on this system." F0307 - ;; - esac - fi + case "$?" in + 0) NETDATA_INSTALL_SUCCESSFUL=1 ;; + *) fatal "Unable to install on this system." F0307 ;; + esac + ;; + esac } install_on_freebsd() { - if [ "${NETDATA_ONLY_NATIVE}" -eq 1 ]; then - fatal "User requested native package, but native packages are not available for FreeBSD. Try installing without \`--only-native\` option." F0308 - elif [ "${NETDATA_ONLY_STATIC}" -eq 1 ]; then - fatal "User requested static build, but static builds are not available for FreeBSD. Try installing without \`--only-static\` option." F0309 - else - SELECTED_INSTALL_METHOD="build" - INSTALL_TYPE="kickstart-build" - try_build_install + case "${NETDATA_FORCE_METHOD}" in + native) fatal "User requested native package, but native packages are not available for FreeBSD. Try installing without \`--only-native\` option." F0308 ;; + static) fatal "User requested static build, but static builds are not available for FreeBSD. Try installing without \`--only-static\` option." F0309 ;; + *) + SELECTED_INSTALL_METHOD="build" + INSTALL_TYPE="kickstart-build" + try_build_install - case "$?" in - 0) - NETDATA_INSTALL_SUCCESSFUL=1 - ;; - *) - fatal "Unable to install on this system." F030A - ;; - esac - fi + case "$?" in + 0) NETDATA_INSTALL_SUCCESSFUL=1 ;; + *) fatal "Unable to install on this system." F030A ;; + esac + ;; + esac } # ====================================================================== @@ -2044,26 +1973,32 @@ install_on_freebsd() { validate_args() { check_claim_opts + if [ -n "${NETDATA_FORCE_METHOD}" ]; then + SELECTED_INSTALL_METHOD="${NETDATA_FORCE_METHOD}" + fi + + if [ "${ACTION}" = "repositories-only" ] && [ "${NETDATA_FORCE_METHOD}" != "native" ]; then + fatal "Repositories can only be installed for native installs." F050D + fi + if [ -n "${NETDATA_OFFLINE_INSTALL_SOURCE}" ]; then - if [ "${NETDATA_ONLY_NATIVE}" -eq 1 ] || [ "${NETDATA_ONLY_BUILD}" -eq 1 ]; then - fatal "Offline installs are only supported for static builds currently." F0502 - fi + case "${NETDATA_FORCE_METHOD}" in + native|build) fatal "Offline installs are only supported for static builds currently." F0502 ;; + esac fi if [ -n "${LOCAL_BUILD_OPTIONS}" ]; then - if [ "${NETDATA_ONLY_BUILD}" -eq 1 ]; then - NETDATA_INSTALLER_OPTIONS="${NETDATA_INSTALLER_OPTIONS} ${LOCAL_BUILD_OPTIONS}" - else - fatal "Specifying local build options is only supported when the --build-only option is also specified." F0401 - fi + case "${NETDATA_FORCE_METHOD}" in + build) NETDATA_INSTALLER_OPTIONS="${NETDATA_INSTALLER_OPTIONS} ${LOCAL_BUILD_OPTIONS}" ;; + *) fatal "Specifying local build options is only supported when the --build-only option is also specified." F0401 ;; + esac fi if [ -n "${STATIC_INSTALL_OPTIONS}" ]; then - if [ "${NETDATA_ONLY_STATIC}" -eq 1 ]; then - NETDATA_INSTALLER_OPTIONS="${NETDATA_INSTALLER_OPTIONS} ${STATIC_INSTALL_OPTIONS}" - else - fatal "Specifying installer options options is only supported when the --static-only option is also specified." F0402 - fi + case "${NETDATA_FORCE_METHOD}" in + static) NETDATA_INSTALLER_OPTIONS="${NETDATA_INSTALLER_OPTIONS} ${STATIC_INSTALL_OPTIONS}" ;; + *) fatal "Specifying installer options options is only supported when the --static-only option is also specified." F0402 ;; + esac fi if [ -n "${NETDATA_OFFLINE_INSTALL_SOURCE}" ] && [ -n "${INSTALL_VERSION}" ]; then @@ -2101,6 +2036,17 @@ validate_args() { fi } +set_action() { + new_action="${1}" + + if [ -n "${ACTION}" ]; then + warning "Ignoring previously specified '${ACTION}' operation in favor of '${new_action}' specified later on the command line." + fi + + ACTION="${new_action}" + NETDATA_COMMAND="${new_action}" +} + parse_args() { while [ -n "${1}" ]; do case "${1}" in @@ -2114,33 +2060,35 @@ parse_args() { "--dont-wait"|"--non-interactive") INTERACTIVE=0 ;; "--interactive") INTERACTIVE=1 ;; "--dry-run") DRY_RUN=1 ;; + "--release-channel") + RELEASE_CHANNEL="$(echo "${2}" | tr '[:upper:]' '[:lower:]')" + case "${RELEASE_CHANNEL}" in + nightly|stable|default) shift 1 ;; + *) + echo "Unrecognized value for --release-channel. Valid release channels are: stable, nightly, default" + exit 1 + ;; + esac + ;; "--stable-channel") RELEASE_CHANNEL="stable" ;; + "--nightly-channel") RELEASE_CHANNEL="nightly" ;; + "--reinstall") set_action 'reinstall' ;; + "--reinstall-even-if-unsafe") set_action 'unsafe-reinstall' ;; + "--reinstall-clean") set_action 'reinstall-clean' ;; + "--uninstall") set_action 'uninstall' ;; + "--claim-only") set_action 'claim' ;; "--no-updates") NETDATA_AUTO_UPDATES=0 ;; "--auto-update") NETDATA_AUTO_UPDATES="1" ;; "--auto-update-method") NETDATA_AUTO_UPDATE_TYPE="$(echo "${2}" | tr '[:upper:]' '[:lower:]')" case "${NETDATA_AUTO_UPDATE_TYPE}" in - systemd|interval|crontab) - shift 1 - ;; + systemd|interval|crontab) shift 1 ;; *) echo "Unrecognized value for --auto-update-type. Valid values are: systemd, interval, crontab" exit 1 ;; esac ;; - "--reinstall") - NETDATA_REINSTALL=1 - NETDATA_COMMAND="reinstall" - ;; - "--reinstall-even-if-unsafe") - NETDATA_UNSAFE_REINSTALL=1 - NETDATA_COMMAND="unsafe-reinstall" - ;; - "--claim-only") - NETDATA_CLAIM_ONLY=1 - NETDATA_COMMAND="claim-only" - ;; "--disable-cloud") NETDATA_DISABLE_CLOUD=1 NETDATA_REQUIRE_CLOUD=0 @@ -2170,36 +2118,29 @@ parse_args() { AUTO_UPDATE=0 shift 1 ;; - "--uninstall") - ACTION="uninstall" - NETDATA_COMMAND="uninstall" - ;; - "--reinstall-clean") - ACTION="reinstall-clean" - NETDATA_COMMAND="reinstall-clean" + "--distro-override") + if [ -n "${2}" ]; then + SKIP_DISTRO_DETECTION=1 + DISTRO="$(echo "${2}" | cut -f 1 -d ':' | tr '[:upper:]' '[:lower:]')" + SYSVERSION="$(echo "${2}" | cut -f 2 -d ':')" + SYSCODENAME="$(echo "${2}" | cut -f 3 -d ':' | tr '[:upper:]' '[:lower:]')" + + if [ -z "${SYSVERSION}" ]; then + fatal "You must specify a release as well as a distribution name." F0510 + fi + + shift 1 + else + fatal "A distribution name and release must be specified for the --distro-override option." F050F + fi ;; "--repositories-only") - REPO_ACTION="repositories-only" - NETDATA_COMMAND="repositories" - ;; - "--native-only") - NETDATA_ONLY_NATIVE=1 - NETDATA_ONLY_STATIC=0 - NETDATA_ONLY_BUILD=0 - SELECTED_INSTALL_METHOD="native" - ;; - "--static-only") - NETDATA_ONLY_STATIC=1 - NETDATA_ONLY_NATIVE=0 - NETDATA_ONLY_BUILD=0 - SELECTED_INSTALL_METHOD="static" - ;; - "--build-only") - NETDATA_ONLY_BUILD=1 - NETDATA_ONLY_NATIVE=0 - NETDATA_ONLY_STATIC=0 - SELECTED_INSTALL_METHOD="build" + set_action 'repositories-only' + NETDATA_FORCE_METHOD="native" ;; + "--native-only") NETDATA_FORCE_METHOD="native" ;; + "--static-only") NETDATA_FORCE_METHOD="static" ;; + "--build-only") NETDATA_FORCE_METHOD="build" ;; "--claim-token") NETDATA_CLAIM_TOKEN="${2}" shift 1 @@ -2219,12 +2160,8 @@ parse_args() { NETDATA_CLAIM_EXTRA="${NETDATA_CLAIM_EXTRA} -${optname}=${2}" shift 1 ;; - verbose|insecure|noproxy|noreload|daemon-not-running) - NETDATA_CLAIM_EXTRA="${NETDATA_CLAIM_EXTRA} -${optname}" - ;; - *) - warning "Ignoring unrecognized claiming option ${optname}" - ;; + verbose|insecure|noproxy|noreload|daemon-not-running) NETDATA_CLAIM_EXTRA="${NETDATA_CLAIM_EXTRA} -${optname}" ;; + *) warning "Ignoring unrecognized claiming option ${optname}" ;; esac ;; "--local-build-options") @@ -2237,8 +2174,7 @@ parse_args() { ;; "--prepare-offline-install-source") if [ -n "${2}" ]; then - ACTION="prepare-offline" - NETDATA_COMMAND="prepare-offline" + set_action 'prepare-offline' OFFLINE_TARGET="${2}" shift 1 else @@ -2253,9 +2189,7 @@ parse_args() { fatal "A source directory must be specified with the --offline-install-source option." F0501 fi ;; - *) - 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 - ;; + *) 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 done diff --git a/packaging/installer/methods/ansible.md b/packaging/installer/methods/ansible.md new file mode 100644 index 00000000..6b4c69f0 --- /dev/null +++ b/packaging/installer/methods/ansible.md @@ -0,0 +1,156 @@ + + +# Deploy Netdata with Ansible + +Netdata's [one-line kickstart](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#install-on-linux-with-one-line-installer) 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. + +But what if you're trying to bootstrap an infrastructure monitoring solution as quickly as possible? What if you need to +deploy Netdata across an entire infrastructure with many nodes? What if you want to make this deployment reliable, +repeatable, and idempotent? What if you want to write and deploy your infrastructure or cloud monitoring system like +code? + +Enter [Ansible](https://ansible.com), a popular system provisioning, configuration management, and infrastructure as +code (IaC) tool. Ansible uses **playbooks** to glue many standardized operations together with a simple syntax, then run +those operations over standard and secure SSH connections. There's no agent to install on the remote system, so all you +have to worry about is your application and your monitoring software. + +Ansible has some competition from the likes of [Puppet](https://puppet.com/) or [Chef](https://www.chef.io/), but the +most valuable feature about Ansible is **idempotent**. From the [Ansible +glossary](https://docs.ansible.com/ansible/latest/reference_appendices/glossary.html) + +> An operation is idempotent if the result of performing it once is exactly the same as the result of performing it +> repeatedly without any intervening actions. + +Idempotency means you can run an Ansible playbook against your nodes any number of times without affecting how they +operate. When you deploy Netdata with Ansible, you're also deploying _monitoring as code_. + +In this guide, we'll walk through the process of using an [Ansible +playbook](https://github.com/netdata/community/tree/main/configuration-management/ansible-quickstart/) to automatically +deploy the Netdata Agent to any number of distributed nodes, manage the configuration of each node, and connect them to +your Netdata Cloud account. You'll go from some unmonitored nodes to a infrastructure monitoring solution in a matter of +minutes. + +## Prerequisites + +- A Netdata Cloud account. [Sign in and create one](https://app.netdata.cloud) if you don't have one already. +- An administration system with [Ansible](https://www.ansible.com/) installed. +- One or more nodes that your administration system can access via [SSH public + keys](https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key) (preferably password-less). + +## Download and configure the playbook + +First, download the +[playbook](https://github.com/netdata/community/tree/main/configuration-management/ansible-quickstart/), move it to the +current directory, and remove the rest of the cloned repository, as it's not required for using the Ansible playbook. + +```bash +git clone https://github.com/netdata/community.git +mv community/netdata-agent-deployment/ansible-quickstart . +rm -rf community +``` + +Or if you don't want to clone the entire repository, use the [gitzip browser extension](https://gitzip.org/) to get the netdata-agent-deployment directory as a zip file. + +Next, `cd` into the Ansible directory. + +```bash +cd ansible-quickstart +``` + +### Edit the `hosts` file + +The `hosts` file contains a list of IP addresses or hostnames that Ansible will try to run the playbook against. The +`hosts` file that comes with the repository contains two example IP addresses, which you should replace according to the +IP address/hostname of your nodes. + +```conf +203.0.113.0 hostname=node-01 +203.0.113.1 hostname=node-02 +``` + +You can also set the `hostname` variable, which appears both on the local Agent dashboard and Netdata Cloud, or you can +omit the `hostname=` string entirely to use the system's default hostname. + +#### Set the login user (optional) + +If you SSH into your nodes as a user other than `root`, you need to configure `hosts` according to those user names. Use +the `ansible_user` variable to set the login user. For example: + +```conf +203.0.113.0 hostname=ansible-01 ansible_user=example +``` + +#### Set your SSH key (optional) + +If you use an SSH key other than `~/.ssh/id_rsa` for logging into your nodes, you can set that on a per-node basis in +the `hosts` file with the `ansible_ssh_private_key_file` variable. For example, to log into a Lightsail instance using +two different SSH keys supplied by AWS. + +```conf +203.0.113.0 hostname=ansible-01 ansible_ssh_private_key_file=~/.ssh/LightsailDefaultKey-us-west-2.pem +203.0.113.1 hostname=ansible-02 ansible_ssh_private_key_file=~/.ssh/LightsailDefaultKey-us-east-1.pem +``` + +### 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](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, +then click on **Manage your Space**. Click on the **Nodes** tab in the panel that appears, which displays a script with +`token` and `room` strings. + +![Animated GIF of finding the claiming script and the token and room +strings](https://user-images.githubusercontent.com/1153921/98740235-f4c3ac00-2367-11eb-8ffd-e9ab0f04c463.gif) + +Copy those strings into the `claim_token` and `claim_rooms` variables. + +```yml +claim_token: XXXXX +claim_rooms: XXXXX +``` + +Change the `dbengine_multihost_disk_space` if you want to change the metrics retention policy by allocating more or less +disk space for storing metrics. The default is 2048 Mib, or 2 GiB. + +Because we're connecting this node to Netdata Cloud, and will view its dashboards there instead of via the IP address or +hostname of the node, the playbook disables that local dashboard by setting `web_mode` to `none`. This gives a small +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://github.com/netdata/netdata/blob/master/docs/netdata-security.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. + +## Run the playbook + +Time to run the playbook from your administration system: + +```bash +ansible-playbook -i hosts tasks/main.yml +``` + +Ansible first connects to your node(s) via SSH, then [collects +facts](https://docs.ansible.com/ansible/latest/user_guide/playbooks_vars_facts.html#ansible-facts) about the system. +This playbook doesn't use these facts, but you could expand it to provision specific types of systems based on the +makeup of your infrastructure. + +Next, Ansible makes changes to each node according to the `tasks` defined in the playbook, and +[returns](https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#changed) whether each +task results in a changed, failure, or was skipped entirely. + +The task to install Netdata will take a few minutes per node, so be patient! Once the playbook reaches the connect to Cloud +task, your nodes start populating your Space in Netdata Cloud. diff --git a/packaging/installer/methods/aws.md b/packaging/installer/methods/aws.md new file mode 100644 index 00000000..c0b92a03 --- /dev/null +++ b/packaging/installer/methods/aws.md @@ -0,0 +1,67 @@ + + +# Install Netdata on AWS + +Netdata is fully compatible with Amazon Web Services (AWS). +You can install Netdata on cloud instances to monitor the apps/services running there, or use +multiple instances in a [parent-child streaming](https://github.com/netdata/netdata/blob/master/streaming/README.md) configuration. + +## Recommended installation method + +The best installation method depends on the instance's operating system, distribution, and version. For Linux instances, +we recommend the [`kickstart.sh` automatic installation script](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md). + +If you have issues with Netdata after installation, look to the sections below to find the issue you're experiencing, +followed by the solution for your provider. + +## Post-installation configuration + +### Add a firewall rule to access Netdata's dashboard + +If you cannot access Netdata's dashboard on your cloud instance via `http://HOST:19999`, and instead get an error page +from your browser that says, "This site can't be reached" (Chrome) or "Unable to connect" (Firefox), you may need to +configure your cloud provider's firewall. + +Cloud providers often create network-level firewalls that run separately from the instance itself. Both AWS and Google +Cloud Platform calls them Virtual Private Cloud (VPC) networks. These firewalls can apply even if you've disabled +firewalls on the instance itself. Because you can modify these firewalls only via the cloud provider's web interface, +it's easy to overlook them when trying to configure and access Netdata's dashboard. + +You can often confirm a firewall issue by querying the dashboard while connected to the instance via SSH: `curl +http://localhost:19999/api/v1/info`. If you see JSON output, Netdata is running properly. If you try the same `curl` +command from a remote system, and it fails, it's likely that a firewall is blocking your requests. + +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](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) + +Sign in to the [AWS console](https://console.aws.amazon.com/) and navigate to the EC2 dashboard. Click on the **Security +Groups** link in the navigation, beneath the **Network & Security** heading. Find the Security Group your instance +belongs to, and either right-click on it or click the **Actions** button above to see a dropdown menu with **Edit +inbound rules**. + +Add a new rule with the following options: + +```conf +Type: Custom TCP +Protocol: TCP +Port Range: 19999 +Source: Anywhere +Description: Netdata +``` + +You can also choose **My IP** as the source if you prefer. + +Click **Save** to apply your new inbound firewall rule. diff --git a/packaging/installer/methods/azure.md b/packaging/installer/methods/azure.md new file mode 100644 index 00000000..4c39a00a --- /dev/null +++ b/packaging/installer/methods/azure.md @@ -0,0 +1,68 @@ + + +# Install Netdata on Azure + +Netdata is fully compatible with Azure. +You can install Netdata on cloud instances to monitor the apps/services running there, or use +multiple instances in a [parent-child streaming](https://github.com/netdata/netdata/blob/master/streaming/README.md) configuration. + +## Recommended installation method + +The best installation method depends on the instance's operating system, distribution, and version. For Linux instances, +we recommend the [`kickstart.sh` automatic installation script](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md). + +If you have issues with Netdata after installation, look to the sections below to find the issue you're experiencing, +followed by the solution for your provider. + +## Post-installation configuration + +### Add a firewall rule to access Netdata's dashboard + +If you cannot access Netdata's dashboard on your cloud instance via `http://HOST:19999`, and instead get an error page +from your browser that says, "This site can't be reached" (Chrome) or "Unable to connect" (Firefox), you may need to +configure your cloud provider's firewall. + +Cloud providers often create network-level firewalls that run separately from the instance itself. Both AWS and Google +Cloud Platform calls them Virtual Private Cloud (VPC) networks. These firewalls can apply even if you've disabled +firewalls on the instance itself. Because you can modify these firewalls only via the cloud provider's web interface, +it's easy to overlook them when trying to configure and access Netdata's dashboard. + +You can often confirm a firewall issue by querying the dashboard while connected to the instance via SSH: `curl +http://localhost:19999/api/v1/info`. If you see JSON output, Netdata is running properly. If you try the same `curl` +command from a remote system, and it fails, it's likely that a firewall is blocking your requests. + +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](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) + +Sign in to the [Azure portal](https://portal.azure.com) and open the virtual machine running Netdata. Click on the +**Networking** link beneath the **Settings** header, then click on the **Add inbound security rule** button. + +Add a new rule with the following options: + +```conf +Source: Any +Source port ranges: 19999 +Destination: Any +Destination port ranges: 19999 +Protocol: TCP +Action: Allow +Priority: 310 +Name: Netdata +``` + +Click **Add** to apply your new inbound security rule. + + diff --git a/packaging/installer/methods/cloud-providers.md b/packaging/installer/methods/cloud-providers.md deleted file mode 100644 index 6b8fa6de..00000000 --- a/packaging/installer/methods/cloud-providers.md +++ /dev/null @@ -1,126 +0,0 @@ - - -# Install Netdata on cloud providers - -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](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. - -> This document is a work-in-progress! If you find new issues specific to a cloud provider, or would like to help -> clarify the correct workaround, please [create an -> issue](https://github.com/netdata/netdata/issues/new?labels=feature+request,+needs+triage&template=feature_request.md) -> with your process and instructions on using the provider's interface to complete the workaround. - -- [Recommended installation methods for cloud providers](#recommended-installation-methods-for-cloud-providers) -- [Post-installation configuration](#post-installation-configuration) - - [Add a firewall rule to access Netdata's dashboard](#add-a-firewall-rule-to-access-netdatas-dashboard) - -## Recommended installation methods for cloud providers - -The best installation method depends on the instance's operating system, distribution, and version. For Linux instances, -we recommend the [`kickstart.sh` automatic installation script](kickstart.md). - -If you have issues with Netdata after installation, look to the sections below to find the issue you're experiencing, -followed by the solution for your provider. - -## Post-installation configuration - -Some cloud providers require you take additional steps to properly configure your instance or its networking to access -all of Netdata's features. - -### Add a firewall rule to access Netdata's dashboard - -If you cannot access Netdata's dashboard on your cloud instance via `http://HOST:19999`, and instead get an error page -from your browser that says, "This site can't be reached" (Chrome) or "Unable to connect" (Firefox), you may need to -configure your cloud provider's firewall. - -Cloud providers often create network-level firewalls that run separately from the instance itself. Both AWS and Google -Cloud Platform calls them Virtual Private Cloud (VPC) networks. These firewalls can apply even if you've disabled -firewalls on the instance itself. Because you can modify these firewalls only via the cloud provider's web interface, -it's easy to overlook them when trying to configure and access Netdata's dashboard. - -You can often confirm a firewall issue by querying the dashboard while connected to the instance via SSH: `curl -http://localhost:19999/api/v1/info`. If you see JSON output, Netdata is running properly. If you try the same `curl` -command from a remote system, and it fails, it's likely that a firewall is blocking your requests. - -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](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. - -#### Google Cloud Platform (GCP) - -To add a firewall rule, go to the [Firewall rules page](https://console.cloud.google.com/networking/firewalls/list) and -click **Create firewall rule**. - -The following configuration has previously worked for Netdata running on GCP instances -([see #7786](https://github.com/netdata/netdata/issues/7786)): - -```conf -Name: -Type: Ingress -Targets: -Filters: 0.0.0.0/0 -Protocols/ports: 19999 -Action: allow -Priority: 1000 -``` - -Read GCP's [firewall documentation](https://cloud.google.com/vpc/docs/using-firewalls) for specific instructions on how -to create a new firewall rule. - -#### Amazon Web Services (AWS) / EC2 - -Sign in to the [AWS console](https://console.aws.amazon.com/) and navigate to the EC2 dashboard. Click on the **Security -Groups** link in the navigation, beneath the **Network & Security** heading. Find the Security Group your instance -belongs to, and either right-click on it or click the **Actions** button above to see a dropdown menu with **Edit -inbound rules**. - -Add a new rule with the following options: - -```conf -Type: Custom TCP -Protocol: TCP -Port Range: 19999 -Source: Anywhere -Description: Netdata -``` - -You can also choose **My IP** as the source if you prefer. - -Click **Save** to apply your new inbound firewall rule. - -#### Azure - -Sign in to the [Azure portal](https://portal.azure.com) and open the virtual machine running Netdata. Click on the -**Networking** link beneath the **Settings** header, then click on the **Add inbound security rule** button. - -Add a new rule with the following options: - -```conf -Source: Any -Source port ranges: 19999 -Destination: Any -Destination port ranges: 19999 -Protocol: TCP -Action: Allow -Priority: 310 -Name: Netdata -``` - -Click **Add** to apply your new inbound security rule. - - diff --git a/packaging/installer/methods/freebsd.md b/packaging/installer/methods/freebsd.md index ea7099b3..21670cdc 100644 --- a/packaging/installer/methods/freebsd.md +++ b/packaging/installer/methods/freebsd.md @@ -2,6 +2,9 @@ title: "Install Netdata on FreeBSD" description: "Install Netdata on FreeBSD to monitor the health and performance of bare metal or VMs with thousands of real-time, per-second metrics." custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/installer/methods/freebsd.md +sidebar_label: "FreeBSD" +learn_status: "Published" +learn_rel_path: "Installation/Install on specific environments" --> # Install Netdata on FreeBSD @@ -13,20 +16,41 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/instal > PR](https://github.com/netdata/netdata/edit/master/packaging/installer/methods/freebsd.md) with your recommended > improvements or changes. Thank you! -## Install latest version +## Install dependencies -This is how to install the latest Netdata version on FreeBSD: - -Install required packages (**need root permission**): +This step needs root privileges. ```sh pkg install bash e2fsprogs-libuuid git curl autoconf automake pkgconf pidof liblz4 libuv json-c cmake gmake ``` +Please respond in the affirmative for any relevant prompts during the installation process. + +## Install Netdata + +The simplest method is to use the single line [kickstart script](https://learn.netdata.cloud/docs/agent/packaging/installer/methods/kickstart) + +If you have a Netdata cloud account then clicking on the **Connect Nodes** button will generate the kickstart command you should use. Use the command from the "Linux" tab, it should look something like this: + +```sh +wget -O /tmp/netdata-kickstart.sh https://my-netdata.io/kickstart.sh && sh /tmp/netdata-kickstart.sh --claim-token --claim-url https://app.netdata.cloud +``` +Please respond in the affirmative for any relevant prompts during the installation process. + +Once the installation is completed, you should be able to start monitoring the FreeBSD server using Netdata. + +![image](https://user-images.githubusercontent.com/24860547/202489210-3c5a3346-8f53-4b7b-9832-f9383b34d864.png) + +Netdata can also be installed via [FreeBSD ports](https://www.freshports.org/net-mgmt/netdata). + +## Manual installation + +If you would prefer to manually install Netdata, the following steps can help you do this. + Download Netdata: ```sh -fetch https://github.com/netdata/netdata/releases/download/v1.26.0/netdata-v1.26.0.tar.gz +fetch https://github.com/netdata/netdata/releases/download/v1.36.1/netdata-v1.36.1.tar.gz ``` > ⚠️ Verify the latest version by either navigating to [Netdata's latest @@ -73,35 +97,52 @@ If you have not passed the `--auto-update` or `-u` parameter for the installer t The `netdata-updater.sh` script will update your Agent. ## Optional parameters to alter your installation -| parameters | Description | -|:-----:|-----------| -|`--install-prefix `| Install netdata in `.` 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| -| `--stable-channel` | Use packages from GitHub release pages instead of GCS (nightly updates). This results in less frequent updates| -| `--nightly-channel` | Use most recent nightly updates instead of GitHub releases. This results in more frequent updates| -| `--disable-go` | Disable installation of go.d.plugin| -| `--disable-ebpf` | Disable eBPF Kernel plugin (Default: enabled)| -| `--disable-cloud` | Disable all Netdata Cloud functionality| -| `--require-cloud` | Fail the install if it can't build Netdata Cloud support| -| `--enable-plugin-freeipmi` | Enable the FreeIPMI plugin. Default: enable it when libipmimonitoring is available| -| `--disable-plugin-freeipmi` | Enable the FreeIPMI plugin| -| `--disable-https` | Explicitly disable TLS support| -| `--disable-dbengine` | Explicitly disable DB engine support| -| `--enable-plugin-nfacct` | Enable nfacct plugin. Default: enable it when libmnl and libnetfilter_acct are available| -| `--disable-plugin-nfacct` | Disable nfacct plugin. Default: enable it when libmnl and libnetfilter_acct are available| -| `--enable-plugin-xenstat` | Enable the xenstat plugin. Default: enable it when libxenstat and libyajl are available| -| `--disable-plugin-xenstat` | Disable the xenstat plugin| -| `--disable-exporting-kinesis` | Disable AWS Kinesis exporting connector. Default: enable it when libaws_cpp_sdk_kinesis and libraries (it depends on are available)| -| `--enable-exporting-prometheus-remote-write` | Enable Prometheus remote write exporting connector. Default: enable it when libprotobuf and libsnappy are available| -| `--disable-exporting-prometheus-remote-write` | Disable Prometheus remote write exporting connector. Default: enable it when libprotobuf and libsnappy are available| -| `--enable-exporting-mongodb` | Enable MongoDB exporting connector. Default: enable it when libmongoc is available| -| `--disable-exporting-mongodb` | Disable MongoDB exporting connector| -| `--enable-lto` | Enable Link-Time-Optimization. Default: enabled| -| `--disable-lto` | Disable Link-Time-Optimization. Default: enabled| -| `--disable-x86-sse` | Disable SSE instructions. By default SSE optimizations are enabled| -| `--zlib-is-really-here` or `--libs-are-really-here` | If you get errors about missing zlib or libuuid but you know it is available, you might have a broken pkg-config. Use this option to proceed without checking pkg-config| -|`--disable-telemetry` | Use this flag to opt-out from our anonymous telemetry program. (DISABLE_TELEMETRY=1)| - +The `kickstart.sh` script accepts a number of optional parameters to control how the installation process works: + +- `--non-interactive`: Don’t prompt for anything and assume yes whenever possible, overriding any automatic detection of an interactive run. +- `--interactive`: Act as if running interactively, even if automatic detection indicates a run is non-interactive. +- `--dont-wait`: Synonym for `--non-interactive` +- `--dry-run`: Show what the installer would do, but don’t actually do any of it. +- `--dont-start-it`: Don’t auto-start the daemon after installing. This parameter is not guaranteed to work. +- `--release-channel`: Specify a particular release channel to install from. Currently supported release channels are: + - `nightly`: Installs a nightly build (this is currently the default). + - `stable`: Installs a stable release. + - `default`: Explicitly request whatever the current default is. +- `--nightly-channel`: Synonym for `--release-channel nightly`. +- `--stable-channel`: Synonym for `--release-channel stable`. +- `--auto-update`: Enable automatic updates (this is the default). +- `--no-updates`: Disable automatic updates. +- `--disable-telemetry`: Disable anonymous statistics. +- `--native-only`: Only install if native binary packages are available. +- `--static-only`: Only install if a static build is available. +- `--build-only`: Only install using a local build. +- `--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-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. +- `--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. + +The following options are mutually exclusive and specifiy special operations other than trying to install Netdata normally or update an existing install: + +- `--reinstall`: If there is an existing install, reinstall it instead of trying to update it. If there is not an existing install, install netdata normally. +- `--reinstall-even-if-unsafe`: If there is an existing install, reinstall it instead of trying to update it, even if doing so is known to potentially break things (for example, if we cannot detect what tyep of installation it is). If there is not an existing install, install Netdata normally. +- `--reinstall-clean`: If there is an existing install, uninstall it before trying to install Netdata. Fails if there is no existing install. +- `--uninstall`: Uninstall an existing installation of Netdata. Fails if there is no existing install. +- `--claim-only`: If there is an existing install, only try to claim it without attempting to update it. If there is no existing install, install and claim Netdata normally. +- `--repositories-only`: Only install repository configuration packages instead of doing a full install of Netdata. Automatically sets --native-only. +- `--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): + +- `TMPDIR`: Used to specify where to put temporary files. On most systems, the default we select automatically + should be fine. The user running the script needs to both be able to write files to the temporary directory, + and run files from that location. +- `ROOTCMD`: Used to specify a command to use to run another command with root privileges if needed. By default + we try to use sudo, doas, or pkexec (in that order of preference), but if you need special options for one of + those to work, or have a different tool to do the same thing on your system, you can specify it here. +- `DISABLE_TELEMETRY`: If set to a value other than 0, behave as if `--disable-telemetry` was specified. diff --git a/packaging/installer/methods/freenas.md b/packaging/installer/methods/freenas.md deleted file mode 100644 index a69f1e3f..00000000 --- a/packaging/installer/methods/freenas.md +++ /dev/null @@ -1,24 +0,0 @@ - - -# Install Netdata on FreeNAS - -On FreeNAS-Corral-RELEASE (>=10.0.3 and <11.3), Netdata is pre-installed. - -To use Netdata, the service will need to be enabled and started from the FreeNAS [CLI](https://github.com/freenas/cli). - -To enable the Netdata service: - -```sh -service netdata config set enable=true -``` - -To start the Netdata service: - -```sh -service netdata start -``` - - diff --git a/packaging/installer/methods/gcp.md b/packaging/installer/methods/gcp.md new file mode 100644 index 00000000..0b16b109 --- /dev/null +++ b/packaging/installer/methods/gcp.md @@ -0,0 +1,70 @@ + + +# Install Netdata on GCP + +Netdata is fully compatible with the Google Cloud Platform (GCP). +You can install Netdata on cloud instances to monitor the apps/services running there, or use +multiple instances in a [parent-child streaming](https://github.com/netdata/netdata/blob/master/streaming/README.md) configuration. + +## Recommended installation method + +The best installation method depends on the instance's operating system, distribution, and version. For Linux instances, +we recommend the [`kickstart.sh` automatic installation script](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md). + +If you have issues with Netdata after installation, look to the sections below to find the issue you're experiencing, +followed by the solution for your provider. + +## Post-installation configuration + +### Add a firewall rule to access Netdata's dashboard + +If you cannot access Netdata's dashboard on your cloud instance via `http://HOST:19999`, and instead get an error page +from your browser that says, "This site can't be reached" (Chrome) or "Unable to connect" (Firefox), you may need to +configure your cloud provider's firewall. + +Cloud providers often create network-level firewalls that run separately from the instance itself. Both AWS and Google +Cloud Platform calls them Virtual Private Cloud (VPC) networks. These firewalls can apply even if you've disabled +firewalls on the instance itself. Because you can modify these firewalls only via the cloud provider's web interface, +it's easy to overlook them when trying to configure and access Netdata's dashboard. + +You can often confirm a firewall issue by querying the dashboard while connected to the instance via SSH: `curl +http://localhost:19999/api/v1/info`. If you see JSON output, Netdata is running properly. If you try the same `curl` +command from a remote system, and it fails, it's likely that a firewall is blocking your requests. + +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](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) + + +To add a firewall rule, go to the [Firewall rules page](https://console.cloud.google.com/networking/firewalls/list) and +click **Create firewall rule**. + +The following configuration has previously worked for Netdata running on GCP instances +([see #7786](https://github.com/netdata/netdata/issues/7786)): + +```conf +Name: +Type: Ingress +Targets: +Filters: 0.0.0.0/0 +Protocols/ports: 19999 +Action: allow +Priority: 1000 +``` + +Read GCP's [firewall documentation](https://cloud.google.com/vpc/docs/using-firewalls) for specific instructions on how +to create a new firewall rule. + diff --git a/packaging/installer/methods/kickstart.md b/packaging/installer/methods/kickstart.md index 7c1f60d1..374ac594 100644 --- a/packaging/installer/methods/kickstart.md +++ b/packaging/installer/methods/kickstart.md @@ -2,12 +2,16 @@ 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" -sidebar_label: "Install Netdata with kickstart.sh" +sidebar_label: "One line installer (kickstart.sh)" learn_status: "Published" -learn_topic_type: "Tasks" -learn_rel_path: "Installation" +learn_rel_path: "Installation/Installation methods" +sidebar_position: 10 --> + import { OneLineInstallWget, OneLineInstallCurl } from '@site/src/components/OneLineInstall/' +import { Install, InstallBox } from '@site/src/components/Install/' +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; # Install Netdata with kickstart.sh @@ -18,17 +22,32 @@ This page covers detailed instructions on using and configuring the automatic on 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](https://github.com/netdata/netdata/blob/master/packaging/installer/README.md#nightly-vs-stable-releases). + +> :bulb: Tip +> +> 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: - + + + + + + + -Or, if you have cURL but not wget (such as on macOS): + - + + +> :bookmark_tabs: Note +> +> If you plan to also connect 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 connect the node to. ## What does `kickstart.sh` do? @@ -39,7 +58,7 @@ The `kickstart.sh` script does the following after being downloaded and run usin - Attempts to install Netdata using our [official native binary packages](#native-packages). - If there are no official native binary packages for your system (or installing that way failed), tries to install using a [static build of Netdata](#static-builds) if one is available. -- If no static build is available, installs required dependencies and then attempts to install by +- If no static build is available, installs required dependencies and then attempts to install by [building Netdata locally](#local-builds) (by downloading the sources and building them directly). - Installs `netdata-updater.sh` to `cron.daily`, so your Netdata installation will be updated with new nightly versions, unless you override that with an [optional parameter](#optional-parameters-to-alter-your-installation). @@ -55,30 +74,41 @@ The `kickstart.sh` script accepts a number of optional parameters to control how - `--dry-run`: Show what the installer would do, but don’t actually do any of it. - `--dont-start-it`: Don’t auto-start the daemon after installing. This parameter is not guaranteed to work. - `--release-channel`: Specify a particular release channel to install from. Currently supported release channels are: - - `nightly`: Installs a nightly build (this is currently the default). - - `stable`: Installs a stable release. - - `default`: Explicitly request whatever the current default is. + - `nightly`: Installs a nightly build (this is currently the default). + - `stable`: Installs a stable release. + - `default`: Explicitly request whatever the current default is. - `--nightly-channel`: Synonym for `--release-channel nightly`. - `--stable-channel`: Synonym for `--release-channel stable`. - `--auto-update`: Enable automatic updates (this is the default). - `--no-updates`: Disable automatic updates. - `--disable-telemetry`: Disable anonymous statistics. -- `--repositories-only`: Only install appropriate repository configuration packages (only for native install). - `--native-only`: Only install if native binary packages are available. - `--static-only`: Only install if a static build is available. - `--build-only`: Only install using a local build. -- `--reinstall`: If an existing install is found, reinstall instead of trying to update it in place. -- `--reinstall-even-if-unsafe`: Even try to reinstall if we don't think we can do so safely (implies `--reinstall`). - `--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-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. -- `--uninstall`: Uninstall an existing installation of Netdata. -- `--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. +- `--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](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`. +- `--override-distro`: Override the distro detection logic and assume the system is using a specific Linux distribution and release. Takes a single argument consisting of the values of the `ID`, `VERSION_ID`, and `VERSION_CODENAME` fields from `/etc/os-release` for the desired distribution. + +The following options are mutually exclusive and specifiy special operations other than trying to install Netdata normally or update an existing install: + +- `--reinstall`: If there is an existing install, reinstall it instead of trying to update it. If there is not an existing install, install netdata normally. +- `--reinstall-even-if-unsafe`: If there is an existing install, reinstall it instead of trying to update it, even if doing so is known to potentially break things (for example, if we cannot detect what tyep of installation it is). If there is not an existing install, install Netdata normally. +- `--reinstall-clean`: If there is an existing install, uninstall it before trying to install Netdata. Fails if there is no existing install. +- `--uninstall`: Uninstall an existing installation of Netdata. Fails if there is no existing install. +- `--claim-only`: If there is an existing install, only try to claim it without attempting to update it. If there is no existing install, install and claim Netdata normally. +- `--repositories-only`: Only install repository configuration packages instead of doing a full install of Netdata. Automatically sets --native-only. - `--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 @@ -92,31 +122,6 @@ should not need to use special values for any of these): those to work, or have a different tool to do the same thing on your system, you can specify it here. - `DISABLE_TELEMETRY`: If set to a value other than 0, behave as if `--disable-telemetry` was specified. -### Connect node to Netdata Cloud during 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](https://github.com/netdata/netdata/blob/master/claim/README.md#connect-an-agent-without-root-privileges) section. - -To automatically claim nodes after installation: - -1. Sign in to [Netdata Cloud](https://app.netdata.cloud/sign-in?cloudRoute=/spaces) -2. Go to the [Spaces management area](https://learn.netdata.cloud/docs/cloud/spaces#manage-spaces) -3. Click on **Connect Nodes** -4. Find the `token` and `rooms` strings and specify your nodes: - -- `--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](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 -wget -O /tmp/netdata-kickstart.sh https://my-netdata.io/kickstart.sh && sh /tmp/netdata-kickstart.sh --claim-token TOKEN --claim-rooms ROOM1,ROOM2 -``` ### Native packages @@ -149,7 +154,6 @@ If you want to enforce the usage of a local build (perhaps because you require a which is not supported with native packages or static builds), you can do so by adding `--build-only` to the options you pass to the installer. - ## Verify script integrity To use `md5sum` to verify the integrity of the `kickstart.sh` script you will download using the one-line command above, @@ -160,16 +164,3 @@ run the following: ``` 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](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](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. - - diff --git a/packaging/installer/methods/kubernetes.md b/packaging/installer/methods/kubernetes.md index 142c098b..4dde3f40 100644 --- a/packaging/installer/methods/kubernetes.md +++ b/packaging/installer/methods/kubernetes.md @@ -1,112 +1,122 @@ - - -# Deploy Kubernetes monitoring with Netdata - -This document details how to install Netdata on an existing Kubernetes (k8s) cluster. By following these directions, you -will use Netdata's [Helm chart](https://github.com/netdata/helmchart) to create a Kubernetes monitoring deployment on -your cluster. - -The Helm chart installs one `parent` pod for storing metrics and managing alarm notifications, plus an additional +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Install Netdata on Kubernetes + +This document details how to install Netdata on an existing Kubernetes (k8s) cluster, and connect it to Netdata Cloud. Read our [Kubernetes visualizations](https://github.com/netdata/netdata/blob/master/docs/cloud/visualize/kubernetes.md) documentation, to see what you will get. + +The [Netdata Helm chart](https://github.com/netdata/helmchart/blob/master/charts/netdata/README.md) installs one `parent` pod for storing metrics and managing alarm notifications, plus an additional `child` pod for every node in the cluster, responsible for collecting metrics from the node, Kubernetes control planes, pods/containers, and [supported application-specific metrics](https://github.com/netdata/helmchart#service-discovery-and-supported-services). +### Prerequisites + To deploy Kubernetes monitoring with Netdata, you need: -- A working cluster running Kubernetes v1.9 or newer. -- The [kubectl](https://kubernetes.io/docs/reference/kubectl/overview/) command line tool, within [one minor version +- A working cluster running Kubernetes v1.9 or newer. +- 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 administrative system. -- The [Helm package manager](https://helm.sh/) v3.0.0 or newer on the same administrative system. +- The [Helm package manager](https://helm.sh/) v3.0.0 or newer on the same administrative system. +- A Netdata Cloud account with a Space to connect the cluster to. -## Install the Netdata Helm chart +## Deploy Netdata on your Kubernetes Cluster -We recommend you install the Helm chart using our Helm repository. In the `helm install` command, replace `netdata` with -the release name of your choice. +First, you need to add the Netdata helm repository, and then install Netdata. +The installation 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. -```bash -helm repo add netdata https://netdata.github.io/helmchart/ -helm install netdata netdata/netdata -``` + + -Run `kubectl get services` and `kubectl get pods` to confirm that your cluster now runs a `netdata` service, one -parent pod, and multiple child pods. +

Install Netdata via the helm install command

-You've now installed Netdata on your Kubernetes cluster. Next, it's time to opt-in and enable the powerful Kubernetes -dashboards available in Netdata Cloud. +#### Steps -## Connect your Kubernetes cluster to Netdata Cloud +1. Add the Netdata Helm chart repository by running: -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. + ```bash + helm repo add netdata https://netdata.github.io/helmchart/ + ``` -### New installations +2. To install Netdata using the `helm install` command, run: -First, find the script to run an `helm install` command. You can get it by clicking on your Space's dropdown, then **Manage your Space**. -Click the **Nodes** tab and select the environment your node is running, in this case **kubernetes**, to reveal the script for your Space in Netdata Cloud. You need the `TOKEN` -and `ROOM` values. + ```bash + helm install netdata netdata/netdata + ``` -The script should be similar to: + > ### Note + > + > If you plan to connect the node to Netdata Cloud, you can find the command with the right parameters by clicking the "Add Nodes" button in your Space's Nodes tab. -```bash -helm install netdata netdata/netdata --set parent.claiming.enabled="true" --set parent.claiming.token="TOKEN" --set parent.claiming.rooms="ROOM" --set child.claiming.enabled=true --set child.claiming.token="TOKEN" --set child.claiming.rooms="ROOM" -``` + For more installation options, please read our [Netdata Helm chart for Kubernetes](https://github.com/netdata/helmchart/blob/master/charts/netdata/README.md) reference. -### Existing installations +#### Expected Result -On an existing installation, you will need to override the configuration values by running the `helm upgrade` command and provide a file with the values to override. You can start with creating a file called `override.yml`. +Run `kubectl get services` and `kubectl get pods` to confirm that your cluster now runs a `netdata` service, one parent pod, and multiple child pods. -```bash -touch override.yml -``` +
+ -Paste the following into your `override.yml` file, replacing instances of `ROOM` and `TOKEN` with those from the script from Netdata Cloud. These settings connect your `parent`/`child` nodes to Netdata Cloud and store more -metrics in the nodes' time-series databases. - -```yaml -parent: - claiming: - enabled: true - token: "TOKEN" - rooms: "ROOM" - -child: - claiming: - enabled: true - token: "TOKEN" - rooms: "ROOM" - configs: - netdata: - data: | - [global] - memory mode = ram - history = 3600 - [health] - enabled = no -``` +

Connect an existing Netdata installation to Netdata Cloud

-> ❗ These override settings, along with the Helm chart's defaults, will retain an hour's worth of metrics (`history = -> 3600`, or `3600 seconds`) on each child node. Based on your metrics retention needs, and the resources available on -> your cluster, you may want to increase the `history` setting. +On an existing installation, in order to connect it to Netdata Cloud you will need to override the configuration values by running the `helm upgrade` command and provide a file with the values to override. -Apply these new settings: +#### Steps -```bash -helm upgrade -f override.yml netdata netdata/netdata -``` +1. You can start with creating a file called `override.yml` -The cluster terminates the old pods and creates new ones with the proper persistence and connection configuration. You'll -see your nodes, containers, and pods appear in Netdata Cloud in a few seconds. + ```bash + touch override.yml + ``` + +2. Paste the following into your `override.yml` file. + + ```yaml + parent: + claiming: + enabled: true + token: YOUR_CLAIM_TOKEN + rooms: YOUR_ROOM_ID_A,YOUR_ROOM_ID_B + + child: + claiming: + enabled: true + token: YOUR_CLAIM_TOKEN + rooms: YOUR_ROOM_ID_A,YOUR_ROOM_ID_B + configs: + netdata: + data: | + [global] + memory mode = ram + history = 3600 + [health] + enabled = no + ``` + + > :bookmark_tabs: Note + > + > 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 connect to. + + These settings connect your `parent`/`child` nodes to Netdata Cloud and store more metrics in the nodes' time-series databases. + + > :bookmark_tabs: Info + > + > These override settings, along with the Helm chart's defaults, will retain an hour's worth of metrics (`history = 3600`, or `3600 seconds`) on each child node. Based on your metrics retention needs, and the resources available on your cluster, you may want to increase the `history` setting. + +3. To apply these new settings, run: + + ```bash + helm upgrade -f override.yml netdata netdata/netdata + ``` + +#### Expected Result + +The cluster terminates the old pods and creates new ones with the proper persistence and connection configuration. You'll see your nodes, containers, and pods appear in Netdata Cloud in a few seconds. + +
+
![Netdata's Kubernetes monitoring visualizations](https://user-images.githubusercontent.com/1153921/107801491-5dcb0f00-6d1d-11eb-9ab1-876c39f556e2.png) @@ -119,8 +129,7 @@ in Netdata, in addition to more guides and resources. Read up on the various configuration options in the [Helm chart documentation](https://github.com/netdata/helmchart#configuration) if you need to tweak your Kubernetes monitoring. -Your first option is to create an `override.yml` file, if you haven't created one already for -[connect](#connect-your-kubernetes-cluster-to-netdata-cloud), then apply the new configuration to your cluster with `helm +Your first option is to create an `override.yml` file, if you haven't created one already upon [deploying](#deploy-netdata-on-your-kubernetes-cluster), then apply the new configuration to your cluster with `helm upgrade`. ```bash @@ -140,8 +149,7 @@ Netdata's [service discovery](https://github.com/netdata/agent-service-discovery of the Helm chart installation, finds what services are running in a cluster's containers and automatically collects service-level metrics from them. -Service discovery supports [popular applications](https://github.com/netdata/helmchart#applications) and [Prometheus -endpoints](https://github.com/netdata/helmchart#prometheus-endpoints). +Service discovery supports [popular applications](https://github.com/netdata/helmchart#applications) and [Prometheus endpoints](https://github.com/netdata/helmchart#prometheus-endpoints). If your cluster runs services on non-default ports or uses non-default names, you may need to configure service discovery to start collecting metrics from your services. You have to edit the default ConfigMap that is shipped with @@ -153,8 +161,7 @@ First, copy the default file to your administrative system. curl https://raw.githubusercontent.com/netdata/helmchart/master/charts/netdata/sdconfig/child.yml -o child.yml ``` -Edit the new `child.yml` file according to your needs. See the [Helm chart -configuration](https://github.com/netdata/helmchart#configuration) and the file itself for details. +Edit the new `child.yml` file according to your needs. See the [Helm chart configuration](https://github.com/netdata/helmchart#configuration) and the file itself for details. You can then run `helm upgrade` with the `--set-file` argument to use your configured `child.yml` file instead of the default, changing the path if you copied it elsewhere. @@ -184,18 +191,10 @@ helm upgrade netdata netdata/netdata ## What's next? -[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_](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. +[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. ### Related reference documentation - [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 f80f4c13..11884f7d 100644 --- a/packaging/installer/methods/macos.md +++ b/packaging/installer/methods/macos.md @@ -1,10 +1,9 @@ # Install Netdata on macOS @@ -35,13 +34,11 @@ curl https://my-netdata.io/kickstart.sh > /tmp/netdata-kickstart.sh && sh /tmp/n The Netdata Agent is installed under `/usr/local/netdata`. Dependencies are handled via Homebrew. **Automatically connect to Netdata Cloud during 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. 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://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx#manage-spaces). +area](https://github.com/netdata/netdata/blob/master/docs/cloud/spaces.md). - `--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. @@ -81,7 +78,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://github.com/netdata/netdata/blob/master/docs/cloud/cloud.mdx), 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/quickstart/infrastructure.md), you can omit `cmake`. ```bash brew install ossp-uuid autoconf automake pkg-config libuv lz4 json-c openssl libtool cmake @@ -103,13 +100,3 @@ We don't recommend installing Netdata from source on macOS, as it can be difficu > Your Netdata configuration directory will be at `/usr/local/netdata/`. > Your stock configuration directory will be at `/usr/local/lib/netdata/conf.d/`. > The installer will also install a startup plist to start Netdata when your macOS system boots. - -## What's next? - -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](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 46bc9a33..9910f7f9 100644 --- a/packaging/installer/methods/manual.md +++ b/packaging/installer/methods/manual.md @@ -2,10 +2,10 @@ 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" -sidebar_label: "Install Netdata on Linux from a Git checkout" +sidebar_label: "From a Git checkout" learn_status: "Published" -learn_topic_type: "Tasks" -learn_rel_path: "Installation" +learn_rel_path: "Installation/Installation methods" +sidebar_position: 30 --> # Install Netdata on Linux from a Git checkout @@ -226,16 +226,3 @@ Our current build process unfortunately has some issues when using certain confi If the installation fails with errors like `/bin/ld: externaldeps/libwebsockets/libwebsockets.a(context.c.o): relocation R_X86_64_32 against '.rodata.str1.1' can not be used when making a PIE object; recompile with -fPIC`, and you are trying to build with `clang` on Linux, you will need to build Netdata using GCC to get a fully functional install. 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](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](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. - - diff --git a/packaging/installer/methods/methods.md b/packaging/installer/methods/methods.md new file mode 100644 index 00000000..f9ca2253 --- /dev/null +++ b/packaging/installer/methods/methods.md @@ -0,0 +1,26 @@ + + +# Installation methods + +Netdata can be installed: + +- [As a DEB/RPM package](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/packages.md) +- [As a static binary](https://github.com/netdata/netdata/blob/master/packaging/makeself/README.md) +- [From a git checkout](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/manual.md) +- [As a docker container](https://github.com/netdata/netdata/blob/master/packaging/docker/README.md) + +The [one line installer kickstart.sh](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md) +picks the most appropriate method out of the first three for any system +and is the recommended installation method, if you don't use containers. + +`kickstart.sh` can also be used for +[offline installation](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/offline.md), +suitable for air-gapped systems. diff --git a/packaging/installer/methods/offline.md b/packaging/installer/methods/offline.md index e49f1d2e..f2b6cc41 100644 --- a/packaging/installer/methods/offline.md +++ b/packaging/installer/methods/offline.md @@ -2,15 +2,15 @@ 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" -sidebar_label: "Install Netdata on offline systems" +sidebar_label: "Offline systems" learn_status: "Published" -learn_topic_type: "Tasks" -learn_rel_path: "Installation" +learn_rel_path: "Installation/Installation methods" +sidebar_position: 50 --> # Install Netdata on offline systems -Our kickstart install script provides support for installing the Netdata Agent on systems which do not have a +Our kickstart install script provides support for installing the Netdata Agent on air-gapped systems which do not have a usable internet connection by prefetching all of the required files so that they can be copied to the target system. Currently, we only support using static installs with this method. There are tentative plans to support building locally on offline systems as well, but there is currently no estimate of when this functionality may be implemented. @@ -57,14 +57,3 @@ offline install source directory. It accepts all the [same options as the kickst 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](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](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. diff --git a/packaging/installer/methods/packages.md b/packaging/installer/methods/packages.md index 13551280..1b687046 100644 --- a/packaging/installer/methods/packages.md +++ b/packaging/installer/methods/packages.md @@ -2,13 +2,13 @@ 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." +sidebar_label: "Native DEB/RPM packages" learn_status: "Published" -learn_topic_type: "Tasks" -learn_rel_path: "Installation" +learn_rel_path: "Installation/Installation methods" +sidebar_position: 20 --> -# Installing Netdata using native DEB or RPM packages. +# Install Netdata using native DEB/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 @@ -41,7 +41,7 @@ Under each of those directories is a directory for each supported release of tha 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/ +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 diff --git a/packaging/installer/methods/pfsense.md b/packaging/installer/methods/pfsense.md index e0556629..1a03afb5 100644 --- a/packaging/installer/methods/pfsense.md +++ b/packaging/installer/methods/pfsense.md @@ -2,6 +2,9 @@ title: "Install Netdata on pfSense" description: "Install Netdata on pfSense to monitor the health and performance of firewalls with thousands of real-time, per-second metrics." custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/installer/methods/pfsense.md +sidebar_label: "pfSense" +learn_status: "Published" +learn_rel_path: "Installation/Install on specific environments" --> # Install Netdata on pfSense diff --git a/packaging/installer/methods/source.md b/packaging/installer/methods/source.md index ecf35382..7b6b88d4 100644 --- a/packaging/installer/methods/source.md +++ b/packaging/installer/methods/source.md @@ -4,8 +4,8 @@ description: "Package maintainers and power users may be interested in manually 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" +learn_rel_path: "Installation/Package maintainers" +sidebar_position: 100 --> # Manually build Netdata from source @@ -233,7 +233,7 @@ using glibc or musl. To use one of these: Alternatively, you may wish to build the eBPF code locally yourself. For instructions, please consult [the README file for our kernel-collector -repository](https://github.com/netdata/kernel-collector/blob/master/README.md), +repository](https://github.com/netdata/kernel-collector/#readme), which outlines both the required dependencies, as well as multiple options for building the code. diff --git a/packaging/installer/methods/synology.md b/packaging/installer/methods/synology.md index e3602df5..3910859b 100644 --- a/packaging/installer/methods/synology.md +++ b/packaging/installer/methods/synology.md @@ -2,6 +2,9 @@ title: "Install Netdata on Synology" description: "The Netdata Agent can be installed on AMD64-compatible NAS systems using the 64-bit pre-compiled static binary." custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/installer/methods/synology.md +sidebar_label: "Synology" +learn_status: "Published" +learn_rel_path: "Installation/Install on specific environments" --> # Install Netdata on Synology @@ -14,7 +17,9 @@ custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/instal > with your recommended improvements or changes. Thank you! -The good news is that our [one-line installation script](kickstart.md) works fine if your NAS is one that uses the amd64 architecture. It +The good news is that our +[one-line installation script](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md) +works fine if your NAS is one that uses the amd64 architecture. It will install the content into `/opt/netdata`, making future removal safe and simple. ## Run as netdata user @@ -25,8 +30,8 @@ installations run it as the `netdata` user, you might wish to do the same. This 1. Create a group `netdata` via the Synology group interface. Give it no access to anything. 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](https://github.com/netdata/netdata/blob/master/docs/netdata-security.md#security-design): +3. Change ownership of the following directories, as defined in + [Netdata 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/methods/systems.md b/packaging/installer/methods/systems.md new file mode 100644 index 00000000..e53c4f4a --- /dev/null +++ b/packaging/installer/methods/systems.md @@ -0,0 +1,18 @@ + + +# Install on specific environments + +This category contains specific instructions for some popular environments. +If you have a standard environment that is not yet listed here, just use the +[one line installer kickstart.sh](https://github.com/netdata/netdata/blob/master/packaging/installer/methods/kickstart.md) + +If your environment is somewhat old or unusual, check our +[platform support policy](https://github.com/netdata/netdata/blob/master/packaging/PLATFORM_SUPPORT.md). + diff --git a/packaging/installer/netdata-uninstaller.sh b/packaging/installer/netdata-uninstaller.sh index 2f2e89ff..419002e6 100755 --- a/packaging/installer/netdata-uninstaller.sh +++ b/packaging/installer/netdata-uninstaller.sh @@ -239,15 +239,18 @@ if [ -x "$(command -v apt-get)" ] && [ "${INSTALL_TYPE}" = "binpkg-deb" ]; then if dpkg -s netdata > /dev/null; then echo "Found netdata native installation" if user_input "Do you want to remove netdata? "; then + # shellcheck disable=SC2086 apt-get remove netdata ${FLAG} fi if dpkg -s netdata-repo-edge > /dev/null; then if user_input "Do you want to remove netdata-repo-edge? "; then + # shellcheck disable=SC2086 apt-get remove netdata-repo-edge ${FLAG} fi fi if dpkg -s netdata-repo > /dev/null; then if user_input "Do you want to remove netdata-repo? "; then + # shellcheck disable=SC2086 apt-get remove netdata-repo ${FLAG} fi fi @@ -257,15 +260,18 @@ elif [ -x "$(command -v dnf)" ] && [ "${INSTALL_TYPE}" = "binpkg-rpm" ]; then if rpm -q netdata > /dev/null; then echo "Found netdata native installation." if user_input "Do you want to remove netdata? "; then + # shellcheck disable=SC2086 dnf remove netdata ${FLAG} fi if rpm -q netdata-repo-edge > /dev/null; then if user_input "Do you want to remove netdata-repo-edge? "; then + # shellcheck disable=SC2086 dnf remove netdata-repo-edge ${FLAG} fi fi if rpm -q netdata-repo > /dev/null; then if user_input "Do you want to remove netdata-repo? "; then + # shellcheck disable=SC2086 dnf remove netdata-repo ${FLAG} fi fi @@ -275,15 +281,18 @@ elif [ -x "$(command -v yum)" ] && [ "${INSTALL_TYPE}" = "binpkg-rpm" ]; then if rpm -q netdata > /dev/null; then echo "Found netdata native installation." if user_input "Do you want to remove netdata? "; then + # shellcheck disable=SC2086 yum remove netdata ${FLAG} fi if rpm -q netdata-repo-edge > /dev/null; then if user_input "Do you want to remove netdata-repo-edge? "; then + # shellcheck disable=SC2086 yum remove netdata-repo-edge ${FLAG} fi fi if rpm -q netdata-repo > /dev/null; then if user_input "Do you want to remove netdata-repo? "; then + # shellcheck disable=SC2086 yum remove netdata-repo ${FLAG} fi fi @@ -296,15 +305,18 @@ elif [ -x "$(command -v zypper)" ] && [ "${INSTALL_TYPE}" = "binpkg-rpm" ]; then if zypper search -i netdata > /dev/null; then echo "Found netdata native installation." if user_input "Do you want to remove netdata? "; then + # shellcheck disable=SC2086 zypper ${FLAG} remove netdata fi if zypper search -i netdata-repo-edge > /dev/null; then if user_input "Do you want to remove netdata-repo-edge? "; then + # shellcheck disable=SC2086 zypper ${FLAG} remove netdata-repo-edge fi fi if zypper search -i netdata-repo > /dev/null; then if user_input "Do you want to remove netdata-repo? "; then + # shellcheck disable=SC2086 zypper ${FLAG} remove netdata-repo fi fi diff --git a/packaging/installer/netdata-updater.sh b/packaging/installer/netdata-updater.sh index 130507c1..f8edb6d7 100755 --- a/packaging/installer/netdata-updater.sh +++ b/packaging/installer/netdata-updater.sh @@ -21,7 +21,7 @@ # - TMPDIR (set to a usable temporary directory) # - NETDATA_NIGHTLIES_BASEURL (set the base url for downloading the dist tarball) # -# Copyright: 2018-2020 Netdata Inc. +# Copyright: 2018-2023 Netdata Inc. # SPDX-License-Identifier: GPL-3.0-or-later # # Author: Paweł Krupa @@ -225,9 +225,8 @@ enable_netdata_updater() { ;; "crontab") if [ -d "/etc/cron.d" ]; then - cat > "/etc/cron.d/netdata-updater" <<-EOF - 2 57 * * * root ${NETDATA_PREFIX}/netdata-updater.sh - EOF + [ -f "/etc/cron.d/netdata-updater" ] && rm -f "/etc/cron.d/netdata-updater" + install -p -m 0644 -o 0 -g 0 "${NETDATA_PREFIX}/usr/lib/system/cron/netdata-updater-daily" "/etc/cron.d/netdata-updater-daily" info "Auto-updating has been ENABLED through cron, using a crontab at /etc/cron.d/netdata-updater\n" info "If the update process fails and you have email notifications set up correctly for cron on this system, you should receive an email notification of the failure." @@ -262,6 +261,7 @@ disable_netdata_updater() { if [ -d /etc/cron.d ]; then rm -f /etc/cron.d/netdata-updater + rm -f /etc/cron.d/netdata-updater-daily fi info "Auto-updates have been DISABLED." @@ -341,11 +341,20 @@ create_tmp_directory() { fi } +check_for_curl() { + if [ -z "${curl}" ]; then + curl="$(PATH="${PATH}:/opt/netdata/bin" command -v curl 2>/dev/null && true)" + fi +} + _safe_download() { url="${1}" dest="${2}" - if command -v curl > /dev/null 2>&1; then - curl -sSL --connect-timeout 10 --retry 3 "${url}" > "${dest}" + + check_for_curl + + if [ -n "${curl}" ]; then + "${curl}" -sSL --connect-timeout 10 --retry 3 "${url}" > "${dest}" return $? elif command -v wget > /dev/null 2>&1; then wget -T 15 -O - "${url}" > "${dest}" @@ -375,8 +384,10 @@ get_netdata_latest_tag() { 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 '[^/]*$') + check_for_curl + + if [ -n "${curl}" ]; 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 -S -O /dev/null "${url}" 2>&1 | grep -m 1 Location | grep -o '[^/]*$') else @@ -704,7 +715,7 @@ update_binpkg() { DISTRO="${ID}" - supported_compat_names="debian ubuntu centos fedora opensuse" + supported_compat_names="debian ubuntu centos fedora opensuse ol amzn" if str_in_list "${DISTRO}" "${supported_compat_names}"; then DISTRO_COMPAT_NAME="${DISTRO}" @@ -731,16 +742,7 @@ update_binpkg() { fi case "${DISTRO_COMPAT_NAME}" in - debian) - pm_cmd="apt-get" - repo_subcmd="update" - upgrade_cmd="--only-upgrade install" - pkg_install_opts="${interactive_opts}" - repo_update_opts="${interactive_opts}" - pkg_installed_check="dpkg -s" - INSTALL_TYPE="binpkg-deb" - ;; - ubuntu) + debian|ubuntu) pm_cmd="apt-get" repo_subcmd="update" upgrade_cmd="--only-upgrade install" @@ -749,20 +751,7 @@ update_binpkg() { pkg_installed_check="dpkg -s" INSTALL_TYPE="binpkg-deb" ;; - centos) - if command -v dnf > /dev/null; then - pm_cmd="dnf" - repo_subcmd="makecache" - else - pm_cmd="yum" - fi - upgrade_cmd="upgrade" - pkg_install_opts="${interactive_opts}" - repo_update_opts="${interactive_opts}" - pkg_installed_check="rpm -q" - INSTALL_TYPE="binpkg-rpm" - ;; - fedora) + centos|fedora|ol|amzn) if command -v dnf > /dev/null; then pm_cmd="dnf" repo_subcmd="makecache" @@ -815,11 +804,10 @@ update_binpkg() { # Simple function to encapsulate original updater behavior. update_legacy() { set_tarball_urls "${RELEASE_CHANNEL}" "${IS_NETDATA_STATIC_BINARY}" - if [ "${IS_NETDATA_STATIC_BINARY}" = "yes" ]; then - update_static && exit 0 - else - update_build && exit 0 - fi + case "${IS_NETDATA_STATIC_BINARY}" in + yes) update_static && exit 0 ;; + *) update_build && exit 0 ;; + esac } logfile= @@ -827,8 +815,8 @@ ndtmpdir= trap cleanup EXIT -if [ -t 2 ]; then - # we are running on a terminal +if [ -t 2 ] || [ "${GITHUB_ACTIONS}" ]; then + # we are running on a terminal or under CI # open fd 3 and send it to stderr exec 3>&2 else @@ -890,9 +878,7 @@ while [ -n "${1}" ]; do disable_netdata_updater exit $? ;; - *) - fatal "Unrecognized option ${1}" U001A - ;; + *) fatal "Unrecognized option ${1}" U001A ;; esac shift 1 @@ -934,9 +920,7 @@ case "${INSTALL_TYPE}" in set_tarball_urls "${RELEASE_CHANNEL}" "${IS_NETDATA_STATIC_BINARY}" update_static && exit 0 ;; - *binpkg*) - update_binpkg && exit 0 - ;; + *binpkg*) update_binpkg && exit 0 ;; "") # Fallback case for no `.install-type` file. This just works like the old install type detection. validate_environment_file update_legacy @@ -950,10 +934,6 @@ case "${INSTALL_TYPE}" in fatal "This script does not support updating custom installations without valid environment files." U0012 fi ;; - oci) - fatal "This script does not support updating Netdata inside our official Docker containers, please instead update the container itself." U0013 - ;; - *) - fatal "Unrecognized installation type (${INSTALL_TYPE}), unable to update." U0014 - ;; + oci) fatal "This script does not support updating Netdata inside our official Docker containers, please instead update the container itself." U0013 ;; + *) fatal "Unrecognized installation type (${INSTALL_TYPE}), unable to update." U0014 ;; esac diff --git a/packaging/maintainers/README.md b/packaging/maintainers/README.md index 249436d6..3d759ecf 100644 --- a/packaging/maintainers/README.md +++ b/packaging/maintainers/README.md @@ -1,10 +1,4 @@ - - -# Package Maintainers +# Package maintainers This page tracks the package maintainers for Netdata, for various operating systems and versions. diff --git a/packaging/makeself/README.md b/packaging/makeself/README.md index 055b6c18..9219aefc 100644 --- a/packaging/makeself/README.md +++ b/packaging/makeself/README.md @@ -2,10 +2,25 @@ title: "Netdata static binary build" description: "Users can build the static 64-bit binary package that we ship with every release of the open-source Netdata Agent for debugging or specialize purposes." custom_edit_url: https://github.com/netdata/netdata/edit/master/packaging/makeself/README.md +sidebar_label: "Static binary packages" +learn_status: "Published" +learn_rel_path: "Installation/Installation methods" +sidebar_position: 30 --> # Netdata static binary build +We publish pre-built static builds of Netdata for Linux systems. Currently, these are published for 64-bit x86, ARMv7, +AArch64, and POWER8+ hardware. These static builds are able to operate in a mostly self-contained manner and only +require a POSIX compliant shell and a supported init system. These static builds install under `/opt/netdata`. If +you are on a platform which we provide static builds for but do not provide native packages for, a static build +will be used by default for installation. + +If you want to enforce the usage of a static build and have the installer return a failure if one is not available, +you can do so by adding `--static-only` to the options you pass to the installer. + +## Building a static binary package + To build the static binary 64-bit distribution package, run: ```bash @@ -22,9 +37,7 @@ The program will: Once finished, a file named `netdata-vX.X.X-gGITHASH-x86_64-DATE-TIME.run` will be created in the current directory. This is the Netdata binary package that can be run to install Netdata on any other computer. ---- - -## building binaries with debug info +## Building binaries with debug info To build Netdata binaries with debugging / tracing information in them, use: @@ -35,7 +48,7 @@ cd /path/to/netdata.git These binaries are not optimized (they are a bit slower), they have certain features disables (like log flood protection), other features enables (like `debug flags`) and are not stripped (the binary files are bigger, since they now include source code tracing information). -### debugging Netdata binaries +## Debugging Netdata binaries Once you have installed a binary package with debugging info, you will need to install `valgrind` and run this command to start Netdata: @@ -50,5 +63,4 @@ If Netdata crashes, `valgrind` will print a stack trace of the issue. Open a git To stop Netdata while it runs under `valgrind`, press Control-C on the console. > If you omit the parameter `--undef-value-errors=no` to valgrind, you will get hundreds of errors about conditional jumps that depend on uninitialized values. This is normal. Valgrind has heuristics to prevent it from printing such errors for system libraries, but for the static Netdata binary, all the required libraries are built into Netdata. So, valgrind cannot apply its heuristics and prints them. - - +> diff --git a/packaging/makeself/functions.sh b/packaging/makeself/functions.sh index 31c28d85..c3289c7c 100755 --- a/packaging/makeself/functions.sh +++ b/packaging/makeself/functions.sh @@ -30,7 +30,8 @@ set -euo pipefail fetch() { local dir="${1}" url="${2}" sha256="${3}" key="${4}" - local tar="${dir}.tar.gz" + local tar + tar="$(basename "${2}")" local cache="${NETDATA_SOURCE_PATH}/artifacts/cache/${BUILDARCH}/${key}" if [ -d "${NETDATA_MAKESELF_PATH}/tmp/${dir}" ]; then @@ -58,10 +59,10 @@ fetch() { echo >&2 "expected: ${sha256}, got $(sha256sum "${NETDATA_MAKESELF_PATH}/tmp/${tar}")" exit 1 fi - set -e + set -e cd "${NETDATA_MAKESELF_PATH}/tmp" - run tar -zxpf "${tar}" + run tar -axpf "${tar}" cd - CACHE_HIT=0 diff --git a/packaging/makeself/install-or-update.sh b/packaging/makeself/install-or-update.sh index 52a23fc7..03f7c2c7 100755 --- a/packaging/makeself/install-or-update.sh +++ b/packaging/makeself/install-or-update.sh @@ -179,7 +179,7 @@ dir_should_be_link() { fi run ln -s "${t}" "${d}" - cd "${old}" + cd "${old}" || true } dir_should_be_link . bin sbin @@ -208,9 +208,28 @@ run chown -R ${NETDATA_USER}:${NETDATA_GROUP} /opt/netdata # ----------------------------------------------------------------------------- -progress "changing plugins ownership and setting setuid" +progress "changing plugins ownership and permissions" -for x in apps.plugin freeipmi.plugin ioping cgroup-network ebpf.plugin perf.plugin slabinfo.plugin nfacct.plugin xenstat.plugin; do +if command -v setcap >/dev/null 2>&1; then + run setcap "cap_dac_read_search,cap_sys_ptrace=ep" "usr/libexec/netdata/plugins.d/apps.plugin" + run setcap "cap_dac_read_search=ep" "usr/libexec/netdata/plugins.d/slabinfo.plugin" + + if command -v capsh >/dev/null 2>&1 && capsh --supports=cap_perfmon 2>/dev/null ; then + run setcap "cap_perfmon=ep" "usr/libexec/netdata/plugins.d/perf.plugin" + else + run setcap "cap_sys_admin=ep" "usr/libexec/netdata/plugins.d/perf.plugin" + fi + + run setcap "cap_net_admin,cap_net_raw=eip" "usr/libexec/netdata/plugins.d/go.d.plugin" +else + for x in apps.plugin perf.plugin slabinfo.plugin; do + f="usr/libexec/netdata/plugins.d/${x}" + run chown root:${NETDATA_GROUP} "${f}" + run chmod 4750 "${f}" + done +fi + +for x in freeipmi.plugin ioping cgroup-network ebpf.plugin nfacct.plugin xenstat.plugin; do f="usr/libexec/netdata/plugins.d/${x}" if [ -f "${f}" ]; then @@ -219,10 +238,6 @@ for x in apps.plugin freeipmi.plugin ioping cgroup-network ebpf.plugin perf.plug fi done -if [ -f "usr/libexec/netdata/plugins.d/go.d.plugin" ] && command -v setcap 1>/dev/null 2>&1; then - run setcap "cap_net_admin+epi cap_net_raw=eip" "usr/libexec/netdata/plugins.d/go.d.plugin" -fi - # ----------------------------------------------------------------------------- echo "Configure TLS certificate paths" diff --git a/packaging/makeself/jobs/50-libnetfilter_acct-1.0.3.install.sh b/packaging/makeself/jobs/50-libnetfilter_acct-1.0.3.install.sh new file mode 100755 index 00000000..2b7a761c --- /dev/null +++ b/packaging/makeself/jobs/50-libnetfilter_acct-1.0.3.install.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Install the libnetfilter_acct and it's dependency libmnl +# + +# shellcheck source=packaging/makeself/functions.sh +. "$(dirname "${0}")/../functions.sh" "${@}" || exit 1 + +version="1.0.3" + +# shellcheck disable=SC2015 +[ "${GITHUB_ACTIONS}" = "true" ] && echo "::group::building libnetfilter_acct" || true + +export CFLAGS="-static -I/usr/include/libmnl -pipe" +export LDFLAGS="-static -L/usr/lib -lmnl" +export PKG_CONFIG="pkg-config --static" +export PKG_CONFIG_PATH="/usr/lib/pkgconfig" + +fetch "libnetfilter_acct-${version}" "https://www.netfilter.org/projects/libnetfilter_acct/files/libnetfilter_acct-${version}.tar.bz2" \ + 4250ceef3efe2034f4ac05906c3ee427db31b9b0a2df41b2744f4bf79a959a1a libnetfilter_acct + + +if [ "${CACHE_HIT:-0}" -eq 0 ]; then + run ./configure \ + --prefix="/libnetfilter-acct-static" \ + --exec-prefix="/libnetfilter-acct-static" + + run make clean + run make -j "$(nproc)" +fi + +run make install + +store_cache libnetfilter_acct "${NETDATA_MAKESELF_PATH}/tmp/libnetfilter_acct-${version}" + + +# 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 2c4fb300..2448a0c2 100755 --- a/packaging/makeself/jobs/70-netdata-git.install.sh +++ b/packaging/makeself/jobs/70-netdata-git.install.sh @@ -7,12 +7,12 @@ cd "${NETDATA_SOURCE_PATH}" || exit 1 if [ "${NETDATA_BUILD_WITH_DEBUG}" -eq 0 ]; then - export CFLAGS="-static -O2 -I/openssl-static/include -pipe" + export CFLAGS="-static -O2 -I/openssl-static/include -I/libnetfilter-acct-static/include/libnetfilter_acct -I/usr/include/libmnl -pipe" else - export CFLAGS="-static -O1 -pipe -ggdb -Wall -Wextra -Wformat-signedness -fstack-protector-all -D_FORTIFY_SOURCE=2 -DNETDATA_INTERNAL_CHECKS=1 -I/openssl-static/include" + export CFLAGS="-static -O1 -pipe -ggdb -Wall -Wextra -Wformat-signedness -fstack-protector-all -D_FORTIFY_SOURCE=2 -DNETDATA_INTERNAL_CHECKS=1 -I/openssl-static/include -I/libnetfilter-acct-static/include/libnetfilter_acct -I/usr/include/libmnl" fi -export LDFLAGS="-static -L/openssl-static/lib" +export LDFLAGS="-static -L/openssl-static/lib -L/libnetfilter-acct-static/lib -lnetfilter_acct -L/usr/lib -lmnl" # We export this to 'yes', installer sets this to .environment. # The updater consumes this one, so that it can tell whether it should update a static install or a non-static one @@ -20,7 +20,8 @@ export IS_NETDATA_STATIC_BINARY="yes" # Set eBPF LIBC to "static" to bundle the `-static` variant of the kernel-collector export EBPF_LIBC="static" -export PKG_CONFIG_PATH="/openssl-static/lib/pkgconfig" +export PKG_CONFIG="pkg-config --static" +export PKG_CONFIG_PATH="/openssl-static/lib/pkgconfig:/libnetfilter-acct-static/lib/pkgconfig:/usr/lib/pkgconfig" # Set correct CMake flags for building against non-System OpenSSL # See: https://github.com/warmcat/libwebsockets/blob/master/READMEs/README.build.md diff --git a/packaging/makeself/jobs/99-makeself.install.sh b/packaging/makeself/jobs/99-makeself.install.sh index d29d0580..12bd59b6 100755 --- a/packaging/makeself/jobs/99-makeself.install.sh +++ b/packaging/makeself/jobs/99-makeself.install.sh @@ -29,11 +29,6 @@ run cp \ packaging/makeself/install-or-update.sh \ packaging/installer/functions.sh \ configs.signatures \ - system/netdata-init-d \ - system/netdata-lsb \ - system/netdata-openrc \ - system/netdata.logrotate \ - system/netdata.service \ "${NETDATA_INSTALL_PATH}/system/" # ----------------------------------------------------------------------------- @@ -65,6 +60,14 @@ run rm "${NETDATA_INSTALL_PATH}/sbin" \ "${NETDATA_INSTALL_PATH}/usr/sbin" \ "${NETDATA_INSTALL_PATH}/usr/local" +# ----------------------------------------------------------------------------- +# ensure required directories actually exist + +for dir in var/lib/netdata var/cache/netdata var/log/netdata ; do + run mkdir -p "${NETDATA_INSTALL_PATH}/${dir}" + run touch "${NETDATA_INSTALL_PATH}/${dir}/.keep" +done + # ----------------------------------------------------------------------------- # create the makeself archive diff --git a/packaging/makeself/makeself-header.sh b/packaging/makeself/makeself-header.sh index 0af3219c..47992b2c 100755 --- a/packaging/makeself/makeself-header.sh +++ b/packaging/makeself/makeself-header.sh @@ -1,9 +1,10 @@ -# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: GPL-2.0-or-later # shellcheck shell=sh # shellcheck disable=SC2154,SC2039 cat << EOF > "$archname" #!/bin/sh # This script was generated using Makeself $MS_VERSION +# The license covering this archive and its contents, if any, is wholly independent of the Makeself license (GPL) ORIG_UMASK=\`umask\` if test "$KEEP_UMASK" = n; then @@ -12,22 +13,31 @@ fi CRCsum="$CRCsum" MD5="$MD5sum" +SHA="$SHAsum" +SIGNATURE="$Signature" TMPROOT=\${TMPDIR:=/tmp} -USER_PWD="\$PWD"; export USER_PWD +USER_PWD="\$PWD" +export USER_PWD +ARCHIVE_DIR=\`dirname "\$0"\` +export ARCHIVE_DIR label="$LABEL" script="$SCRIPT" scriptargs="$SCRIPTARGS" +cleanup_script="${CLEANUP_SCRIPT}" licensetxt="$LICENSE" -helpheader='$HELPHEADER' +helpheader="${HELPHEADER}" targetdir="$archdirname" filesizes="$filesizes" +totalsize="$totalsize" keep="$KEEP" nooverwrite="$NOOVERWRITE" quiet="n" accept="n" nodiskspace="n" export_conf="$EXPORT_CONF" +decrypt_cmd="$DECRYPT_CMD" +skip="$SKIP" print_cmd_arg="" if type printf > /dev/null; then @@ -43,6 +53,11 @@ if test -d /usr/xpg4/bin; then export PATH fi +if test -d /usr/sfw/bin; then + PATH=\$PATH:/usr/sfw/bin + export PATH +fi + unset CDPATH MS_Printf() @@ -52,8 +67,14 @@ MS_Printf() MS_PrintLicense() { + PAGER=\${PAGER:=more} if test x"\$licensetxt" != x; then - echo "\$licensetxt" + PAGER_PATH=\`exec <&- 2>&-; which \$PAGER || command -v \$PAGER || type \$PAGER\` + if test -x "\$PAGER_PATH" && test x"\$accept" != xy; then + echo "\$licensetxt" | \$PAGER + else + echo "\$licensetxt" + fi if test x"\$accept" != xy; then while true do @@ -74,7 +95,7 @@ MS_PrintLicense() MS_diskspace() { ( - df -kP "\$1" | tail -1 | awk '{ if (\$4 ~ /%/) {print \$3} else {print \$4} }' + df -k "\$1" | tail -1 | awk '{ if (\$4 ~ /%/) {print \$3} else {print \$4} }' ) } @@ -82,15 +103,20 @@ MS_dd() { blocks=\`expr \$3 / 1024\` bytes=\`expr \$3 % 1024\` - dd if="\$1" ibs=\$2 skip=1 obs=1024 conv=sync 2> /dev/null | \\ - { test \$blocks -gt 0 && dd ibs=1024 obs=1024 count=\$blocks ; \\ - test \$bytes -gt 0 && dd ibs=1 obs=1024 count=\$bytes ; } 2> /dev/null + # Test for ibs, obs and conv feature + if dd if=/dev/zero of=/dev/null count=1 ibs=512 obs=512 conv=sync 2> /dev/null; then + dd if="\$1" ibs=\$2 skip=1 obs=1024 conv=sync 2> /dev/null | \\ + { test \$blocks -gt 0 && dd ibs=1024 obs=1024 count=\$blocks ; \\ + test \$bytes -gt 0 && dd ibs=1 obs=1024 count=\$bytes ; } 2> /dev/null + else + dd if="\$1" bs=\$2 skip=1 2> /dev/null + fi } MS_dd_Progress() { if test x"\$noprogress" = xy; then - MS_dd \$@ + MS_dd "\$@" return \$? fi file="\$1" @@ -104,7 +130,7 @@ MS_dd_Progress() blocks=\`expr \$length / \$bsize\` bytes=\`expr \$length % \$bsize\` ( - dd ibs=\$offset skip=1 2>/dev/null + dd ibs=\$offset skip=1 count=1 2>/dev/null pos=\`expr \$pos \+ \$bsize\` MS_Printf " 0%% " 1>&2 if test \$blocks -gt 0; then @@ -134,34 +160,68 @@ MS_dd_Progress() MS_Help() { cat << EOH >&2 -\${helpheader}Makeself version $MS_VERSION +Makeself version $MS_VERSION 1) Getting help or info about \$0 : \$0 --help Print this message \$0 --info Print embedded info : title, default target directory, embedded script ... \$0 --lsm Print embedded lsm entry (or no LSM) \$0 --list Print the list of files in the archive \$0 --check Checks integrity of the archive + \$0 --verify-sig key Verify signature agains a provided key id 2) Running \$0 : \$0 [options] [--] [additional arguments to embedded script] with following options (in that order) --confirm Ask before running embedded script - --quiet Do not print anything except error messages + --quiet Do not print anything except error messages --accept Accept the license - --noexec Do not run embedded script + --noexec Do not run embedded script (implies --noexec-cleanup) + --noexec-cleanup Do not run embedded cleanup script --keep Do not erase target directory after running - the embedded script + the embedded script --noprogress Do not show the progress during the decompression --nox11 Do not spawn an xterm - --nochown Do not give the extracted files to the current user + --nochown Do not give the target folder to the current user + --chown Give the target folder to the current user recursively --nodiskspace Do not check for available disk space - --target dir Extract directly to a target directory - directory path can be either absolute or relative + --target dir Extract directly to a target directory (absolute or relative) + This directory may undergo recursive chown (see --nochown). --tar arg1 [arg2 ...] Access the contents of the archive through the tar command - -- Following arguments will be passed to the embedded script + --ssl-pass-src src Use the given src as the source of password to decrypt the data + using OpenSSL. See "PASS PHRASE ARGUMENTS" in man openssl. + Default is to prompt the user to enter decryption password + on the current terminal. + --cleanup-args args Arguments to the cleanup script. Wrap in quotes to provide + multiple arguments. + -- Following arguments will be passed to the embedded script\${helpheader} EOH } +MS_Verify_Sig() +{ + GPG_PATH=\`exec <&- 2>&-; which gpg || command -v gpg || type gpg\` + MKTEMP_PATH=\`exec <&- 2>&-; which mktemp || command -v mktemp || type mktemp\` + test -x "\$GPG_PATH" || GPG_PATH=\`exec <&- 2>&-; which gpg || command -v gpg || type gpg\` + test -x "\$MKTEMP_PATH" || MKTEMP_PATH=\`exec <&- 2>&-; which mktemp || command -v mktemp || type mktemp\` + offset=\`head -n "\$skip" "\$1" | wc -c | sed "s/ //g"\` + temp_sig=\`mktemp -t XXXXX\` + echo \$SIGNATURE | base64 --decode > "\$temp_sig" + gpg_output=\`MS_dd "\$1" \$offset \$totalsize | LC_ALL=C "\$GPG_PATH" --verify "\$temp_sig" - 2>&1\` + gpg_res=\$? + rm -f "\$temp_sig" + if test \$gpg_res -eq 0 && test \`echo \$gpg_output | grep -c Good\` -eq 1; then + if test \`echo \$gpg_output | grep -c \$sig_key\` -eq 1; then + test x"\$quiet" = xn && echo "GPG signature is good" >&2 + else + echo "GPG Signature key does not match" >&2 + exit 2 + fi + else + test x"\$quiet" = xn && echo "GPG signature failed to verify" >&2 + exit 2 + fi +} + MS_Check() { OLD_PATH="\$PATH" @@ -169,18 +229,44 @@ MS_Check() MD5_ARG="" MD5_PATH=\`exec <&- 2>&-; which md5sum || command -v md5sum || type md5sum\` test -x "\$MD5_PATH" || MD5_PATH=\`exec <&- 2>&-; which md5 || command -v md5 || type md5\` - test -x "\$MD5_PATH" || MD5_PATH=\`exec <&- 2>&-; which digest || command -v digest || type digest\` + test -x "\$MD5_PATH" || MD5_PATH=\`exec <&- 2>&-; which digest || command -v digest || type digest\` PATH="\$OLD_PATH" + SHA_PATH=\`exec <&- 2>&-; which shasum || command -v shasum || type shasum\` + test -x "\$SHA_PATH" || SHA_PATH=\`exec <&- 2>&-; which sha256sum || command -v sha256sum || type sha256sum\` + if test x"\$quiet" = xn; then MS_Printf "Verifying archive integrity..." fi - offset=\`head -n $SKIP "\$1" | wc -c | tr -d " "\` + offset=\`head -n "\$skip" "\$1" | wc -c | sed "s/ //g"\` + fsize=\`cat "\$1" | wc -c | sed "s/ //g"\` + if test \$totalsize -ne \`expr \$fsize - \$offset\`; then + echo " Unexpected archive size." >&2 + exit 2 + fi verb=\$2 i=1 for s in \$filesizes do crc=\`echo \$CRCsum | cut -d" " -f\$i\` + if test -x "\$SHA_PATH"; then + if test x"\`basename \$SHA_PATH\`" = xshasum; then + SHA_ARG="-a 256" + fi + sha=\`echo \$SHA | cut -d" " -f\$i\` + if test x"\$sha" = x0000000000000000000000000000000000000000000000000000000000000000; then + test x"\$verb" = xy && echo " \$1 does not contain an embedded SHA256 checksum." >&2 + else + shasum=\`MS_dd_Progress "\$1" \$offset \$s | eval "\$SHA_PATH \$SHA_ARG" | cut -b-64\`; + if test x"\$shasum" != x"\$sha"; then + echo "Error in SHA256 checksums: \$shasum is different from \$sha" >&2 + exit 2 + elif test x"\$quiet" = xn; then + MS_Printf " SHA256 checksums are OK." >&2 + fi + crc="0000000000"; + fi + fi if test -x "\$MD5_PATH"; then if test x"\`basename \$MD5_PATH\`" = xdigest; then MD5_ARG="-a md5" @@ -193,8 +279,8 @@ MS_Check() if test x"\$md5sum" != x"\$md5"; then echo "Error in MD5 checksums: \$md5sum is different from \$md5" >&2 exit 2 - else - test x"\$verb" = xy && MS_Printf " MD5 checksums are OK." >&2 + elif test x"\$quiet" = xn; then + MS_Printf " MD5 checksums are OK." >&2 fi crc="0000000000"; verb=n fi @@ -203,11 +289,11 @@ MS_Check() test x"\$verb" = xy && echo " \$1 does not contain a CRC checksum." >&2 else sum1=\`MS_dd_Progress "\$1" \$offset \$s | CMD_ENV=xpg4 cksum | awk '{print \$1}'\` - if test x"\$sum1" = x"\$crc"; then - test x"\$verb" = xy && MS_Printf " CRC checksums are OK." >&2 - else + if test x"\$sum1" != x"\$crc"; then echo "Error in checksums: \$sum1 is different from \$crc" >&2 - exit 2; + exit 2 + elif test x"\$quiet" = xn; then + MS_Printf " CRC checksums are OK." >&2 fi fi i=\`expr \$i + 1\` @@ -218,22 +304,55 @@ MS_Check() fi } +MS_Decompress() +{ + if test x"\$decrypt_cmd" != x""; then + { eval "\$decrypt_cmd" || echo " ... Decryption failed." >&2; } | eval "$GUNZIP_CMD" + else + eval "$GUNZIP_CMD" + fi + + if test \$? -ne 0; then + echo " ... Decompression failed." >&2 + fi +} + UnTAR() { if test x"\$quiet" = xn; then - tar \$1vf - $UNTAR_EXTRA 2>&1 || { echo " ... Extraction failed." > /dev/tty; kill -15 \$$; } + tar \$1vf - $UNTAR_EXTRA 2>&1 || { echo " ... Extraction failed." >&2; kill -15 \$$; } else - tar \$1f - $UNTAR_EXTRA 2>&1 || { echo Extraction failed. > /dev/tty; kill -15 \$$; } + tar \$1f - $UNTAR_EXTRA 2>&1 || { echo Extraction failed. >&2; kill -15 \$$; } + fi +} + +MS_exec_cleanup() { + if test x"\$cleanup" = xy && test x"\$cleanup_script" != x""; then + cleanup=n + cd "\$tmpdir" + eval "\"\$cleanup_script\" \$scriptargs \$cleanupargs" fi } +MS_cleanup() +{ + echo 'Signal caught, cleaning up' >&2 + MS_exec_cleanup + cd "\$TMPROOT" + rm -rf "\$tmpdir" + eval \$finish; exit 15 +} + finish=true xterm_loop= noprogress=$NOPROGRESS nox11=$NOX11 copy=$COPY -ownership=y +ownership=$OWNERSHIP verbose=n +cleanup=y +cleanupargs= +sig_key= initargs="\$@" @@ -258,8 +377,11 @@ do echo Target directory: "\$targetdir" echo Uncompressed size: $USIZE KB echo Compression: $COMPRESS + if test x"$ENCRYPT" != x""; then + echo Encryption: $ENCRYPT + fi echo Date of packaging: $DATE - echo Built with Makeself version $MS_VERSION on $OSTYPE + echo Built with Makeself version $MS_VERSION echo Build command was: "$MS_COMMAND" if test x"\$script" != x; then echo Script run after extraction: @@ -282,15 +404,17 @@ do echo LABEL=\"\$label\" echo SCRIPT=\"\$script\" echo SCRIPTARGS=\"\$scriptargs\" + echo CLEANUPSCRIPT=\"\$cleanup_script\" echo archdirname=\"$archdirname\" echo KEEP=$KEEP echo NOOVERWRITE=$NOOVERWRITE echo COMPRESS=$COMPRESS echo filesizes=\"\$filesizes\" + echo totalsize=\"\$totalsize\" echo CRCsum=\"\$CRCsum\" - echo MD5sum=\"\$MD5\" - echo OLDUSIZE=$USIZE - echo OLDSKIP=$((SKIP + 1)) + echo MD5sum=\"\$MD5sum\" + echo SHAsum=\"\$SHAsum\" + echo SKIP=\"\$skip\" exit 0 ;; --lsm) @@ -303,21 +427,21 @@ EOLSM ;; --list) echo Target directory: \$targetdir - offset=\`head -n $SKIP "\$0" | wc -c | tr -d " "\` + offset=\`head -n "\$skip" "\$0" | wc -c | sed "s/ //g"\` for s in \$filesizes do - MS_dd "\$0" \$offset \$s | eval "$GUNZIP_CMD" | UnTAR t + MS_dd "\$0" \$offset \$s | MS_Decompress | UnTAR t offset=\`expr \$offset + \$s\` done exit 0 ;; --tar) - offset=\`head -n $SKIP "\$0" | wc -c | tr -d " "\` + offset=\`head -n "\$skip" "\$0" | wc -c | sed "s/ //g"\` arg1="\$2" - if ! shift 2; then MS_Help; exit 1; fi + shift 2 || { MS_Help; exit 1; } for s in \$filesizes do - MS_dd "\$0" \$offset \$s | eval "$GUNZIP_CMD" | tar "\$arg1" - "\$@" + MS_dd "\$0" \$offset \$s | MS_Decompress | tar "\$arg1" - "\$@" offset=\`expr \$offset + \$s\` done exit 0 @@ -326,22 +450,32 @@ EOLSM MS_Check "\$0" y exit 0 ;; + --verify-sig) + sig_key="\$2" + shift 2 || { MS_Help; exit 1; } + MS_Verify_Sig "\$0" + ;; --confirm) verbose=y shift ;; --noexec) script="" + cleanup_script="" shift ;; + --noexec-cleanup) + cleanup_script="" + shift + ;; --keep) keep=y shift ;; --target) keep=y - targetdir=\${2:-.} - if ! shift 2; then MS_Help; exit 1; fi + targetdir="\${2:-.}" + shift 2 || { MS_Help; exit 1; } ;; --noprogress) noprogress=y @@ -355,6 +489,10 @@ EOLSM ownership=n shift ;; + --chown) + ownership=y + shift + ;; --nodiskspace) nodiskspace=y shift @@ -370,6 +508,18 @@ EOLSM copy=phase2 shift ;; + --ssl-pass-src) + if test x"$ENCRYPT" != x"openssl"; then + echo "Invalid option --ssl-pass-src: \$0 was not encrypted with OpenSSL!" >&2 + exit 1 + fi + decrypt_cmd="\$decrypt_cmd -pass \$2" + shift 2 || { MS_Help; exit 1; } + ;; + --cleanup-args) + cleanupargs="\$2" + shift 2 || { MS_Help; exit 1; } + ;; --) shift break ;; @@ -390,7 +540,7 @@ fi if test x"$NEED_ROOT" = xy -a \`id -u\` -ne 0; then echo "Administrative privileges required for this archive (use su or sudo)" >&2 - exit 1 + exit 1 fi if test x"\$copy" \!= xphase2; then @@ -399,7 +549,7 @@ fi case "\$copy" in copy) - tmpdir=\$TMPROOT/makeself.\$RANDOM.\`date +"%y%m%d%H%M%S"\`.\$\$ + tmpdir="\$TMPROOT"/makeself.\$RANDOM.\`date +"%y%m%d%H%M%S"\`.\$\$ mkdir "\$tmpdir" || { echo "Could not create temporary directory \$tmpdir" >&2 exit 1 @@ -409,6 +559,7 @@ copy) cp "\$0" "\$SCRIPT_COPY" chmod +x "\$SCRIPT_COPY" cd "\$TMPROOT" + export USER_PWD="\$tmpdir" exec "\$SCRIPT_COPY" --phase2 -- \$initargs ;; phase2) @@ -417,7 +568,7 @@ phase2) esac if test x"\$nox11" = xn; then - if tty -s; then # Do we have a terminal? + if test -t 1; then # Do we have a terminal on stdout? : else if test x"\$DISPLAY" != x -a x"\$xterm_loop" = x; then # No, but do we have X? @@ -429,11 +580,11 @@ if test x"\$nox11" = xn; then break fi done - chmod a+x \$0 || echo Please add execution rights on \$0 + chmod a+x \$0 || echo Please add execution rights on \$0 >&2 if test \`echo "\$0" | cut -c1\` = "/"; then # Spawn a terminal! - exec \$XTERM -title "\$label" -e "\$0" --xwin "\$initargs" + exec \$XTERM -e "\$0 --xwin \$initargs" else - exec \$XTERM -title "\$label" -e "./\$0" --xwin "\$initargs" + exec \$XTERM -e "./\$0 --xwin \$initargs" fi fi fi @@ -457,7 +608,7 @@ else tmpdir="\$TMPROOT/selfgz\$\$\$RANDOM" dashp="" fi - mkdir \$dashp \$tmpdir || { + mkdir \$dashp "\$tmpdir" || { echo 'Cannot create target directory' \$tmpdir >&2 echo 'You should try option --target dir' >&2 eval \$finish @@ -469,7 +620,7 @@ location="\`pwd\`" if test x"\$SETUP_NOCHECK" != x1; then MS_Check "\$0" fi -offset=\`head -n $SKIP "\$0" | wc -c | tr -d " "\` +offset=\`head -n "\$skip" "\$0" | wc -c | sed "s/ //g"\` if test x"\$verbose" = xy; then MS_Printf "About to extract $USIZE KB in \$tmpdir ... Proceed ? [Y/n] " @@ -480,15 +631,21 @@ if test x"\$verbose" = xy; then fi if test x"\$quiet" = xn; then - MS_Printf "Uncompressing \$label" + # Decrypting with openssl will ask for password, + # the prompt needs to start on new line + if test x"$ENCRYPT" = x"openssl"; then + echo "Decrypting and uncompressing \$label..." + else + MS_Printf "Uncompressing \$label" + fi fi res=3 if test x"\$keep" = xn; then - trap 'echo Signal caught, cleaning up >&2; cd \$TMPROOT; /bin/rm -rf \$tmpdir; eval \$finish; exit 15' 1 2 3 15 + trap MS_cleanup 1 2 3 15 fi if test x"\$nodiskspace" = xn; then - leftspace=\`MS_diskspace \$tmpdir\` + leftspace=\`MS_diskspace "\$tmpdir"\` if test -n "\$leftspace"; then if test "\$leftspace" -lt $USIZE; then echo @@ -504,7 +661,7 @@ fi for s in \$filesizes do - if MS_dd_Progress "\$0" \$offset \$s | eval "$GUNZIP_CMD" | ( cd "\$tmpdir"; umask \$ORIG_UMASK ; UnTAR xp ) 1>/dev/null; then + if MS_dd_Progress "\$0" \$offset \$s | MS_Decompress | ( cd "\$tmpdir"; umask \$ORIG_UMASK ; UnTAR xp ) 1>/dev/null; then if test x"\$ownership" = xy; then (cd "\$tmpdir"; chown -R \`id -u\` .; chgrp -R \`id -g\` .) fi @@ -531,6 +688,7 @@ if test x"\$script" != x; then MS_KEEP="\$KEEP" MS_NOOVERWRITE="\$NOOVERWRITE" MS_COMPRESS="\$COMPRESS" + MS_CLEANUP="\$cleanup" export MS_BUNDLE MS_LABEL MS_SCRIPT MS_SCRIPTARGS export MS_ARCHDIRNAME MS_KEEP MS_NOOVERWRITE MS_COMPRESS fi @@ -548,9 +706,12 @@ if test x"\$script" != x; then test x"\$verbose" = xy && echo "The program '\$script' returned an error code (\$res)" >&2 fi fi + +MS_exec_cleanup + if test x"\$keep" = xn; then - cd \$TMPROOT - /bin/rm -rf \$tmpdir + cd "\$TMPROOT" + rm -rf "\$tmpdir" fi eval \$finish; exit \$res EOF diff --git a/packaging/makeself/makeself-license.txt b/packaging/makeself/makeself-license.txt index b844e9a9..684f16ae 100644 --- a/packaging/makeself/makeself-license.txt +++ b/packaging/makeself/makeself-license.txt @@ -4,7 +4,7 @@ | '-' '-' '-' '-' real-time performance monitoring, done right! +----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+---> - (C) Copyright 2017, Costa Tsaousis + (C) Copyright 2017-2023, Costa Tsaousis All rights reserved Released under GPL v3+ @@ -24,13 +24,10 @@ - /etc/logrotate.d/netdata # SYSTEM INIT - This file will be installed if this system runs with systemd: - - - /lib/systemd/system/netdata.service - - or, for older CentOS, Debian/Ubuntu or OpenRC Gentoo: - - - /etc/init.d/netdata will be created + If a supported init system is detected, appropriate configuration will be + installed to allow Netdata to run as a system service. We currently support + systemd, OpenRC, LSB init scripts, and traditional init.d setups, as well as + having experimental support for runit. This package can also update a netdata installation that has been diff --git a/packaging/makeself/makeself.lsm b/packaging/makeself/makeself.lsm index 6bd4703d..7d635646 100644 --- a/packaging/makeself/makeself.lsm +++ b/packaging/makeself/makeself.lsm @@ -10,7 +10,7 @@ Description: netdata is a system for distributed real-time performance and he Keywords: real-time performance and health monitoring Author: Costa Tsaousis (costa@tsaousis.gr) Maintained-by: Costa Tsaousis (costa@tsaousis.gr) -Original-site: https://my-netdata.io/ +Original-site: https://netdata.cloud/ Platform: Unix Copying-policy: GPL End diff --git a/packaging/makeself/makeself.sh b/packaging/makeself/makeself.sh index 1581f499..3a975068 100755 --- a/packaging/makeself/makeself.sh +++ b/packaging/makeself/makeself.sh @@ -1,85 +1,31 @@ #!/bin/sh -# SPDX-License-Identifier: GPL-3.0-or-later # -# Makeself version 2.3.x +# SPDX-License-Identifier: GPL-2.0-or-later +# +# shellcheck disable=SC2209,SC2006,SC2016,SC2034,SC2086,SC2003,SC2268,SC1090,SC2002,SC2046 +# +# Makeself version 2.5.x # by Stephane Peter # # Utility to create self-extracting tar.gz archives. # The resulting archive is a file holding the tar.gz archive with # a small Shell script stub that uncompresses the archive to a temporary -# directory and then executes a given script from within that directory. -# -# Makeself home page: http://makeself.io/ +# directory and then executes a given script from withing that directory. # -# Version 2.0 is a rewrite of version 1.0 to make the code easier to read and maintain. +# Makeself home page: https://makeself.io/ - Version history available on GitHub # -# Version history : -# - 1.0 : Initial public release -# - 1.1 : The archive can be passed parameters that will be passed on to -# the embedded script, thanks to John C. Quillan -# - 1.2 : Package distribution, bzip2 compression, more command line options, -# support for non-temporary archives. Ideas thanks to Francois Petitjean -# - 1.3 : More patches from Bjarni R. Einarsson and Francois Petitjean: -# Support for no compression (--nocomp), script is no longer mandatory, -# automatic launch in an xterm, optional verbose output, and -target -# archive option to indicate where to extract the files. -# - 1.4 : Improved UNIX compatibility (Francois Petitjean) -# Automatic integrity checking, support of LSM files (Francois Petitjean) -# - 1.5 : Many bugfixes. Optionally disable xterm spawning. -# - 1.5.1 : More bugfixes, added archive options -list and -check. -# - 1.5.2 : Cosmetic changes to inform the user of what's going on with big -# archives (Quake III demo) -# - 1.5.3 : Check for validity of the DISPLAY variable before launching an xterm. -# More verbosity in xterms and check for embedded command's return value. -# Bugfix for Debian 2.0 systems that have a different "print" command. -# - 1.5.4 : Many bugfixes. Print out a message if the extraction failed. -# - 1.5.5 : More bugfixes. Added support for SETUP_NOCHECK environment variable to -# bypass checksum verification of archives. -# - 1.6.0 : Compute MD5 checksums with the md5sum command (patch from Ryan Gordon) -# - 2.0 : Brand new rewrite, cleaner architecture, separated header and UNIX ports. -# - 2.0.1 : Added --copy -# - 2.1.0 : Allow multiple tarballs to be stored in one archive, and incremental updates. -# Added --nochown for archives -# Stopped doing redundant checksums when not necessary -# - 2.1.1 : Work around insane behavior from certain Linux distros with no 'uncompress' command -# Cleaned up the code to handle error codes from compress. Simplified the extraction code. -# - 2.1.2 : Some bug fixes. Use head -n to avoid problems. -# - 2.1.3 : Bug fixes with command line when spawning terminals. -# Added --tar for archives, allowing to give arbitrary arguments to tar on the contents of the archive. -# Added --noexec to prevent execution of embedded scripts. -# Added --nomd5 and --nocrc to avoid creating checksums in archives. -# Added command used to create the archive in --info output. -# Run the embedded script through eval. -# - 2.1.4 : Fixed --info output. -# Generate random directory name when extracting files to . to avoid problems. (Jason Trent) -# Better handling of errors with wrong permissions for the directory containing the files. (Jason Trent) -# Avoid some race conditions (Ludwig Nussel) -# Unset the $CDPATH variable to avoid problems if it is set. (Debian) -# Better handling of dot files in the archive directory. -# - 2.1.5 : Made the md5sum detection consistent with the header code. -# Check for the presence of the archive directory -# Added --encrypt for symmetric encryption through gpg (Eric Windisch) -# Added support for the digest command on Solaris 10 for MD5 checksums -# Check for available disk space before extracting to the target directory (Andreas Schweitzer) -# Allow extraction to run asynchronously (patch by Peter Hatch) -# Use file descriptors internally to avoid error messages (patch by Kay Tiong Khoo) -# - 2.1.6 : Replaced one dot per file progress with a realtime progress percentage and a spinning cursor (Guy Baconniere) -# Added --noprogress to prevent showing the progress during the decompression (Guy Baconniere) -# Added --target dir to allow extracting directly to a target directory (Guy Baconniere) -# - 2.2.0 : Many bugfixes, updates and contributions from users. Check out the project page on Github for the details. -# - 2.3.0 : Option to specify packaging date to enable byte-for-byte reproducibility. (Marc Pawlowsky) -# -# (C) 1998-2017 by Stephane Peter +# (C) 1998-2023 by Stephane Peter # # This software is released under the terms of the GNU GPL version 2 and above # Please read the license at http://www.gnu.org/copyleft/gpl.html +# Self-extracting archives created with this script are explictly NOT released under the term of the GPL # -MS_VERSION=2.3.1 +MS_VERSION=2.5.0 MS_COMMAND="$0" unset CDPATH -for f in "${1+"$@"}"; do +for f in ${1+"$@"}; do MS_COMMAND="$MS_COMMAND \\\\ \\\"$f\\\"" done @@ -94,30 +40,46 @@ fi MS_Usage() { - echo "Usage: $0 [params] archive_dir file_name label startup_script [args]" - echo "params can be one or more of the following :" + echo "Usage: $0 [args] archive_dir file_name label startup_script [script_args]" + echo "args can be one or more of the following :" echo " --version | -v : Print out Makeself version number and exit" echo " --help | -h : Print out this help message" echo " --tar-quietly : Suppress verbose output from the tar command" echo " --quiet | -q : Do not print any messages other than errors." echo " --gzip : Compress using gzip (default if detected)" echo " --pigz : Compress with pigz" + echo " --zstd : Compress with zstd" echo " --bzip2 : Compress using bzip2 instead of gzip" echo " --pbzip2 : Compress using pbzip2 instead of gzip" + echo " --bzip3 : Compress using bzip3 instead of gzip" echo " --xz : Compress using xz instead of gzip" echo " --lzo : Compress using lzop instead of gzip" echo " --lz4 : Compress using lz4 instead of gzip" echo " --compress : Compress using the UNIX 'compress' command" - echo " --complevel lvl : Compression level for gzip pigz xz lzo lz4 bzip2 and pbzip2 (default 9)" + echo " --complevel lvl : Compression level for gzip pigz zstd xz lzo lz4 bzip2 pbzip2 and bzip3 (default 9)" + echo " --threads thds : Number of threads to be used by compressors that support parallelization." + echo " Omit to use compressor's default. Most useful (and required) for opting" + echo " into xz's threading, usually with '--threads=0' for all available cores." + echo " pbzip2 and pigz are parallel by default, and setting this value allows" + echo " limiting the number of threads they use." echo " --base64 : Instead of compressing, encode the data using base64" echo " --gpg-encrypt : Instead of compressing, encrypt the data using GPG" echo " --gpg-asymmetric-encrypt-sign" echo " : Instead of compressing, asymmetrically encrypt and sign the data using GPG" echo " --gpg-extra opt : Append more options to the gpg command line" echo " --ssl-encrypt : Instead of compressing, encrypt the data using OpenSSL" + echo " --ssl-passwd pass : Use the given password to encrypt the data using OpenSSL" + echo " --ssl-pass-src src : Use the given src as the source of password to encrypt the data" + echo " using OpenSSL. See \"PASS PHRASE ARGUMENTS\" in man openssl." + echo " If this option is not supplied, the user will be asked to enter" + echo " encryption password on the current terminal." + echo " --ssl-no-md : Do not use \"-md\" option not supported by older OpenSSL." + echo " --nochown : Do not give the target folder to the current user (default)" + echo " --chown : Give the target folder to the current user recursively" echo " --nocomp : Do not compress the data" - echo " --notemp : The archive will create archive_dir in the" - echo " current directory and uncompress in ./archive_dir" + echo " --notemp : The archive will create archive_dir in the current directory" + echo " and uncompress in ./archive_dir" + echo " Note: persistent archives do not strictly require a startup_script" echo " --needroot : Check that the root user is extracting the archive before proceeding" echo " --copy : Upon extraction, the archive will first copy itself to" echo " a temporary directory" @@ -125,19 +87,23 @@ MS_Usage() echo " The label and startup scripts will then be ignored" echo " --target dir : Extract directly to a target directory" echo " directory path can be either absolute or relative" - echo " --nooverwrite : Do not extract the archive if the specified target directory exists" echo " --current : Files will be extracted to the current directory" - echo " Both --current and --target imply --notemp" + echo " Both --current and --target imply --notemp, and do not require a startup_script" + echo " --nooverwrite : Do not extract the archive if the specified target directory exists" + echo " --tar-format opt : Specify a tar archive format (default is ustar)" echo " --tar-extra opt : Append more options to the tar command line" echo " --untar-extra opt : Append more options to the during the extraction of the tar archive" echo " --nomd5 : Don't calculate an MD5 for archive" echo " --nocrc : Don't calculate a CRC for archive" + echo " --sha256 : Compute a SHA256 checksum for the archive" echo " --header file : Specify location of the header script" + echo " --cleanup file : Specify a cleanup script that executes on interrupt and when finished successfully." echo " --follow : Follow the symlinks in the archive" echo " --noprogress : Do not show the progress during the decompression" echo " --nox11 : Disable automatic spawn of a xterm" echo " --nowait : Do not wait for user input after executing embedded" echo " program from an xterm" + echo " --sign passphrase : Signature private key to sign the package with" echo " --lsm file : LSM file describing the package" echo " --license file : Append a license file" echo " --help-header file : Add a header to the archive's --help output" @@ -154,12 +120,21 @@ MS_Usage() } # Default settings -if type gzip 2>&1 > /dev/null; then +if type gzip >/dev/null 2>&1; then COMPRESS=gzip +elif type compress >/dev/null 2>&1; then + COMPRESS=compress else - COMPRESS=Unix + echo "ERROR: missing commands: gzip, compress" >&2 + MS_Usage fi +ENCRYPT=n +PASSWD="" +PASSWD_SRC="" +OPENSSL_NO_MD=n COMPRESS_LEVEL=9 +DEFAULT_THREADS=123456 # Sentinel value +THREADS=$DEFAULT_THREADS KEEP=n CURRENT=n NOX11=n @@ -171,15 +146,21 @@ QUIET=n NOPROGRESS=n COPY=none NEED_ROOT=n -TAR_ARGS=cvf +TAR_ARGS=rvf +TAR_FORMAT=ustar TAR_EXTRA="" GPG_EXTRA="" DU_ARGS=-ks HEADER=`dirname "$0"`/makeself-header.sh +SIGNATURE="" TARGETDIR="" NOOVERWRITE=n DATE=`LC_ALL=C date` EXPORT_CONF=n +SHA256=n +OWNERSHIP=n +SIGN=n +GPG_PASSPHRASE="" # LSM file stuff LSM_CMD="echo No LSM. >> \"\$archname\"" @@ -195,6 +176,10 @@ do COMPRESS=pbzip2 shift ;; + --bzip3) + COMPRESS=bzip3 + shift + ;; --bzip2) COMPRESS=bzip2 shift @@ -204,9 +189,13 @@ do shift ;; --pigz) - COMPRESS=pigz - shift - ;; + COMPRESS=pigz + shift + ;; + --zstd) + COMPRESS=zstd + shift + ;; --xz) COMPRESS=xz shift @@ -220,7 +209,7 @@ do shift ;; --compress) - COMPRESS=Unix + COMPRESS=compress shift ;; --base64) @@ -232,24 +221,48 @@ do shift ;; --gpg-asymmetric-encrypt-sign) - COMPRESS=gpg-asymmetric - shift - ;; + COMPRESS=gpg-asymmetric + shift + ;; --gpg-extra) - GPG_EXTRA="$2" - if ! shift 2; then MS_Help; exit 1; fi - ;; + GPG_EXTRA="$2" + shift 2 || { MS_Usage; exit 1; } + ;; --ssl-encrypt) - COMPRESS=openssl - shift - ;; + ENCRYPT=openssl + shift + ;; + --ssl-passwd) + PASSWD=$2 + shift 2 || { MS_Usage; exit 1; } + ;; + --ssl-pass-src) + PASSWD_SRC=$2 + shift 2 || { MS_Usage; exit 1; } + ;; + --ssl-no-md) + OPENSSL_NO_MD=y + shift + ;; --nocomp) COMPRESS=none shift ;; --complevel) COMPRESS_LEVEL="$2" - if ! shift 2; then MS_Help; exit 1; fi + shift 2 || { MS_Usage; exit 1; } + ;; + --threads) + THREADS="$2" + shift 2 || { MS_Usage; exit 1; } + ;; + --nochown) + OWNERSHIP=n + shift + ;; + --chown) + OWNERSHIP=y + shift ;; --notemp) KEEP=y @@ -264,19 +277,28 @@ do KEEP=y shift ;; + --tar-format) + TAR_FORMAT="$2" + shift 2 || { MS_Usage; exit 1; } + ;; --tar-extra) - TAR_EXTRA="$2" - if ! shift 2; then MS_Help; exit 1; fi - ;; + TAR_EXTRA="$2" + shift 2 || { MS_Usage; exit 1; } + ;; --untar-extra) UNTAR_EXTRA="$2" - if ! shift 2; then MS_Help; exit 1; fi + shift 2 || { MS_Usage; exit 1; } ;; --target) - TARGETDIR="$2" - KEEP=y - if ! shift 2; then MS_Help; exit 1; fi - ;; + TARGETDIR="$2" + KEEP=y + shift 2 || { MS_Usage; exit 1; } + ;; + --sign) + SIGN=y + GPG_PASSPHRASE="$2" + shift 2 || { MS_Usage; exit 1; } + ;; --nooverwrite) NOOVERWRITE=y shift @@ -287,14 +309,19 @@ do ;; --header) HEADER="$2" - if ! shift 2; then MS_Help; exit 1; fi + shift 2 || { MS_Usage; exit 1; } ;; + --cleanup) + CLEANUP_SCRIPT="$2" + shift 2 || { MS_Usage; exit 1; } + ;; --license) - LICENSE=`cat $2` - if ! shift 2; then MS_Help; exit 1; fi + # We need to escape all characters having a special meaning in double quotes + LICENSE=$(sed 's/\\/\\\\/g; s/"/\\\"/g; s/`/\\\`/g; s/\$/\\\$/g' "$2") + shift 2 || { MS_Usage; exit 1; } ;; --follow) - TAR_ARGS=cvhf + TAR_ARGS=rvhf DU_ARGS=-ksL shift ;; @@ -314,6 +341,10 @@ do NOMD5=y shift ;; + --sha256) + SHA256=y + shift + ;; --nocrc) NOCRC=y shift @@ -323,16 +354,16 @@ do shift ;; --lsm) - LSM_CMD="cat \"$2\" >> \"\$archname\"" - if ! shift 2; then MS_Help; exit 1; fi + LSM_CMD="awk 1 \"$2\" >> \"\$archname\"" + shift 2 || { MS_Usage; exit 1; } ;; --packaging-date) DATE="$2" - if ! shift 2; then MS_Help; exit 1; fi + shift 2 || { MS_Usage; exit 1; } ;; --help-header) HELPHEADER=`sed -e "s/'/'\\\\\''/g" $2` - if ! shift 2; then MS_Help; exit 1; fi + shift 2 || { MS_Usage; exit 1; } [ -n "$HELPHEADER" ] && HELPHEADER="$HELPHEADER " ;; @@ -378,43 +409,44 @@ fi archname="$2" if test "$QUIET" = "y" || test "$TAR_QUIETLY" = "y"; then - if test "$TAR_ARGS" = "cvf"; then - TAR_ARGS="cf" - elif test "$TAR_ARGS" = "cvhf";then - TAR_ARGS="chf" + if test "$TAR_ARGS" = "rvf"; then + TAR_ARGS="rf" + elif test "$TAR_ARGS" = "rvhf"; then + TAR_ARGS="rhf" fi fi if test "$APPEND" = y; then if test $# -lt 2; then - MS_Usage + MS_Usage fi # Gather the info from the original archive OLDENV=`sh "$archname" --dumpconf` if test $? -ne 0; then - echo "Unable to update archive: $archname" >&2 - exit 1 + echo "Unable to update archive: $archname" >&2 + exit 1 else - eval "$OLDENV" + eval "$OLDENV" + OLDSKIP=`expr $SKIP + 1` fi else if test "$KEEP" = n -a $# = 3; then - echo "ERROR: Making a temporary archive with no embedded command does not make sense!" >&2 - echo >&2 - MS_Usage + echo "ERROR: Making a temporary archive with no embedded command does not make sense!" >&2 + echo >&2 + MS_Usage fi # We don't want to create an absolute directory unless a target directory is defined if test "$CURRENT" = y; then - archdirname="." - elif test x$TARGETDIR != x; then - archdirname="$TARGETDIR" + archdirname="." + elif test x"$TARGETDIR" != x; then + archdirname="$TARGETDIR" else - archdirname=`basename "$1"` + archdirname=`basename "$1"` fi if test $# -lt 3; then - MS_Usage + MS_Usage fi LABEL="$3" @@ -434,20 +466,47 @@ gzip) GZIP_CMD="gzip -c$COMPRESS_LEVEL" GUNZIP_CMD="gzip -cd" ;; -pigz) +pigz) GZIP_CMD="pigz -$COMPRESS_LEVEL" + if test $THREADS -ne $DEFAULT_THREADS; then # Leave as the default if threads not indicated + GZIP_CMD="$GZIP_CMD --processes $THREADS" + fi GUNZIP_CMD="gzip -cd" ;; +zstd) + GZIP_CMD="zstd -$COMPRESS_LEVEL" + if test $THREADS -ne $DEFAULT_THREADS; then # Leave as the default if threads not indicated + GZIP_CMD="$GZIP_CMD --threads=$THREADS" + fi + GUNZIP_CMD="zstd -cd" + ;; pbzip2) GZIP_CMD="pbzip2 -c$COMPRESS_LEVEL" + if test $THREADS -ne $DEFAULT_THREADS; then # Leave as the default if threads not indicated + GZIP_CMD="$GZIP_CMD -p$THREADS" + fi GUNZIP_CMD="bzip2 -d" ;; +bzip3) + # Map the compression level to a block size in MiB as 2^(level-1). + BZ3_COMPRESS_LEVEL=`echo "2^($COMPRESS_LEVEL-1)" | bc` + GZIP_CMD="bzip3 -b$BZ3_COMPRESS_LEVEL" + if test $THREADS -ne $DEFAULT_THREADS; then # Leave as the default if threads not indicated + GZIP_CMD="$GZIP_CMD -j$THREADS" + fi + JOBS=`echo "10-$COMPRESS_LEVEL" | bc` + GUNZIP_CMD="bzip3 -dj$JOBS" + ;; bzip2) GZIP_CMD="bzip2 -$COMPRESS_LEVEL" GUNZIP_CMD="bzip2 -d" ;; xz) GZIP_CMD="xz -c$COMPRESS_LEVEL" + # Must opt-in by specifying a value since not all versions of xz support threads + if test $THREADS -ne $DEFAULT_THREADS; then + GZIP_CMD="$GZIP_CMD --threads=$THREADS" + fi GUNZIP_CMD="xz -d" ;; lzo) @@ -460,23 +519,21 @@ lz4) ;; base64) GZIP_CMD="base64" - GUNZIP_CMD="base64 -d -i" + GUNZIP_CMD="base64 --decode -i -" ;; gpg) GZIP_CMD="gpg $GPG_EXTRA -ac -z$COMPRESS_LEVEL" GUNZIP_CMD="gpg -d" + ENCRYPT="gpg" ;; gpg-asymmetric) GZIP_CMD="gpg $GPG_EXTRA -z$COMPRESS_LEVEL -es" GUNZIP_CMD="gpg --yes -d" + ENCRYPT="gpg" ;; -openssl) - GZIP_CMD="openssl aes-256-cbc -a -salt -md sha256" - GUNZIP_CMD="openssl aes-256-cbc -d -a -md sha256" - ;; -Unix) - GZIP_CMD="compress -cf" - GUNZIP_CMD="exec 2>&-; uncompress -c || test \\\$? -eq 2 || gzip -cd" +compress) + GZIP_CMD="compress -fc" + GUNZIP_CMD="(type compress >/dev/null 2>&1 && compress -fcd || gzip -cd)" ;; none) GZIP_CMD="cat" @@ -484,29 +541,48 @@ none) ;; esac -tmpfile="${TMPDIR:=/tmp}/mkself$$" +if test x"$ENCRYPT" = x"openssl"; then + if test x"$APPEND" = x"y"; then + echo "Appending to existing archive is not compatible with OpenSSL encryption." >&2 + fi + + ENCRYPT_CMD="openssl enc -aes-256-cbc -salt" + DECRYPT_CMD="openssl enc -aes-256-cbc -d" + + if test x"$OPENSSL_NO_MD" != x"y"; then + ENCRYPT_CMD="$ENCRYPT_CMD -md sha256" + DECRYPT_CMD="$DECRYPT_CMD -md sha256" + fi + + if test -n "$PASSWD_SRC"; then + ENCRYPT_CMD="$ENCRYPT_CMD -pass $PASSWD_SRC" + elif test -n "$PASSWD"; then + ENCRYPT_CMD="$ENCRYPT_CMD -pass pass:$PASSWD" + fi +fi + +tmpfile="${TMPDIR:-/tmp}/mkself$$" if test -f "$HEADER"; then oldarchname="$archname" archname="$tmpfile" # Generate a fake header to count its lines SKIP=0 - . "$HEADER" - SKIP=`cat "$tmpfile" |wc -l` + . "$HEADER" + SKIP=`cat "$tmpfile" |wc -l` # Get rid of any spaces SKIP=`expr $SKIP` rm -f "$tmpfile" - if test "$QUIET" = "n";then - echo Header is $SKIP lines long >&2 - fi - + if test "$QUIET" = "n"; then + echo "Header is $SKIP lines long" >&2 + fi archname="$oldarchname" else echo "Unable to open header file: $HEADER" >&2 exit 1 fi -if test "$QUIET" = "n";then +if test "$QUIET" = "n"; then echo fi @@ -525,36 +601,101 @@ if test "." = "$archdirname"; then fi test -d "$archdir" || { echo "Error: $archdir does not exist."; rm -f "$tmpfile"; exit 1; } -if test "$QUIET" = "n";then - echo About to compress $USIZE KB of data... - echo Adding files to archive named \"$archname\"... +if test "$QUIET" = "n"; then + echo "About to compress $USIZE KB of data..." + echo "Adding files to archive named \"$archname\"..." +fi + +# See if we have GNU tar +TAR=`exec <&- 2>&-; which gtar || command -v gtar || type gtar` +test -x "$TAR" || TAR=`exec <&- 2>&-; which bsdtar || command -v bsdtar || type bsdtar` +test -x "$TAR" || TAR=tar + +tmparch="${TMPDIR:-/tmp}/mkself$$.tar" +( + if test "$APPEND" = "y"; then + tail -n "+$OLDSKIP" "$archname" | eval "$GUNZIP_CMD" > "$tmparch" + fi + cd "$archdir" + # "Determining if a directory is empty" + # https://www.etalabs.net/sh_tricks.html + find . \ + \( \ + ! -type d \ + -o \ + \( -links 2 -exec sh -c ' + is_empty () ( + cd "$1" + set -- .[!.]* ; test -f "$1" && return 1 + set -- ..?* ; test -f "$1" && return 1 + set -- * ; test -f "$1" && return 1 + return 0 + ) + is_empty "$0"' {} \; \ + \) \ + \) -print \ + | LC_ALL=C sort \ + | sed 's/./\\&/g' \ + | xargs $TAR $TAR_EXTRA --format $TAR_FORMAT -$TAR_ARGS "$tmparch" +) || { + echo "ERROR: failed to create temporary archive: $tmparch" + rm -f "$tmparch" "$tmpfile" + exit 1 +} + +USIZE=`du $DU_ARGS "$tmparch" | awk '{print $1}'` + +eval "$GZIP_CMD" <"$tmparch" >"$tmpfile" || { + echo "ERROR: failed to create temporary file: $tmpfile" + rm -f "$tmparch" "$tmpfile" + exit 1 +} +rm -f "$tmparch" + +if test x"$ENCRYPT" = x"openssl"; then + echo "About to encrypt archive \"$archname\"..." + { eval "$ENCRYPT_CMD -in $tmpfile -out ${tmpfile}.enc" && mv -f ${tmpfile}.enc $tmpfile; } || \ + { echo Aborting: could not encrypt temporary file: "$tmpfile".; rm -f "$tmpfile"; exit 1; } fi -exec 3<> "$tmpfile" -( cd "$archdir" && ( tar $TAR_EXTRA -$TAR_ARGS - . | eval "$GZIP_CMD" >&3 ) ) || \ - { echo Aborting: archive directory not found or temporary file: "$tmpfile" could not be created.; exec 3>&-; rm -f "$tmpfile"; exit 1; } -exec 3>&- # try to close the archive fsize=`cat "$tmpfile" | wc -c | tr -d " "` # Compute the checksums +shasum=0000000000000000000000000000000000000000000000000000000000000000 md5sum=00000000000000000000000000000000 crcsum=0000000000 if test "$NOCRC" = y; then - if test "$QUIET" = "n";then + if test "$QUIET" = "n"; then echo "skipping crc at user request" fi else - crcsum=`cat "$tmpfile" | CMD_ENV=xpg4 cksum | sed -e 's/ /Z/' -e 's/ /Z/' | cut -dZ -f1` - if test "$QUIET" = "n";then + crcsum=`CMD_ENV=xpg4 cksum < "$tmpfile" | sed -e 's/ /Z/' -e 's/ /Z/' | cut -dZ -f1` + if test "$QUIET" = "n"; then echo "CRC: $crcsum" fi fi +if test "$SHA256" = y; then + SHA_PATH=`exec <&- 2>&-; which shasum || command -v shasum || type shasum` + if test -x "$SHA_PATH"; then + shasum=`eval "$SHA_PATH -a 256" < "$tmpfile" | cut -b-64` + else + SHA_PATH=`exec <&- 2>&-; which sha256sum || command -v sha256sum || type sha256sum` + shasum=`eval "$SHA_PATH" < "$tmpfile" | cut -b-64` + fi + if test "$QUIET" = "n"; then + if test -x "$SHA_PATH"; then + echo "SHA256: $shasum" + else + echo "SHA256: none, SHA command not found" + fi + fi +fi if test "$NOMD5" = y; then - if test "$QUIET" = "n";then - echo "skipping md5sum at user request" + if test "$QUIET" = "n"; then + echo "Skipping md5sum at user request" fi else # Try to locate a MD5 binary @@ -569,53 +710,71 @@ else if test `basename ${MD5_PATH}`x = digestx; then MD5_ARG="-a md5" fi - md5sum=`cat "$tmpfile" | eval "$MD5_PATH $MD5_ARG" | cut -b-32`; - if test "$QUIET" = "n";then + md5sum=`eval "$MD5_PATH $MD5_ARG" < "$tmpfile" | cut -b-32` + if test "$QUIET" = "n"; then echo "MD5: $md5sum" fi else - if test "$QUIET" = "n";then + if test "$QUIET" = "n"; then echo "MD5: none, MD5 command not found" fi fi fi +if test "$SIGN" = y; then + GPG_PATH=`exec <&- 2>&-; which gpg || command -v gpg || type gpg` + if test -x "$GPG_PATH"; then + SIGNATURE=`$GPG_PATH --pinentry-mode=loopback --batch --yes $GPG_EXTRA --passphrase "$GPG_PASSPHRASE" --output - --detach-sig $tmpfile | base64 | tr -d \\\\n` + if test "$QUIET" = "n"; then + echo "Signature: $SIGNATURE" + fi + else + echo "Missing gpg command" >&2 + fi +fi + +totalsize=0 +for size in $fsize; +do + totalsize=`expr $totalsize + $size` +done if test "$APPEND" = y; then mv "$archname" "$archname".bak || exit # Prepare entry for new archive - filesizes="$filesizes $fsize" - CRCsum="$CRCsum $crcsum" - MD5sum="$MD5sum $md5sum" - USIZE=`expr $USIZE + $OLDUSIZE` + filesizes="$fsize" + CRCsum="$crcsum" + MD5sum="$md5sum" + SHAsum="$shasum" + Signature="$SIGNATURE" # Generate the header . "$HEADER" - # Append the original data - tail -n +$OLDSKIP "$archname".bak >> "$archname" # Append the new data cat "$tmpfile" >> "$archname" chmod +x "$archname" rm -f "$archname".bak - if test "$QUIET" = "n";then - echo Self-extractable archive \"$archname\" successfully updated. + if test "$QUIET" = "n"; then + echo "Self-extractable archive \"$archname\" successfully updated." fi else filesizes="$fsize" CRCsum="$crcsum" MD5sum="$md5sum" + SHAsum="$shasum" + Signature="$SIGNATURE" # Generate the header . "$HEADER" # Append the compressed tar data after the stub - if test "$QUIET" = "n";then - echo + if test "$QUIET" = "n"; then + echo fi cat "$tmpfile" >> "$archname" chmod +x "$archname" - if test "$QUIET" = "n";then - echo Self-extractable archive \"$archname\" successfully created. + if test "$QUIET" = "n"; then + echo Self-extractable archive \"$archname\" successfully created. fi fi rm -f "$tmpfile" diff --git a/packaging/repoconfig/netdata-edge.repo.al b/packaging/repoconfig/netdata-edge.repo.al new file mode 100644 index 00000000..4a300a26 --- /dev/null +++ b/packaging/repoconfig/netdata-edge.repo.al @@ -0,0 +1,21 @@ +[netdata-edge] +name=Netdata Edge +baseurl=https://repo.netdata.cloud/repos/edge/amazonlinux/$releasever/$basearch +repo_gpgcheck=1 +gpgcheck=1 +gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key +enabled=1 +sslverify=1 +sslcacert=/etc/pki/tls/certs/ca-bundle.crt +priority=50 + +[netdata-repoconfig] +name=Netdata Repository Config +baseurl=https://repo.netdata.cloud/repos/repoconfig/amazonlinux/$releasever/$basearch +repo_gpgcheck=1 +gpgcheck=1 +gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key +enabled=1 +sslverify=1 +sslcacert=/etc/pki/tls/certs/ca-bundle.crt +priority=50 diff --git a/packaging/repoconfig/netdata-repo.spec b/packaging/repoconfig/netdata-repo.spec index cc53fd8c..6139e52b 100644 --- a/packaging/repoconfig/netdata-repo.spec +++ b/packaging/repoconfig/netdata-repo.spec @@ -16,6 +16,8 @@ Source4: netdata.repo.centos Source5: netdata-edge.repo.centos Source6: netdata.repo.ol Source7: netdata-edge.repo.ol +Source8: netdata.repo.al +Source9: netdata-edge.repo.al BuildArch: noarch @@ -43,9 +45,15 @@ install -pm 644 %{SOURCE3} ./netdata-edge.repo %endif %if 0%{?centos_ver} +# Amazon Linux 2 looks like CentOS, but with extra macros. +%if 0%{?amzn2} +install -pm 644 %{SOURCE8} ./netdata.repo +install -pm 644 %{SOURCE9} ./netdata-edge.repo +%else install -pm 644 %{SOURCE4} ./netdata.repo install -pm 644 %{SOURCE5} ./netdata-edge.repo %endif +%endif %if 0%{?oraclelinux} install -pm 644 %{SOURCE6} ./netdata.repo diff --git a/packaging/repoconfig/netdata.repo.al b/packaging/repoconfig/netdata.repo.al new file mode 100644 index 00000000..0bacb3a1 --- /dev/null +++ b/packaging/repoconfig/netdata.repo.al @@ -0,0 +1,21 @@ +[netdata] +name=Netdata +baseurl=https://repo.netdata.cloud/repos/stable/amazonlinux/$releasever/$basearch +repo_gpgcheck=1 +gpgcheck=1 +gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key +enabled=1 +sslverify=1 +sslcacert=/etc/pki/tls/certs/ca-bundle.crt +priority=50 + +[netdata-repoconfig] +name=Netdata Repository Config +baseurl=https://repo.netdata.cloud/repos/repoconfig/amazonlinux/$releasever/$basearch +repo_gpgcheck=1 +gpgcheck=1 +gpgkey=https://repo.netdata.cloud/netdatabot.gpg.key +enabled=1 +sslverify=1 +sslcacert=/etc/pki/tls/certs/ca-bundle.crt +priority=50 diff --git a/packaging/version b/packaging/version index cbec0de0..5b813750 100644 --- a/packaging/version +++ b/packaging/version @@ -1 +1 @@ -v1.38.1 +v1.39.0 diff --git a/packaging/yaml.checksums b/packaging/yaml.checksums new file mode 100644 index 00000000..563c273d --- /dev/null +++ b/packaging/yaml.checksums @@ -0,0 +1 @@ +c642ae9b75fee120b2d96c712538bd2cf283228d2337df2cf2988e3c02678ef4 yaml-0.2.5.tar.gz diff --git a/packaging/yaml.version b/packaging/yaml.version new file mode 100644 index 00000000..3a4036fb --- /dev/null +++ b/packaging/yaml.version @@ -0,0 +1 @@ +0.2.5 diff --git a/parser/Makefile.am b/parser/Makefile.am deleted file mode 100644 index 02fe3a31..00000000 --- a/parser/Makefile.am +++ /dev/null @@ -1,9 +0,0 @@ -# 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/parser/README.md b/parser/README.md deleted file mode 100644 index b7951864..00000000 --- a/parser/README.md +++ /dev/null @@ -1,152 +0,0 @@ - - - -#### Introduction - -The parser will be used to process streaming and plugins input as well as metadata - -Usage - -1. Define a structure that will be used to share user state across calls -1. Initialize the parser using `parser_init` -2. Register keywords and associated callback function using `parser_add_keyword` -3. Register actions on the keywords -4. Start a loop until EOF - 1. Fetch the next line using `parser_next` - 2. Process the line using `parser_action` - 1. The registered callbacks are executed to parse the input - 2. The registered action for the callback is called for processing -4. Release the parser using `parser_destroy` -5. Release the user structure - -#### 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. - -Input -- Host - - The host this parser will be dealing with. For streaming with SSL enabled for this host -- user - - User defined structure that is passed in all the calls -- input - - Where the parser will get the input from -- flags - - flags to define processing on the input - -Output -- A parser structure - - - -##### parse_push(PARSER *parser, char *line) - -Push a new line for processing - -Input - -- parser - - The parser object as returned by the `parser_init` -- line - - The new line to process - - -Output -- The line will be injected into the stream and will be the next one to be processed - -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 - -`typedef PARSER_RC (*keyword_function)(char **, void *);` - -Input - -- parser - - The parser object as returned by the `parser_init` -- keyword - - The keyword to register -- 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 - -Output -- The corresponding keyword and callback will be registered - -Returns -- 0 maximum callbacks already registered for this keyword -- > 0 which is the number of callbacks associated with this keyword. - - -##### parser_next(PARSER *parser) -Return the next item to parse - -Input -- parser - - The parser object as returned by the `parser_init` - -Output -- The parser will store internally the next item to parse - -Returns -- 0 Next item fetched successfully -- 1 No more items to parse - - -##### parser_action(PARSER *parser, char *input) -Return the next item to parse - -Input -- parser - - The parser object as returned by the `parser_init` -- input - - Process the input specified instead of using the internal buffer - -Output -- The current keyword will be processed by calling all the registered callbacks - -Returns -- 0 Callbacks called successfully -- 1 Failed - - -##### parser_destroy(PARSER *parser) -Cleanup a previously allocated parser - -Input -- parser - - The parser object as returned by the `parser_init` - -Output -- The parser is deallocated - -Returns -- none - - -##### parser_recover_input(PARSER *parser) -Cleanup a previously allocated parser - -Input -- parser - - The parser object as returned by the `parser_init` - -Output -- The parser is deallocated - -Returns -- none diff --git a/parser/parser.c b/parser/parser.c deleted file mode 100644 index c687c7af..00000000 --- a/parser/parser.c +++ /dev/null @@ -1,391 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "parser.h" -#include "collectors/plugins.d/pluginsd_parser.h" - -inline int find_first_keyword(const char *str, char *keyword, int max_size, int (*custom_isspace)(char)) -{ - const char *s = str, *keyword_start; - - while (unlikely(custom_isspace(*s))) s++; - keyword_start = s; - - while (likely(*s && !custom_isspace(*s)) && max_size > 1) { - *keyword++ = *s++; - max_size--; - } - *keyword = '\0'; - return max_size == 0 ? 0 : (int) (s - keyword_start); -} - -/* - * Initialize a parser - * user : as defined by the user, will be shared across calls - * input : main input stream (auto detect stream -- file, socket, pipe) - * buffer : This is the buffer to be used (if null a buffer of size will be allocated) - * size : buffer size either passed or will be allocated - * If the buffer is auto allocated, it will auto freed when the parser is destroyed - * - * - */ - -PARSER *parser_init(RRDHOST *host, void *user, FILE *fp_input, FILE *fp_output, int fd, PARSER_INPUT_TYPE flags, void *ssl __maybe_unused) -{ - PARSER *parser; - - parser = callocz(1, sizeof(*parser)); - parser->user = user; - parser->fd = fd; - parser->fp_input = fp_input; - parser->fp_output = fp_output; -#ifdef ENABLE_HTTPS - parser->ssl_output = ssl; -#endif - parser->flags = flags; - parser->host = host; - parser->worker_job_next_id = WORKER_PARSER_FIRST_JOB; - inflight_functions_init(parser); - -#ifdef ENABLE_HTTPS - parser->bytesleft = 0; - parser->readfrom = NULL; -#endif - - if (unlikely(!(flags & PARSER_NO_PARSE_INIT))) { - parser_add_keyword(parser, PLUGINSD_KEYWORD_FLUSH, pluginsd_flush); - parser_add_keyword(parser, PLUGINSD_KEYWORD_CHART, pluginsd_chart); - parser_add_keyword(parser, PLUGINSD_KEYWORD_CHART_DEFINITION_END, pluginsd_chart_definition_end); - parser_add_keyword(parser, PLUGINSD_KEYWORD_DIMENSION, pluginsd_dimension); - parser_add_keyword(parser, PLUGINSD_KEYWORD_DISABLE, pluginsd_disable); - parser_add_keyword(parser, PLUGINSD_KEYWORD_VARIABLE, pluginsd_variable); - parser_add_keyword(parser, PLUGINSD_KEYWORD_LABEL, pluginsd_label); - parser_add_keyword(parser, PLUGINSD_KEYWORD_OVERWRITE, pluginsd_overwrite); - parser_add_keyword(parser, PLUGINSD_KEYWORD_END, pluginsd_end); - parser_add_keyword(parser, PLUGINSD_KEYWORD_CLABEL_COMMIT, pluginsd_clabel_commit); - parser_add_keyword(parser, PLUGINSD_KEYWORD_CLABEL, pluginsd_clabel); - parser_add_keyword(parser, PLUGINSD_KEYWORD_BEGIN, pluginsd_begin); - parser_add_keyword(parser, PLUGINSD_KEYWORD_SET, pluginsd_set); - - parser_add_keyword(parser, PLUGINSD_KEYWORD_FUNCTION, pluginsd_function); - parser_add_keyword(parser, PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN, pluginsd_function_result_begin); - - parser_add_keyword(parser, PLUGINSD_KEYWORD_REPLAY_BEGIN, pluginsd_replay_rrdset_begin); - parser_add_keyword(parser, PLUGINSD_KEYWORD_REPLAY_SET, pluginsd_replay_set); - parser_add_keyword(parser, PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE, pluginsd_replay_rrddim_collection_state); - parser_add_keyword(parser, PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE, pluginsd_replay_rrdset_collection_state); - parser_add_keyword(parser, PLUGINSD_KEYWORD_REPLAY_END, pluginsd_replay_end); - } - - return parser; -} - - -/* - * Push a new line into the parsing stream - * - * This line will be the next one to process ie the next fetch will get this one - * - */ - -int parser_push(PARSER *parser, char *line) -{ - PARSER_DATA *tmp_parser_data; - - if (unlikely(!parser)) - return 1; - - if (unlikely(!line)) - return 0; - - tmp_parser_data = callocz(1, sizeof(*tmp_parser_data)); - tmp_parser_data->line = strdupz(line); - tmp_parser_data->next = parser->data; - parser->data = tmp_parser_data; - - return 0; -} - -/* - * Add a keyword and the corresponding function that will be called - * Multiple functions may be added - * Input : keyword - * : callback function - * : flags - * Output: > 0 registered function number - * : 0 Error - */ - -int parser_add_keyword(PARSER *parser, char *keyword, keyword_function func) -{ - PARSER_KEYWORD *tmp_keyword; - - if (strcmp(keyword, "_read") == 0) { - parser->read_function = (void *) func; - return 0; - } - - if (strcmp(keyword, "_eof") == 0) { - parser->eof_function = (void *) func; - return 0; - } - - if (strcmp(keyword, "_unknown") == 0) { - parser->unknown_function = (void *) func; - return 0; - } - - uint32_t keyword_hash = simple_hash(keyword); - - tmp_keyword = parser->keyword; - - while (tmp_keyword) { - if (tmp_keyword->keyword_hash == keyword_hash && (!strcmp(tmp_keyword->keyword, keyword))) { - if (tmp_keyword->func_no == PARSER_MAX_CALLBACKS) - return 0; - tmp_keyword->func[tmp_keyword->func_no++] = (void *) func; - return tmp_keyword->func_no; - } - tmp_keyword = tmp_keyword->next; - } - - tmp_keyword = callocz(1, sizeof(*tmp_keyword)); - - tmp_keyword->worker_job_id = parser->worker_job_next_id++; - tmp_keyword->keyword = strdupz(keyword); - tmp_keyword->keyword_hash = keyword_hash; - tmp_keyword->func[tmp_keyword->func_no++] = (void *) func; - - worker_register_job_name(tmp_keyword->worker_job_id, tmp_keyword->keyword); - - tmp_keyword->next = parser->keyword; - parser->keyword = tmp_keyword; - return tmp_keyword->func_no; -} - -/* - * Cleanup a previously allocated parser - */ - -void parser_destroy(PARSER *parser) -{ - if (unlikely(!parser)) - return; - - dictionary_destroy(parser->inflight.functions); - - PARSER_KEYWORD *tmp_keyword, *tmp_keyword_next; - PARSER_DATA *tmp_parser_data, *tmp_parser_data_next; - - // Remove keywords - tmp_keyword = parser->keyword; - while (tmp_keyword) { - tmp_keyword_next = tmp_keyword->next; - freez(tmp_keyword->keyword); - freez(tmp_keyword); - tmp_keyword = tmp_keyword_next; - } - - // Remove pushed data if any - tmp_parser_data = parser->data; - while (tmp_parser_data) { - tmp_parser_data_next = tmp_parser_data->next; - freez(tmp_parser_data->line); - freez(tmp_parser_data); - tmp_parser_data = tmp_parser_data_next; - } - - freez(parser); -} - - -/* - * Fetch the next line to process - * - */ - -int parser_next(PARSER *parser) -{ - char *tmp = NULL; - - if (unlikely(!parser)) - return 1; - - parser->flags &= ~(PARSER_INPUT_PROCESSED); - - PARSER_DATA *tmp_parser_data = parser->data; - - if (unlikely(tmp_parser_data)) { - strncpyz(parser->buffer, tmp_parser_data->line, PLUGINSD_LINE_MAX); - parser->data = tmp_parser_data->next; - freez(tmp_parser_data->line); - freez(tmp_parser_data); - return 0; - } - - if (unlikely(parser->read_function)) - tmp = parser->read_function(parser->buffer, PLUGINSD_LINE_MAX, parser->fp_input); - else if(likely(parser->fp_input)) - tmp = fgets(parser->buffer, PLUGINSD_LINE_MAX, (FILE *)parser->fp_input); - else - tmp = NULL; - - if (unlikely(!tmp)) { - if (unlikely(parser->eof_function)) { - int rc = parser->eof_function(parser->fp_input); - error("read failed: user defined function returned %d", rc); - } - else { - if (feof((FILE *)parser->fp_input)) - error("read failed: end of file"); - else if (ferror((FILE *)parser->fp_input)) - error("read failed: input error"); - else - error("read failed: unknown error"); - } - return 1; - } - return 0; -} - - -/* -* Takes an initialized parser object that has an unprocessed entry (by calling parser_next) -* and if it contains a valid keyword, it will execute all the callbacks -* -*/ - -inline int parser_action(PARSER *parser, char *input) -{ - parser->line++; - - PARSER_RC rc = PARSER_RC_OK; - char *words[PLUGINSD_MAX_WORDS]; - char command[PLUGINSD_LINE_MAX + 1]; - keyword_function action_function; - keyword_function *action_function_list = NULL; - - if (unlikely(!parser)) { - internal_error(true, "parser is NULL"); - return 1; - } - - parser->recover_location[0] = 0x0; - - // if not direct input check if we have reprocessed this - if (unlikely(!input && parser->flags & PARSER_INPUT_PROCESSED)) - return 0; - - PARSER_KEYWORD *tmp_keyword = parser->keyword; - if (unlikely(!tmp_keyword)) { - internal_error(true, "called without a keyword"); - return 1; - } - - if (unlikely(!input)) - input = parser->buffer; - - if(unlikely(parser->flags & PARSER_DEFER_UNTIL_KEYWORD)) { - bool has_keyword = find_first_keyword(input, command, PLUGINSD_LINE_MAX, pluginsd_space); - - if(!has_keyword || strcmp(command, parser->defer.end_keyword) != 0) { - if(parser->defer.response) { - buffer_strcat(parser->defer.response, input); - if(buffer_strlen(parser->defer.response) > 10 * 1024 * 1024) { - // more than 10MB of data - // a bad plugin that did not send the end_keyword - internal_error(true, "PLUGINSD: deferred response is too big (%zu bytes). Stopping this plugin.", buffer_strlen(parser->defer.response)); - return 1; - } - } - return 0; - } - else { - // call the action - parser->defer.action(parser, parser->defer.action_data); - - // empty everything - parser->defer.action = NULL; - parser->defer.action_data = NULL; - parser->defer.end_keyword = NULL; - parser->defer.response = NULL; - parser->flags &= ~PARSER_DEFER_UNTIL_KEYWORD; - } - return 0; - } - - if (unlikely(!find_first_keyword(input, command, PLUGINSD_LINE_MAX, pluginsd_space))) - return 0; - - size_t num_words = 0; - if ((parser->flags & PARSER_INPUT_KEEP_ORIGINAL) == PARSER_INPUT_KEEP_ORIGINAL) - num_words = pluginsd_split_words(input, words, PLUGINSD_MAX_WORDS, parser->recover_input, parser->recover_location, PARSER_MAX_RECOVER_KEYWORDS); - else - num_words = pluginsd_split_words(input, words, PLUGINSD_MAX_WORDS, NULL, NULL, 0); - - uint32_t command_hash = simple_hash(command); - - size_t worker_job_id = WORKER_UTILIZATION_MAX_JOB_TYPES + 1; // set an invalid value by default - while(tmp_keyword) { - if (command_hash == tmp_keyword->keyword_hash && (!strcmp(command, tmp_keyword->keyword))) { - action_function_list = &tmp_keyword->func[0]; - worker_job_id = tmp_keyword->worker_job_id; - break; - } - tmp_keyword = tmp_keyword->next; - } - - if (unlikely(!action_function_list)) { - if (unlikely(parser->unknown_function)) - rc = parser->unknown_function(words, num_words, parser->user); - else - rc = PARSER_RC_ERROR; - } - else { - worker_is_busy(worker_job_id); - while ((action_function = *action_function_list) != NULL) { - rc = action_function(words, num_words, parser->user); - if (unlikely(rc == PARSER_RC_ERROR || rc == PARSER_RC_STOP)) - break; - - action_function_list++; - } - worker_is_idle(); - } - - if (likely(input == parser->buffer)) - parser->flags |= PARSER_INPUT_PROCESSED; - -#ifdef NETDATA_INTERNAL_CHECKS - if(rc == PARSER_RC_ERROR) { - BUFFER *wb = buffer_create(PLUGINSD_LINE_MAX, NULL); - for(size_t i = 0; i < num_words ;i++) { - if(i) buffer_fast_strcat(wb, " ", 1); - - buffer_fast_strcat(wb, "\"", 1); - const char *s = get_word(words, num_words, i); - buffer_strcat(wb, s?s:""); - buffer_fast_strcat(wb, "\"", 1); - } - - internal_error(true, "PLUGINSD: parser_action('%s') failed on line %zu: { %s } (quotes added to show parsing)", - command, parser->line, buffer_tostring(wb)); - - buffer_free(wb); - } -#endif - - return (rc == PARSER_RC_ERROR); -} - -inline int parser_recover_input(PARSER *parser) -{ - if (unlikely(!parser)) - return 1; - - for(int i=0; i < PARSER_MAX_RECOVER_KEYWORDS && parser->recover_location[i]; i++) - *(parser->recover_location[i]) = parser->recover_input[i]; - - parser->recover_location[0] = 0x0; - - return 0; -} diff --git a/parser/parser.h b/parser/parser.h deleted file mode 100644 index ad748838..00000000 --- a/parser/parser.h +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_INCREMENTAL_PARSER_H -#define NETDATA_INCREMENTAL_PARSER_H 1 - -#include "daemon/common.h" - -#define PARSER_MAX_CALLBACKS 20 -#define PARSER_MAX_RECOVER_KEYWORDS 128 -#define WORKER_PARSER_FIRST_JOB 3 - -// this has to be in-sync with the same at receiver.c -#define WORKER_RECEIVER_JOB_REPLICATION_COMPLETION (WORKER_PARSER_FIRST_JOB - 3) - -// PARSER return codes -typedef enum parser_rc { - PARSER_RC_OK, // Callback was successful, go on - PARSER_RC_STOP, // Callback says STOP - PARSER_RC_ERROR // Callback failed (abort rest of callbacks) -} PARSER_RC; - -typedef enum parser_input_type { - PARSER_INPUT_SPLIT = (1 << 1), - PARSER_INPUT_KEEP_ORIGINAL = (1 << 2), - PARSER_INPUT_PROCESSED = (1 << 3), - PARSER_NO_PARSE_INIT = (1 << 4), - PARSER_NO_ACTION_INIT = (1 << 5), - PARSER_DEFER_UNTIL_KEYWORD = (1 << 6), -} PARSER_INPUT_TYPE; - -#define PARSER_INPUT_FULL (PARSER_INPUT_SPLIT|PARSER_INPUT_ORIGINAL) - -typedef PARSER_RC (*keyword_function)(char **words, size_t num_words, void *user_data); - -typedef struct parser_keyword { - size_t worker_job_id; - char *keyword; - uint32_t keyword_hash; - int func_no; - keyword_function func[PARSER_MAX_CALLBACKS+1]; - struct parser_keyword *next; -} PARSER_KEYWORD; - -typedef struct parser_data { - char *line; - struct parser_data *next; -} PARSER_DATA; - -typedef struct parser { - size_t worker_job_next_id; - uint8_t version; // Parser version - RRDHOST *host; - int fd; // Socket - FILE *fp_input; // Input source e.g. stream - FILE *fp_output; // Stream to send commands to plugin -#ifdef ENABLE_HTTPS - struct netdata_ssl *ssl_output; -#endif - PARSER_DATA *data; // extra input - PARSER_KEYWORD *keyword; // List of parse keywords and functions - void *user; // User defined structure to hold extra state between calls - uint32_t flags; - size_t line; - - char *(*read_function)(char *buffer, long unsigned int, void *input); - int (*eof_function)(void *input); - keyword_function unknown_function; - char buffer[PLUGINSD_LINE_MAX]; - char *recover_location[PARSER_MAX_RECOVER_KEYWORDS+1]; - char recover_input[PARSER_MAX_RECOVER_KEYWORDS]; -#ifdef ENABLE_HTTPS - int bytesleft; - char tmpbuffer[PLUGINSD_LINE_MAX]; - char *readfrom; -#endif - - struct { - const char *end_keyword; - BUFFER *response; - void (*action)(struct parser *parser, void *action_data); - void *action_data; - } defer; - - struct { - DICTIONARY *functions; - usec_t smaller_timeout; - } inflight; - -} PARSER; - -int find_first_keyword(const char *str, char *keyword, int max_size, int (*custom_isspace)(char)); - -PARSER *parser_init(RRDHOST *host, void *user, FILE *fp_input, FILE *fp_output, int fd, PARSER_INPUT_TYPE flags, void *ssl); -int parser_add_keyword(PARSER *working_parser, char *keyword, keyword_function func); -int parser_next(PARSER *working_parser); -int parser_action(PARSER *working_parser, char *input); -int parser_push(PARSER *working_parser, char *line); -void parser_destroy(PARSER *working_parser); -int parser_recover_input(PARSER *working_parser); - -size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp_plugin_input, FILE *fp_plugin_output, int trust_durations); - -PARSER_RC pluginsd_set(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_begin(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_end(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_chart(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_chart_definition_end(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_dimension(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_variable(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_flush(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_disable(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_label(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_overwrite(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_clabel_commit(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_clabel(char **words, size_t num_words, void *user); - -PARSER_RC pluginsd_replay_rrdset_begin(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_replay_rrddim_collection_state(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_replay_rrdset_collection_state(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_replay_set(char **words, size_t num_words, void *user); -PARSER_RC pluginsd_replay_end(char **words, size_t num_words, void *user); - -#endif diff --git a/registry/README.md b/registry/README.md index 827eea13..0bf174e1 100644 --- a/registry/README.md +++ b/registry/README.md @@ -4,8 +4,7 @@ description: "Netdata utilizes a central registry of machines/person GUIDs, URLs 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" +learn_rel_path: "Configuration" --> # Registry diff --git a/registry/registry.c b/registry/registry.c index e6c2172b..f8de470e 100644 --- a/registry/registry.c +++ b/registry/registry.c @@ -4,6 +4,7 @@ #include "registry_internals.h" #define REGISTRY_STATUS_OK "ok" +#define REGISTRY_STATUS_REDIRECT "redirect" #define REGISTRY_STATUS_FAILED "failed" #define REGISTRY_STATUS_DISABLED "disabled" @@ -18,35 +19,26 @@ static inline void registry_unlock(void) { netdata_mutex_unlock(®istry.lock); } - // ---------------------------------------------------------------------------- // COOKIES static void registry_set_cookie(struct web_client *w, const char *guid) { - char edate[100], domain[512]; + char edate[100]; time_t et = now_realtime_sec() + registry.persons_expiration; struct tm etmbuf, *etm = gmtime_r(&et, &etmbuf); strftime(edate, sizeof(edate), "%a, %d %b %Y %H:%M:%S %Z", etm); - snprintfz(w->cookie1, NETDATA_WEB_REQUEST_COOKIE_SIZE, NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s", guid, edate); - - if(registry.registry_domain && registry.registry_domain[0]) - snprintfz(domain, 511, "Domain=%s", registry.registry_domain); - else - domain[0]='\0'; - - int length = snprintfz(w->cookie2, NETDATA_WEB_REQUEST_COOKIE_SIZE, - NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s; %s", - guid, edate, domain); + buffer_sprintf(w->response.header, "Set-Cookie: " NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s\r\n", guid, edate); + if(registry.enable_cookies_samesite_secure) + buffer_sprintf(w->response.header, "Set-Cookie: " NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s; SameSite=None; Secure\r\n", guid, edate); - size_t remaining_length = NETDATA_WEB_REQUEST_COOKIE_SIZE - length; - // 25 is the necessary length to add new cookies - if (registry.enable_cookies_samesite_secure) { - if (length > 0 && remaining_length > 25) - snprintfz(&w->cookie2[length], remaining_length, "; SameSite=None; Secure"); - else - error("Netdata does not have enough space to store cookies SameSite and Secure"); + if(registry.registry_domain && *registry.registry_domain) { + buffer_sprintf(w->response.header, "Set-Cookie: " NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s; Domain=%s\r\n", guid, edate, registry.registry_domain); + if(registry.enable_cookies_samesite_secure) + buffer_sprintf(w->response.header, "Set-Cookie: " NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s; Domain=%s; SameSite=None; Secure\r\n", guid, edate, registry.registry_domain); } + + w->response.has_cookies = true; } static inline void registry_set_person_cookie(struct web_client *w, REGISTRY_PERSON *p) { @@ -59,23 +51,25 @@ static inline void registry_set_person_cookie(struct web_client *w, REGISTRY_PER static inline void registry_json_header(RRDHOST *host, struct web_client *w, const char *action, const char *status) { buffer_flush(w->response.data); - w->response.data->contenttype = CT_APPLICATION_JSON; - buffer_sprintf(w->response.data, "{\n\t\"action\": \"%s\",\n\t\"status\": \"%s\",\n\t\"hostname\": \"%s\",\n\t\"machine_guid\": \"%s\"", - action, status, rrdhost_registry_hostname(host), host->machine_guid); + w->response.data->content_type = CT_APPLICATION_JSON; + buffer_json_initialize(w->response.data, "\"", "\"", 0, true, false); + buffer_json_member_add_string(w->response.data, "action", action); + buffer_json_member_add_string(w->response.data, "status", status); + buffer_json_member_add_string(w->response.data, "hostname", rrdhost_registry_hostname(host)); + buffer_json_member_add_string(w->response.data, "machine_guid", host->machine_guid); } static inline void registry_json_footer(struct web_client *w) { - buffer_strcat(w->response.data, "\n}\n"); + buffer_json_finalize(w->response.data); } static inline int registry_json_disabled(RRDHOST *host, struct web_client *w, const char *action) { registry_json_header(host, w, action, REGISTRY_STATUS_DISABLED); - buffer_sprintf(w->response.data, ",\n\t\"registry\": \"%s\"", - registry.registry_to_announce); + buffer_json_member_add_string(w->response.data, "registry", registry.registry_to_announce); registry_json_footer(w); - return 200; + return HTTP_RESP_OK; } @@ -97,14 +91,16 @@ static int registry_json_person_url_callback(void *entry, void *data) { struct web_client *w = c->w; if (!strcmp(pu->url->url,"***")) return 0; - - if(unlikely(c->count++)) - buffer_strcat(w->response.data, ","); - buffer_sprintf(w->response.data, "\n\t\t[ \"%s\", \"%s\", %u000, %u, \"%s\" ]", - pu->machine->guid, pu->url->url, pu->last_t, pu->usages, pu->machine_name); + buffer_json_add_array_item_array(w->response.data); + buffer_json_add_array_item_string(w->response.data, pu->machine->guid); + buffer_json_add_array_item_string(w->response.data, pu->url->url); + buffer_json_add_array_item_uint64(w->response.data, pu->last_t * (uint64_t) 1000); + buffer_json_add_array_item_uint64(w->response.data, pu->usages); + buffer_json_add_array_item_string(w->response.data, pu->machine_name); + buffer_json_array_close(w->response.data); - return 0; + return 1; } // callback for rendering MACHINE_URLs @@ -114,13 +110,14 @@ static int registry_json_machine_url_callback(const DICTIONARY_ITEM *item __mayb struct web_client *w = c->w; REGISTRY_MACHINE *m = c->m; - if (!strcmp(mu->url->url,"***")) return 1; + if (!strcmp(mu->url->url,"***")) return 0; - if(unlikely(c->count++)) - buffer_strcat(w->response.data, ","); - - buffer_sprintf(w->response.data, "\n\t\t[ \"%s\", \"%s\", %u000, %u ]", - m->guid, mu->url->url, mu->last_t, mu->usages); + buffer_json_add_array_item_array(w->response.data); + buffer_json_add_array_item_string(w->response.data, m->guid); + buffer_json_add_array_item_string(w->response.data, mu->url->url); + buffer_json_add_array_item_uint64(w->response.data, mu->last_t * (uint64_t) 1000); + buffer_json_add_array_item_uint64(w->response.data, mu->usages); + buffer_json_array_close(w->response.data); return 1; } @@ -149,30 +146,23 @@ static inline int registry_person_url_callback_verify_machine_exists(void *entry // The registry does not seem to be designed to support this and I cannot see any concurrency protection // that could make this safe, so try to be as atomic as possible. -void registry_update_cloud_base_url() -{ - // This is guaranteed to be set early in main via post_conf_load() - registry.cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", NULL); - if (registry.cloud_base_url == NULL) - fatal("Do not move the cloud base url out of post_conf_load!!"); - +void registry_update_cloud_base_url() { + registry.cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", DEFAULT_CLOUD_BASE_URL); setenv("NETDATA_REGISTRY_CLOUD_BASE_URL", registry.cloud_base_url, 1); } + // ---------------------------------------------------------------------------- // public HELLO request int registry_request_hello_json(RRDHOST *host, struct web_client *w) { registry_json_header(host, w, "hello", REGISTRY_STATUS_OK); - const char *cloud_ui_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud ui url", DEFAULT_CLOUD_UI_URL); - - buffer_sprintf(w->response.data, - ",\n\t\"registry\": \"%s\",\n\t\"cloud_base_url\": \"%s\",\n\t\"anonymous_statistics\": %s", - registry.registry_to_announce, - cloud_ui_url, netdata_anonymous_statistics_enabled?"true":"false"); + buffer_json_member_add_string(w->response.data, "registry", registry.registry_to_announce); + buffer_json_member_add_string(w->response.data, "cloud_base_url", registry.cloud_base_url); + buffer_json_member_add_boolean(w->response.data, "anonymous_statistics", netdata_anonymous_statistics_enabled); registry_json_footer(w); - return 200; + return HTTP_RESP_OK; } // ---------------------------------------------------------------------------- @@ -191,9 +181,11 @@ int registry_request_access_json(RRDHOST *host, struct web_client *w, char *pers if(registry.verify_cookies_redirects > 0 && !person_guid[0]) { buffer_flush(w->response.data); registry_set_cookie(w, REGISTRY_VERIFY_COOKIES_GUID); - w->response.data->contenttype = CT_APPLICATION_JSON; - buffer_sprintf(w->response.data, "{ \"status\": \"redirect\", \"registry\": \"%s\" }", registry.registry_to_announce); - return 200; + w->response.data->content_type = CT_APPLICATION_JSON; + registry_json_header(host, w, "access", REGISTRY_STATUS_REDIRECT); + buffer_json_member_add_string(w->response.data, "registry", registry.registry_to_announce); + registry_json_footer(w); + return HTTP_RESP_OK; } if(unlikely(person_guid[0] && !strcmp(person_guid, REGISTRY_VERIFY_COOKIES_GUID))) @@ -208,7 +200,7 @@ int registry_request_access_json(RRDHOST *host, struct web_client *w, char *pers registry_json_header(host, w, "access", REGISTRY_STATUS_FAILED); registry_json_footer(w); registry_unlock(); - return 412; + return HTTP_RESP_PRECOND_FAIL; } // set the cookie @@ -216,15 +208,16 @@ int registry_request_access_json(RRDHOST *host, struct web_client *w, char *pers // generate the response registry_json_header(host, w, "access", REGISTRY_STATUS_OK); + buffer_json_member_add_string(w->response.data, "person_guid", p->guid); + buffer_json_member_add_array(w->response.data, "urls"); - buffer_sprintf(w->response.data, ",\n\t\"person_guid\": \"%s\",\n\t\"urls\": [", p->guid); struct registry_json_walk_person_urls_callback c = { p, NULL, w, 0 }; avl_traverse(&p->person_urls, registry_json_person_url_callback, &c); - buffer_strcat(w->response.data, "\n\t]\n"); + buffer_json_array_close(w->response.data); // urls registry_json_footer(w); registry_unlock(); - return 200; + return HTTP_RESP_OK; } // ---------------------------------------------------------------------------- @@ -242,14 +235,14 @@ int registry_request_delete_json(RRDHOST *host, struct web_client *w, char *pers registry_json_header(host, w, "delete", REGISTRY_STATUS_FAILED); registry_json_footer(w); registry_unlock(); - return 412; + return HTTP_RESP_PRECOND_FAIL; } // generate the response registry_json_header(host, w, "delete", REGISTRY_STATUS_OK); registry_json_footer(w); registry_unlock(); - return 200; + return HTTP_RESP_OK; } // ---------------------------------------------------------------------------- @@ -267,19 +260,19 @@ int registry_request_search_json(RRDHOST *host, struct web_client *w, char *pers registry_json_header(host, w, "search", REGISTRY_STATUS_FAILED); registry_json_footer(w); registry_unlock(); - return 404; + return HTTP_RESP_NOT_FOUND; } registry_json_header(host, w, "search", REGISTRY_STATUS_OK); - buffer_strcat(w->response.data, ",\n\t\"urls\": ["); + buffer_json_member_add_array(w->response.data, "urls"); struct registry_json_walk_person_urls_callback c = { NULL, m, w, 0 }; dictionary_walkthrough_read(m->machine_urls, registry_json_machine_url_callback, &c); - buffer_strcat(w->response.data, "\n\t]\n"); + buffer_json_array_close(w->response.data); registry_json_footer(w); registry_unlock(); - return 200; + return HTTP_RESP_OK; } // ---------------------------------------------------------------------------- @@ -346,11 +339,11 @@ int registry_request_switch_json(RRDHOST *host, struct web_client *w, char *pers // generate the response registry_json_header(host, w, "switch", REGISTRY_STATUS_OK); - buffer_sprintf(w->response.data, ",\n\t\"person_guid\": \"%s\"", np->guid); + buffer_json_member_add_string(w->response.data, "person_guid", np->guid); registry_json_footer(w); registry_unlock(); - return 200; + return HTTP_RESP_OK; } // ---------------------------------------------------------------------------- @@ -380,7 +373,7 @@ void registry_statistics(void) { rrddim_add(sts, "sessions", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); } - rrddim_set(sts, "sessions", registry.usages_count); + rrddim_set(sts, "sessions", (collected_number)registry.usages_count); rrdset_done(sts); // ------------------------------------------------------------------------ @@ -408,11 +401,11 @@ void registry_statistics(void) { rrddim_add(stc, "machines_urls", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); } - rrddim_set(stc, "persons", registry.persons_count); - rrddim_set(stc, "machines", registry.machines_count); - rrddim_set(stc, "urls", registry.urls_count); - rrddim_set(stc, "persons_urls", registry.persons_urls_count); - rrddim_set(stc, "machines_urls", registry.machines_urls_count); + rrddim_set(stc, "persons", (collected_number)registry.persons_count); + rrddim_set(stc, "machines", (collected_number)registry.machines_count); + rrddim_set(stc, "urls", (collected_number)registry.urls_count); + rrddim_set(stc, "persons_urls", (collected_number)registry.persons_urls_count); + rrddim_set(stc, "machines_urls", (collected_number)registry.machines_urls_count); rrdset_done(stc); // ------------------------------------------------------------------------ @@ -440,10 +433,10 @@ void registry_statistics(void) { rrddim_add(stm, "machines_urls", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE); } - rrddim_set(stm, "persons", registry.persons_memory + dictionary_stats_for_registry(registry.persons)); - rrddim_set(stm, "machines", registry.machines_memory + dictionary_stats_for_registry(registry.machines)); - rrddim_set(stm, "urls", registry.urls_memory); - rrddim_set(stm, "persons_urls", registry.persons_urls_memory); - rrddim_set(stm, "machines_urls", registry.machines_urls_memory); + rrddim_set(stm, "persons", (collected_number)registry.persons_memory + dictionary_stats_for_registry(registry.persons)); + rrddim_set(stm, "machines", (collected_number)registry.machines_memory + dictionary_stats_for_registry(registry.machines)); + rrddim_set(stm, "urls", (collected_number)registry.urls_memory); + rrddim_set(stm, "persons_urls", (collected_number)registry.persons_urls_memory); + rrddim_set(stm, "machines_urls", (collected_number)registry.machines_urls_memory); rrdset_done(stm); } diff --git a/streaming/README.md b/streaming/README.md index 37d2c261..bf11f32e 100644 --- a/streaming/README.md +++ b/streaming/README.md @@ -1,152 +1,160 @@ ---- -title: "Streaming and replication" -description: "Replicate and mirror Netdata's metrics through real-time streaming from child to parent nodes. Then combine, correlate, and export." -custom_edit_url: https://github.com/netdata/netdata/edit/master/streaming/README.md ---- +# Streaming and replication reference +This document contains advanced streaming options and suggested deployment options for production. +If you haven't already done so, we suggest you first go through the +[quick introduction to streaming](https://github.com/netdata/netdata/blob/master/docs/metrics-storage-management/enable-streaming.md) +, for your first, basic parent child setup. -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](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. - -When one Netdata node streams metrics another, the receiving instance can use the data for all features of a typical Netdata node, for example: - -- Visualize metrics with a dashboard -- Run health checks that trigger alarms and send alarm notifications -- Export metrics to an external time-series database - - - - -## Supported configurations - -### Netdata without a database or web API (headless collector) - -A local Netdata Agent (child), **without any database or alarms**, collects metrics and sends them to another Netdata node -(parent). -The same parent can collect data for any number of child nodes and serves alerts for each child. - -The node menu shows a list of all "databases streamed to" the parent. Clicking one of those links allows the user to -view the full dashboard of the child node. The URL has the form -`http://parent-host:parent-port/host/child-host/`. +## Configuration +There are two files responsible for configuring Netdata's streaming capabilities: `stream.conf` and `netdata.conf`. -In a headless setup, the child acts as a plain data collector. It spawns all external plugins, but instead of maintaining a -local database and accepting dashboard requests, it streams all metrics to the parent. +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`. -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](https://github.com/netdata/netdata/blob/master/docs/guides/configure/performance.md). +``` +sudo ./edit-config stream.conf +sudo ./edit-config netdata.conf +``` +### `stream.conf` -### Database Replication +The `stream.conf` file contains three sections. The `[stream]` section is for configuring child nodes. -The local Netdata Agent (child), **with a local database (and possibly alarms)**, collects metrics and -sends them to another Netdata node (parent). +The `[API_KEY]` and `[MACHINE_GUID]` sections are both for configuring parent nodes, and share the same settings. +`[API_KEY]` settings affect every child node using that key, whereas `[MACHINE_GUID]` settings affect only the child +node with a matching GUID. -The user can use all the functions **at both** `http://child-ip:child-port/` and -`http://parent-host:parent-port/host/child-host/`. +The file `/var/lib/netdata/registry/netdata.public.unique.id` contains a random GUID that **uniquely identifies each +node**. This file is automatically generated by Netdata the first time it is started and remains unaltered forever. -The child and the parent may have different data retention policies for the same metrics. +#### `[stream]` section -Alerts for the child are triggered by **both** the child and the parent. -It is possible to enable different alert configurations on the parent and the child. +| Setting | Default | Description | +| :---------------------------------------------- | :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `enabled` | `no` | Whether this node streams metrics to any parent. Change to `yes` to enable streaming. | +| [`destination`](#destination) | ` ` | A space-separated list of parent nodes to attempt to stream to, with the first available parent receiving metrics, using the following format: `[PROTOCOL:]HOST[%INTERFACE][:PORT][:SSL]`. [Read more →](#destination) | +| `ssl skip certificate verification` | `yes` | If you want to accept self-signed or expired certificates, set to `yes` and uncomment. | +| `CApath` | `/etc/ssl/certs/` | The directory where known certificates are found. Defaults to OpenSSL's default path. | +| `CAfile` | `/etc/ssl/certs/cert.pem` | Add a parent node certificate to the list of known certificates in `CAPath`. | +| `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](https://github.com/netdata/netdata/blob/master/libnetdata/simple_pattern/README.md) to filter which charts are streamed. [Read more →](#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. | -In order for custom chart names on the child to work correctly, follow the form `type.name`. The parent will truncate the `type` part and substitute the original chart `type` to store the name in the database. +### `[API_KEY]` and `[MACHINE_GUID]` sections -### Netdata proxies +| Setting | Default | Description | +| :---------------------------------------------- | :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `enabled` | `no` | Whether this API KEY enabled or disabled. | +| [`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 →](#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](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 →](#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. | +| `default proxy destination` | ` ` | Space-separated list of `IP:PORT` for proxies. | +| `default proxy api key` | ` ` | The `API_KEY` of the proxy. | +| `default send charts matching` | `*` | See [`send charts matching`](#send-charts-matching). | -The local Netdata Agent(child), with or without a database, collects metrics and sends them to another -Netdata node(**proxy**), which may or may not maintain a database, which forwards them to another -Netdata (parent). +#### `destination` -Alerts for the child can be triggered by any of the involved hosts that maintains a database. +A space-separated list of parent nodes to attempt to stream to, with the first available parent receiving metrics, using +the following format: `[PROTOCOL:]HOST[%INTERFACE][:PORT][:SSL]`. -You can daisy-chain any number of Netdata, each with or without a database and -with or without alerts for the child metrics. +- `PROTOCOL`: `tcp`, `udp`, or `unix`. (only tcp and unix are supported by parent nodes) +- `HOST`: A IPv4, IPv6 IP, or a hostname, or a unix domain socket path. IPv6 IPs should be given with brackets + `[ip:address]`. +- `INTERFACE` (IPv6 only): The network interface to use. +- `PORT`: The port number or service name (`/etc/services`) to use. +- `SSL`: To enable TLS/SSL encryption of the streaming connection. -### Mix and match with exporting engine +To enable TCP streaming to a parent node at `203.0.113.0` on port `20000` and with TLS/SSL encryption: -All nodes that maintain a database can also send their data to an external database. -This allows quite complex setups. +```conf +[stream] + destination = tcp:203.0.113.0:20000:SSL +``` -Example: +#### `send charts matching` -1. Netdata nodes `A` and `B` do not maintain a database and stream metrics to Netdata node `C`(live streaming functionality). -2. Netdata node `C` maintains a database for `A`, `B`, `C` and archives all metrics to `graphite` with 10 second detail (exporting functionality). -3. Netdata node `C` also streams data for `A`, `B`, `C` to Netdata `D`, which also collects data from `E`, `F` and `G` from another DMZ (live streaming functionality). -4. Netdata node `D` is just a proxy, without a database, that streams all data to a remote site at Netdata `H`. -5. Netdata node `H` maintains a database for `A`, `B`, `C`, `D`, `E`, `F`, `G`, `H` and sends all data to `opentsdb` with 5 seconds detail (exporting functionality) -6. Alerts are triggered by `H` for all hosts. -7. Users can use all Netdata nodes that maintain a database to view metrics (i.e. at `H` all hosts can be viewed). +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. -## Configuration +The default is a single wildcard `*`, which streams all charts. -The following options affect how Netdata streams: +To send only a few charts, list them explicitly, or list a group using a wildcard. To send _only_ the `apps.cpu` chart +and charts with contexts beginning with `system.`: -``` -[global] - memory mode = none | ram | save | map | dbengine +```conf +[stream] + send charts matching = apps.cpu system.* ``` -`[global].memory mode = none` disables the database at this host. This also disables health -monitoring because a node can't have health monitoring without a database. +To send all but a few charts, use `!` to create a negative match. To send _all_ charts _but_ `apps.cpu`: +```conf +[stream] + send charts matching = !apps.cpu * ``` -[web] - mode = none | static-threaded - accept a streaming request every seconds = 0 -``` - -`[web].mode = none` disables the API (Netdata will not listen to any ports). -This also disables the registry (there cannot be a registry without an API). -`accept a streaming request every seconds` can be used to set a limit on how often a parent node will accept streaming -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". +#### `allow from` -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). +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. -### Streaming configuration +The default is `*`, which accepts all requests including the `API_KEY`. -The new file `stream.conf` contains streaming configuration for a sending and a receiving Netdata node. +To allow from only a specific IP address: -To configure streaming on your system: -1. Generate an API key using `uuidgen`. Note: API keys are just random GUIDs. You can use the same API key on all your Netdata, or use a different API key for any pair of sending-receiving Netdata nodes. +```conf +[API_KEY] + allow from = 203.0.113.10 +``` -2. Authorize the communication between a pair of sending-receiving Netdata nodes using the generated API key. -Once the communication is authorized, the sending Netdata node can push metrics for any number of hosts. +To allow all IPs starting with `10.*`, except `10.1.2.3`: -3. To edit `stream.conf`, run `/etc/netdata/edit-config stream.conf` +```conf +[API_KEY] + allow from = !10.1.2.3 10.* +``` -The following sections describe how you can configure sending and receiving Netdata nodes. +> If you set specific IP addresses here, and also use the `allow connections` setting in the `[web]` section of +> `netdata.conf`, be sure to add the IP address there so that it can access the API port. +#### `default memory mode` - +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 + efficiently spills them to disk for long-term storage. +- `ram`: Stores metrics _only_ in memory, which means metrics are lost when Netdata stops or restarts. Ideal for + streaming configurations that use ephemeral nodes. +- `save`: Stores metrics in memory, but saves metrics to disk when Netdata stops or restarts, and loads historical + metrics on start. +- `map`: Stores metrics in memory-mapped files, like swap, with constant disk write. +- `none`: No database. -##### Options for the sending node +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](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`. -This is the section for the sending Netdata node. On the receiving node, `[stream].enabled` can be `no`. -If it is `yes`, the receiving node will also stream the metrics to another node (i.e. it will be -a proxy). +### `netdata.conf` -``` -[stream] - enabled = yes | no - destination = IP:PORT[:SSL] ... - api key = XXXXXXXXXXX +| Setting | Default | Description | +| :----------------------------------------- | :---------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`[global]` section** | | | +| `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](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`. | -[API_KEY] - enabled = yes | no +### Basic use cases -[MACHINE_GUID] - enabled = yes | no -``` -This is an overview of how these options can be combined: +This is an overview of how the main options can be combined: | target|memory
mode|web
mode|stream
enabled|exporting|alarms|dashboard| |------|:-------------:|:----------:|:----------------:|:-----:|:----:|:-------:| @@ -155,171 +163,26 @@ This is an overview of how these options can be combined: | proxy with db|not `none`|not `none`|`yes`|possible|possible|yes| | central netdata|not `none`|not `none`|`no`|possible|possible|yes| -For the options to encrypt the data stream between the child and the parent, refer to [securing the communication](#securing-streaming-communications) - +### Per-child settings -##### Options for the receiving node +While the `[API_KEY]` section applies settings for any child node using that key, you can also use per-child settings +with the `[MACHINE_GUID]` section. -For a receiving Netdata node, the `stream.conf` looks like this: +For example, the metrics streamed from only the child node with `MACHINE_GUID` are saved in memory, not using the +default `dbengine` as specified by the `API_KEY`, and alarms are disabled. -```sh -# replace API_KEY with your uuidgen generated GUID +```conf [API_KEY] enabled = yes - default history = 3600 - default memory mode = save + default memory mode = dbengine health enabled by default = auto allow from = * -``` - -You can add many such sections, one for each API key. The above are used as default values for -all hosts pushed with this API key. - -You can also add sections like this: -```sh -# replace MACHINE_GUID with the child /var/lib/netdata/registry/netdata.public.unique.id [MACHINE_GUID] enabled = yes - history = 3600 memory mode = save - health enabled = yes - allow from = * -``` - -The above is the parent configuration of a single host, at the parent end. `MACHINE_GUID` is -the unique id the Netdata generating the metrics (i.e. the Netdata that originally collects -them `/var/lib/netdata/registry/netdata.unique.id`). So, metrics for Netdata `A` that pass through -any number of other Netdata, will have the same `MACHINE_GUID`. - -You can also use `default memory mode = dbengine` for an API key or `memory mode = dbengine` for - a single host. The additional `page cache size` and `dbengine multihost disk space` configuration options - are inherited from the global Netdata configuration. - -##### Allow from - -`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. - - -##### Tracing - -When a child is trying to push metrics to a parent or proxy, it logs entries like these: - + health enabled = no ``` -2017-02-25 01:57:44: netdata: ERROR: Failed to connect to '10.11.12.1', port '19999' (errno 111, Connection refused) -2017-02-25 01:57:44: netdata: ERROR: STREAM costa-pc [send to 10.11.12.1:19999]: failed to connect -2017-02-25 01:58:04: netdata: INFO : STREAM costa-pc [send to 10.11.12.1:19999]: initializing communication... -2017-02-25 01:58:04: netdata: INFO : STREAM costa-pc [send to 10.11.12.1:19999]: waiting response from remote netdata... -2017-02-25 01:58:14: netdata: INFO : STREAM costa-pc [send to 10.11.12.1:19999]: established communication - sending metrics... -2017-02-25 01:58:14: netdata: ERROR: STREAM costa-pc [send]: discarding 1900 bytes of metrics already in the buffer. -2017-02-25 01:58:14: netdata: INFO : STREAM costa-pc [send]: ready - sending metrics... -``` - -The receiving end (proxy or parent) logs entries like these: - -``` -2017-02-25 01:58:04: netdata: INFO : STREAM [receive from [10.11.12.11]:33554]: new client connection. -2017-02-25 01:58:04: netdata: INFO : STREAM costa-pc [10.11.12.11]:33554: receive thread created (task id 7698) -2017-02-25 01:58:14: netdata: INFO : Host 'costa-pc' with guid '12345678-b5a6-11e6-8a50-00508db7e9c9' initialized, os: linux, update every: 1, memory mode: ram, history entries: 3600, streaming: disabled, health: enabled, cache_dir: '/var/cache/netdata/12345678-b5a6-11e6-8a50-00508db7e9c9', varlib_dir: '/var/lib/netdata/12345678-b5a6-11e6-8a50-00508db7e9c9', health_log: '/var/lib/netdata/12345678-b5a6-11e6-8a50-00508db7e9c9/health/health-log.db', alarms default handler: '/usr/libexec/netdata/plugins.d/alarm-notify.sh', alarms default recipient: 'root' -2017-02-25 01:58:14: netdata: INFO : STREAM costa-pc [receive from [10.11.12.11]:33554]: initializing communication... -2017-02-25 01:58:14: netdata: INFO : STREAM costa-pc [receive from [10.11.12.11]:33554]: receiving metrics... -``` - -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](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: - -``` -[stream] - destination = host:port:SSL -``` - -The word `SSL` appended to the end of the destination tells the child that connections must be encrypted. - -> While Netdata uses Transport Layer Security (TLS) 1.2 to encrypt communications rather than the obsolete SSL protocol, -> it's still common practice to refer to encrypted web connections as `SSL`. Many vendors, like Nginx and even Netdata -> itself, use `SSL` in configuration files, whereas documentation will always refer to encrypted communications as `TLS` -> or `TLS/SSL`. - -#### Certificate verification - -When TLS/SSL is enabled on the child, the default behavior will be to not connect with the parent unless the server's certificate can be verified via the default chain. In case you want to avoid this check, add the following to the child's `stream.conf` file: - -``` -[stream] - ssl skip certificate verification = yes -``` - -#### Trusted certificate - -If you've enabled [certificate verification](#certificate-verification), you might see errors from the OpenSSL library when there's a problem with checking the certificate chain (`X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY`). More importantly, OpenSSL will reject self-signed certificates. - -Given these known issues, you have two options. If you trust your certificate, you can set the options `CApath` and `CAfile` to inform Netdata where your certificates, and the certificate trusted file, are stored. - -For more details about these options, you can read about [verify locations](https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_load_verify_locations.html). - -Before you changed your streaming configuration, you need to copy your trusted certificate to your child system and add the certificate to OpenSSL's list. - -On most Linux distributions, the `update-ca-certificates` command searches inside the `/usr/share/ca-certificates` directory for certificates. You should double-check by reading the `update-ca-certificate` manual (`man update-ca-certificate`), and then change the directory in the below commands if needed. - -If you have `sudo` configured on your child system, you can use that to run the following commands. If not, you'll have to log in as `root` to complete them. - -``` -# mkdir /usr/share/ca-certificates/netdata -# cp parent_cert.pem /usr/share/ca-certificates/netdata/parent_cert.crt -# chown -R netdata.netdata /usr/share/ca-certificates/netdata/ -``` - -First, you create a new directory to store your certificates for Netdata. Next, you need to change the extension on your certificate from `.pem` to `.crt` so it's compatible with `update-ca-certificate`. Finally, you need to change permissions so the user that runs Netdata can access the directory where you copied in your certificate. - -Next, edit the file `/etc/ca-certificates.conf` and add the following line: - -``` -netdata/parent_cert.crt -``` - -Now you update the list of certificates running the following, again either as `sudo` or `root`: - -``` -# update-ca-certificates -``` - -> Some Linux distributions have different methods of updating the certificate list. For more details, please read this -> guide on [adding trusted root certificates](https://github.com/Busindre/How-to-Add-trusted-root-certificates). - -Once you update your certificate list, you can set the stream parameters for Netdata to trust the parent certificate. Open `stream.conf` for editing and change the following lines: - -``` -[stream] - CApath = /etc/ssl/certs/ - CAfile = /etc/ssl/certs/parent_cert.pem -``` - -With this configuration, the `CApath` option tells Netdata to search for trusted certificates inside `/etc/ssl/certs`. The `CAfile` option specifies the Netdata parent certificate is located at `/etc/ssl/certs/parent_cert.pem`. With this configuration, you can skip using the system's entire list of certificates and use Netdata's parent certificate instead. - -#### Expected behaviors - -With the introduction of TLS/SSL, the parent-child communication behaves as shown in the table below, depending on the following configurations: - -- **Parent TLS (Yes/No)**: Whether the `[web]` section in `netdata.conf` has `ssl key` and `ssl certificate`. -- **Parent port TLS (-/force/optional)**: Depends on whether the `[web]` section `bind to` contains a `^SSL=force` or `^SSL=optional` directive on the port(s) used for streaming. -- **Child TLS (Yes/No)**: Whether the destination in the child's `stream.conf` has `:SSL` at the end. -- **Child TLS Verification (yes/no)**: Value of the child's `stream.conf` `ssl skip certificate verification` parameter (default is no). - -| Parent TLS enabled|Parent port SSL|Child TLS|Child SSL Ver.|Behavior| -|:----------------:|:-------------:|:-------:|:------------:|:-------| -| No|-|No|no|Legacy behavior. The parent-child stream is unencrypted.| -| Yes|force|No|no|The parent rejects the child connection.| -| Yes|-/optional|No|no|The parent-child stream is unencrypted (expected situation for legacy child nodes and newer parent nodes)| -| Yes|-/force/optional|Yes|no|The parent-child stream is encrypted, provided that the parent has a valid TLS/SSL certificate. Otherwise, the child refuses to connect.| -| Yes|-/force/optional|Yes|yes|The parent-child stream is encrypted.| ### Streaming compression @@ -416,83 +279,147 @@ Same thing applies with the `[MACHINE_GUID]` configuration. [MACHINE_GUID] enable compression = yes | no ``` -## Viewing remote host dashboards, using mirrored databases -On any receiving Netdata, that maintains remote databases and has its web server enabled, -The node menu will include a list of the mirrored databases. +### Securing streaming with TLS/SSL -![image](https://cloud.githubusercontent.com/assets/2662304/24080824/24cd2d3c-0caf-11e7-909d-a8dd1dbb95d7.png) +Netdata does not activate TLS encryption by default. To encrypt streaming connections, you first need to [enable TLS +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: -Selecting any of these, the server will offer a dashboard using the mirrored metrics. +``` +[stream] + destination = host:port:SSL +``` -## Monitoring ephemeral nodes +The word `SSL` appended to the end of the destination tells the child that connections must be encrypted. -Auto-scaling is probably the most trendy service deployment strategy these days. +> While Netdata uses Transport Layer Security (TLS) 1.2 to encrypt communications rather than the obsolete SSL protocol, +> it's still common practice to refer to encrypted web connections as `SSL`. Many vendors, like Nginx and even Netdata +> itself, use `SSL` in configuration files, whereas documentation will always refer to encrypted communications as `TLS` +> or `TLS/SSL`. -Auto-scaling detects the need for additional resources and boots VMs on demand, based on a template. Soon after they start running the applications, a load balancer starts distributing traffic to them, allowing the service to grow horizontally to the scale needed to handle the load. When demands falls, auto-scaling starts shutting down VMs that are no longer needed. +#### Certificate verification -![Monitoring ephemeral nodes with Netdata](https://cloud.githubusercontent.com/assets/2662304/23627426/65a9074a-02b9-11e7-9664-cd8f258a00af.png) +When TLS/SSL is enabled on the child, the default behavior will be to not connect with the parent unless the server's +certificate can be verified via the default chain. In case you want to avoid this check, add the following to the +child's `stream.conf` file: -What a fantastic feature for controlling infrastructure costs! Pay only for what you need for the time you need it! +``` +[stream] + ssl skip certificate verification = yes +``` + +#### Trusted certificate + +If you've enabled [certificate verification](#certificate-verification), you might see errors from the OpenSSL library +when there's a problem with checking the certificate chain (`X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY`). More +importantly, OpenSSL will reject self-signed certificates. -In auto-scaling, all servers are ephemeral, they live for just a few hours. Every VM is a brand new instance of the application, that was automatically created based on a template. +Given these known issues, you have two options. If you trust your certificate, you can set the options `CApath` and +`CAfile` to inform Netdata where your certificates, and the certificate trusted file, are stored. -So, how can we monitor them? How can we be sure that everything is working as expected on all of them? +For more details about these options, you can read about [verify +locations](https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_load_verify_locations.html). -### The Netdata way +Before you changed your streaming configuration, you need to copy your trusted certificate to your child system and add +the certificate to OpenSSL's list. -We recently made a significant improvement at the core of Netdata to support monitoring such setups. +On most Linux distributions, the `update-ca-certificates` command searches inside the `/usr/share/ca-certificates` +directory for certificates. You should double-check by reading the `update-ca-certificate` manual (`man +update-ca-certificate`), and then change the directory in the below commands if needed. -Following the Netdata way of monitoring, we wanted: +If you have `sudo` configured on your child system, you can use that to run the following commands. If not, you'll have +to log in as `root` to complete them. + +``` +# mkdir /usr/share/ca-certificates/netdata +# cp parent_cert.pem /usr/share/ca-certificates/netdata/parent_cert.crt +# chown -R netdata.netdata /usr/share/ca-certificates/netdata/ +``` -1. **real-time performance monitoring**, collecting ***thousands of metrics per server per second***, visualized in interactive, automatically created dashboards. -2. **real-time alarms**, for all nodes. -3. **zero configuration**, all ephemeral servers should have exactly the same configuration, and nothing should be configured at any system for each of the ephemeral nodes. We shouldn't care if 10 or 100 servers are spawned to handle the load. -4. **self-cleanup**, so that nothing needs to be done for cleaning up the monitoring infrastructure from the hundreds of nodes that may have been monitored through time. +First, you create a new directory to store your certificates for Netdata. Next, you need to change the extension on your +certificate from `.pem` to `.crt` so it's compatible with `update-ca-certificate`. Finally, you need to change +permissions so the user that runs Netdata can access the directory where you copied in your certificate. + +Next, edit the file `/etc/ca-certificates.conf` and add the following line: + +``` +netdata/parent_cert.crt +``` + +Now you update the list of certificates running the following, again either as `sudo` or `root`: + +``` +# update-ca-certificates +``` -### How it works +> Some Linux distributions have different methods of updating the certificate list. For more details, please read this +> guide on [adding trusted root certificates](https://github.com/Busindre/How-to-Add-trusted-root-certificates). -All monitoring solutions, including Netdata, work like this: +Once you update your certificate list, you can set the stream parameters for Netdata to trust the parent certificate. +Open `stream.conf` for editing and change the following lines: -1. Collect metrics from the system and the running applications -2. Store metrics in a time-series database -3. Examine metrics periodically, for triggering alarms and sending alarm notifications -4. Visualize metrics so that users can see what exactly is happening +``` +[stream] + CApath = /etc/ssl/certs/ + CAfile = /etc/ssl/certs/parent_cert.pem +``` -Netdata used to be self-contained, so that all these functions were handled entirely by each server. The changes we made, allow each Netdata to be configured independently for each function. So, each Netdata can now act as: +With this configuration, the `CApath` option tells Netdata to search for trusted certificates inside `/etc/ssl/certs`. +The `CAfile` option specifies the Netdata parent certificate is located at `/etc/ssl/certs/parent_cert.pem`. With this +configuration, you can skip using the system's entire list of certificates and use Netdata's parent certificate instead. -- A self-contained system, much like it used to be. -- A data collector that collects metrics from a host and pushes them to another Netdata (with or without a local database and alarms). -- A proxy, which receives metrics from other hosts and pushes them immediately to other Netdata servers. Netdata proxies can also be `store and forward proxies` meaning that they are able to maintain a local database for all metrics passing through them (with or without alarms). -- A time-series database node, where data are kept, alarms are run and queries are served to visualise the metrics. +#### Expected behaviors -### Configuring an auto-scaling setup +With the introduction of TLS/SSL, the parent-child communication behaves as shown in the table below, depending on the +following configurations: -![A diagram of an auto-scaling setup with Netdata](https://user-images.githubusercontent.com/1153921/84290043-0c1c1600-aaf8-11ea-9757-dd8dd8a8ec6c.png) +- **Parent TLS (Yes/No)**: Whether the `[web]` section in `netdata.conf` has `ssl key` and `ssl certificate`. +- **Parent port TLS (-/force/optional)**: Depends on whether the `[web]` section `bind to` contains a `^SSL=force` or + `^SSL=optional` directive on the port(s) used for streaming. +- **Child TLS (Yes/No)**: Whether the destination in the child's `stream.conf` has `:SSL` at the end. +- **Child TLS Verification (yes/no)**: Value of the child's `stream.conf` `ssl skip certificate verification` + parameter (default is no). -You need a Netdata parent. This node should not be ephemeral. It will be the node where all ephemeral child -nodes will send their metrics. +| Parent TLS enabled | Parent port SSL | Child TLS | Child SSL Ver. | Behavior | +| :----------------- | :--------------- | :-------- | :------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | +| No | - | No | no | Legacy behavior. The parent-child stream is unencrypted. | +| Yes | force | No | no | The parent rejects the child connection. | +| Yes | -/optional | No | no | The parent-child stream is unencrypted (expected situation for legacy child nodes and newer parent nodes) | +| Yes | -/force/optional | Yes | no | The parent-child stream is encrypted, provided that the parent has a valid TLS/SSL certificate. Otherwise, the child refuses to connect. | +| Yes | -/force/optional | Yes | yes | The parent-child stream is encrypted. | -The parent will need to authorize child nodes to receive their metrics. This is done with an API key. +### Proxy -#### API keys +A proxy is a node that receives metrics from a child, then streams them onward to a parent. To configure a proxy, +configure it as a receiving and a sending Netdata at the same time. -API keys are just random GUIDs. Use the Linux command `uuidgen` to generate one. You can use the same API key for all your child nodes, or you can configure one API for each of them. This is entirely your decision. +Netdata proxies may or may not maintain a database for the metrics passing through them. When they maintain a database, +they can also run health checks (alarms and notifications) for the remote host that is streaming the metrics. -We suggest to use the same API key for each ephemeral node template you have, so that all replicas of the same ephemeral node will have exactly the same configuration. +In the following example, the proxy receives metrics from a child node using the `API_KEY` of +`66666666-7777-8888-9999-000000000000`, then stores metrics using `dbengine`. It then uses the `API_KEY` of +`11111111-2222-3333-4444-555555555555` to proxy those same metrics on to a parent node at `203.0.113.0`. -I will use this API_KEY: `11111111-2222-3333-4444-555555555555`. Replace it with your own. +```conf +[stream] + enabled = yes + destination = 203.0.113.0 + api key = 11111111-2222-3333-4444-555555555555 -#### Configuring the parent +[66666666-7777-8888-9999-000000000000] + enabled = yes + default memory mode = dbengine +``` -To configure the parent node: +### Ephemeral nodes -1. On the parent node, edit `stream.conf` by using the `edit-config` script: -`/etc/netdata/edit-config stream.conf` +Netdata can help you monitor ephemeral nodes, such as containers in an auto-scaling infrastructure, by always streaming +metrics to any number of permanently-running parent nodes. -2. Set the following parameters: +On the parent, set the following in `stream.conf`: -```bash +```conf [11111111-2222-3333-4444-555555555555] # enable/disable this API key enabled = yes @@ -507,24 +434,7 @@ To configure the parent node: health enabled by default = auto ``` -_`stream.conf` on the parent, to enable receiving metrics from its child nodes using the API key._ - -If you used many API keys, you can add one such section for each API key. - -When done, restart Netdata on the parent node. It is now ready to receive metrics. - -Note that `health enabled by default = auto` will still trigger `last_collected` alarms, if a connected child does not exit gracefully. If the `netdata` process running on the child is -stopped, it will close the connection to the parent, ensuring that no `last_collected` alarms are triggered. For example, a proper container restart would first terminate -the `netdata` process, but a system power issue would leave the connection open on the parent side. In the second case, you will still receive alarms. - -#### Configuring the child nodes - -To configure the child node: - -1. On the child node, edit `stream.conf` by using the `edit-config` script: -`/etc/netdata/edit-config stream.conf` - -2. Set the following parameters: +On the child nodes, set the following in `stream.conf`: ```bash [stream] @@ -534,44 +444,26 @@ To configure the child node: # the IP and PORT of the parent destination = 10.11.12.13:19999 - # the API key to use + # the API key to use api key = 11111111-2222-3333-4444-555555555555 ``` -_`stream.conf` on child nodes, to enable pushing metrics to their parent at `10.11.12.13:19999`._ - -Using just the above configuration, the child nodes will be pushing their metrics to the parent Netdata, but they will still maintain a local database of the metrics and run health checks. To disable them, edit `/etc/netdata/netdata.conf` and set: +In addition, edit `netdata.conf` on each child node to disable the database and alarms. ```bash [global] # disable the local database - memory mode = none + memory mode = none [health] # disable health checks enabled = no ``` -_`netdata.conf` configuration on child nodes, to disable the local database and health checks._ - -Keep in mind that setting `memory mode = none` will also force `[health].enabled = no` (health checks require access to a local database). But you can keep the database and disable health checks if you need to. You are however sending all the metrics to the parent node, which can handle the health checking (`[health].enabled = yes`) - -#### Netdata unique ID - -The file `/var/lib/netdata/registry/netdata.public.unique.id` contains a random GUID that **uniquely identifies each Netdata Agent**. This file is automatically generated, by Netdata, the first time it is started and remains unaltered forever. - -> If you are building an image to be used for automated provisioning of autoscaled VMs, it important to delete that file from the image, so that each instance of your image will generate its own. - -#### Troubleshooting metrics streaming +## Troubleshooting Both parent and child nodes log information at `/var/log/netdata/error.log`. -To obtain the error logs, run the following on both the parent and child nodes: - -``` -tail -f /var/log/netdata/error.log | grep STREAM -``` - If the child manages to connect to the parent you will see something like (on the parent): ``` @@ -591,53 +483,7 @@ and something like this on the child: 2017-03-09 09:38:28: netdata: INFO : STREAM xxx [send to box:19999]: established communication - sending metrics... ``` -### Archiving to a time-series database - -The parent Netdata node can also archive metrics, for all its child nodes, to a time-series database. At the time of -this writing, Netdata supports: - -- graphite -- opentsdb -- prometheus -- json document DBs -- all the compatibles to the above (e.g. kairosdb, influxdb, etc) - -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: - -![Diagram showing an example configuration for archiving to a time-series -database](https://user-images.githubusercontent.com/1153921/84291308-c2ccc600-aaf9-11ea-98a9-89ccbf3a62dd.png) - -### An advanced setup - -Netdata also supports `proxies` with and without a local database, and data retention can be different between all nodes. - -This means a setup like the following is also possible: - -

- -

- -## Proxies - -A proxy is a Netdata node that is receiving metrics from a Netdata node, and streams them to another Netdata node. - -Netdata proxies may or may not maintain a database for the metrics passing through them. -When they maintain a database, they can also run health checks (alarms and notifications) -for the remote host that is streaming the metrics. - -To configure a proxy, configure it as a receiving and a sending Netdata at the same time, -using `stream.conf`. - -The sending side of a Netdata proxy, connects and disconnects to the final destination of the -metrics, following the same pattern of the receiving side. - -For a practical example see [Monitoring ephemeral nodes](#monitoring-ephemeral-nodes). - -## Troubleshooting streaming connections - -This section describes the most common issues you might encounter when connecting parent and child nodes. +The following sections describe the most common issues you might encounter when connecting parent and child nodes. ### Slow connections between parent and child @@ -655,8 +501,8 @@ On the parent side, you may see various error messages, most commonly the follow netdata ERROR : STREAM_PARENT[CHILD HOSTNAME,[CHILD IP]:CHILD PORT] : read failed: end of file ``` -Another common problem in slow connections is the CHILD sending a partial message to the parent. In this case, -the parent will write the following in its `error.log`: +Another common problem in slow connections is the child sending a partial message to the parent. In this case, the +parent will write the following to its `error.log`: ``` ERROR : STREAM_RECEIVER[CHILD HOSTNAME,[CHILD IP]:CHILD PORT] : sent command 'B' which is not known by netdata, for host 'HOSTNAME'. Disabling it. @@ -681,11 +527,11 @@ down), you will see the following in the child's `error.log`. ERROR : STREAM_SENDER[HOSTNAME] : Failed to connect to 'PARENT IP', port 'PARENT PORT' (errno 113, No route to host) ``` -### 'Is this a Netdata node?' +### 'Is this a Netdata?' This question can appear when Netdata starts the stream and receives an unexpected response. This error can appear when the parent is using SSL and the child tries to connect using plain text. You will also see this message when -Netdata connects to another server that isn't a Netdata node. The complete error message will look like this: +Netdata connects to another server that isn't Netdata. The complete error message will look like this: ``` ERROR : STREAM_SENDER[CHILD HOSTNAME] : STREAM child HOSTNAME [send to PARENT HOSTNAME:PARENT PORT]: server is not replying properly (is it a netdata?). @@ -710,15 +556,15 @@ STREAM [receive from [child HOSTNAME]:child IP]: `MESSAGE`. Forbidding access." `MESSAGE` will have one of the following patterns: -- `request without KEY` : The message received is incomplete and the KEY value can be API, hostname, machine GUID. -- `API key 'VALUE' is not valid GUID`: The UUID received from child does not have the format defined in [RFC 4122] - (https://tools.ietf.org/html/rfc4122) -- `machine GUID 'VALUE' is not GUID.`: This error with machine GUID is like the previous one. -- `API key 'VALUE' is not allowed`: This stream has a wrong API key. -- `API key 'VALUE' is not permitted from this IP`: The IP is not allowed to use STREAM with this parent. -- `machine GUID 'VALUE' is not allowed.`: The GUID that is trying to send stream is not allowed. -- `Machine GUID 'VALUE' is not permitted from this IP. `: The IP does not match the pattern or IP allowed to connect - to use stream. +- `request without KEY` : The message received is incomplete and the KEY value can be API, hostname, machine GUID. +- `API key 'VALUE' is not valid GUID`: The UUID received from child does not have the format defined in [RFC + 4122](https://tools.ietf.org/html/rfc4122) +- `machine GUID 'VALUE' is not GUID.`: This error with machine GUID is like the previous one. +- `API key 'VALUE' is not allowed`: This stream has a wrong API key. +- `API key 'VALUE' is not permitted from this IP`: The IP is not allowed to use STREAM with this parent. +- `machine GUID 'VALUE' is not allowed.`: The GUID that is trying to send stream is not allowed. +- `Machine GUID 'VALUE' is not permitted from this IP. `: The IP does not match the pattern or IP allowed to connect to + use stream. ### Netdata could not create a stream @@ -730,5 +576,3 @@ file descriptor given is not a valid stream ``` After logging this error, Netdata will close the stream. - - diff --git a/streaming/receiver.c b/streaming/receiver.c index 95652942..ff7a9562 100644 --- a/streaming/receiver.c +++ b/streaming/receiver.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "rrdpush.h" -#include "parser/parser.h" // IMPORTANT: to add workers, you have to edit WORKER_PARSER_FIRST_JOB accordingly #define WORKER_RECEIVER_JOB_BYTES_READ (WORKER_PARSER_FIRST_JOB - 1) @@ -340,10 +339,14 @@ static size_t streaming_parser(struct receiver_state *rpt, struct plugind *cd, i .host = rpt->host, .opaque = rpt, .cd = cd, - .trust_durations = 1 + .trust_durations = 1, + .capabilities = rpt->capabilities, }; - PARSER *parser = parser_init(rpt->host, &user, NULL, NULL, fd, PARSER_INPUT_SPLIT, ssl); + PARSER *parser = parser_init(&user, NULL, NULL, fd, + PARSER_INPUT_SPLIT, ssl); + + pluginsd_keywords_init(parser, PARSER_INIT_STREAMING); rrd_collector_started(); @@ -416,7 +419,7 @@ static size_t streaming_parser(struct receiver_state *rpt, struct plugind *cd, i } done: - result = user.count; + result = user.data_collections_count; // free parser with the pop function netdata_thread_cleanup_pop(1); @@ -434,7 +437,7 @@ static void rrdpush_receiver_replication_reset(RRDHOST *host) { rrdhost_receiver_replicating_charts_zero(host); } -bool rrdhost_set_receiver(RRDHOST *host, struct receiver_state *rpt) { +static bool rrdhost_set_receiver(RRDHOST *host, struct receiver_state *rpt) { bool signal_rrdcontext = false; bool set_this = false; @@ -469,6 +472,7 @@ bool rrdhost_set_receiver(RRDHOST *host, struct receiver_state *rpt) { rrdpush_receiver_replication_reset(host); rrdhost_flag_clear(rpt->host, RRDHOST_FLAG_RRDPUSH_RECEIVER_DISCONNECTED); + aclk_queue_node_info(rpt->host, true); set_this = true; } @@ -689,6 +693,12 @@ static int rrdpush_receive(struct receiver_state *rpt) return 1; } + if (unlikely(rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD))) { + rrdpush_receive_log_status(rpt, "host is initializing", "INITIALIZATION IN PROGRESS RETRY LATER"); + close(rpt->fd); + return 1; + } + // system_info has been consumed by the host structure rpt->system_info = NULL; @@ -724,16 +734,12 @@ static int rrdpush_receive(struct receiver_state *rpt) struct plugind cd = { .update_every = default_rrd_update_every, - .serial_failures = 0, - .successful_collections = 0, .unsafe = { .spinlock = NETDATA_SPINLOCK_INITIALIZER, .running = true, .enabled = true, }, .started_t = now_realtime_sec(), - .next = NULL, - .capabilities = 0, }; // put the client IP and port into the buffers used by plugins.d @@ -804,8 +810,6 @@ static int rrdpush_receive(struct receiver_state *rpt) rrdpush_receive_log_status(rpt, "ready to receive data", "CONNECTED"); - cd.capabilities = rpt->capabilities; - #ifdef ENABLE_ACLK // in case we have cloud connection we inform cloud // new child connected diff --git a/streaming/replication.c b/streaming/replication.c index 7c1f16b4..a50913a1 100644 --- a/streaming/replication.c +++ b/streaming/replication.c @@ -88,6 +88,7 @@ struct replication_query { bool locked_data_collection; bool execute; bool interrupted; + STREAM_CAPABILITIES capabilities; } query; time_t wall_clock_time; @@ -95,7 +96,7 @@ struct replication_query { size_t points_read; size_t points_generated; - struct storage_engine_query_ops *ops; + STORAGE_ENGINE_BACKEND backend; struct replication_request *rq; size_t dimensions; @@ -112,7 +113,8 @@ static struct replication_query *replication_query_prepare( time_t query_after, time_t query_before, bool query_enable_streaming, - time_t wall_clock_time + time_t wall_clock_time, + STREAM_CAPABILITIES capabilities ) { size_t dimensions = rrdset_number_of_dimensions(st); struct replication_query *q = callocz(1, sizeof(struct replication_query) + dimensions * sizeof(struct replication_dimension)); @@ -131,6 +133,7 @@ static struct replication_query *replication_query_prepare( q->query.after = query_after; q->query.before = query_before; q->query.enable_streaming = query_enable_streaming; + q->query.capabilities = capabilities; q->wall_clock_time = wall_clock_time; @@ -159,7 +162,7 @@ static struct replication_query *replication_query_prepare( } } - q->ops = &st->rrdhost->db[0].eng->api.query_ops; + q->backend = st->rrdhost->db[0].eng->backend; // prepare our array of dimensions size_t count = 0; @@ -181,7 +184,7 @@ static struct replication_query *replication_query_prepare( d->rda = dictionary_acquired_item_dup(rd_dfe.dict, rd_dfe.item); d->rd = rd; - q->ops->init(rd->tiers[0].db_metric_handle, &d->handle, q->query.after, q->query.before, + storage_engine_query_init(q->backend, 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; @@ -209,32 +212,40 @@ static struct replication_query *replication_query_prepare( return q; } -static void replication_send_chart_collection_state(BUFFER *wb, RRDSET *st) { +static void replication_send_chart_collection_state(BUFFER *wb, RRDSET *st, STREAM_CAPABILITIES capabilities) { + NUMBER_ENCODING encoding = (capabilities & STREAM_CAP_IEEE754) ? NUMBER_ENCODING_BASE64 : NUMBER_ENCODING_DECIMAL; 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_read(rd, st){ + if (!rd->exposed) continue; + + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE " '", + sizeof(PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE) - 1 + 2); + buffer_fast_strcat(wb, rrddim_id(rd), string_strlen(rd->id)); + buffer_fast_strcat(wb, "' ", 2); + buffer_print_uint64_encoded(wb, encoding, (usec_t) rd->last_collected_time.tv_sec * USEC_PER_SEC + + (usec_t) rd->last_collected_time.tv_usec); + buffer_fast_strcat(wb, " ", 1); + buffer_print_int64_encoded(wb, encoding, rd->last_collected_value); + buffer_fast_strcat(wb, " ", 1); + buffer_print_netdata_double_encoded(wb, encoding, rd->last_calculated_value); + buffer_fast_strcat(wb, " ", 1); + buffer_print_netdata_double_encoded(wb, encoding, rd->last_stored_value); + buffer_fast_strcat(wb, "\n", 1); + } 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 - ); + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE " ", sizeof(PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE) - 1 + 1); + buffer_print_uint64_encoded(wb, encoding, (usec_t) st->last_collected_time.tv_sec * USEC_PER_SEC + (usec_t) st->last_collected_time.tv_usec); + buffer_fast_strcat(wb, " ", 1); + buffer_print_uint64_encoded(wb, encoding, (usec_t) st->last_updated.tv_sec * USEC_PER_SEC + (usec_t) st->last_updated.tv_usec); + buffer_fast_strcat(wb, "\n", 1); } 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); + replication_send_chart_collection_state(wb, q->st, q->query.capabilities); if(q->query.locked_data_collection) { netdata_spinlock_unlock(&q->st->data_collection_lock); @@ -249,7 +260,7 @@ static void replication_query_finalize(BUFFER *wb, struct replication_query *q, struct replication_dimension *d = &q->data[i]; if (unlikely(!d->enabled)) continue; - q->ops->finalize(&d->handle); + storage_engine_query_finalize(&d->handle); dictionary_acquired_item_release(d->dict, d->rda); @@ -281,7 +292,7 @@ static void replication_query_align_to_optimal_before(struct replication_query * struct replication_dimension *d = &q->data[i]; if(unlikely(!d->enabled)) continue; - time_t new_before = q->ops->align_to_optimal_before(&d->handle); + time_t new_before = storage_engine_align_to_optimal_before(&d->handle); if (!expanded_before || new_before < expanded_before) expanded_before = new_before; } @@ -296,13 +307,14 @@ static void replication_query_align_to_optimal_before(struct replication_query * static bool replication_query_execute(BUFFER *wb, struct replication_query *q, size_t max_msg_size) { replication_query_align_to_optimal_before(q); + NUMBER_ENCODING encoding = (q->query.capabilities & STREAM_CAP_IEEE754) ? NUMBER_ENCODING_BASE64 : NUMBER_ENCODING_DECIMAL; 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; + bool finished_with_gap = false; + size_t points_read = 0, points_generated = 0; #ifdef NETDATA_LOG_REPLICATION_REQUESTS time_t actual_after = 0, actual_before = 0; @@ -318,8 +330,8 @@ static bool replication_query_execute(BUFFER *wb, struct replication_query *q, s // fetch the first valid point for the dimension 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); + while(d->sp.end_time_s < now && !storage_engine_query_is_finished(&d->handle) && max_skip-- >= 0) { + d->sp = storage_engine_query_next_metric(&d->handle); points_read++; } @@ -328,9 +340,10 @@ static bool replication_query_execute(BUFFER *wb, struct replication_query *q, s 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); + "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); continue; } @@ -374,9 +387,10 @@ static bool replication_query_execute(BUFFER *wb, struct replication_query *q, s else fix_min_start_time = min_end_time - min_update_every; +#ifdef NETDATA_INTERNAL_CHECKS error_limit_static_global_var(erl, 1, 0); error_limit(&erl, "REPLAY WARNING: 'host:%s/chart:%s' " - "misaligned dimensions " + "misaligned dimensions, " "update every (min: %ld, max: %ld), " "start time (min: %ld, max: %ld), " "end time (min %ld, max %ld), " @@ -389,6 +403,7 @@ static bool replication_query_execute(BUFFER *wb, struct replication_query *q, s now, last_end_time_in_buffer, fix_min_start_time ); +#endif min_start_time = fix_min_start_time; } @@ -410,7 +425,8 @@ static bool replication_query_execute(BUFFER *wb, struct replication_query *q, s q->query.before = last_end_time_in_buffer; q->query.enable_streaming = false; - internal_error(true, "REPLICATION: buffer size %zu is more than the max message size %zu for chart '%s' of host '%s'. " + internal_error(true, "REPLICATION: current 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", @@ -422,11 +438,13 @@ static bool replication_query_execute(BUFFER *wb, struct replication_query *q, s } last_end_time_in_buffer = min_end_time; - 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 - ); + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_REPLAY_BEGIN " '' ", sizeof(PLUGINSD_KEYWORD_REPLAY_BEGIN) - 1 + 4); + buffer_print_uint64_encoded(wb, encoding, min_start_time); + buffer_fast_strcat(wb, " ", 1); + buffer_print_uint64_encoded(wb, encoding, min_end_time); + buffer_fast_strcat(wb, " ", 1); + buffer_print_uint64_encoded(wb, encoding, wall_clock_time); + buffer_fast_strcat(wb, "\n", 1); // output the replay values for this time for (size_t i = 0; i < dimensions; i++) { @@ -438,8 +456,13 @@ static bool replication_query_execute(BUFFER *wb, struct replication_query *q, s !storage_point_is_unset(d->sp) && !storage_point_is_gap(d->sp))) { - 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" : ""); + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_REPLAY_SET " \"", sizeof(PLUGINSD_KEYWORD_REPLAY_SET) - 1 + 2); + buffer_fast_strcat(wb, rrddim_id(d->rd), string_strlen(d->rd->id)); + buffer_fast_strcat(wb, "\" ", 2); + buffer_print_netdata_double_encoded(wb, encoding, d->sp.sum); + buffer_fast_strcat(wb, " ", 1); + buffer_print_sn_flags(wb, d->sp.flags, q->query.capabilities & STREAM_CAP_INTERPOLATED); + buffer_fast_strcat(wb, "\n", 1); points_generated++; } @@ -450,9 +473,16 @@ static bool replication_query_execute(BUFFER *wb, struct replication_query *q, s else if(unlikely(min_end_time < now)) // the query does not progress break; - else + else { // we have gap - all points are in the future now = min_start_time; + + if(min_start_time > before && !points_generated) { + before = q->query.before = min_start_time - 1; + finished_with_gap = true; + break; + } + } } #ifdef NETDATA_LOG_REPLICATION_REQUESTS @@ -462,28 +492,33 @@ static bool replication_query_execute(BUFFER *wb, struct replication_query *q, s log_date(actual_before_buf, LOG_DATE_LENGTH, actual_before); internal_error(true, "STREAM_SENDER REPLAY: 'host:%s/chart:%s': sending data %llu [%s] to %llu [%s] (requested %llu [delta %lld] to %llu [delta %lld])", - rrdhost_hostname(st->rrdhost), rrdset_id(st), + rrdhost_hostname(q->st->rrdhost), rrdset_id(q->st), (unsigned long long)actual_after, actual_after_buf, (unsigned long long)actual_before, actual_before_buf, (unsigned long long)after, (long long)(actual_after - after), (unsigned long long)before, (long long)(actual_before - before)); } else internal_error(true, "STREAM_SENDER REPLAY: 'host:%s/chart:%s': nothing to send (requested %llu to %llu)", - rrdhost_hostname(st->rrdhost), rrdset_id(st), + rrdhost_hostname(q->st->rrdhost), rrdset_id(q->st), (unsigned long long)after, (unsigned long long)before); #endif // NETDATA_LOG_REPLICATION_REQUESTS - q->points_read = points_read; - q->points_generated = points_generated; + q->points_read += points_read; + q->points_generated += points_generated; - bool finished_with_gap = false; if(last_end_time_in_buffer < before - q->st->update_every) finished_with_gap = true; return finished_with_gap; } -static struct replication_query *replication_response_prepare(RRDSET *st, bool requested_enable_streaming, time_t requested_after, time_t requested_before) { +static struct replication_query *replication_response_prepare( + RRDSET *st, + bool requested_enable_streaming, + time_t requested_after, + time_t requested_before, + STREAM_CAPABILITIES capabilities + ) { time_t wall_clock_time = now_realtime_sec(); if(requested_after > requested_before) { @@ -509,7 +544,8 @@ static struct replication_query *replication_response_prepare(RRDSET *st, bool r bool query_enable_streaming = requested_enable_streaming; 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); + 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 @@ -543,7 +579,7 @@ static struct replication_query *replication_response_prepare(RRDSET *st, bool r db_first_entry, db_last_entry, requested_after, requested_before, requested_enable_streaming, query_after, query_before, query_enable_streaming, - wall_clock_time); + wall_clock_time, capabilities); } void replication_response_cancel_and_finalize(struct replication_query *q) { @@ -553,6 +589,7 @@ void replication_response_cancel_and_finalize(struct replication_query *q) { static bool sender_is_still_connected_for_this_request(struct replication_request *rq); bool replication_response_execute_and_finalize(struct replication_query *q, size_t max_msg_size) { + NUMBER_ENCODING encoding = (q->query.capabilities & STREAM_CAP_IEEE754) ? NUMBER_ENCODING_BASE64 : NUMBER_ENCODING_DECIMAL; struct replication_request *rq = q->rq; RRDSET *st = q->st; RRDHOST *host = st->rrdhost; @@ -562,7 +599,11 @@ bool replication_response_execute_and_finalize(struct replication_query *q, size // holding the host's buffer lock for too long BUFFER *wb = sender_start(host->sender); - buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_BEGIN " \"%s\"\n", rrdset_id(st)); + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_REPLAY_BEGIN " '", sizeof(PLUGINSD_KEYWORD_REPLAY_BEGIN) - 1 + 2); + buffer_fast_strcat(wb, rrdset_id(st), string_strlen(st->id)); + buffer_fast_strcat(wb, "'\n", 2); + +// buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_BEGIN " \"%s\"\n", rrdset_id(st)); bool locked_data_collection = q->query.locked_data_collection; q->query.locked_data_collection = false; @@ -585,23 +626,22 @@ bool replication_response_execute_and_finalize(struct replication_query *q, size // end with first/last entries we have, and the first start time and // last end time of the data we sent - buffer_sprintf(wb, PLUGINSD_KEYWORD_REPLAY_END " %d %llu %llu %s %llu %llu %llu\n", - // current chart update every - (int)st->update_every + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_REPLAY_END " ", sizeof(PLUGINSD_KEYWORD_REPLAY_END) - 1 + 1); + buffer_print_int64_encoded(wb, encoding, st->update_every); + buffer_fast_strcat(wb, " ", 1); + buffer_print_uint64_encoded(wb, encoding, db_first_entry); + buffer_fast_strcat(wb, " ", 1); + buffer_print_uint64_encoded(wb, encoding, db_last_entry); - // child first db time, child end db time - , (unsigned long long)db_first_entry, (unsigned long long)db_last_entry + buffer_fast_strcat(wb, enable_streaming ? " true " : " false ", 7); - // start streaming boolean - , enable_streaming ? "true" : "false" - - // after requested, before requested ('before' can be altered by the child when the request had enable_streaming true) - , (unsigned long long)after, (unsigned long long)before - - // child world clock time - , (unsigned long long)wall_clock_time - ); + buffer_print_uint64_encoded(wb, encoding, after); + buffer_fast_strcat(wb, " ", 1); + buffer_print_uint64_encoded(wb, encoding, before); + buffer_fast_strcat(wb, " ", 1); + buffer_print_uint64_encoded(wb, encoding, wall_clock_time); + buffer_fast_strcat(wb, "\n", 1); worker_is_busy(WORKER_JOB_BUFFER_COMMIT); sender_commit(host->sender, wb); @@ -733,9 +773,9 @@ static bool send_replay_chart_cmd(struct replication_request_details *r, const c , msg , r->last_request.after, r->last_request.before , 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->child_db.wall_clock_time, (r->child_db.wall_clock_time == r->local_db.wall_clock_time) ? "SAME" : (r->child_db.wall_clock_time < r->local_db.wall_clock_time) ? "BEHIND" : "AHEAD" , r->local_db.first_entry_t, r->local_db.last_entry_t - , r->local_db.now + , r->local_db.wall_clock_time , r->gap.from, r->gap.to , (r->gap.from == r->wanted.after) ? "FULL" : "PARTIAL" , (st->replay.after != 0 || st->replay.before != 0) ? "OVERLAPPING" : "" @@ -928,11 +968,12 @@ struct replication_sort_entry { // the global variables for the replication thread static struct replication_thread { + ARAL *aral_rse; + SPINLOCK spinlock; struct { size_t pending; // number of requests pending in the queue - Word_t unique_id; // the last unique id we gave to a request (auto-increment, starting from 1) // statistics size_t added; // number of requests added to the queue @@ -951,6 +992,7 @@ static struct replication_thread { } unsafe; // protected from replication_recursive_lock() struct { + Word_t unique_id; // the last unique id we gave to a request (auto-increment, starting from 1) 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 @@ -964,10 +1006,10 @@ static struct replication_thread { } main_thread; // access is allowed only by the main thread } replication_globals = { + .aral_rse = NULL, .spinlock = NETDATA_SPINLOCK_INITIALIZER, .unsafe = { .pending = 0, - .unique_id = 0, .added = 0, .removed = 0, @@ -984,6 +1026,7 @@ static struct replication_thread { }, }, .atomic = { + .unique_id = 0, .executed = 0, .latest_first_time = 0, .memory = 0, @@ -1047,17 +1090,15 @@ void replication_set_next_point_in_time(time_t after, size_t unique_id) { // ---------------------------------------------------------------------------- // replication sort entry management -static struct replication_sort_entry *replication_sort_entry_create_unsafe(struct replication_request *rq) { - fatal_when_replication_is_not_locked_for_me(); - - struct replication_sort_entry *rse = mallocz(sizeof(struct replication_sort_entry)); +static struct replication_sort_entry *replication_sort_entry_create(struct replication_request *rq) { + struct replication_sort_entry *rse = aral_mallocz(replication_globals.aral_rse); __atomic_add_fetch(&replication_globals.atomic.memory, sizeof(struct replication_sort_entry), __ATOMIC_RELAXED); rrdpush_sender_pending_replication_requests_plus_one(rq->sender); // copy the request rse->rq = rq; - rse->unique_id = ++replication_globals.unsafe.unique_id; + rse->unique_id = __atomic_add_fetch(&replication_globals.atomic.unique_id, 1, __ATOMIC_SEQ_CST); // save the unique id into the request, to be able to delete it later rq->unique_id = rse->unique_id; @@ -1068,26 +1109,30 @@ static struct replication_sort_entry *replication_sort_entry_create_unsafe(struc } static void replication_sort_entry_destroy(struct replication_sort_entry *rse) { - freez(rse); + aral_freez(replication_globals.aral_rse, 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) { - replication_recursive_lock(); - 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_recursive_lock(); replication_globals.unsafe.pending_no_room++; replication_recursive_unlock(); return; } - if(rq->not_indexed_buffer_full) - replication_globals.unsafe.pending_no_room--; + // cache this, because it will be changed + bool decrement_no_room = rq->not_indexed_buffer_full; + + struct replication_sort_entry *rse = replication_sort_entry_create(rq); + + replication_recursive_lock(); - struct replication_sort_entry *rse = replication_sort_entry_create_unsafe(rq); + if(decrement_no_room) + replication_globals.unsafe.pending_no_room--; // if(rq->after < (time_t)replication_globals.protected.queue.after && // rq->sender->buffer_used_percentage <= MAX_SENDER_BUFFER_PERCENTAGE_ALLOWED && @@ -1371,7 +1416,12 @@ static bool replication_execute_request(struct replication_request *rq, bool wor if(likely(workers)) worker_is_busy(WORKER_JOB_PREPARE_QUERY); - rq->q = replication_response_prepare(rq->st, rq->start_streaming, rq->after, rq->before); + rq->q = replication_response_prepare( + rq->st, + rq->start_streaming, + rq->after, + rq->before, + rq->sender->capabilities); } if(likely(workers)) @@ -1580,67 +1630,85 @@ 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(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; +static __thread struct replication_thread_pipeline { + int max_requests_ahead; + struct replication_request *rqs; + int rqs_last_executed, rqs_last_prepared; + size_t queue_rounds; +} rtp = { + .max_requests_ahead = 0, + .rqs = NULL, + .rqs_last_executed = 0, + .rqs_last_prepared = 0, + .queue_rounds = 0, +}; + +static void replication_pipeline_cancel_and_cleanup(void) { + if(!rtp.rqs) + return; + struct replication_request *rq; + size_t cancelled = 0; + + do { + if (++rtp.rqs_last_executed >= rtp.max_requests_ahead) + rtp.rqs_last_executed = 0; - if(unlikely(cancel)) { - if(rqs) { - size_t cancelled = 0; - do { - if (++rqs_last_executed >= max_requests_ahead) - rqs_last_executed = 0; + rq = &rtp.rqs[rtp.rqs_last_executed]; - 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"); - 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++; + } - replication_response_cancel_and_finalize(rq->q); - rq->q = NULL; - cancelled++; - } + rq->executed = true; + rq->found = false; - rq->executed = true; - rq->found = false; + } while (rtp.rqs_last_executed != rtp.rqs_last_prepared); - } while (rqs_last_executed != rqs_last_prepared); + internal_error(true, "REPLICATION: cancelled %zu inflight queries", cancelled); - internal_error(true, "REPLICATION: cancelled %zu inflight queries", cancelled); - } - return REQUEST_QUEUE_EMPTY; - } + freez(rtp.rqs); + rtp.rqs = NULL; + rtp.max_requests_ahead = 0; + rtp.rqs_last_executed = 0; + rtp.rqs_last_prepared = 0; + rtp.queue_rounds = 0; +} + +static int replication_pipeline_execute_next(void) { + struct replication_request *rq; - if(unlikely(!rqs)) { - max_requests_ahead = get_netdata_cpus() / 2; + if(unlikely(!rtp.rqs)) { + rtp.max_requests_ahead = (int)get_netdata_cpus() / 2; - if(max_requests_ahead > libuv_worker_threads * 2) - max_requests_ahead = libuv_worker_threads * 2; + if(rtp.max_requests_ahead > libuv_worker_threads * 2) + rtp.max_requests_ahead = libuv_worker_threads * 2; - if(max_requests_ahead < 2) - max_requests_ahead = 2; + if(rtp.max_requests_ahead < 2) + rtp.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); + rtp.rqs = callocz(rtp.max_requests_ahead, sizeof(struct replication_request)); + __atomic_add_fetch(&replication_buffers_allocated, rtp.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++; + if(++rtp.rqs_last_prepared >= rtp.max_requests_ahead) { + rtp.rqs_last_prepared = 0; + rtp.queue_rounds++; } - internal_fatal(rqs[rqs_last_prepared].q, + internal_fatal(rtp.rqs[rtp.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]; + rtp.rqs[rtp.rqs_last_prepared] = replication_request_get_first_available(); + rq = &rtp.rqs[rtp.rqs_last_prepared]; if(rq->found) { if (!rq->st) { @@ -1650,20 +1718,25 @@ static int replication_execute_next_pending_request(bool cancel) { 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); + rq->q = replication_response_prepare( + rq->st, + rq->start_streaming, + rq->after, + rq->before, + rq->sender->capabilities); } rq->executed = false; } - } while(rq->found && rqs_last_prepared != rqs_last_executed); + } while(rq->found && rtp.rqs_last_prepared != rtp.rqs_last_executed); // pick the first usable do { - if (++rqs_last_executed >= max_requests_ahead) - rqs_last_executed = 0; + if (++rtp.rqs_last_executed >= rtp.max_requests_ahead) + rtp.rqs_last_executed = 0; - rq = &rqs[rqs_last_executed]; + rq = &rtp.rqs[rtp.rqs_last_executed]; if(rq->found) { internal_fatal(rq->executed, "REPLAY FATAL: query has already been executed!"); @@ -1696,7 +1769,7 @@ static int replication_execute_next_pending_request(bool cancel) { 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); + } while(!rq->found && rtp.rqs_last_executed != rtp.rqs_last_prepared); if(unlikely(!rq->found)) { worker_is_idle(); @@ -1720,7 +1793,7 @@ static int replication_execute_next_pending_request(bool cancel) { } static void replication_worker_cleanup(void *ptr __maybe_unused) { - replication_execute_next_pending_request(true); + replication_pipeline_cancel_and_cleanup(); worker_unregister(); } @@ -1730,7 +1803,7 @@ static void *replication_worker_thread(void *ptr) { netdata_thread_cleanup_push(replication_worker_cleanup, ptr); while(service_running(SERVICE_REPLICATION)) { - if(unlikely(replication_execute_next_pending_request(false) == REQUEST_QUEUE_EMPTY)) { + if(unlikely(replication_pipeline_execute_next() == REQUEST_QUEUE_EMPTY)) { sender_thread_buffer_free(); worker_is_busy(WORKER_JOB_WAIT); worker_is_idle(); @@ -1746,7 +1819,7 @@ 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); + replication_pipeline_cancel_and_cleanup(); int threads = (int)replication_globals.main_thread.threads; for(int i = 0; i < threads ;i++) { @@ -1758,12 +1831,21 @@ static void replication_main_cleanup(void *ptr) { replication_globals.main_thread.threads_ptrs = NULL; __atomic_sub_fetch(&replication_buffers_allocated, threads * sizeof(netdata_thread_t *), __ATOMIC_RELAXED); + aral_destroy(replication_globals.aral_rse); + replication_globals.aral_rse = NULL; + // custom code worker_unregister(); static_thread->enabled = NETDATA_MAIN_THREAD_EXITED; } +void replication_initialize(void) { + replication_globals.aral_rse = aral_create("rse", sizeof(struct replication_sort_entry), + 0, 65536, aral_by_size_statistics(), + NULL, NULL, false, false); +} + void *replication_thread_main(void *ptr __maybe_unused) { replication_initialize_workers(true); @@ -1863,7 +1945,7 @@ void *replication_thread_main(void *ptr __maybe_unused) { worker_is_idle(); } - if(unlikely(replication_execute_next_pending_request(false) == REQUEST_QUEUE_EMPTY)) { + if(unlikely(replication_pipeline_execute_next() == REQUEST_QUEUE_EMPTY)) { worker_is_busy(WORKER_JOB_WAIT); replication_recursive_lock(); diff --git a/streaming/rrdpush.c b/streaming/rrdpush.c index 256fa828..62b537f0 100644 --- a/streaming/rrdpush.c +++ b/streaming/rrdpush.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "rrdpush.h" -#include "parser/parser.h" /* * rrdpush @@ -69,6 +68,23 @@ static void load_stream_conf() { freez(filename); } +STREAM_CAPABILITIES stream_our_capabilities() { + return STREAM_CAP_V1 | + STREAM_CAP_V2 | + STREAM_CAP_VN | + STREAM_CAP_VCAPS | + STREAM_CAP_HLABELS | + STREAM_CAP_CLAIM | + STREAM_CAP_CLABELS | + STREAM_CAP_FUNCTIONS | + STREAM_CAP_REPLICATION | + STREAM_CAP_BINARY | + STREAM_CAP_INTERPOLATED | + STREAM_HAS_COMPRESSION | + (ieee754_doubles ? STREAM_CAP_IEEE754 : 0) | + 0; +} + bool rrdpush_receiver_needs_dbengine() { struct section *co; @@ -174,8 +190,8 @@ static inline bool should_send_chart_matching(RRDSET *st, RRDSET_FLAGS flags) { else rrdset_flag_set(st, RRDSET_FLAG_UPSTREAM_IGNORE); } - else if(simple_pattern_matches(host->rrdpush_send_charts_matching, rrdset_id(st)) || - simple_pattern_matches(host->rrdpush_send_charts_matching, rrdset_name(st))) + else if(simple_pattern_matches_string(host->rrdpush_send_charts_matching, st->id) || + simple_pattern_matches_string(host->rrdpush_send_charts_matching, st->name)) rrdset_flag_set(st, RRDSET_FLAG_UPSTREAM_SEND); else @@ -305,9 +321,11 @@ static inline bool rrdpush_send_chart_definition(BUFFER *wb, RRDSET *st) { (unsigned long long)db_last_time_t, (unsigned long long)now); - rrdset_flag_set(st, RRDSET_FLAG_SENDER_REPLICATION_IN_PROGRESS); - rrdset_flag_clear(st, RRDSET_FLAG_SENDER_REPLICATION_FINISHED); - rrdhost_sender_replicating_charts_plus_one(st->rrdhost); + if(!rrdset_flag_check(st, RRDSET_FLAG_SENDER_REPLICATION_IN_PROGRESS)) { + rrdset_flag_set(st, RRDSET_FLAG_SENDER_REPLICATION_IN_PROGRESS); + rrdset_flag_clear(st, RRDSET_FLAG_SENDER_REPLICATION_FINISHED); + rrdhost_sender_replicating_charts_plus_one(st->rrdhost); + } replication_progress = true; #ifdef NETDATA_LOG_REPLICATION_REQUESTS @@ -327,7 +345,7 @@ static void rrdpush_send_chart_metrics(BUFFER *wb, RRDSET *st, struct sender_sta buffer_fast_strcat(wb, "\" ", 2); if(st->last_collected_time.tv_sec > st->upstream_resync_time_s) - buffer_print_llu(wb, st->usec_since_last_update); + buffer_print_uint64(wb, st->usec_since_last_update); else buffer_fast_strcat(wb, "0", 1); @@ -342,7 +360,7 @@ static void rrdpush_send_chart_metrics(BUFFER *wb, RRDSET *st, struct sender_sta buffer_fast_strcat(wb, "SET \"", 5); buffer_fast_strcat(wb, rrddim_id(rd), string_strlen(rd->id)); buffer_fast_strcat(wb, "\" = ", 4); - buffer_print_ll(wb, rd->collected_value); + buffer_print_int64(wb, rd->collected_value); buffer_fast_strcat(wb, "\n", 1); } else { @@ -378,7 +396,74 @@ bool rrdset_push_chart_definition_now(RRDSET *st) { return true; } -void rrdset_done_push(RRDSET *st) { +void rrdset_push_metrics_v1(RRDSET_STREAM_BUFFER *rsb, RRDSET *st) { + RRDHOST *host = st->rrdhost; + rrdpush_send_chart_metrics(rsb->wb, st, host->sender, rsb->rrdset_flags); +} + +void rrddim_push_metrics_v2(RRDSET_STREAM_BUFFER *rsb, RRDDIM *rd, usec_t point_end_time_ut, NETDATA_DOUBLE n, SN_FLAGS flags) { + if(!rsb->wb || !rsb->v2 || !netdata_double_isnumber(n) || !does_storage_number_exist(flags)) + return; + + NUMBER_ENCODING integer_encoding = stream_has_capability(rsb, STREAM_CAP_IEEE754) ? NUMBER_ENCODING_BASE64 : NUMBER_ENCODING_HEX; + NUMBER_ENCODING doubles_encoding = stream_has_capability(rsb, STREAM_CAP_IEEE754) ? NUMBER_ENCODING_BASE64 : NUMBER_ENCODING_DECIMAL; + BUFFER *wb = rsb->wb; + time_t point_end_time_s = (time_t)(point_end_time_ut / USEC_PER_SEC); + if(unlikely(rsb->last_point_end_time_s != point_end_time_s)) { + + if(unlikely(rsb->begin_v2_added)) + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_END_V2 "\n", sizeof(PLUGINSD_KEYWORD_END_V2) - 1 + 1); + + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_BEGIN_V2 " '", sizeof(PLUGINSD_KEYWORD_BEGIN_V2) - 1 + 2); + buffer_fast_strcat(wb, rrdset_id(rd->rrdset), string_strlen(rd->rrdset->id)); + buffer_fast_strcat(wb, "' ", 2); + buffer_print_uint64_encoded(wb, integer_encoding, rd->rrdset->update_every); + buffer_fast_strcat(wb, " ", 1); + buffer_print_uint64_encoded(wb, integer_encoding, point_end_time_s); + buffer_fast_strcat(wb, " ", 1); + if(point_end_time_s == rsb->wall_clock_time) + buffer_fast_strcat(wb, "#", 1); + else + buffer_print_uint64_encoded(wb, integer_encoding, rsb->wall_clock_time); + buffer_fast_strcat(wb, "\n", 1); + + rsb->last_point_end_time_s = point_end_time_s; + rsb->begin_v2_added = true; + } + + buffer_fast_strcat(wb, PLUGINSD_KEYWORD_SET_V2 " '", sizeof(PLUGINSD_KEYWORD_SET_V2) - 1 + 2); + buffer_fast_strcat(wb, rrddim_id(rd), string_strlen(rd->id)); + buffer_fast_strcat(wb, "' ", 2); + buffer_print_int64_encoded(wb, integer_encoding, rd->last_collected_value); + buffer_fast_strcat(wb, " ", 1); + + if((NETDATA_DOUBLE)rd->last_collected_value == n) + buffer_fast_strcat(wb, "#", 1); + else + buffer_print_netdata_double_encoded(wb, doubles_encoding, n); + + buffer_fast_strcat(wb, " ", 1); + buffer_print_sn_flags(wb, flags, true); + buffer_fast_strcat(wb, "\n", 1); +} + +void rrdset_push_metrics_finished(RRDSET_STREAM_BUFFER *rsb, RRDSET *st) { + if(!rsb->wb) + return; + + if(rsb->v2 && rsb->begin_v2_added) { + if(unlikely(rsb->rrdset_flags & RRDSET_FLAG_UPSTREAM_SEND_VARIABLES)) + rrdsetvar_print_to_streaming_custom_chart_variables(st, rsb->wb); + + buffer_fast_strcat(rsb->wb, PLUGINSD_KEYWORD_END_V2 "\n", sizeof(PLUGINSD_KEYWORD_END_V2) - 1 + 1); + } + + sender_commit(st->rrdhost->sender, rsb->wb); + + *rsb = (RRDSET_STREAM_BUFFER){ .wb = NULL, }; +} + +RRDSET_STREAM_BUFFER rrdset_push_metric_initialize(RRDSET *st, time_t wall_clock_time) { RRDHOST *host = st->rrdhost; // fetch the flags we need to check with one atomic operation @@ -395,7 +480,7 @@ void rrdset_done_push(RRDSET *st) { error("STREAM %s [send]: not ready - collected metrics are not sent to parent.", rrdhost_hostname(host)); } - return; + return (RRDSET_STREAM_BUFFER) { .wb = NULL, }; } else if(unlikely(host_flags & RRDHOST_FLAG_RRDPUSH_SENDER_LOGGED_STATUS)) { info("STREAM %s [send]: sending metrics to parent...", rrdhost_hostname(host)); @@ -408,17 +493,24 @@ void rrdset_done_push(RRDSET *st) { if(unlikely((exposed_upstream && replication_in_progress) || !should_send_chart_matching(st, rrdset_flags))) - return; - - BUFFER *wb = sender_start(host->sender); + return (RRDSET_STREAM_BUFFER) { .wb = NULL, }; - if(unlikely(!exposed_upstream)) + if(unlikely(!exposed_upstream)) { + BUFFER *wb = sender_start(host->sender); replication_in_progress = rrdpush_send_chart_definition(wb, st); + sender_commit(host->sender, wb); + } - if (likely(!replication_in_progress)) - rrdpush_send_chart_metrics(wb, st, host->sender, rrdset_flags); + if(replication_in_progress) + return (RRDSET_STREAM_BUFFER) { .wb = NULL, }; - sender_commit(host->sender, wb); + return (RRDSET_STREAM_BUFFER) { + .capabilities = host->sender->capabilities, + .v2 = stream_has_capability(host->sender, STREAM_CAP_INTERPOLATED), + .rrdset_flags = rrdset_flags, + .wb = sender_start(host->sender), + .wall_clock_time = wall_clock_time, + }; } // labels @@ -633,7 +725,7 @@ int rrdpush_receiver_too_busy_now(struct web_client *w) { } void *rrdpush_receiver_thread(void *ptr); -int rrdpush_receiver_thread_spawn(struct web_client *w, char *url) { +int rrdpush_receiver_thread_spawn(struct web_client *w, char *decoded_query_string) { if(!service_running(ABILITY_STREAMING_CONNECTIONS)) return rrdpush_receiver_too_busy_now(w); @@ -665,11 +757,11 @@ int rrdpush_receiver_thread_spawn(struct web_client *w, char *url) { // parse the parameters and fill rpt and rpt->system_info - while(url) { - char *value = mystrsep(&url, "&"); + while(decoded_query_string) { + char *value = strsep_skip_consecutive_separators(&decoded_query_string, "&"); if(!value || !*value) continue; - char *name = mystrsep(&value, "="); + char *name = strsep_skip_consecutive_separators(&value, "="); if(!name || !*name) continue; if(!value || !*value) continue; @@ -851,7 +943,7 @@ int rrdpush_receiver_thread_spawn(struct web_client *w, char *url) { { SIMPLE_PATTERN *key_allow_from = simple_pattern_create( appconfig_get(&stream_config, rpt->key, "allow from", "*"), - NULL, SIMPLE_PATTERN_EXACT); + NULL, SIMPLE_PATTERN_EXACT, true); if(key_allow_from) { if(!simple_pattern_matches(key_allow_from, w->client_ip)) { @@ -898,7 +990,7 @@ int rrdpush_receiver_thread_spawn(struct web_client *w, char *url) { { SIMPLE_PATTERN *machine_allow_from = simple_pattern_create( appconfig_get(&stream_config, rpt->machine_guid, "allow from", "*"), - NULL, SIMPLE_PATTERN_EXACT); + NULL, SIMPLE_PATTERN_EXACT, true); if(machine_allow_from) { if(!simple_pattern_matches(machine_allow_from, w->client_ip)) { @@ -1077,6 +1169,8 @@ static void stream_capabilities_to_string(BUFFER *wb, STREAM_CAPABILITIES caps) if(caps & STREAM_CAP_FUNCTIONS) buffer_strcat(wb, "FUNCTIONS "); if(caps & STREAM_CAP_REPLICATION) buffer_strcat(wb, "REPLICATION "); if(caps & STREAM_CAP_BINARY) buffer_strcat(wb, "BINARY "); + if(caps & STREAM_CAP_INTERPOLATED) buffer_strcat(wb, "INTERPOLATED "); + if(caps & STREAM_CAP_IEEE754) buffer_strcat(wb, "IEEE754 "); } void log_receiver_capabilities(struct receiver_state *rpt) { @@ -1118,7 +1212,7 @@ STREAM_CAPABILITIES convert_stream_version_to_capabilities(int32_t version) { if(caps & STREAM_CAP_V2) caps &= ~(STREAM_CAP_V1); - return caps & STREAM_OUR_CAPABILITIES; + return caps & stream_our_capabilities(); } int32_t stream_capabilities_to_vn(uint32_t caps) { diff --git a/streaming/rrdpush.h b/streaming/rrdpush.h index 94c1320e..ff895844 100644 --- a/streaming/rrdpush.h +++ b/streaming/rrdpush.h @@ -41,6 +41,8 @@ typedef enum { STREAM_CAP_FUNCTIONS = (1 << 11), // plugin functions supported STREAM_CAP_REPLICATION = (1 << 12), // replication supported STREAM_CAP_BINARY = (1 << 13), // streaming supports binary data + STREAM_CAP_INTERPOLATED = (1 << 14), // streaming supports interpolated streaming of values + STREAM_CAP_IEEE754 = (1 << 15), // streaming supports binary/hex transfer of double values 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 @@ -53,12 +55,9 @@ typedef enum { #define STREAM_HAS_COMPRESSION 0 #endif // ENABLE_COMPRESSION -#define STREAM_OUR_CAPABILITIES ( \ - STREAM_CAP_V1 | STREAM_CAP_V2 | STREAM_CAP_VN | STREAM_CAP_VCAPS | \ - STREAM_CAP_HLABELS | STREAM_CAP_CLAIM | STREAM_CAP_CLABELS | \ - STREAM_HAS_COMPRESSION | STREAM_CAP_FUNCTIONS | STREAM_CAP_REPLICATION | STREAM_CAP_BINARY ) +STREAM_CAPABILITIES stream_our_capabilities(); -#define stream_has_capability(rpt, capability) ((rpt) && ((rpt)->capabilities & (capability))) +#define stream_has_capability(rpt, capability) ((rpt) && ((rpt)->capabilities & (capability)) == (capability)) // ---------------------------------------------------------------------------- // stream handshake @@ -187,12 +186,17 @@ struct sender_state { } replication; struct { + bool pending_data; 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_pipe_has_pending_data(sender) __atomic_load_n(&(sender)->atomic.pending_data, __ATOMIC_RELAXED) +#define rrdpush_sender_pipe_set_pending_data(sender) __atomic_store_n(&(sender)->atomic.pending_data, true, __ATOMIC_RELAXED) +#define rrdpush_sender_pipe_clear_pending_data(sender) __atomic_store_n(&(sender)->atomic.pending_data, false, __ATOMIC_RELAXED) + #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) @@ -303,7 +307,22 @@ void sender_commit(struct sender_state *s, BUFFER *wb); int rrdpush_init(); bool rrdpush_receiver_needs_dbengine(); int configured_as_parent(); -void rrdset_done_push(RRDSET *st); + +typedef struct rrdset_stream_buffer { + STREAM_CAPABILITIES capabilities; + bool v2; + bool begin_v2_added; + time_t wall_clock_time; + uint64_t rrdset_flags; // RRDSET_FLAGS + time_t last_point_end_time_s; + BUFFER *wb; +} RRDSET_STREAM_BUFFER; + +RRDSET_STREAM_BUFFER rrdset_push_metric_initialize(RRDSET *st, time_t wall_clock_time); +void rrdset_push_metrics_v1(RRDSET_STREAM_BUFFER *rsb, RRDSET *st); +void rrdset_push_metrics_finished(RRDSET_STREAM_BUFFER *rsb, RRDSET *st); +void rrddim_push_metrics_v2(RRDSET_STREAM_BUFFER *rsb, RRDDIM *rd, usec_t point_end_time_ut, NETDATA_DOUBLE n, SN_FLAGS flags); + bool rrdset_push_chart_definition_now(RRDSET *st); void *rrdpush_sender_thread(void *ptr); void rrdpush_send_host_labels(RRDHOST *host); @@ -312,7 +331,7 @@ 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); +int rrdpush_receiver_thread_spawn(struct web_client *w, char *decoded_query_string); 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); diff --git a/streaming/sender.c b/streaming/sender.c index 854b57fc..179c2dc6 100644 --- a/streaming/sender.c +++ b/streaming/sender.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "rrdpush.h" -#include "parser/parser.h" #define WORKER_SENDER_JOB_CONNECT 0 #define WORKER_SENDER_JOB_PIPE_READ 1 @@ -104,6 +103,14 @@ void sender_commit(struct sender_state *s, BUFFER *wb) { netdata_mutex_lock(&s->mutex); +// FILE *fp = fopen("/tmp/stream.txt", "a"); +// fprintf(fp, +// "\n--- SEND BEGIN: %s ----\n" +// "%s" +// "--- SEND END ----------------------------------------\n" +// , rrdhost_hostname(s->host), src); +// fclose(fp); + 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->buffer->max_size, buffer_strlen(wb) + 1, SENDER_BUFFER_ADAPT_TO_TIMES_MAX_SIZE); @@ -171,8 +178,16 @@ void sender_commit(struct sender_state *s, BUFFER *wb) { replication_recalculate_buffer_used_ratio_unsafe(s); + bool signal_sender = false; + if(!rrdpush_sender_pipe_has_pending_data(s)) { + rrdpush_sender_pipe_set_pending_data(s); + signal_sender = true; + } + netdata_mutex_unlock(&s->mutex); - rrdpush_signal_sender_to_wake_up(s); + + if(signal_sender) + rrdpush_signal_sender_to_wake_up(s); } static inline void rrdpush_sender_add_host_variable_to_buffer(BUFFER *wb, const RRDVAR_ACQUIRED *rva) { @@ -522,7 +537,7 @@ static bool rrdpush_sender_thread_connect_to_parent(RRDHOST *host, int default_p #endif // reset our capabilities to default - s->capabilities = STREAM_OUR_CAPABILITIES; + s->capabilities = stream_our_capabilities(); #ifdef ENABLE_COMPRESSION // If we don't want compression, remove it from our capabilities @@ -894,7 +909,7 @@ void stream_execute_function_callback(BUFFER *func_wb, int code, void *data) { pluginsd_function_result_begin_to_buffer(wb , string2str(tmp->transaction) , code - , functions_content_type_to_format(func_wb->contenttype) + , functions_content_type_to_format(func_wb->content_type) , func_wb->expires); buffer_fast_strcat(wb, buffer_tostring(func_wb), buffer_strlen(func_wb)); @@ -929,7 +944,7 @@ void execute_commands(struct sender_state *s) { // 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); + size_t num_words = pluginsd_split_words(start, words, PLUGINSD_MAX_WORDS); const char *keyword = get_word(words, num_words, 0); @@ -1009,7 +1024,6 @@ void execute_commands(struct sender_state *s) { } struct rrdpush_sender_thread_data { - struct sender_state *sender_state; RRDHOST *host; char *pipe_buffer; }; @@ -1242,7 +1256,6 @@ void *rrdpush_sender_thread(void *ptr) { struct rrdpush_sender_thread_data *thread_data = callocz(1, sizeof(struct rrdpush_sender_thread_data)); thread_data->pipe_buffer = mallocz(pipe_buffer_size); - thread_data->sender_state = s; thread_data->host = s->host; netdata_thread_cleanup_push(rrdpush_sender_thread_cleanup_callback, thread_data); @@ -1298,8 +1311,10 @@ void *rrdpush_sender_thread(void *ptr) { netdata_mutex_lock(&s->mutex); size_t outstanding = cbuffer_next_unsafe(s->buffer, NULL); size_t available = cbuffer_available_size_unsafe(s->buffer); - if (unlikely(!outstanding)) + if (unlikely(!outstanding)) { + rrdpush_sender_pipe_clear_pending_data(s); 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->buffer->max_size - available) * 100.0 / (NETDATA_DOUBLE)s->buffer->max_size); diff --git a/system/Makefile.am b/system/Makefile.am index 1a1b41e2..13466639 100644 --- a/system/Makefile.am +++ b/system/Makefile.am @@ -3,16 +3,18 @@ MAINTAINERCLEANFILES = $(srcdir)/Makefile.in CLEANFILES = \ - netdata-openrc \ - netdata.logrotate \ - netdata.service \ - netdata.service.v235 \ - netdata-init-d \ - netdata-lsb \ - netdata-freebsd \ - netdata.plist \ - netdata.crontab \ - netdata-updater.service \ + cron/netdata-updater-daily \ + freebsd/rc.d/netdata \ + initd/init.d/netdata \ + launchd/netdata.plist \ + logrotate/netdata \ + lsb/init.d/netdata \ + openrc/conf.d/netdata \ + openrc/init.d/netdata \ + runit/run \ + systemd/netdata.service \ + systemd/netdata.service.v235 \ + systemd/netdata-updater.service \ $(NULL) include $(top_srcdir)/build/subst.inc @@ -26,44 +28,107 @@ dist_config_DATA = \ .install-type \ $(NULL) +libconfigvnodesdir=$(libconfigdir)/vnodes +libsyscrondir=$(libsysdir)/cron +libsysfreebsddir=$(libsysdir)/freebsd +libsysfreebsdrcddir=$(libsysfreebsddir)/rc.d +libsysinitddir=$(libsysdir)/initd +libsysinitdinitddir=$(libsysinitddir)/init.d +libsyslaunchddir=$(libsysdir)/launchd +libsyslogrotatedir=$(libsysdir)/logrotate +libsyslsbdir=$(libsysdir)/lsb +libsyslsbinitddir=$(libsyslsbdir)/init.d +libsysopenrcdir=$(libsysdir)/openrc +libsysopenrcinitddir=$(libsysopenrcdir)/init.d +libsysopenrcconfddir=$(libsysopenrcdir)/conf.d +libsysrunitdir=$(libsysdir)/runit +libsyssystemddir=$(libsysdir)/systemd + # Explicitly install directories to avoid permission issues due to umask install-exec-local: $(INSTALL) -d $(DESTDIR)$(configdir) $(INSTALL) -d $(DESTDIR)$(libsysdir) + $(INSTALL) -d $(DESTDIR)$(libsyscrondir) + $(INSTALL) -d $(DESTDIR)$(libsysfreebsdrcddir) + $(INSTALL) -d $(DESTDIR)$(libsysinitdinitddir) + $(INSTALL) -d $(DESTDIR)$(libsyslaunchddir) + $(INSTALL) -d $(DESTDIR)$(libsyslogrotatedir) + $(INSTALL) -d $(DESTDIR)$(libsyslsbinitddir) + $(INSTALL) -d $(DESTDIR)$(libsyssystemddir) + $(INSTALL) -d $(DESTDIR)$(libsysopenrcinitddir) + $(INSTALL) -d $(DESTDIR)$(libsysopenrcconfddir) + $(INSTALL) -d $(DESTDIR)$(libsysrunitdir) + $(INSTALL) -d $(DESTDIR)$(libconfigvnodesdir) + +dist_libconfigvnodes_DATA = \ + vnodes/vnodes.conf + $(NULL) libexecnetdatadir=$(libexecdir)/netdata nodist_libexecnetdata_SCRIPTS = \ install-service.sh \ $(NULL) -nodist_libsys_DATA = \ - netdata-openrc \ - netdata.logrotate \ - netdata.service \ - netdata.service.v235 \ - netdata-init-d \ - netdata-lsb \ - netdata-freebsd \ - netdata.plist \ - netdata.crontab \ - netdata-updater.service \ +nodist_libsyscron_DATA = \ + cron/netdata-updater-daily \ + $(NULL) + +nodist_libsysfreebsdrcd_DATA = \ + freebsd/rc.d/netdata \ + $(NULL) + +nodist_libsysinitdinitd_DATA = \ + initd/init.d/netdata \ + $(NULL) + +nodist_libsyslaunchd_DATA = \ + launchd/netdata.plist \ + $(NULL) + +nodist_libsyslogrotate_DATA = \ + logrotate/netdata \ + $(NULL) + +nodist_libsyslsbinitd_DATA = \ + lsb/init.d/netdata \ + $(NULL) + +nodist_libsysopenrcinitd_DATA = \ + openrc/init.d/netdata \ + $(NULL) + +nodist_libsysopenrcconfd_DATA = \ + openrc/conf.d/netdata \ + $(NULL) + +nodist_libsysrunit_DATA = \ + runit/run \ + $(NULL) + +nodist_libsyssystemd_DATA = \ + systemd/netdata.service \ + systemd/netdata.service.v235 \ + systemd/netdata-updater.service \ $(NULL) -dist_libsys_DATA = \ - netdata-updater.timer \ +dist_libsyssystemd_DATA = \ + systemd/netdata-updater.timer \ + systemd/50-netdata.preset $(NULL) dist_noinst_DATA = \ install-service.sh.in \ - netdata-openrc.in \ - netdata.logrotate.in \ - netdata.service.in \ - netdata.service.v235.in \ - netdata-init-d.in \ - netdata-lsb.in \ - netdata-freebsd.in \ - netdata.plist.in \ netdata.conf \ - netdata.crontab.in \ - netdata-updater.service.in \ + cron/netdata-updater-daily.in \ + freebsd/rc.d/netdata.in \ + initd/init.d/netdata.in \ + launchd/netdata.plist.in \ + logrotate/netdata.in \ + lsb/init.d/netdata.in \ + openrc/conf.d/netdata.in \ + openrc/init.d/netdata.in \ + runit/run.in \ + systemd/netdata.service.in \ + systemd/netdata.service.v235.in \ + systemd/netdata-updater.service.in \ $(NULL) diff --git a/system/cron/netdata-updater-daily.in b/system/cron/netdata-updater-daily.in new file mode 100644 index 00000000..8f0527e0 --- /dev/null +++ b/system/cron/netdata-updater-daily.in @@ -0,0 +1 @@ +2 57 * * * root @pkglibexecdir_POST@/netdata-updater.sh diff --git a/system/edit-config b/system/edit-config index 754f9374..96ee82d4 100755 --- a/system/edit-config +++ b/system/edit-config @@ -34,10 +34,20 @@ error() { } abspath() { - if [ -d "${1}" ]; then + if [ -d "${1}/" ]; then echo "$(cd "${1}" && /usr/bin/env PWD= pwd -P)/" - else + elif [ -f "${1}" ]; then echo "$(cd "$(dirname "${1}")" && /usr/bin/env PWD= pwd -P)/$(basename "${1}")" + elif echo "${1}" | grep -q '/'; then + if echo "${1}" | grep -q '^/'; then + mkdir -p "$(dirname "${1}")" + echo "$(cd "$(dirname "${1}")" && /usr/bin/env PWD= pwd -P)/$(basename "${1}")" + else + mkdir -p "${script_dir}/$(dirname "${1}")" + echo "${script_dir}/${1}" + fi + else + echo "${script_dir}/${1}" fi } @@ -153,7 +163,7 @@ list_files() { check_directories handle_container - if test -t; then + if test -t && command -v tput > /dev/null 2>&1; then width="$(tput cols)" fi diff --git a/system/freebsd/rc.d/netdata.in b/system/freebsd/rc.d/netdata.in new file mode 100644 index 00000000..fd544c86 --- /dev/null +++ b/system/freebsd/rc.d/netdata.in @@ -0,0 +1,53 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later + +. /etc/rc.subr + +name=netdata +rcvar=netdata_enable + +piddir="@localstatedir_POST@/run/netdata" +pidfile="${piddir}/netdata.pid" + +command="@sbindir_POST@/netdata" +command_args="-P ${pidfile}" + +required_files="@configdir_POST@/netdata.conf" + +start_precmd="netdata_prestart" +stop_postcmd="netdata_poststop" + +extra_commands="reloadhealth savedb" + +reloadhealth_cmd="netdata_reloadhealth" +savedb_cmd="netdata_savedb" + +netdata_prestart() +{ + [ ! -d "${piddir}" ] && mkdir -p "${piddir}" + chown @netdata_user_POST@:@netdata_user_POST@ "${piddir}" + return 0 +} + +netdata_poststop() +{ + [ -f "${pidfile}" ] && rm "${pidfile}" + return 0 +} + +netdata_reloadhealth() +{ + p=`cat ${pidfile}` + kill -USR2 ${p} && echo "Sent USR2 (reload health) to pid ${p}" + return 0 +} + +netdata_savedb() +{ + p=`cat ${pidfile}` + kill -USR2 ${p} && echo "Sent USR1 (save db) to pid ${p}" + return 0 +} + +load_rc_config $name +run_rc_command "$1" diff --git a/system/initd/init.d/netdata.in b/system/initd/init.d/netdata.in new file mode 100644 index 00000000..c0257ffa --- /dev/null +++ b/system/initd/init.d/netdata.in @@ -0,0 +1,95 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +# +# netdata Real-time performance monitoring, done right +# chkconfig: 345 99 01 +# description: Netdata is a daemon that collects data in real-time (per second) +# and presents a web site to view and analyze them. The presentation +# is also real-time and full of interactive charts that precisely +# render all collected values. +# processname: netdata + +# Source functions +. /etc/rc.d/init.d/functions + +DAEMON="netdata" +DAEMON_PATH=@sbindir_POST@ +PIDFILE_PATH=@localstatedir_POST@/run/netdata +PIDFILE=$PIDFILE_PATH/$DAEMON.pid +DAEMONOPTS="-P $PIDFILE" +STOP_TIMEOUT="60" + +[ -e /etc/sysconfig/$DAEMON ] && . /etc/sysconfig/$DAEMON + +LOCKFILE=/var/lock/subsys/$DAEMON + +service_start() +{ + [ -x $DAEMON_PATH ] || exit 5 + [ ! -d $PIDFILE_PATH ] && mkdir -p $PIDFILE_PATH + chown @netdata_user_POST@:@netdata_user_POST@ $PIDFILE_PATH + echo -n "Starting $DAEMON..." + daemon $DAEMON_PATH/$DAEMON $DAEMONOPTS + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch $LOCKFILE + return $RETVAL +} + +service_stop() +{ + printf "%-50s" "Stopping $DAEMON..." + killproc -p ${PIDFILE} -d ${STOP_TIMEOUT} $DAEMON + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f ${PIDFILE} ${LOCKFILE} + return $RETVAL +} + +condrestart() +{ + if ! service_status > /dev/null; then + RETVAL=$1 + return $RETVAL + fi + + service_stop + service_start +} + +service_status() +{ + status -p ${PIDFILE} $DAEMON_PATH/$DAEMON +} + +service_status_quiet() +{ + status -p ${PIDFILE} $DAEMON_PATH/$DAEMON >/dev/null 2>&1 +} + +case "$1" in +start) + service_status_quiet && exit 0 + service_start +;; +stop) + service_status_quiet || exit 0 + service_stop +;; +restart) + service_stop + service_start +;; +try-restart) + condrestart 0 + ;; +force-reload) + condrestart 7 +;; +status) + service_status +;; +*) + echo "Usage: $0 {start|stop|restart|try-restart|force-reload|status}" + exit 3 +esac diff --git a/system/install-service.sh.in b/system/install-service.sh.in index 59cff0a0..a885a861 100755 --- a/system/install-service.sh.in +++ b/system/install-service.sh.in @@ -87,7 +87,7 @@ get_os_key() { valid_types() { case "${PLATFORM}" in Linux) - echo "detect systemd openrc lsb initd" + echo "detect ${LINUX_INIT_TYPES}" ;; FreeBSD) echo "detect freebsd" @@ -102,7 +102,7 @@ valid_types() { } install_generic_service() { - svc_type="${1}" + svc_path="${1}" svc_type_name="${2}" svc_file="${3}" svc_enable_hook="${4}" @@ -113,7 +113,7 @@ install_generic_service() { ENABLE="enable" fi - if ! install -p -m 0755 -o 0 -g 0 "${SVC_SOURCE}/netdata-${svc_type}" "${svc_file}"; then + if ! install -p -m 0755 -o 0 -g 0 "${SVC_SOURCE}/${svc_path}/netdata" "${svc_file}"; then error "Failed to install service file." exit 4 fi @@ -236,10 +236,12 @@ get_systemd_service_dir() { } install_systemd_service() { - SRCFILE="${SVC_SOURCE}/netdata.service" + SRCFILE="${SVC_SOURCE}/systemd/netdata.service" + PRESET_FILE="${SVC_SOURCE}/systemd/50-netdata.preset" + SVCDIR="$(get_systemd_service_dir)" if [ "$(systemctl --version | head -n 1 | cut -f 2 -d ' ')" -le 235 ]; then - SRCFILE="${SVC_SOURCE}/netdata.service.v235" + SRCFILE="${SVC_SOURCE}/systemd/netdata.service.v235" fi if [ "${ENABLE}" = "auto" ]; then @@ -255,18 +257,24 @@ install_systemd_service() { fi info "Installing systemd service..." - if ! install -p -m 0644 -o 0 -g 0 "${SRCFILE}" "$(get_systemd_service_dir)/netdata.service"; then + if ! install -p -m 0644 -o 0 -g 0 "${SRCFILE}" "${SVCDIR}/netdata.service"; then error "Failed to install systemd service file." exit 4 fi + if [ -f "${PRESET_FILE}" ]; then + if ! install -p -m 0644 -o 0 -g 0 "${PRESET_FILE}" "${SVCDIR}-preset/50-netdata.preset"; then + warning "Failed to install netdata preset file." + fi + fi + if [ "$(check_systemd)" = "YES" ]; then if ! systemctl daemon-reload; then - warning "Failed to reload systemd unit files." + warning "Failed to reload systemd unit files." fi - if ! systemctl ${ENABLE} netdata; then - warning "Failed to ${ENABLE} Netdata service." + if ! systemctl "${ENABLE}" netdata; then + warning "Failed to ${ENABLE} Netdata service." fi fi } @@ -290,6 +298,9 @@ _check_openrc() { # if there is no /etc/init.d, it's not OpenRC [ ! -d /etc/init.d ] && echo "NO" && return 0 + # if there is no /etc/conf.d, it's not OpenRC + [ ! -d /etc/conf.d ] && echo "NO" && return 0 + # if there is no rc-update command, it's not OpenRC [ -z "$(command -v rc-update 2>/dev/null || true)" ] && echo "NO" && return 0 @@ -341,7 +352,15 @@ disable_openrc() { } install_openrc_service() { - install_generic_service openrc OpenRC /etc/init.d/netdata enable_openrc disable_openrc + install_generic_service openrc/init.d OpenRC /etc/init.d/netdata enable_openrc disable_openrc + + if [ ! -f /etc/conf.d/netdata ]; then + info "Installing OpenRC configuration file." + + if ! install -p -m 0755 -o 0 -g 0 "${SVC_SOURCE}/openrc/conf.d/netdata" "/etc/conf.d/netdata"; then + warning "Failed to install configuration file, however the service will still work." + fi + fi } openrc_cmds() { @@ -399,7 +418,7 @@ disable_lsb() { } install_lsb_service() { - install_generic_service lsb LSB /etc/init.d/netdata enable_lsb disable_lsb + install_generic_service lsb/init.d LSB /etc/init.d/netdata enable_lsb disable_lsb } lsb_cmds() { @@ -454,7 +473,7 @@ disable_initd() { } install_initd_service() { - install_generic_service init-d init.d /etc/init.d/netdata enable_initd disable_initd + install_generic_service initd/init.d init.d /etc/init.d/netdata enable_initd disable_initd } initd_cmds() { @@ -464,8 +483,6 @@ initd_cmds() { # ===================================================================== # runit support functions -# -# Currently not supported, this exists to provide useful error messages. _check_runit() { # if there is no runsvdir command, then it's not runit @@ -492,19 +509,61 @@ check_runit() { } install_runit_service() { - error "Detected runit, which we do not currently support." - exit 3 + if [ -d /etc/sv ]; then + svc_dir="/etc/sv/netdata" + elif [ -d /etc/runit/sv ]; then + svc_dir="/etc/runit/sv/netdata" + else + error "Failed to locate service directory" + exit 4 + fi + + if [ -d /service ]; then + live_svc_dir="/service" + elif [ -d /var/service ]; then + live_svc_dir="/var/service" + elif [ -d /run/runit/service ]; then + live_svc_dir="/run/runit/service" + elif [ -d /etc/runit/runsvdir/default ]; then + live_svc_dir="/etc/runit/runsvdir/default" + else + error "Failed to locate live service directory" + exit 4 + fi + + svc_file="${svc_dir}/run" + + info "Installing runit service file." + if [ ! -f "${svc_file}" ] && [ "${ENABLE}" = "auto" ]; then + ENABLE="enable" + fi + + if ! install -D -p -m 0755 -o 0 -g 0 "${SVC_SOURCE}/runit/run" "${svc_file}"; then + error "Failed to install service file." + exit 4 + fi + + case ${ENABLE} in + enable) + if ! ln -s "${svc_dir}" "${live_svc_dir}"; then + warning "Failed to enable the Netdata service." + fi + ;; + disable) + if ! rm "${live_svc_dir}/netdata"; then + warning "Failed to disable the Netdata service." + fi + ;; + esac } runit_cmds() { - error "Detected runit, which we do not currently support." - exit 3 + NETDATA_START_CMD="sv start netdata" + NETDATA_STOP_CMD="sv stop netdata" } # ===================================================================== # WSL support functions -# -# Cannot be supported, this exists to provide useful error messages. _check_wsl() { # If uname -r contains the string WSL, then it's WSL. @@ -552,7 +611,7 @@ disable_freebsd() { } install_freebsd_service() { - install_generic_service freebsd "FreeBSD rc.d" /usr/local/etc/rc.d/netdata enable_freebsd disable_freebsd + install_generic_service freebsd/rc.d "FreeBSD rc.d" /usr/local/etc/rc.d/netdata enable_freebsd disable_freebsd } freebsd_cmds() { @@ -566,7 +625,7 @@ freebsd_cmds() { install_darwin_service() { info "Installing macOS plist file for launchd." - if ! install -C -S -p -m 0644 -o 0 -g 0 system/netdata.plist /Library/LaunchDaemons/com.github.netdata.plist; then + if ! install -C -S -p -m 0644 -o 0 -g 0 system/launchd/netdata.plist /Library/LaunchDaemons/com.github.netdata.plist; then error "Failed to copy plist file." exit 4 fi @@ -633,7 +692,7 @@ detect_linux_svc_type() { install_linux_service() { t="$(detect_linux_svc_type)" - if [ -z "${t}" ]; then + if [ -z "${t}" ] || [ "${t}" = 'detect' ]; then exit 2 fi @@ -643,7 +702,7 @@ install_linux_service() { linux_cmds() { t="$(detect_linux_svc_type)" - if [ -z "${t}" ]; then + if [ -z "${t}" ] || [ "${t}" = 'detect' ]; then exit 2 fi diff --git a/system/launchd/netdata.plist.in b/system/launchd/netdata.plist.in new file mode 100644 index 00000000..a969b317 --- /dev/null +++ b/system/launchd/netdata.plist.in @@ -0,0 +1,15 @@ + + + + + + Label + com.github.netdata + ProgramArguments + + @sbindir_POST@/netdata + + RunAtLoad + + + diff --git a/system/logrotate/netdata.in b/system/logrotate/netdata.in new file mode 100644 index 00000000..2c4949e5 --- /dev/null +++ b/system/logrotate/netdata.in @@ -0,0 +1,12 @@ +@localstatedir_POST@/log/netdata/*.log { + daily + missingok + rotate 14 + compress + delaycompress + notifempty + sharedscripts + postrotate + /bin/kill -HUP `cat /run/netdata/netdata.pid 2>/dev/null` 2>/dev/null || true + endscript +} diff --git a/system/lsb/init.d/netdata.in b/system/lsb/init.d/netdata.in new file mode 100644 index 00000000..e429ad1c --- /dev/null +++ b/system/lsb/init.d/netdata.in @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# +# Netdata LSB start script +# +# Copyright: +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Author: +# Costa Tsaousis +# Pavlos Emm. Katsoulakis + +### BEGIN INIT INFO +# Provides: netdata +# Required-Start: $local_fs $remote_fs $network $named $time +# Required-Stop: $local_fs $remote_fs $network $named $time +# Should-Start: $local_fs $network $named $remote_fs $time $all +# Should-Stop: $local_fs $network $named $remote_fs $time $all +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start and stop the netdata real-time monitoring server daemon +# Description: Controls the main netdata monitoring server daemon "netdata". +# and all its plugins. +### END INIT INFO +# +set -e +set -u +${DEBIAN_SCRIPT_DEBUG:+ set -v -x} + +DAEMON="netdata" +DAEMON_PATH=@sbindir_POST@ +PIDFILE_PATH=@localstatedir_POST@/run/netdata +PIDFILE=$PIDFILE_PATH/$DAEMON.pid +DAEMONOPTS="-P $PIDFILE" + +test -x $DAEMON_PATH/$DAEMON || exit 0 + +. /lib/lsb/init-functions + +# Safeguard (relative paths, core dumps..) +cd / +umask 022 + +service_start() { + if [ ! -d $PIDFILE_PATH ]; then + mkdir -p $PIDFILE_PATH + fi + + chown @netdata_user_POST@:@netdata_user_POST@ $PIDFILE_PATH + + log_daemon_msg "Starting real-time performance monitoring" "netdata" + start_daemon -p $PIDFILE $DAEMON_PATH/$DAEMON $DAEMONOPTS + RETVAL=$? + log_end_msg $RETVAL + return $RETVAL +} + +service_stop() { + log_daemon_msg "Stopping real-time performance monitoring" "netdata" + killproc -p ${PIDFILE} $DAEMON_PATH/$DAEMON + RETVAL=$? + log_end_msg $RETVAL + + if [ $RETVAL -eq 0 ]; then + rm -f ${PIDFILE} + fi + return $RETVAL +} + +condrestart() { + if ! service_status > /dev/null; then + RETVAL=$1 + return + fi + + service_stop + service_start +} + +service_status() { + status_of_proc -p $PIDFILE $DAEMON_PATH/$DAEMON netdata +} + + +# +# main() +# + +case "${1:-''}" in + 'start') + service_start + ;; + + 'stop') + service_stop + ;; + + 'restart') + service_stop + service_start + ;; + + 'try-restart') + condrestart 0 + ;; + + 'force-reload') + condrestart 7 + ;; + + 'status') + service_status && exit 0 || exit $? + ;; + *) + echo "Usage: $0 {start|stop|restart|try-restart|force-reload|status}" + exit 1 +esac diff --git a/system/netdata-freebsd.in b/system/netdata-freebsd.in deleted file mode 100644 index fd544c86..00000000 --- a/system/netdata-freebsd.in +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-3.0-or-later - -. /etc/rc.subr - -name=netdata -rcvar=netdata_enable - -piddir="@localstatedir_POST@/run/netdata" -pidfile="${piddir}/netdata.pid" - -command="@sbindir_POST@/netdata" -command_args="-P ${pidfile}" - -required_files="@configdir_POST@/netdata.conf" - -start_precmd="netdata_prestart" -stop_postcmd="netdata_poststop" - -extra_commands="reloadhealth savedb" - -reloadhealth_cmd="netdata_reloadhealth" -savedb_cmd="netdata_savedb" - -netdata_prestart() -{ - [ ! -d "${piddir}" ] && mkdir -p "${piddir}" - chown @netdata_user_POST@:@netdata_user_POST@ "${piddir}" - return 0 -} - -netdata_poststop() -{ - [ -f "${pidfile}" ] && rm "${pidfile}" - return 0 -} - -netdata_reloadhealth() -{ - p=`cat ${pidfile}` - kill -USR2 ${p} && echo "Sent USR2 (reload health) to pid ${p}" - return 0 -} - -netdata_savedb() -{ - p=`cat ${pidfile}` - kill -USR2 ${p} && echo "Sent USR1 (save db) to pid ${p}" - return 0 -} - -load_rc_config $name -run_rc_command "$1" diff --git a/system/netdata-init-d.in b/system/netdata-init-d.in deleted file mode 100644 index c0257ffa..00000000 --- a/system/netdata-init-d.in +++ /dev/null @@ -1,95 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-3.0-or-later -# -# netdata Real-time performance monitoring, done right -# chkconfig: 345 99 01 -# description: Netdata is a daemon that collects data in real-time (per second) -# and presents a web site to view and analyze them. The presentation -# is also real-time and full of interactive charts that precisely -# render all collected values. -# processname: netdata - -# Source functions -. /etc/rc.d/init.d/functions - -DAEMON="netdata" -DAEMON_PATH=@sbindir_POST@ -PIDFILE_PATH=@localstatedir_POST@/run/netdata -PIDFILE=$PIDFILE_PATH/$DAEMON.pid -DAEMONOPTS="-P $PIDFILE" -STOP_TIMEOUT="60" - -[ -e /etc/sysconfig/$DAEMON ] && . /etc/sysconfig/$DAEMON - -LOCKFILE=/var/lock/subsys/$DAEMON - -service_start() -{ - [ -x $DAEMON_PATH ] || exit 5 - [ ! -d $PIDFILE_PATH ] && mkdir -p $PIDFILE_PATH - chown @netdata_user_POST@:@netdata_user_POST@ $PIDFILE_PATH - echo -n "Starting $DAEMON..." - daemon $DAEMON_PATH/$DAEMON $DAEMONOPTS - RETVAL=$? - echo - [ $RETVAL -eq 0 ] && touch $LOCKFILE - return $RETVAL -} - -service_stop() -{ - printf "%-50s" "Stopping $DAEMON..." - killproc -p ${PIDFILE} -d ${STOP_TIMEOUT} $DAEMON - RETVAL=$? - echo - [ $RETVAL -eq 0 ] && rm -f ${PIDFILE} ${LOCKFILE} - return $RETVAL -} - -condrestart() -{ - if ! service_status > /dev/null; then - RETVAL=$1 - return $RETVAL - fi - - service_stop - service_start -} - -service_status() -{ - status -p ${PIDFILE} $DAEMON_PATH/$DAEMON -} - -service_status_quiet() -{ - status -p ${PIDFILE} $DAEMON_PATH/$DAEMON >/dev/null 2>&1 -} - -case "$1" in -start) - service_status_quiet && exit 0 - service_start -;; -stop) - service_status_quiet || exit 0 - service_stop -;; -restart) - service_stop - service_start -;; -try-restart) - condrestart 0 - ;; -force-reload) - condrestart 7 -;; -status) - service_status -;; -*) - echo "Usage: $0 {start|stop|restart|try-restart|force-reload|status}" - exit 3 -esac diff --git a/system/netdata-lsb.in b/system/netdata-lsb.in deleted file mode 100644 index e429ad1c..00000000 --- a/system/netdata-lsb.in +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env bash -# -# Netdata LSB start script -# -# Copyright: -# SPDX-License-Identifier: GPL-3.0-or-later -# -# Author: -# Costa Tsaousis -# Pavlos Emm. Katsoulakis - -### BEGIN INIT INFO -# Provides: netdata -# Required-Start: $local_fs $remote_fs $network $named $time -# Required-Stop: $local_fs $remote_fs $network $named $time -# Should-Start: $local_fs $network $named $remote_fs $time $all -# Should-Stop: $local_fs $network $named $remote_fs $time $all -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Start and stop the netdata real-time monitoring server daemon -# Description: Controls the main netdata monitoring server daemon "netdata". -# and all its plugins. -### END INIT INFO -# -set -e -set -u -${DEBIAN_SCRIPT_DEBUG:+ set -v -x} - -DAEMON="netdata" -DAEMON_PATH=@sbindir_POST@ -PIDFILE_PATH=@localstatedir_POST@/run/netdata -PIDFILE=$PIDFILE_PATH/$DAEMON.pid -DAEMONOPTS="-P $PIDFILE" - -test -x $DAEMON_PATH/$DAEMON || exit 0 - -. /lib/lsb/init-functions - -# Safeguard (relative paths, core dumps..) -cd / -umask 022 - -service_start() { - if [ ! -d $PIDFILE_PATH ]; then - mkdir -p $PIDFILE_PATH - fi - - chown @netdata_user_POST@:@netdata_user_POST@ $PIDFILE_PATH - - log_daemon_msg "Starting real-time performance monitoring" "netdata" - start_daemon -p $PIDFILE $DAEMON_PATH/$DAEMON $DAEMONOPTS - RETVAL=$? - log_end_msg $RETVAL - return $RETVAL -} - -service_stop() { - log_daemon_msg "Stopping real-time performance monitoring" "netdata" - killproc -p ${PIDFILE} $DAEMON_PATH/$DAEMON - RETVAL=$? - log_end_msg $RETVAL - - if [ $RETVAL -eq 0 ]; then - rm -f ${PIDFILE} - fi - return $RETVAL -} - -condrestart() { - if ! service_status > /dev/null; then - RETVAL=$1 - return - fi - - service_stop - service_start -} - -service_status() { - status_of_proc -p $PIDFILE $DAEMON_PATH/$DAEMON netdata -} - - -# -# main() -# - -case "${1:-''}" in - 'start') - service_start - ;; - - 'stop') - service_stop - ;; - - 'restart') - service_stop - service_start - ;; - - 'try-restart') - condrestart 0 - ;; - - 'force-reload') - condrestart 7 - ;; - - 'status') - service_status && exit 0 || exit $? - ;; - *) - echo "Usage: $0 {start|stop|restart|try-restart|force-reload|status}" - exit 1 -esac diff --git a/system/netdata-openrc.in b/system/netdata-openrc.in deleted file mode 100644 index 15887892..00000000 --- a/system/netdata-openrc.in +++ /dev/null @@ -1,88 +0,0 @@ -#!/sbin/openrc-run -# SPDX-License-Identifier: GPL-3.0-or-later - -# The user netdata is configured to run as. -# If you edit its configuration file to set a different -# user, set it here too, to have its files switch ownership -: "${NETDATA_OWNER:=@netdata_user_POST@:@netdata_user_POST@}" - -# The timeout in seconds to wait for netdata -# to save its database on disk and exit. -: "${NETDATA_WAIT_EXIT_TIMEOUT:=60}" - -# When set to 1, if netdata does not exit in -# NETDATA_WAIT_EXIT_TIMEOUT, we will force it -# to exit. -: "${NETDATA_FORCE_EXIT:=0}" - -# When set to 1, we use netdatacli for reload/rotate/save commands instead of s-s-d. -: "${NETDATA_USE_NETDATACLI:=0}" - -# Specifies the pidfile to use when running in the background. -: "${NETDATA_PIDFILE:=@localstatedir_POST@/run/netdata/netdata.pid}" - -extra_started_commands="reload rotate save" -command_prefix="@sbindir_POST@" -command="${command_prefix}/netdata" -command_args="-P ${NETDATA_PIDFILE} ${NETDATA_EXTRA_ARGS}" -command_args_foreground="-D" -start_stop_daemon_args="-u ${NETDATA_OWNER}" -if [ "${NETDATA_FORCE_EXIT}" -eq 1 ]; then - retry="TERM/${NETDATA_WAIT_EXIT_TIMEOUT}/KILL/1" -else - retry="TERM/${NETDATA_WAIT_EXIT_TIMEOUT}" -fi - -depend() { - use logger - need net - after apache2 squid nginx mysql named opensips upsd hostapd postfix lm_sensors -} - -start_pre() { - checkpath -o ${NETDATA_OWNER} -d @localstatedir_POST@/cache/netdata @localstatedir_POST@/run/netdata - - if [ -z "${supervisor}" ]; then - pidfile="${NETDATA_PIDFILE}" - fi -} - -run_cmd() { - cmd="${1}" - msg="${2}" - failmsg="${3}" - signal="${4}" - - ebegin "${msg}" - if [ "${NETDATA_USE_NETDATACLI}" = 1 ]; then - "${command_prefix}/netdatacli" "${cmd}" >/dev/null - eend $? "${failmsg}" - elif [ "${supervisor}" = "supervise-daemon" ]; then - supervise-daemon "${RC_SVCNAME}" --signal "${signal}" - eend $? "${failmsg}" - else - start-stop-daemon --signal "${signal}" --pidfile "${pidfile}" - eend $? "${failmsg}" - fi -} - -reload() { - run_cmd reload-health \ - "Reloading Netdata health configuration" \ - "Failed to reload Netdata health configuration" \ - SIGUSR2 -} - -rotate() { - run_cmd reopen-logs \ - "Reopening Netdata log files" \ - "Failed to reopen Netdata log files" \ - SIGHUP -} - -save() { - run_cmd save-database \ - "Saving Netdata database" \ - "Failed to save Netdata database" \ - SIGUSR1 -} diff --git a/system/netdata-updater.service.in b/system/netdata-updater.service.in deleted file mode 100644 index d0bd4994..00000000 --- a/system/netdata-updater.service.in +++ /dev/null @@ -1,8 +0,0 @@ -[Unit] -Description=Daily auto-updates for Netdata -RefuseManualStart=no -RefuseManualStop=yes - -[Service] -Type=oneshot -ExecStart=@pkglibexecdir_POST@/netdata-updater.sh diff --git a/system/netdata-updater.timer b/system/netdata-updater.timer deleted file mode 100644 index 8b36e46f..00000000 --- a/system/netdata-updater.timer +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=Daily auto-updates for Netdata -RefuseManualStart=no -RefuseManualStop=no - -[Timer] -Persistent=false -OnCalendar=daily -Unit=netdata-updater.service - -[Install] -WantedBy=timers.target diff --git a/system/netdata.conf b/system/netdata.conf index 70edb082..94d9ab82 100644 --- a/system/netdata.conf +++ b/system/netdata.conf @@ -1,6 +1,10 @@ # netdata configuration # -# You can download the latest version of this file, using: +# You can get the latest version of this file, using: +# +# netdatacli dumpconfig > /etc/netdata/netdata.conf +# +# You can also download it using: # # wget -O /etc/netdata/netdata.conf http://localhost:19999/netdata.conf # or diff --git a/system/netdata.crontab.in b/system/netdata.crontab.in deleted file mode 100644 index 8f0527e0..00000000 --- a/system/netdata.crontab.in +++ /dev/null @@ -1 +0,0 @@ -2 57 * * * root @pkglibexecdir_POST@/netdata-updater.sh diff --git a/system/netdata.logrotate.in b/system/netdata.logrotate.in deleted file mode 100644 index 2c4949e5..00000000 --- a/system/netdata.logrotate.in +++ /dev/null @@ -1,12 +0,0 @@ -@localstatedir_POST@/log/netdata/*.log { - daily - missingok - rotate 14 - compress - delaycompress - notifempty - sharedscripts - postrotate - /bin/kill -HUP `cat /run/netdata/netdata.pid 2>/dev/null` 2>/dev/null || true - endscript -} diff --git a/system/netdata.plist.in b/system/netdata.plist.in deleted file mode 100644 index a969b317..00000000 --- a/system/netdata.plist.in +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - Label - com.github.netdata - ProgramArguments - - @sbindir_POST@/netdata - - RunAtLoad - - - diff --git a/system/netdata.service.in b/system/netdata.service.in deleted file mode 100644 index 25d95b2b..00000000 --- a/system/netdata.service.in +++ /dev/null @@ -1,80 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -[Unit] -Description=Real time performance monitoring - -# append here other services you want netdata to wait for them to start -After=network.target httpd.service squid.service nfs-server.service mysqld.service mysql.service named.service postfix.service chronyd.service - -[Service] -Type=simple -User=@netdata_user_POST@ -Group=netdata -RuntimeDirectory=netdata -RuntimeDirectoryMode=0775 -PIDFile=/run/netdata/netdata.pid -ExecStart=@sbindir_POST@/netdata -P /run/netdata/netdata.pid -D -ExecStartPre=/bin/mkdir -p @localstatedir_POST@/cache/netdata -ExecStartPre=/bin/chown -R @netdata_user_POST@ @localstatedir_POST@/cache/netdata -ExecStartPre=/bin/mkdir -p /run/netdata -ExecStartPre=/bin/chown -R @netdata_user_POST@ /run/netdata -PermissionsStartOnly=true - -# saving a big db on slow disks may need some time -TimeoutStopSec=150 - -# restart netdata if it crashes -Restart=on-failure -RestartSec=30 - -# Valid policies: other (the system default) | batch | idle | fifo | rr -# To give netdata the max priority, set CPUSchedulingPolicy=rr and CPUSchedulingPriority=99 -CPUSchedulingPolicy=batch - -# This sets the scheduling priority (for policies: rr and fifo). -# Priority gets values 1 (lowest) to 99 (highest). -#CPUSchedulingPriority=1 - -# For scheduling policy 'other' and 'batch', this sets the lowest niceness of netdata (-20 highest to 19 lowest). -Nice=0 - -# Capabilities -# is required for freeipmi and slabinfo plugins -CapabilityBoundingSet=CAP_DAC_OVERRIDE -# is required for apps plugin -CapabilityBoundingSet=CAP_DAC_READ_SEARCH -# is required for freeipmi plugin -CapabilityBoundingSet=CAP_FOWNER CAP_SYS_RAWIO -# is required for apps, perf and slabinfo plugins -CapabilityBoundingSet=CAP_SETPCAP -# is required for perf plugin -CapabilityBoundingSet=CAP_SYS_ADMIN CAP_PERFMON -# is required for apps plugin -CapabilityBoundingSet=CAP_SYS_PTRACE -# is required for ebpf plugin -CapabilityBoundingSet=CAP_SYS_RESOURCE -# is required for go.d/ping app -CapabilityBoundingSet=CAP_NET_RAW -# is required for cgroups plugin -CapabilityBoundingSet=CAP_SYS_CHROOT -# is required for nfacct plugin (bandwidth accounting) -CapabilityBoundingSet=CAP_NET_ADMIN -# is required for plugins that use sudo -CapabilityBoundingSet=CAP_SETGID CAP_SETUID - -# Sandboxing -ProtectSystem=full -ProtectHome=read-only -# PrivateTmp break netdatacli functionality. See - https://github.com/netdata/netdata/issues/7587 -#PrivateTmp=true -ProtectControlGroups=on -# We whitelist this because it's the standard location to listen on a UNIX socket. -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/system/netdata.service.v235.in b/system/netdata.service.v235.in deleted file mode 100644 index e3232056..00000000 --- a/system/netdata.service.v235.in +++ /dev/null @@ -1,42 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -[Unit] -Description=Real time performance monitoring - -# append here other services you want netdata to wait for them to start -After=network.target httpd.service squid.service nfs-server.service mysqld.service mysql.service named.service postfix.service chronyd.service - -[Service] -Type=simple -User=@netdata_user_POST@ -Group=netdata -RuntimeDirectory=netdata -CacheDirectory=netdata -StateDirectory=netdata -LogsDirectory=netdata -RuntimeDirectoryMode=0775 -StateDirectoryMode=0755 -CacheDirectoryMode=0755 -LogsDirectoryMode=2750 -EnvironmentFile=-/etc/default/netdata -ExecStart=@sbindir_POST@/netdata -D $EXTRA_OPTS - -# saving a big db on slow disks may need some time -TimeoutStopSec=150 - -# restart netdata if it crashes -Restart=on-failure -RestartSec=30 - -# Valid policies: other (the system default) | batch | idle | fifo | rr -# To give netdata the max priority, set CPUSchedulingPolicy=rr and CPUSchedulingPriority=99 -CPUSchedulingPolicy=batch - -# This sets the scheduling priority (for policies: rr and fifo). -# Priority gets values 1 (lowest) to 99 (highest). -#CPUSchedulingPriority=1 - -# For scheduling policy 'other' and 'batch', this sets the lowest niceness of netdata (-20 highest to 19 lowest). -Nice=0 - -[Install] -WantedBy=multi-user.target diff --git a/system/openrc/conf.d/netdata.in b/system/openrc/conf.d/netdata.in new file mode 100644 index 00000000..92f88260 --- /dev/null +++ b/system/openrc/conf.d/netdata.in @@ -0,0 +1,24 @@ +# The user netdata is configured to run as. +# If you edit its configuration file to set a different user, set it +# here too, to have its files switch ownership +NETDATA_OWNER="@netdata_user_POST@:@netdata_user_POST@" + +# How long to wait for the agent to save it's database during shutdown. +NETDATA_WAIT_EXIT_TIMEOUT=60 + +# If set to 1, force an exit if we time out waiting for the agent to +# save it's database during shutdown. +NETDATA_FORCE_EXIT=0 + +# If set to 1, use netdatacli when sending commands to the agent. +# This should not be needed in most cases, but it can sometimes help +# work around issues. +#NETDATA_USE_NETDATACLI=1 + +# Specify the path to the pidfile to be used when running in the +# background. +NETDATA_PIDFILE="@localstatedir_POST@/run/netdata/netdata.pid" + +# Uncomment the below line to run Netdata under OpenRC's native process +# supervision. +#supervisor="supervise-daemon" diff --git a/system/openrc/init.d/netdata.in b/system/openrc/init.d/netdata.in new file mode 100644 index 00000000..74242b3c --- /dev/null +++ b/system/openrc/init.d/netdata.in @@ -0,0 +1,80 @@ +#!/sbin/openrc-run +# SPDX-License-Identifier: GPL-3.0-or-later + +NETDATA_OWNER="@netdata_user_POST@:@netdata_user_POST@" +NETDATA_PIDFILE="@localstatedir_POST@/run/netdata/netdata.pid" + +description="Run the Netdata system monitoring agent." + +extra_started_commands="reload rotate save" +description_reload="Reload health configuration." +description_rotate="Reopen log files." +description_save="Force sync of database to disk." + +command_prefix="@sbindir_POST@" +command="${command_prefix}/netdata" +command_args="-P ${NETDATA_PIDFILE} ${NETDATA_EXTRA_ARGS}" +command_args_foreground="-D" +start_stop_daemon_args="-u ${NETDATA_OWNER}" + +depend() { + use logger + need net + after apache2 squid nginx mysql named opensips upsd hostapd postfix lm_sensors +} + +start_pre() { + checkpath -o ${NETDATA_OWNER} -d @localstatedir_POST@/cache/netdata @localstatedir_POST@/run/netdata + + if [ -z "${supervisor}" ]; then + pidfile="${NETDATA_PIDFILE}" + fi +} + +stop_pre() { + if [ "0${NETDATA_FORCE_EXIT}" -eq 1 ]; then + retry="TERM/${NETDATA_WAIT_EXIT_TIMEOUT:-60}/KILL/1" + else + retry="TERM/${NETDATA_WAIT_EXIT_TIMEOUT:-60}" + fi +} + +run_cmd() { + cmd="${1}" + msg="${2}" + failmsg="${3}" + signal="${4}" + + ebegin "${msg}" + if [ "${NETDATA_USE_NETDATACLI}" = 1 ]; then + "${command_prefix}/netdatacli" "${cmd}" >/dev/null + eend $? "${failmsg}" + elif [ "${supervisor}" = "supervise-daemon" ]; then + supervise-daemon "${RC_SVCNAME}" --signal "${signal}" + eend $? "${failmsg}" + else + start-stop-daemon --signal "${signal}" --pidfile "${pidfile}" + eend $? "${failmsg}" + fi +} + +reload() { + run_cmd reload-health \ + "Reloading Netdata health configuration" \ + "Failed to reload Netdata health configuration" \ + SIGUSR2 +} + +rotate() { + run_cmd reopen-logs \ + "Reopening Netdata log files" \ + "Failed to reopen Netdata log files" \ + SIGHUP +} + +save() { + run_cmd save-database \ + "Saving Netdata database" \ + "Failed to save Netdata database" \ + SIGUSR1 +} diff --git a/system/runit/run.in b/system/runit/run.in new file mode 100644 index 00000000..4ea783cc --- /dev/null +++ b/system/runit/run.in @@ -0,0 +1,16 @@ +#!/bin/sh + +piddir="@localstatedir_POST@/run/netdata/netdata.pid" +pidfile="${piddir}/netdata.pid" + +cachedir="@localstatedir_POST@/cache/netdata" + +command="@sbindir_POST@/netdata" +command_args="-P ${pidfile} -D" + +[ ! -d "${piddir}" ] && mkdir -p "${piddir}" +[ ! -d "${cachedir}" ] && mkdir -p "${cachedir}" +chown -R @netdata_user_POST@ "${piddir}" +chown -R @netdata_user_POST@ "${cachedir}" + +exec ${command} ${command_args} diff --git a/system/systemd/50-netdata.preset b/system/systemd/50-netdata.preset new file mode 100644 index 00000000..fe4e5a19 --- /dev/null +++ b/system/systemd/50-netdata.preset @@ -0,0 +1 @@ +enable netdata.service diff --git a/system/systemd/netdata-updater.service.in b/system/systemd/netdata-updater.service.in new file mode 100644 index 00000000..d0bd4994 --- /dev/null +++ b/system/systemd/netdata-updater.service.in @@ -0,0 +1,8 @@ +[Unit] +Description=Daily auto-updates for Netdata +RefuseManualStart=no +RefuseManualStop=yes + +[Service] +Type=oneshot +ExecStart=@pkglibexecdir_POST@/netdata-updater.sh diff --git a/system/systemd/netdata-updater.timer b/system/systemd/netdata-updater.timer new file mode 100644 index 00000000..8b36e46f --- /dev/null +++ b/system/systemd/netdata-updater.timer @@ -0,0 +1,12 @@ +[Unit] +Description=Daily auto-updates for Netdata +RefuseManualStart=no +RefuseManualStop=no + +[Timer] +Persistent=false +OnCalendar=daily +Unit=netdata-updater.service + +[Install] +WantedBy=timers.target diff --git a/system/systemd/netdata.service.in b/system/systemd/netdata.service.in new file mode 100644 index 00000000..25d95b2b --- /dev/null +++ b/system/systemd/netdata.service.in @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +[Unit] +Description=Real time performance monitoring + +# append here other services you want netdata to wait for them to start +After=network.target httpd.service squid.service nfs-server.service mysqld.service mysql.service named.service postfix.service chronyd.service + +[Service] +Type=simple +User=@netdata_user_POST@ +Group=netdata +RuntimeDirectory=netdata +RuntimeDirectoryMode=0775 +PIDFile=/run/netdata/netdata.pid +ExecStart=@sbindir_POST@/netdata -P /run/netdata/netdata.pid -D +ExecStartPre=/bin/mkdir -p @localstatedir_POST@/cache/netdata +ExecStartPre=/bin/chown -R @netdata_user_POST@ @localstatedir_POST@/cache/netdata +ExecStartPre=/bin/mkdir -p /run/netdata +ExecStartPre=/bin/chown -R @netdata_user_POST@ /run/netdata +PermissionsStartOnly=true + +# saving a big db on slow disks may need some time +TimeoutStopSec=150 + +# restart netdata if it crashes +Restart=on-failure +RestartSec=30 + +# Valid policies: other (the system default) | batch | idle | fifo | rr +# To give netdata the max priority, set CPUSchedulingPolicy=rr and CPUSchedulingPriority=99 +CPUSchedulingPolicy=batch + +# This sets the scheduling priority (for policies: rr and fifo). +# Priority gets values 1 (lowest) to 99 (highest). +#CPUSchedulingPriority=1 + +# For scheduling policy 'other' and 'batch', this sets the lowest niceness of netdata (-20 highest to 19 lowest). +Nice=0 + +# Capabilities +# is required for freeipmi and slabinfo plugins +CapabilityBoundingSet=CAP_DAC_OVERRIDE +# is required for apps plugin +CapabilityBoundingSet=CAP_DAC_READ_SEARCH +# is required for freeipmi plugin +CapabilityBoundingSet=CAP_FOWNER CAP_SYS_RAWIO +# is required for apps, perf and slabinfo plugins +CapabilityBoundingSet=CAP_SETPCAP +# is required for perf plugin +CapabilityBoundingSet=CAP_SYS_ADMIN CAP_PERFMON +# is required for apps plugin +CapabilityBoundingSet=CAP_SYS_PTRACE +# is required for ebpf plugin +CapabilityBoundingSet=CAP_SYS_RESOURCE +# is required for go.d/ping app +CapabilityBoundingSet=CAP_NET_RAW +# is required for cgroups plugin +CapabilityBoundingSet=CAP_SYS_CHROOT +# is required for nfacct plugin (bandwidth accounting) +CapabilityBoundingSet=CAP_NET_ADMIN +# is required for plugins that use sudo +CapabilityBoundingSet=CAP_SETGID CAP_SETUID + +# Sandboxing +ProtectSystem=full +ProtectHome=read-only +# PrivateTmp break netdatacli functionality. See - https://github.com/netdata/netdata/issues/7587 +#PrivateTmp=true +ProtectControlGroups=on +# We whitelist this because it's the standard location to listen on a UNIX socket. +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/system/systemd/netdata.service.v235.in b/system/systemd/netdata.service.v235.in new file mode 100644 index 00000000..e3232056 --- /dev/null +++ b/system/systemd/netdata.service.v235.in @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +[Unit] +Description=Real time performance monitoring + +# append here other services you want netdata to wait for them to start +After=network.target httpd.service squid.service nfs-server.service mysqld.service mysql.service named.service postfix.service chronyd.service + +[Service] +Type=simple +User=@netdata_user_POST@ +Group=netdata +RuntimeDirectory=netdata +CacheDirectory=netdata +StateDirectory=netdata +LogsDirectory=netdata +RuntimeDirectoryMode=0775 +StateDirectoryMode=0755 +CacheDirectoryMode=0755 +LogsDirectoryMode=2750 +EnvironmentFile=-/etc/default/netdata +ExecStart=@sbindir_POST@/netdata -D $EXTRA_OPTS + +# saving a big db on slow disks may need some time +TimeoutStopSec=150 + +# restart netdata if it crashes +Restart=on-failure +RestartSec=30 + +# Valid policies: other (the system default) | batch | idle | fifo | rr +# To give netdata the max priority, set CPUSchedulingPolicy=rr and CPUSchedulingPriority=99 +CPUSchedulingPolicy=batch + +# This sets the scheduling priority (for policies: rr and fifo). +# Priority gets values 1 (lowest) to 99 (highest). +#CPUSchedulingPriority=1 + +# For scheduling policy 'other' and 'batch', this sets the lowest niceness of netdata (-20 highest to 19 lowest). +Nice=0 + +[Install] +WantedBy=multi-user.target diff --git a/system/vnodes/vnodes.conf b/system/vnodes/vnodes.conf new file mode 100644 index 00000000..abcd5784 --- /dev/null +++ b/system/vnodes/vnodes.conf @@ -0,0 +1,23 @@ +## This file is in YAML format. +## +## It contains a list of virtual nodes. Virtual node, defined with the following parameters: +## +## 1. hostname +## The hostname of the virtual node. String. Required. +## 2. guid +## Uniquely identifies the node. String. Required. +## 3. labels +## The virtual node host labels. Dictionary in the form key: value. Optional. +## Special labels that are used to set virtual host system information: +## https://learn.netdata.cloud/docs/improving-netdata---developers/external-plugins#host_label +## +## You can create GUIDs online: https://www.guidgen.com/ +## or generate with the linux command: uuidgen +## +## --------------------------------------------------------------------------------------------------------------------- + +#- hostname: HOSTNAME1 +# guid: GUID1 +# +#- hostname: HOSTNAME2 +# guid: GUID2 diff --git a/tests/health_mgmtapi/README.md b/tests/health_mgmtapi/README.md index aa51c0d6..73e217c2 100644 --- a/tests/health_mgmtapi/README.md +++ b/tests/health_mgmtapi/README.md @@ -1,6 +1,10 @@ # Health command API tester diff --git a/web/Makefile.am b/web/Makefile.am index ccaccd76..be2c545c 100644 --- a/web/Makefile.am +++ b/web/Makefile.am @@ -7,6 +7,7 @@ SUBDIRS = \ api \ gui \ server \ + rtc \ $(NULL) usersslconfigdir=$(configdir)/ssl diff --git a/web/README.md b/web/README.md index eae57934..0e6c90fc 100644 --- a/web/README.md +++ b/web/README.md @@ -1,10 +1,4 @@ - - -# Dashboards +# Agent Dashboards Because Netdata is a health monitoring and _performance troubleshooting_ system, we put a lot of emphasis on real-time, meaningful, and context-aware charts. @@ -12,7 +6,7 @@ we put a lot of emphasis on real-time, meaningful, and context-aware charts. We bundle Netdata with a dashboard and hundreds of charts, designed by both our team and the community, but you can also customize them yourself. -There are two primary ways to view Netdata's dashboards: +There are two primary ways to view Netdata's dashboards on the agent: 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 @@ -26,188 +20,6 @@ There are two primary ways to view Netdata's dashboards: 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 -[contexts](#contexts). - -## Using charts - -Netdata's charts are far from static. They are interactive, real-time, and work -with your mouse, touchpad, or touchscreen! - -Hover over any chart to temporarily pause it and see the exact values presented -as different [dimensions](#dimensions). Click or tap stop the chart from automatically updating with new metrics, thereby locking it to a single timeframe. - -![Animated GIF of hovering over a chart to see -values](https://user-images.githubusercontent.com/1153921/62968279-9227dd00-bdbf-11e9-9112-1d21444d0f31.gif) - -You can change how charts show their metrics by zooming in or out, moving -forward or backward in time, or selecting a specific timeframe for more in-depth -analysis. - -Whenever you use a chart in this way, Netdata synchronizes all the other charts -to match it. - -You can change how charts show their metrics in a few different ways, each of -which have a few methods: - -| Manipulation | Method #1 | Method #2 | Method #3 | -|--- |--- |--- |--- | -| **Reset** charts to default auto-refreshing state  | `double click` | `double tap` (touchpad/touchscreen)  |  | -| **Select** a certain timeframe | `ALT` + `mouse selection`  | `⌘` + `mouse selection` (macOS)  |  | -| **Pan** forward or back in time  | `click and drag` | `touch and drag` (touchpad/touchscreen)  |  | -| **Zoom** to a specific timeframe | `SHIFT` + `mouse selection`  |  |  | -| **Zoom** in/out  | `SHIFT`/`ALT` + `mouse scrollwheel`  | `SHIFT`/`ALT` + `two-finger pinch` (touchpad/touchscreen)  | `SHIFT`/`ALT` + `two-finger scroll` (touchpad/touchscreen) | - -Here's how chart synchronization looks while zooming and panning: - -![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) - -You can also perform all these actions using the small -rewind/play/fast-forward/zoom-in/zoom-out buttons that appear in the -bottom-right corner of each chart. - -Additionally, resize charts by clicking-and-dragging the icon on the bottom-right corner of any chart. To restore the -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) - -## Charts, contexts, families - -Before customizing the standard web dashboard, creating a custom dashboard, -configuring an alarm, or writing a collector, it's crucial to understand how -Netdata organizes metrics into charts, dimensions, families, and contexts. - -### Charts - -A **chart** is an individual, interactive, always-updating graphic displaying -one or more collected/calculated metrics. Charts are generated by -[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: - -![Screenshot of the system CPU chart in the Netdata -dashboard](https://user-images.githubusercontent.com/1153921/62720972-0b8a8e80-b9c0-11e9-930b-4829f7b17cfd.png) - -Netdata displays a chart's name in parentheses above the chart. For example, if -you navigate to the system CPU chart, you'll see the label: **Total CPU -utilization (system.cpu)**. In this case, the chart's name is `system.cpu`. -Netdata derives the name from the chart's [context](#contexts). - -### Dimensions - -A **dimension** is a value that gets shown on a chart. The value can be raw data -or calculated values, such as percentages, aggregates, and more. - -Charts are capable of showing more than one dimension. Netdata shows these -dimensions on the right side of the chart, beneath the date and time. Again, the -`system.cpu` chart will serve as a good example. - -![Screenshot of the dimensions shown in the system CPU chart in the Netdata -dashboard](https://user-images.githubusercontent.com/1153921/62721031-2bba4d80-b9c0-11e9-9dca-32403617ce72.png) - -Here, the `system.cpu` chart is showing many dimensions, such as `user`, -`system`, `softirq`, `irq`, and more. - -Note that other applications sometimes use the word _series_ instead of -_dimension_. - -### Families - -A **family** is _one_ instance of a monitored hardware or software resource that -needs to be monitored and displayed separately from similar instances. - -For example, if your system has multiple disk drives at `sda` and `sdb`, Netdata -will put each interface into their own family. Same goes for software resources, -like multiple MySQL instances. We call these instances "families" because the -charts associated with a single disk instance, for example, are often related to -each other. Relatives, family... get it? - -When relevant, Netdata prefers to organize charts by family. When you visit the -**Disks** section, you will see your disk drives organized into families, and -each family will have one or more charts: `disk`, `disk_ops`, `disk_backlog`, -`disk_util`, `disk_await`, `disk_avgsz`, `disk_svctm`, `disk_mops`, and -`disk_iotime`. - -In the screenshot below, the disk family `sdb` shows a few gauges, followed by a -few of the associated charts: - -![Screenshot of a disk drive family and associated charts in the Netdata -dashboard](https://user-images.githubusercontent.com/1153921/62721362-e34f5f80-b9c0-11e9-8d2e-9a3bec48e920.png) - -Netdata also creates separate submenu entries for each family in the right -navigation page so you can easily navigate to the instance you're interested in. -Here, Netdata has made several submenus under the **Disk** menu. - -![Screenshot of the disks menu and -submenus](https://user-images.githubusercontent.com/1153921/62721531-3cb78e80-b9c1-11e9-89c2-fdd736aec7d4.png) - -### Contexts - -A **context** is a way of grouping charts by the types of metrics collected and -dimensions displayed. Different charts with the same context will show the same -dimensions, but for different instances (families) of hardware/software -resources. - -For example, the **Disks** section will often use many contexts (`disk.io`, -`disk.ops`, `disk.backlog`, `disk.util`, and so on). Netdata then creates an -individual chart for each context, and groups them by family. - -Netdata names charts according to their context according to the following -structure: `[context].[family]`. A chart with the `disk.util` context, in the -`sdb` family, gets the name `disk_util.sdb`. Netdata shows that name in the -top-left corner of a chart. - -Given the four example contexts, and two families of `sdb` and `sdd`, Netdata -will create the following charts and their names: - -| Context | `sdb` family | `sdd` family | -|----------------|--------------------|--------------------| -| `disk.io` | `disk_io.sdb` | `disk_io.sdd` | -| `disk.ops` | `disk_ops.sdb` | `disk_ops.sdd` | -| `disk.backlog` | `disk_backlog.sdb` | `disk_backlog.sdd` | -| `disk.util` | `disk_util.sdb` | `disk_util.sdd` | - -And here's what two of those charts in the `disk.io` context look like under -`sdb` and `sdd` families: - -![context_01](https://user-images.githubusercontent.com/1153921/62728232-177e4c80-b9d0-11e9-9e29-2a6c59d4d873.png) -![context_02](https://user-images.githubusercontent.com/1153921/62728234-1b11d380-b9d0-11e9-8904-07befd8ac592.png) - -As you can see in the screenshot, you can view the context of a chart if you -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](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 - -To improve clarity on charts, Netdata dashboards present **positive** values for -metrics representing `read`, `input`, `inbound`, `received` and **negative** -values for metrics representing `write`, `output`, `outbound`, `sent`. - -![Screenshot showing positive and negative -values](https://user-images.githubusercontent.com/1153921/81870401-9d649080-952a-11ea-80e3-4a7b480252ee.gif) - -_Netdata charts showing the bandwidth and packets of a network interface. -`received` is positive and `sent` is negative._ - -## Autoscaled y-axis - -Netdata charts automatically zoom vertically, to visualize the variation of each -metric within the visible timeframe. - -![Animated GIF showing the auso-scaling Y -axis](https://user-images.githubusercontent.com/1153921/80838276-8084a080-8bad-11ea-8167-8d5ab2fb1be1.gif) - -_A zero-based `stacked` chart, automatically switches to an auto-scaled `area` -chart when a single dimension is selected._ - ## dashboard.js Netdata uses the `dashboards.js` file to define, configure, create, and update diff --git a/web/api/README.md b/web/api/README.md index 82a55eb2..237394a8 100644 --- a/web/api/README.md +++ b/web/api/README.md @@ -1,16 +1,12 @@ - - # API -## Netdata REST API +## Netdata agent REST API + +The complete documentation of the Netdata agent's REST API is documented in the OpenAPI format [in our GitHub repository](https://raw.githubusercontent.com/netdata/netdata/master/web/api/netdata-swagger.yaml). + +You can explore it using the **[Swagger UI](https://learn.netdata.cloud/api)**, or the **[Swagger Editor](https://editor.swagger.io/?url=https://raw.githubusercontent.com/netdata/netdata/master/web/api/netdata-swagger.yaml)**. -The complete documentation of the Netdata API is available as a Swagger API document [in our GitHub repository](https://raw.githubusercontent.com/netdata/netdata/master/web/api/netdata-swagger.yaml). You can view it online using the **[Swagger Editor](https://editor.swagger.io/?url=https://raw.githubusercontent.com/netdata/netdata/master/web/api/netdata-swagger.yaml)**. +## Netdata cloud API -If your prefer it over the Swagger Editor, you can also use [Swagger UI](https://github.com/swagger-api/swagger-ui) by pointing at `web/api/netdata-swagger.yaml` in the Netdata source tree (or at https://raw.githubusercontent.com/netdata/netdata/master/web/api/netdata-swagger.yaml if you want to use the Swagger API definitions directly from our GitHub repository). This however does not provide all the information available. +A very basic Netdata cloud REST API supports the [Grafana data source plugin](https://github.com/netdata/netdata-grafana-datasource-plugin/blob/master/README.md), +but has not yet been expanded for wider use. We intend to provide a properly documented API in the future. diff --git a/web/api/badges/README.md b/web/api/badges/README.md index 8f6eca62..e40e706e 100644 --- a/web/api/badges/README.md +++ b/web/api/badges/README.md @@ -1,6 +1,10 @@ # Netdata badges diff --git a/web/api/badges/web_buffer_svg.c b/web/api/badges/web_buffer_svg.c index ca0f4b7a..b69f35af 100644 --- a/web/api/badges/web_buffer_svg.c +++ b/web/api/badges/web_buffer_svg.c @@ -767,7 +767,7 @@ void buffer_svg(BUFFER *wb, const char *label, label_color_parsed = parse_color_argument(label_color, "555"); value_color_parsed = parse_color_argument(value_color_buffer, "555"); - wb->contenttype = CT_IMAGE_SVG_XML; + wb->content_type = CT_IMAGE_SVG_XML; total_width = total_width * scale / 100.0; height = height * scale / 100.0; @@ -898,10 +898,10 @@ int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *u RRDSET *st = NULL; while(url) { - char *value = mystrsep(&url, "&"); + char *value = strsep_skip_consecutive_separators(&url, "&"); if(!value || !*value) continue; - char *name = mystrsep(&value, "="); + char *name = strsep_skip_consecutive_separators(&value, "="); if(!name || !*name) continue; if(!value || !*value) continue; @@ -923,7 +923,7 @@ int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *u else if(!strcmp(name, "points")) points_str = value; else if(!strcmp(name, "group_options")) group_options = value; else if(!strcmp(name, "group")) { - group = web_client_api_request_v1_data_group(value, RRDR_GROUPING_AVERAGE); + group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE); } else if(!strcmp(name, "options")) { options |= web_client_api_request_v1_data_options(value); diff --git a/web/api/exporters/README.md b/web/api/exporters/README.md index 1d517a91..4be56769 100644 --- a/web/api/exporters/README.md +++ b/web/api/exporters/README.md @@ -1,6 +1,10 @@ # Exporters diff --git a/web/api/exporters/allmetrics.c b/web/api/exporters/allmetrics.c index 88065400..cad52a7d 100644 --- a/web/api/exporters/allmetrics.c +++ b/web/api/exporters/allmetrics.c @@ -39,10 +39,10 @@ inline int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client prometheus_prefix = global_exporting_prefix; while(url) { - char *value = mystrsep(&url, "&"); + char *value = strsep_skip_consecutive_separators(&url, "&"); if (!value || !*value) continue; - char *name = mystrsep(&value, "="); + char *name = strsep_skip_consecutive_separators(&value, "="); if(!name || !*name) continue; if(!value || !*value) continue; @@ -90,17 +90,17 @@ inline int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client switch(format) { case ALLMETRICS_JSON: - w->response.data->contenttype = CT_APPLICATION_JSON; + w->response.data->content_type = CT_APPLICATION_JSON; rrd_stats_api_v1_charts_allmetrics_json(host, filter, w->response.data); return HTTP_RESP_OK; case ALLMETRICS_SHELL: - w->response.data->contenttype = CT_TEXT_PLAIN; + w->response.data->content_type = CT_TEXT_PLAIN; rrd_stats_api_v1_charts_allmetrics_shell(host, filter, w->response.data); return HTTP_RESP_OK; case ALLMETRICS_PROMETHEUS: - w->response.data->contenttype = CT_PROMETHEUS; + w->response.data->content_type = CT_PROMETHEUS; rrd_stats_api_v1_charts_allmetrics_prometheus_single_host( host , filter @@ -113,7 +113,7 @@ inline int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client return HTTP_RESP_OK; case ALLMETRICS_PROMETHEUS_ALL_HOSTS: - w->response.data->contenttype = CT_PROMETHEUS; + w->response.data->content_type = CT_PROMETHEUS; rrd_stats_api_v1_charts_allmetrics_prometheus_all_hosts( host , filter @@ -126,7 +126,7 @@ inline int web_client_api_request_v1_allmetrics(RRDHOST *host, struct web_client return HTTP_RESP_OK; default: - w->response.data->contenttype = CT_TEXT_PLAIN; + w->response.data->content_type = CT_TEXT_PLAIN; buffer_strcat(w->response.data, "Which format? '" ALLMETRICS_FORMAT_SHELL "', '" ALLMETRICS_FORMAT_PROMETHEUS "', '" ALLMETRICS_FORMAT_PROMETHEUS_ALL_HOSTS "' and '" ALLMETRICS_FORMAT_JSON "' are currently supported."); return HTTP_RESP_BAD_REQUEST; } diff --git a/web/api/exporters/prometheus/README.md b/web/api/exporters/prometheus/README.md index 1ff86f4e..5e0f98c1 100644 --- a/web/api/exporters/prometheus/README.md +++ b/web/api/exporters/prometheus/README.md @@ -1,6 +1,10 @@ # Prometheus exporter diff --git a/web/api/exporters/shell/README.md b/web/api/exporters/shell/README.md index a41326c9..a17ff114 100644 --- a/web/api/exporters/shell/README.md +++ b/web/api/exporters/shell/README.md @@ -1,9 +1,13 @@ -# shell exporter +# Shell exporter Shell scripts can now query Netdata: diff --git a/web/api/exporters/shell/allmetrics_shell.c b/web/api/exporters/shell/allmetrics_shell.c index dded5a53..fbfd6b57 100644 --- a/web/api/exporters/shell/allmetrics_shell.c +++ b/web/api/exporters/shell/allmetrics_shell.c @@ -24,12 +24,12 @@ static inline size_t shell_name_copy(char *d, const char *s, size_t usable) { void rrd_stats_api_v1_charts_allmetrics_shell(RRDHOST *host, const char *filter_string, BUFFER *wb) { analytics_log_shell(); - SIMPLE_PATTERN *filter = simple_pattern_create(filter_string, NULL, SIMPLE_PATTERN_EXACT); + SIMPLE_PATTERN *filter = simple_pattern_create(filter_string, NULL, SIMPLE_PATTERN_EXACT, true); // for each chart RRDSET *st; rrdset_foreach_read(st, host) { - if (filter && !simple_pattern_matches(filter, rrdset_name(st))) + if (filter && !simple_pattern_matches_string(filter, st->name)) continue; NETDATA_DOUBLE total = 0.0; @@ -97,7 +97,7 @@ void rrd_stats_api_v1_charts_allmetrics_shell(RRDHOST *host, const char *filter_ void rrd_stats_api_v1_charts_allmetrics_json(RRDHOST *host, const char *filter_string, BUFFER *wb) { analytics_log_json(); - SIMPLE_PATTERN *filter = simple_pattern_create(filter_string, NULL, SIMPLE_PATTERN_EXACT); + SIMPLE_PATTERN *filter = simple_pattern_create(filter_string, NULL, SIMPLE_PATTERN_EXACT, true); buffer_strcat(wb, "{"); @@ -107,7 +107,7 @@ void rrd_stats_api_v1_charts_allmetrics_json(RRDHOST *host, const char *filter_s // for each chart RRDSET *st; rrdset_foreach_read(st, host) { - if (filter && !(simple_pattern_matches(filter, rrdset_id(st)) || simple_pattern_matches(filter, rrdset_name(st)))) + if (filter && !(simple_pattern_matches_string(filter, st->id) || simple_pattern_matches_string(filter, st->name))) continue; if(rrdset_is_available_for_viewers(st)) { diff --git a/web/api/formatters/README.md b/web/api/formatters/README.md index 4c281f06..ddc70d90 100644 --- a/web/api/formatters/README.md +++ b/web/api/formatters/README.md @@ -1,6 +1,10 @@ # Query formatting diff --git a/web/api/formatters/charts2json.c b/web/api/formatters/charts2json.c index 61a9ecf2..4b6b095c 100644 --- a/web/api/formatters/charts2json.c +++ b/web/api/formatters/charts2json.c @@ -137,55 +137,3 @@ void charts2json(RRDHOST *host, BUFFER *wb, int skip_volatile, int show_archived buffer_sprintf(wb, "\n\t]\n}\n"); } - -// generate collectors list for the api/v1/info call - -struct collector { - const char *plugin; - const char *module; -}; - -struct array_printer { - int c; - BUFFER *wb; -}; - -static int print_collector_callback(const DICTIONARY_ITEM *item __maybe_unused, void *entry, void *data) { - struct array_printer *ap = (struct array_printer *)data; - BUFFER *wb = ap->wb; - struct collector *col=(struct collector *) entry; - if(ap->c) buffer_strcat(wb, ","); - buffer_strcat(wb, "\n\t\t{\n\t\t\t\"plugin\": \""); - buffer_strcat(wb, col->plugin); - buffer_strcat(wb, "\",\n\t\t\t\"module\": \""); - buffer_strcat(wb, col->module); - buffer_strcat(wb, "\"\n\t\t}"); - (ap->c)++; - return 0; -} - -void chartcollectors2json(RRDHOST *host, BUFFER *wb) { - DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED); - RRDSET *st; - char name[500]; - - time_t now = now_realtime_sec(); - rrdset_foreach_read(st, host) { - if (rrdset_is_available_for_viewers(st)) { - struct collector col = { - .plugin = rrdset_plugin_name(st), - .module = rrdset_module_name(st) - }; - sprintf(name, "%s:%s", col.plugin, col.module); - dictionary_set(dict, name, &col, sizeof(struct collector)); - st->last_accessed_time_s = now; - } - } - rrdset_foreach_done(st); - struct array_printer ap = { - .c = 0, - .wb = wb - }; - dictionary_walkthrough_read(dict, print_collector_callback, &ap); - dictionary_destroy(dict); -} diff --git a/web/api/formatters/charts2json.h b/web/api/formatters/charts2json.h index d4b04af5..96720d4b 100644 --- a/web/api/formatters/charts2json.h +++ b/web/api/formatters/charts2json.h @@ -6,7 +6,6 @@ #include "rrd2json.h" void charts2json(RRDHOST *host, BUFFER *wb, int skip_volatile, int show_archived); -void chartcollectors2json(RRDHOST *host, BUFFER *wb); const char* get_release_channel(); #endif //NETDATA_API_FORMATTER_CHARTS2JSON_H diff --git a/web/api/formatters/csv/README.md b/web/api/formatters/csv/README.md index fc5ffec1..4585710b 100644 --- a/web/api/formatters/csv/README.md +++ b/web/api/formatters/csv/README.md @@ -1,6 +1,10 @@ # CSV formatter diff --git a/web/api/formatters/csv/csv.c b/web/api/formatters/csv/csv.c index 18009f14..8f4950dd 100644 --- a/web/api/formatters/csv/csv.c +++ b/web/api/formatters/csv/csv.c @@ -5,15 +5,13 @@ void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, const char *startline, const char *separator, const char *endline, const char *betweenlines) { //info("RRD2CSV(): %s: BEGIN", r->st->id); - QUERY_TARGET *qt = r->internal.qt; long c, i; - const long used = qt->query.used; + const long used = (long)r->d; // print the csv header for(c = 0, i = 0; c < used ; c++) { - if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; if(!i) { buffer_strcat(wb, startline); @@ -23,7 +21,7 @@ void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, const } buffer_strcat(wb, separator); if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); - buffer_strcat(wb, string2str(qt->query.array[c].dimension.name)); + buffer_strcat(wb, string2str(r->dn[c])); if(options & RRDR_OPTION_LABEL_QUOTES) buffer_strcat(wb, "\""); i++; } @@ -32,9 +30,8 @@ void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, const if(format == DATASOURCE_CSV_MARKDOWN) { // print the --- line after header for(c = 0, i = 0; c < used ;c++) { - if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; if(!i) { buffer_strcat(wb, startline); @@ -64,7 +61,6 @@ void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, const } // for each line in the array - NETDATA_DOUBLE total = 1; for(i = start; i != end ;i += step) { NETDATA_DOUBLE *cn = &r->v[ i * r->d ]; RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ]; @@ -76,7 +72,7 @@ void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, const if((options & RRDR_OPTION_SECONDS) || (options & RRDR_OPTION_MILLISECONDS)) { // print the timestamp of the line - buffer_rrd_value(wb, (NETDATA_DOUBLE)now); + buffer_print_netdata_double(wb, (NETDATA_DOUBLE) now); // in ms if(options & RRDR_OPTION_MILLISECONDS) buffer_strcat(wb, "000"); } @@ -87,29 +83,10 @@ void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, const buffer_date(wb, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); } - int set_min_max = 0; - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { - total = 0; - for(c = 0; c < used ;c++) { - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - - NETDATA_DOUBLE n = cn[c]; - - if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; - - total += n; - } - // prevent a division by zero - if(total == 0) total = 1; - set_min_max = 1; - } - // for each dimension for(c = 0; c < used ;c++) { - if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; buffer_strcat(wb, separator); @@ -121,24 +98,8 @@ void rrdr2csv(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, const else buffer_strcat(wb, "null"); } - else { - if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; - - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { - n = n * 100 / total; - - if(unlikely(set_min_max)) { - r->min = r->max = n; - set_min_max = 0; - } - - if(n < r->min) r->min = n; - if(n > r->max) r->max = n; - } - - buffer_rrd_value(wb, n); - } + else + buffer_print_netdata_double(wb, n); } buffer_strcat(wb, endline); diff --git a/web/api/formatters/json/README.md b/web/api/formatters/json/README.md index 75f729ad..bc70aec0 100644 --- a/web/api/formatters/json/README.md +++ b/web/api/formatters/json/README.md @@ -1,6 +1,10 @@ # JSON formatter diff --git a/web/api/formatters/json/json.c b/web/api/formatters/json/json.c index 3cad3e91..d5b8c757 100644 --- a/web/api/formatters/json/json.c +++ b/web/api/formatters/json/json.c @@ -42,7 +42,7 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable) { strcpy(post_value, "}"); strcpy(post_line, "]}"); snprintfz(data_begin, 100, "\n ],\n %srows%s:\n [\n", kq, kq); - strcpy(finish, "\n]\n}"); + strcpy(finish, "\n ]\n }"); snprintfz(overflow_annotation, 200, ",{%sv%s:%sRESET OR OVERFLOW%s},{%sv%s:%sThe counters have been wrapped.%s}", kq, kq, sq, sq, kq, kq, sq, sq); snprintfz(normal_annotation, 200, ",{%sv%s:null},{%sv%s:null}", kq, kq, kq, kq); @@ -69,9 +69,9 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable) { dates_with_new = 0; } if( options & RRDR_OPTION_OBJECTSROWS ) - strcpy(pre_date, " { "); + strcpy(pre_date, " {"); else - strcpy(pre_date, " [ "); + strcpy(pre_date, " ["); strcpy(pre_label, ",\""); strcpy(post_label, "\""); strcpy(pre_value, ","); @@ -79,10 +79,10 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable) { strcpy(post_line, "}"); else strcpy(post_line, "]"); - snprintfz(data_begin, 100, "],\n %sdata%s:\n [\n", kq, kq); - strcpy(finish, "\n]\n}"); + snprintfz(data_begin, 100, "],\n %sdata%s:[\n", kq, kq); + strcpy(finish, "\n ]\n }"); - buffer_sprintf(wb, "{\n %slabels%s: [", kq, kq); + buffer_sprintf(wb, "{\n %slabels%s:[", kq, kq); buffer_sprintf(wb, "%stime%s", sq, sq); if( options & RRDR_OPTION_OBJECTSROWS ) @@ -104,18 +104,16 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable) { // ------------------------------------------------------------------------- // print the JSON header - QUERY_TARGET *qt = r->internal.qt; long c, i; - const long used = qt->query.used; + const long used = (long)r->d; // print the header lines for(c = 0, i = 0; c < used ; c++) { - if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; buffer_fast_strcat(wb, pre_label, pre_label_len); - buffer_strcat(wb, string2str(qt->query.array[c].dimension.name)); + buffer_strcat(wb, string2str(r->dn[c])); buffer_fast_strcat(wb, post_label, post_label_len); i++; } @@ -151,7 +149,6 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable) { ); // for each line in the array - NETDATA_DOUBLE total = 1; for(i = start; i != end ;i += step) { NETDATA_DOUBLE *cn = &r->v[ i * r->d ]; RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ]; @@ -203,7 +200,7 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable) { if(unlikely( options & RRDR_OPTION_OBJECTSROWS )) buffer_fast_strcat(wb, object_rows_time, object_rows_time_len); - buffer_rrd_value(wb, (NETDATA_DOUBLE)r->t[i]); + buffer_print_netdata_double(wb, (NETDATA_DOUBLE) r->t[i]); // in ms if(unlikely(options & RRDR_OPTION_MILLISECONDS)) @@ -212,33 +209,10 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable) { buffer_fast_strcat(wb, post_date, post_date_len); } - int set_min_max = 0; - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { - total = 0; - for(c = 0; c < used ;c++) { - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - - NETDATA_DOUBLE n; - if(unlikely(options & RRDR_OPTION_INTERNAL_AR)) - n = ar[c]; - else - n = cn[c]; - - if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; - - total += n; - } - // prevent a division by zero - if(total == 0) total = 1; - set_min_max = 1; - } - // for each dimension for(c = 0; c < used ;c++) { - if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; NETDATA_DOUBLE n; if(unlikely(options & RRDR_OPTION_INTERNAL_AR)) @@ -249,39 +223,119 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable) { buffer_fast_strcat(wb, pre_value, pre_value_len); if(unlikely( options & RRDR_OPTION_OBJECTSROWS )) - buffer_sprintf(wb, "%s%s%s: ", kq, string2str(qt->query.array[c].dimension.name), kq); + buffer_sprintf(wb, "%s%s%s: ", kq, string2str(r->dn[c]), kq); - if(co[c] & RRDR_VALUE_EMPTY && !(options & RRDR_OPTION_INTERNAL_AR)) { + if(co[c] & RRDR_VALUE_EMPTY && !(options & (RRDR_OPTION_INTERNAL_AR))) { if(unlikely(options & RRDR_OPTION_NULL2ZERO)) buffer_fast_strcat(wb, "0", 1); else buffer_fast_strcat(wb, "null", 4); } - else { - if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; + else + buffer_print_netdata_double(wb, n); - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { - n = n * 100 / total; + buffer_fast_strcat(wb, post_value, post_value_len); + } - if(unlikely(set_min_max)) { - r->min = r->max = n; - set_min_max = 0; - } + buffer_fast_strcat(wb, post_line, post_line_len); + } + + buffer_strcat(wb, finish); + //info("RRD2JSON(): %s: END", r->st->id); +} + + +void rrdr2json_v2(RRDR *r, BUFFER *wb) { + QUERY_TARGET *qt = r->internal.qt; + RRDR_OPTIONS options = qt->window.options; + + bool expose_gbc = query_target_aggregatable(qt); + + buffer_json_member_add_object(wb, "result"); - if(n < r->min) r->min = n; - if(n > r->max) r->max = n; + buffer_json_member_add_array(wb, "labels"); + buffer_json_add_array_item_string(wb, "time"); + long d, i; + const long used = (long)r->d; + for(d = 0, i = 0; d < used ; d++) { + if(!rrdr_dimension_should_be_exposed(r->od[d], options)) + continue; + + buffer_json_add_array_item_string(wb, string2str(r->di[d])); + i++; + } + buffer_json_array_close(wb); // labels + + buffer_json_member_add_object(wb, "point"); + buffer_json_member_add_uint64(wb, "value", 0); + buffer_json_member_add_uint64(wb, "arp", 1); + buffer_json_member_add_uint64(wb, "pa", 2); + if(expose_gbc) + buffer_json_member_add_uint64(wb, "count", 3); + buffer_json_object_close(wb); + + buffer_json_member_add_array(wb, "data"); + if(i) { + long start = 0, end = rrdr_rows(r), step = 1; + if (!(options & RRDR_OPTION_REVERSED)) { + start = rrdr_rows(r) - 1; + end = -1; + step = -1; + } + + // for each line in the array + for (i = start; i != end; i += step) { + NETDATA_DOUBLE *cn = &r->v[ i * r->d ]; + RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ]; + NETDATA_DOUBLE *ar = &r->ar[ i * r->d ]; + uint32_t *gbc = &r->gbc [ i * r->d ]; + time_t now = r->t[i]; + + buffer_json_add_array_item_array(wb); // row + + if (options & RRDR_OPTION_MILLISECONDS) + buffer_json_add_array_item_time_ms(wb, now); // the time + else + buffer_json_add_array_item_time_t(wb, now); // the time + + for (d = 0; d < used; d++) { + if (!rrdr_dimension_should_be_exposed(r->od[d], options)) + continue; + + RRDR_VALUE_FLAGS o = co[d]; + + buffer_json_add_array_item_array(wb); // point + + // add the value + NETDATA_DOUBLE n = cn[d]; + + if(o & RRDR_VALUE_EMPTY) { + if (unlikely(options & RRDR_OPTION_NULL2ZERO)) + buffer_json_add_array_item_double(wb, 0); + else + buffer_json_add_array_item_double(wb, NAN); } + else + buffer_json_add_array_item_double(wb, n); + + // add the anomaly + buffer_json_add_array_item_double(wb, ar[d]); - buffer_rrd_value(wb, n); + // add the point annotations + buffer_json_add_array_item_uint64(wb, o); + + // add the count + if(expose_gbc) + buffer_json_add_array_item_uint64(wb, gbc[d]); + + buffer_json_array_close(wb); // point } - buffer_fast_strcat(wb, post_value, post_value_len); + buffer_json_array_close(wb); // row } - - buffer_fast_strcat(wb, post_line, post_line_len); } - buffer_strcat(wb, finish); - //info("RRD2JSON(): %s: END", r->st->id); + buffer_json_array_close(wb); // data + + buffer_json_object_close(wb); // annotations } diff --git a/web/api/formatters/json/json.h b/web/api/formatters/json/json.h index fb59e5c9..d1ab4f90 100644 --- a/web/api/formatters/json/json.h +++ b/web/api/formatters/json/json.h @@ -6,5 +6,6 @@ #include "../rrd2json.h" void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable); +void rrdr2json_v2(RRDR *r, BUFFER *wb); #endif //NETDATA_API_FORMATTER_JSON_H diff --git a/web/api/formatters/json_wrapper.c b/web/api/formatters/json_wrapper.c index aa663495..6bcbb8d5 100644 --- a/web/api/formatters/json_wrapper.c +++ b/web/api/formatters/json_wrapper.c @@ -2,441 +2,868 @@ #include "json_wrapper.h" -struct value_output { - int c; - BUFFER *wb; -}; +static void jsonwrap_query_metric_plan(BUFFER *wb, QUERY_METRIC *qm) { + buffer_json_member_add_array(wb, "plans"); + for (size_t p = 0; p < qm->plan.used; p++) { + QUERY_PLAN_ENTRY *qp = &qm->plan.array[p]; + + buffer_json_add_array_item_object(wb); + buffer_json_member_add_uint64(wb, "tr", qp->tier); + buffer_json_member_add_time_t(wb, "af", qp->after); + buffer_json_member_add_time_t(wb, "bf", qp->before); + buffer_json_object_close(wb); + } + buffer_json_array_close(wb); + + buffer_json_member_add_array(wb, "tiers"); + for (size_t tier = 0; tier < storage_tiers; tier++) { + buffer_json_add_array_item_object(wb); + buffer_json_member_add_uint64(wb, "tr", tier); + buffer_json_member_add_time_t(wb, "fe", qm->tiers[tier].db_first_time_s); + buffer_json_member_add_time_t(wb, "le", qm->tiers[tier].db_last_time_s); + buffer_json_member_add_int64(wb, "wg", qm->tiers[tier].weight); + buffer_json_object_close(wb); + } + buffer_json_array_close(wb); +} -static int value_list_output_callback(const DICTIONARY_ITEM *item __maybe_unused, void *entry, void *data) { - struct value_output *ap = (struct value_output *)data; - BUFFER *wb = ap->wb; - char *output = (char *) entry; - if(ap->c) buffer_strcat(wb, ","); - buffer_strcat(wb, output); - (ap->c)++; - return 0; +void jsonwrap_query_plan(RRDR *r, BUFFER *wb) { + QUERY_TARGET *qt = r->internal.qt; + + buffer_json_member_add_object(wb, "query_plan"); + for(size_t m = 0; m < qt->query.used; m++) { + QUERY_METRIC *qm = query_metric(qt, m); + buffer_json_member_add_object(wb, query_metric_id(qt, qm)); + jsonwrap_query_metric_plan(wb, qm); + buffer_json_object_close(wb); + } + buffer_json_object_close(wb); } -static int fill_formatted_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) { - (void)ls; - DICTIONARY *dict = (DICTIONARY *)data; - char n[RRD_ID_LENGTH_MAX * 2 + 2]; - char output[RRD_ID_LENGTH_MAX * 2 + 8]; - char v[RRD_ID_LENGTH_MAX * 2 + 1]; +static inline size_t rrdr_dimension_names(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options) { + const size_t dimensions = r->d; + size_t c, i; - sanitize_json_string(v, (char *)value, RRD_ID_LENGTH_MAX * 2); - int len = snprintfz(output, RRD_ID_LENGTH_MAX * 2 + 7, "[\"%s\", \"%s\"]", name, v); - snprintfz(n, RRD_ID_LENGTH_MAX * 2, "%s:%s", name, v); - dictionary_set(dict, n, output, len + 1); + buffer_json_member_add_array(wb, key); + for(c = 0, i = 0; c < dimensions ; c++) { + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; - return 1; + buffer_json_add_array_item_string(wb, string2str(r->dn[c])); + i++; + } + buffer_json_array_close(wb); + + return i; } -void rrdr_show_plan(RRDR *r, BUFFER *wb, const char *kq, const char *sq __maybe_unused) { +static inline size_t rrdr_dimension_ids(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options) { + const size_t dimensions = r->d; + size_t c, i; + + buffer_json_member_add_array(wb, key); + for(c = 0, i = 0; c < dimensions ; c++) { + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; + + buffer_json_add_array_item_string(wb, string2str(r->di[c])); + i++; + } + buffer_json_array_close(wb); + + return i; +} + +static inline long jsonwrap_v1_chart_ids(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options) { QUERY_TARGET *qt = r->internal.qt; + const long query_used = qt->query.used; + long c, i; - buffer_sprintf(wb, "\n\t%squery_plan%s: {", kq, kq); + buffer_json_member_add_array(wb, key); + for (c = 0, i = 0; c < query_used; c++) { + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; - for(size_t m = 0; m < qt->query.used; m++) { - QUERY_METRIC *qm = &qt->query.array[m]; + QUERY_METRIC *qm = query_metric(qt, c); + QUERY_INSTANCE *qi = query_instance(qt, qm->link.query_instance_id); + buffer_json_add_array_item_string(wb, rrdinstance_acquired_id(qi->ria)); + i++; + } + buffer_json_array_close(wb); - if(m) - buffer_strcat(wb, ","); + return i; +} - buffer_sprintf(wb, "\n\t\t%s%s%s: {", kq, string2str(qm->dimension.id), kq); +struct summary_total_counts { + size_t selected; + size_t excluded; + size_t queried; + size_t failed; +}; - 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, ","); +static inline void aggregate_into_summary_totals(struct summary_total_counts *totals, QUERY_METRICS_COUNTS *metrics) { + if(unlikely(!totals || !metrics)) + return; - 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]"); + if(metrics->selected) { + totals->selected++; + + if(metrics->queried) + totals->queried++; - buffer_strcat(wb, "\n\t\t}"); + else if(metrics->failed) + totals->failed++; } + else + totals->excluded++; +} + +static inline void query_target_total_counts(BUFFER *wb, const char *key, struct summary_total_counts *totals) { + if(!totals->selected && !totals->queried && !totals->failed && !totals->excluded) + return; - buffer_strcat(wb, "\n\t},"); + buffer_json_member_add_object(wb, key); + + if(totals->selected) + buffer_json_member_add_uint64(wb, "sl", totals->selected); + + if(totals->excluded) + buffer_json_member_add_uint64(wb, "ex", totals->excluded); + + if(totals->queried) + buffer_json_member_add_uint64(wb, "qr", totals->queried); + + if(totals->failed) + buffer_json_member_add_uint64(wb, "fl", totals->failed); + + buffer_json_object_close(wb); } -void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, int string_value, - RRDR_GROUPING group_method) -{ - QUERY_TARGET *qt = r->internal.qt; +static inline void query_target_metric_counts(BUFFER *wb, QUERY_METRICS_COUNTS *metrics) { + if(!metrics->selected && !metrics->queried && !metrics->failed && !metrics->excluded) + return; - long rows = rrdr_rows(r); - long c, i; - const long query_used = qt->query.used; + buffer_json_member_add_object(wb, "ds"); - //info("JSONWRAPPER(): %s: BEGIN", r->st->id); - char kq[2] = "", // key quote - sq[2] = ""; // string quote + if(metrics->selected) + buffer_json_member_add_uint64(wb, "sl", metrics->selected); - if( options & RRDR_OPTION_GOOGLE_JSON ) { - kq[0] = '\0'; - sq[0] = '\''; + if(metrics->excluded) + buffer_json_member_add_uint64(wb, "ex", metrics->excluded); + + if(metrics->queried) + buffer_json_member_add_uint64(wb, "qr", metrics->queried); + + if(metrics->failed) + buffer_json_member_add_uint64(wb, "fl", metrics->failed); + + buffer_json_object_close(wb); +} + +static inline void query_target_instance_counts(BUFFER *wb, QUERY_INSTANCES_COUNTS *instances) { + if(!instances->selected && !instances->queried && !instances->failed && !instances->excluded) + return; + + buffer_json_member_add_object(wb, "is"); + + if(instances->selected) + buffer_json_member_add_uint64(wb, "sl", instances->selected); + + if(instances->excluded) + buffer_json_member_add_uint64(wb, "ex", instances->excluded); + + if(instances->queried) + buffer_json_member_add_uint64(wb, "qr", instances->queried); + + if(instances->failed) + buffer_json_member_add_uint64(wb, "fl", instances->failed); + + buffer_json_object_close(wb); +} + +static inline void query_target_alerts_counts(BUFFER *wb, QUERY_ALERTS_COUNTS *alerts, const char *name, bool array) { + if(!alerts->clear && !alerts->other && !alerts->critical && !alerts->warning) + return; + + if(array) + buffer_json_add_array_item_object(wb); + else + buffer_json_member_add_object(wb, "al"); + + if(name) + buffer_json_member_add_string(wb, "nm", name); + + if(alerts->clear) + buffer_json_member_add_uint64(wb, "cl", alerts->clear); + + if(alerts->warning) + buffer_json_member_add_uint64(wb, "wr", alerts->warning); + + if(alerts->critical) + buffer_json_member_add_uint64(wb, "cr", alerts->critical); + + if(alerts->other) + buffer_json_member_add_uint64(wb, "ot", alerts->other); + + buffer_json_object_close(wb); +} + +static inline void query_target_points_statistics(BUFFER *wb, QUERY_TARGET *qt, STORAGE_POINT *sp) { + if(!sp->count) + return; + + buffer_json_member_add_object(wb, "sts"); + + buffer_json_member_add_double(wb, "min", sp->min); + buffer_json_member_add_double(wb, "max", sp->max); + + if(query_target_aggregatable(qt)) { + buffer_json_member_add_uint64(wb, "cnt", sp->count); + + if(sp->sum != 0.0) { + buffer_json_member_add_double(wb, "sum", sp->sum); + buffer_json_member_add_double(wb, "vol", sp->sum * (NETDATA_DOUBLE) query_view_update_every(qt)); + } + + if(sp->anomaly_count != 0) + buffer_json_member_add_uint64(wb, "arc", sp->anomaly_count); } else { - kq[0] = '"'; - sq[0] = '"'; + NETDATA_DOUBLE avg = (sp->count) ? sp->sum / (NETDATA_DOUBLE)sp->count : 0.0; + if(avg != 0.0) + buffer_json_member_add_double(wb, "avg", avg); + + NETDATA_DOUBLE arp = storage_point_anomaly_rate(*sp); + if(arp != 0.0) + buffer_json_member_add_double(wb, "arp", arp); + + NETDATA_DOUBLE con = (qt->query_points.sum > 0.0) ? sp->sum * 100.0 / qt->query_points.sum : 0.0; + if(con != 0.0) + buffer_json_member_add_double(wb, "con", con); } + buffer_json_object_close(wb); +} - buffer_sprintf(wb, "{\n" - " %sapi%s: 1,\n" - " %sid%s: %s%s%s,\n" - " %sname%s: %s%s%s,\n" - " %sview_update_every%s: %lld,\n" - " %supdate_every%s: %lld,\n" - " %sfirst_entry%s: %lld,\n" - " %slast_entry%s: %lld,\n" - " %sbefore%s: %lld,\n" - " %safter%s: %lld,\n" - " %sgroup%s: %s%s%s,\n" - " %soptions%s: %s" - , kq, kq - , 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_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 - , kq, kq, sq); - - web_client_api_request_v1_data_options_to_buffer(wb, r->internal.query_options); - - buffer_sprintf(wb, "%s,\n %sdimension_names%s: [", sq, kq, kq); - - for(c = 0, i = 0; c < query_used ; c++) { - if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; - - if(i) buffer_strcat(wb, ", "); - buffer_strcat(wb, sq); - buffer_strcat(wb, string2str(qt->query.array[c].dimension.name)); - buffer_strcat(wb, sq); - i++; +static void query_target_summary_nodes_v2(BUFFER *wb, QUERY_TARGET *qt, const char *key, struct summary_total_counts *totals) { + buffer_json_member_add_array(wb, key); + for (size_t c = 0; c < qt->nodes.used; c++) { + QUERY_NODE *qn = query_node(qt, c); + RRDHOST *host = qn->rrdhost; + buffer_json_add_array_item_object(wb); + buffer_json_node_add_v2(wb, host, qn->slot, qn->duration_ut); + query_target_instance_counts(wb, &qn->instances); + query_target_metric_counts(wb, &qn->metrics); + query_target_alerts_counts(wb, &qn->alerts, NULL, false); + query_target_points_statistics(wb, qt, &qn->query_points); + buffer_json_object_close(wb); + + aggregate_into_summary_totals(totals, &qn->metrics); } - if(!i) { -#ifdef NETDATA_INTERNAL_CHECKS - error("QUERY: '%s', RRDR is empty, %zu dimensions, options is 0x%08x", qt->id, r->d, options); -#endif - rows = 0; - buffer_strcat(wb, sq); - buffer_strcat(wb, "no data"); - buffer_strcat(wb, sq); + buffer_json_array_close(wb); +} + +static size_t query_target_summary_contexts_v2(BUFFER *wb, QUERY_TARGET *qt, const char *key, struct summary_total_counts *totals) { + buffer_json_member_add_array(wb, key); + DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE); + + struct { + STORAGE_POINT query_points; + QUERY_INSTANCES_COUNTS instances; + QUERY_METRICS_COUNTS metrics; + QUERY_ALERTS_COUNTS alerts; + } *z; + + for (long c = 0; c < (long) qt->contexts.used; c++) { + QUERY_CONTEXT *qc = query_context(qt, c); + + z = dictionary_set(dict, rrdcontext_acquired_id(qc->rca), NULL, sizeof(*z)); + + z->instances.selected += qc->instances.selected; + z->instances.excluded += qc->instances.selected; + z->instances.queried += qc->instances.queried; + z->instances.failed += qc->instances.failed; + + z->metrics.selected += qc->metrics.selected; + z->metrics.excluded += qc->metrics.excluded; + z->metrics.queried += qc->metrics.queried; + z->metrics.failed += qc->metrics.failed; + + z->alerts.clear += qc->alerts.clear; + z->alerts.warning += qc->alerts.warning; + z->alerts.critical += qc->alerts.critical; + + storage_point_merge_to(z->query_points, qc->query_points); } - buffer_sprintf(wb, "],\n" - " %sdimension_ids%s: [" - , kq, kq); + size_t unique_contexts = dictionary_entries(dict); + dfe_start_read(dict, z) { + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "id", z_dfe.name); + query_target_instance_counts(wb, &z->instances); + query_target_metric_counts(wb, &z->metrics); + query_target_alerts_counts(wb, &z->alerts, NULL, false); + query_target_points_statistics(wb, qt, &z->query_points); + buffer_json_object_close(wb); + + aggregate_into_summary_totals(totals, &z->metrics); + } + dfe_done(z); + buffer_json_array_close(wb); + dictionary_destroy(dict); - for(c = 0, i = 0; c < query_used ; c++) { - if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + return unique_contexts; +} - if(i) buffer_strcat(wb, ", "); - buffer_strcat(wb, sq); - buffer_strcat(wb, string2str(qt->query.array[c].dimension.id)); - buffer_strcat(wb, sq); - i++; +static void query_target_summary_instances_v1(BUFFER *wb, QUERY_TARGET *qt, const char *key) { + char name[RRD_ID_LENGTH_MAX * 2 + 2]; + + buffer_json_member_add_array(wb, key); + DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE); + for (long c = 0; c < (long) qt->instances.used; c++) { + QUERY_INSTANCE *qi = query_instance(qt, c); + + snprintfz(name, RRD_ID_LENGTH_MAX * 2 + 1, "%s:%s", + rrdinstance_acquired_id(qi->ria), + rrdinstance_acquired_name(qi->ria)); + + bool *set = dictionary_set(dict, name, NULL, sizeof(*set)); + if (!*set) { + *set = true; + buffer_json_add_array_item_array(wb); + buffer_json_add_array_item_string(wb, rrdinstance_acquired_id(qi->ria)); + buffer_json_add_array_item_string(wb, rrdinstance_acquired_name(qi->ria)); + buffer_json_array_close(wb); + } } - if(!i) { - rows = 0; - buffer_strcat(wb, sq); - buffer_strcat(wb, "no data"); - buffer_strcat(wb, sq); + dictionary_destroy(dict); + buffer_json_array_close(wb); +} + +static void query_target_summary_instances_v2(BUFFER *wb, QUERY_TARGET *qt, const char *key, struct summary_total_counts *totals) { + buffer_json_member_add_array(wb, key); + for (long c = 0; c < (long) qt->instances.used; c++) { + QUERY_INSTANCE *qi = query_instance(qt, c); +// QUERY_HOST *qh = query_host(qt, qi->query_host_id); + + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "id", rrdinstance_acquired_id(qi->ria)); + + if(!rrdinstance_acquired_id_and_name_are_same(qi->ria)) + buffer_json_member_add_string(wb, "nm", rrdinstance_acquired_name(qi->ria)); + + buffer_json_member_add_uint64(wb, "ni", qi->query_host_id); +// buffer_json_member_add_string(wb, "id", string2str(qi->id_fqdn)); +// buffer_json_member_add_string(wb, "nm", string2str(qi->name_fqdn)); +// buffer_json_member_add_string(wb, "lc", rrdinstance_acquired_name(qi->ria)); +// buffer_json_member_add_string(wb, "mg", qh->host->machine_guid); +// if(qh->node_id[0]) +// buffer_json_member_add_string(wb, "nd", qh->node_id); + query_target_metric_counts(wb, &qi->metrics); + query_target_alerts_counts(wb, &qi->alerts, NULL, false); + query_target_points_statistics(wb, qt, &qi->query_points); + buffer_json_object_close(wb); + + aggregate_into_summary_totals(totals, &qi->metrics); } - buffer_strcat(wb, "],\n"); + buffer_json_array_close(wb); +} - if (r->internal.query_options & RRDR_OPTION_ALL_DIMENSIONS) { - buffer_sprintf(wb, " %sfull_dimension_list%s: [", kq, kq); +struct dimensions_sorted_walkthrough_data { + BUFFER *wb; + struct summary_total_counts *totals; + QUERY_TARGET *qt; +}; - char name[RRD_ID_LENGTH_MAX * 2 + 2]; - char output[RRD_ID_LENGTH_MAX * 2 + 8]; +struct dimensions_sorted_entry { + const char *id; + const char *name; + STORAGE_POINT query_points; + QUERY_METRICS_COUNTS metrics; + uint32_t priority; +}; - struct value_output co = {.c = 0, .wb = wb}; +static int dimensions_sorted_walktrhough_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) { + struct dimensions_sorted_walkthrough_data *sdwd = data; + BUFFER *wb = sdwd->wb; + struct summary_total_counts *totals = sdwd->totals; + QUERY_TARGET *qt = sdwd->qt; + struct dimensions_sorted_entry *z = value; - DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE); - for (c = 0; c < (long)qt->metrics.used ;c++) { - snprintfz(name, RRD_ID_LENGTH_MAX * 2 + 1, "%s:%s", - rrdmetric_acquired_id(qt->metrics.array[c]), - rrdmetric_acquired_name(qt->metrics.array[c])); + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "id", z->id); + if (z->id != z->name && z->name) + buffer_json_member_add_string(wb, "nm", z->name); - int len = snprintfz(output, RRD_ID_LENGTH_MAX * 2 + 7, "[\"%s\",\"%s\"]", - rrdmetric_acquired_id(qt->metrics.array[c]), - rrdmetric_acquired_name(qt->metrics.array[c])); + query_target_metric_counts(wb, &z->metrics); + query_target_points_statistics(wb, qt, &z->query_points); + buffer_json_member_add_uint64(wb, "pri", z->priority); + buffer_json_object_close(wb); - dictionary_set(dict, name, output, len + 1); - } - dictionary_walkthrough_read(dict, value_list_output_callback, &co); - dictionary_destroy(dict); + aggregate_into_summary_totals(totals, &z->metrics); - co.c = 0; - buffer_sprintf(wb, "],\n %sfull_chart_list%s: [", kq, kq); - dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE); - for (c = 0; c < (long)qt->instances.used ; c++) { - RRDINSTANCE_ACQUIRED *ria = qt->instances.array[c]; + return 1; +} - snprintfz(name, RRD_ID_LENGTH_MAX * 2 + 1, "%s:%s", - rrdinstance_acquired_id(ria), - rrdinstance_acquired_name(ria)); +int dimensions_sorted_compar(const DICTIONARY_ITEM **item1, const DICTIONARY_ITEM **item2) { + struct dimensions_sorted_entry *z1 = dictionary_acquired_item_value(*item1); + struct dimensions_sorted_entry *z2 = dictionary_acquired_item_value(*item2); - int len = snprintfz(output, RRD_ID_LENGTH_MAX * 2 + 7, "[\"%s\",\"%s\"]", - rrdinstance_acquired_id(ria), - rrdinstance_acquired_name(ria)); + if(z1->priority == z2->priority) + return strcmp(dictionary_acquired_item_name(*item1), dictionary_acquired_item_name(*item2)); + else if(z1->priority < z2->priority) + return -1; + else + return 1; +} - dictionary_set(dict, name, output, len + 1); +static void query_target_summary_dimensions_v12(BUFFER *wb, QUERY_TARGET *qt, const char *key, bool v2, struct summary_total_counts *totals) { + char buf[RRD_ID_LENGTH_MAX * 2 + 2]; + + buffer_json_member_add_array(wb, key); + DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE); + struct dimensions_sorted_entry *z; + size_t q = 0; + for (long c = 0; c < (long) qt->dimensions.used; c++) { + QUERY_DIMENSION * qd = query_dimension(qt, c); + RRDMETRIC_ACQUIRED *rma = qd->rma; + + QUERY_METRIC *qm = NULL; + for( ; q < qt->query.used ;q++) { + QUERY_METRIC *tqm = query_metric(qt, q); + QUERY_DIMENSION *tqd = query_dimension(qt, tqm->link.query_dimension_id); + if(tqd->rma != rma) break; + qm = tqm; } - dictionary_walkthrough_read(dict, value_list_output_callback, &co); - dictionary_destroy(dict); - co.c = 0; - buffer_sprintf(wb, "],\n %sfull_chart_labels%s: [", kq, kq); - dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE); - for (c = 0; c < (long)qt->instances.used ; c++) { - RRDINSTANCE_ACQUIRED *ria = qt->instances.array[c]; - rrdlabels_walkthrough_read(rrdinstance_acquired_labels(ria), fill_formatted_callback, dict); + const char *key, *id, *name; + + if(v2) { + key = rrdmetric_acquired_name(rma); + id = key; + name = key; + } + else { + snprintfz(buf, RRD_ID_LENGTH_MAX * 2 + 1, "%s:%s", + rrdmetric_acquired_id(rma), + rrdmetric_acquired_name(rma)); + key = buf; + id = rrdmetric_acquired_id(rma); + name = rrdmetric_acquired_name(rma); } - dictionary_walkthrough_read(dict, value_list_output_callback, &co); - dictionary_destroy(dict); - buffer_strcat(wb, "],\n"); - } - // functions - { - DICTIONARY *funcs = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE); - RRDINSTANCE_ACQUIRED *ria = NULL; - for (c = 0; c < query_used ; c++) { - QUERY_METRIC *qm = &qt->query.array[c]; - if(qm->link.ria == ria) - continue; + z = dictionary_set(dict, key, NULL, sizeof(*z)); + if(!z->id) { + z->id = id; + z->name = name; + z->priority = qd->priority; + } + else { + if(qd->priority < z->priority) + z->priority = qd->priority; + } - ria = qm->link.ria; - chart_functions_to_dict(rrdinstance_acquired_functions(ria), funcs); + if(qm) { + z->metrics.selected += (qm->status & RRDR_DIMENSION_SELECTED) ? 1 : 0; + z->metrics.failed += (qm->status & RRDR_DIMENSION_FAILED) ? 1 : 0; + + if(qm->status & RRDR_DIMENSION_QUERIED) { + z->metrics.queried++; + storage_point_merge_to(z->query_points, qm->query_points); + } } + else + z->metrics.excluded++; + } - buffer_sprintf(wb, " %sfunctions%s: [", kq, kq); - void *t; (void)t; - dfe_start_read(funcs, t) { - const char *comma = ""; - if(t_dfe.counter) comma = ", "; - buffer_sprintf(wb, "%s%s%s%s", comma, sq, t_dfe.name, sq); + if(v2) { + struct dimensions_sorted_walkthrough_data t = { + .wb = wb, + .totals = totals, + .qt = qt, + }; + dictionary_sorted_walkthrough_rw(dict, DICTIONARY_LOCK_READ, dimensions_sorted_walktrhough_cb, + &t, dimensions_sorted_compar); + } + else { + // v1 + dfe_start_read(dict, z) { + buffer_json_add_array_item_array(wb); + buffer_json_add_array_item_string(wb, z->id); + buffer_json_add_array_item_string(wb, z->name); + buffer_json_array_close(wb); } - dfe_done(t); - dictionary_destroy(funcs); - buffer_strcat(wb, "],\n"); + dfe_done(z); } + dictionary_destroy(dict); + buffer_json_array_close(wb); +} - // context query - if (!qt->request.st) { - buffer_sprintf( - wb, - " %schart_ids%s: [", - kq, kq); +struct rrdlabels_formatting_v2 { + DICTIONARY *keys; + QUERY_INSTANCE *qi; + bool v2; +}; - for (c = 0, i = 0; c < query_used; c++) { - QUERY_METRIC *qm = &qt->query.array[c]; +struct rrdlabels_keys_dict_entry { + const char *name; + DICTIONARY *values; + STORAGE_POINT query_points; + QUERY_METRICS_COUNTS metrics; +}; - if (unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if (unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - if (unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) - continue; +struct rrdlabels_key_value_dict_entry { + const char *key; + const char *value; + STORAGE_POINT query_points; + QUERY_METRICS_COUNTS metrics; +}; - if (i) - buffer_strcat(wb, ", "); - buffer_strcat(wb, sq); - buffer_strcat(wb, string2str(qm->chart.id)); - buffer_strcat(wb, sq); - i++; - } - if (!i) { - rows = 0; - buffer_strcat(wb, sq); - buffer_strcat(wb, "no data"); - buffer_strcat(wb, sq); - } - buffer_strcat(wb, "],\n"); - if (qt->instances.chart_label_key_pattern) { - buffer_sprintf(wb, " %schart_labels%s: { ", kq, kq); - - SIMPLE_PATTERN *pattern = qt->instances.chart_label_key_pattern; - char *label_key = NULL; - int keys = 0; - while (pattern && (label_key = simple_pattern_iterate(&pattern))) { - if (keys) - buffer_strcat(wb, ", "); - buffer_sprintf(wb, "%s%s%s : [", kq, label_key, kq); - keys++; - - for (c = 0, i = 0; c < query_used; c++) { - QUERY_METRIC *qm = &qt->query.array[c]; - - if (unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if (unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - if (unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) - continue; +static int rrdlabels_formatting_v2(const char *name, const char *value, RRDLABEL_SRC ls __maybe_unused, void *data) { + struct rrdlabels_formatting_v2 *t = data; - if (i) - buffer_strcat(wb, ", "); - rrdlabels_get_value_to_buffer_or_null(rrdinstance_acquired_labels(qm->link.ria), wb, label_key, sq, "null"); - i++; + struct rrdlabels_keys_dict_entry *d = dictionary_set(t->keys, name, NULL, sizeof(*d)); + if(!d->values) { + d->name = name; + d->values = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE); + } + + char n[RRD_ID_LENGTH_MAX * 2 + 2]; + snprintfz(n, RRD_ID_LENGTH_MAX * 2, "%s:%s", name, value); + + struct rrdlabels_key_value_dict_entry *z = dictionary_set(d->values, n, NULL, sizeof(*z)); + if(!z->key) { + z->key = name; + z->value = value; + } + + if(t->v2) { + QUERY_INSTANCE *qi = t->qi; + + z->metrics.selected += qi->metrics.selected; + z->metrics.excluded += qi->metrics.excluded; + z->metrics.queried += qi->metrics.queried; + z->metrics.failed += qi->metrics.failed; + + d->metrics.selected += qi->metrics.selected; + d->metrics.excluded += qi->metrics.excluded; + d->metrics.queried += qi->metrics.queried; + d->metrics.failed += qi->metrics.failed; + + storage_point_merge_to(z->query_points, qi->query_points); + storage_point_merge_to(d->query_points, qi->query_points); + } + + return 1; +} + +static void query_target_summary_labels_v12(BUFFER *wb, QUERY_TARGET *qt, const char *key, bool v2, struct summary_total_counts *key_totals, struct summary_total_counts *value_totals) { + buffer_json_member_add_array(wb, key); + struct rrdlabels_formatting_v2 t = { + .keys = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE), + .v2 = v2, + }; + for (long c = 0; c < (long) qt->instances.used; c++) { + QUERY_INSTANCE *qi = query_instance(qt, c); + RRDINSTANCE_ACQUIRED *ria = qi->ria; + t.qi = qi; + rrdlabels_walkthrough_read(rrdinstance_acquired_labels(ria), rrdlabels_formatting_v2, &t); + } + struct rrdlabels_keys_dict_entry *d; + dfe_start_read(t.keys, d) { + if(v2) { + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "id", d_dfe.name); + query_target_metric_counts(wb, &d->metrics); + query_target_points_statistics(wb, qt, &d->query_points); + aggregate_into_summary_totals(key_totals, &d->metrics); + buffer_json_member_add_array(wb, "vl"); } - if (!i) { - rows = 0; - buffer_strcat(wb, sq); - buffer_strcat(wb, "no data"); - buffer_strcat(wb, sq); + struct rrdlabels_key_value_dict_entry *z; + dfe_start_read(d->values, z){ + if (v2) { + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "id", z->value); + query_target_metric_counts(wb, &z->metrics); + query_target_points_statistics(wb, qt, &z->query_points); + buffer_json_object_close(wb); + aggregate_into_summary_totals(value_totals, &z->metrics); + } else { + buffer_json_add_array_item_array(wb); + buffer_json_add_array_item_string(wb, z->key); + buffer_json_add_array_item_string(wb, z->value); + buffer_json_array_close(wb); + } + } + dfe_done(z); + dictionary_destroy(d->values); + if(v2) { + buffer_json_array_close(wb); + buffer_json_object_close(wb); } - buffer_strcat(wb, "]"); } - buffer_strcat(wb, "},\n"); + dfe_done(d); + dictionary_destroy(t.keys); + buffer_json_array_close(wb); +} + +static void query_target_summary_alerts_v2(BUFFER *wb, QUERY_TARGET *qt, const char *key) { + buffer_json_member_add_array(wb, key); + QUERY_ALERTS_COUNTS *z; + + DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE); + for (long c = 0; c < (long) qt->instances.used; c++) { + QUERY_INSTANCE *qi = query_instance(qt, c); + RRDSET *st = rrdinstance_acquired_rrdset(qi->ria); + if (st) { + netdata_rwlock_rdlock(&st->alerts.rwlock); + if (st->alerts.base) { + for (RRDCALC *rc = st->alerts.base; rc; rc = rc->next) { + z = dictionary_set(dict, string2str(rc->name), NULL, sizeof(*z)); + + switch(rc->status) { + case RRDCALC_STATUS_CLEAR: + z->clear++; + break; + + case RRDCALC_STATUS_WARNING: + z->warning++; + break; + + case RRDCALC_STATUS_CRITICAL: + z->critical++; + break; + + default: + case RRDCALC_STATUS_UNINITIALIZED: + case RRDCALC_STATUS_UNDEFINED: + case RRDCALC_STATUS_REMOVED: + z->other++; + break; + } + } + } + netdata_rwlock_unlock(&st->alerts.rwlock); + } + } + dfe_start_read(dict, z) + query_target_alerts_counts(wb, z, z_dfe.name, true); + dfe_done(z); + dictionary_destroy(dict); + buffer_json_array_close(wb); // alerts +} + +static inline void query_target_functions(BUFFER *wb, const char *key, RRDR *r) { + QUERY_TARGET *qt = r->internal.qt; + const long query_used = qt->query.used; + + DICTIONARY *funcs = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE); + RRDINSTANCE_ACQUIRED *ria = NULL; + for (long c = 0; c < query_used ; c++) { + QUERY_METRIC *qm = query_metric(qt, c); + QUERY_INSTANCE *qi = query_instance(qt, qm->link.query_instance_id); + if(qi->ria == ria) + continue; + + ria = qi->ria; + chart_functions_to_dict(rrdinstance_acquired_functions(ria), funcs); + } + + buffer_json_member_add_array(wb, key); + void *t; (void)t; + dfe_start_read(funcs, t) + buffer_json_add_array_item_string(wb, t_dfe.name); + dfe_done(t); + dictionary_destroy(funcs); + buffer_json_array_close(wb); +} + +static inline long query_target_chart_labels_filter_v1(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options) { + QUERY_TARGET *qt = r->internal.qt; + const long query_used = qt->query.used; + long c, i = 0; + + buffer_json_member_add_object(wb, key); + + SIMPLE_PATTERN *pattern = qt->instances.chart_label_key_pattern; + char *label_key = NULL; + while (pattern && (label_key = simple_pattern_iterate(&pattern))) { + buffer_json_member_add_array(wb, label_key); + + for (c = 0, i = 0; c < query_used; c++) { + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; + + QUERY_METRIC *qm = query_metric(qt, c); + QUERY_INSTANCE *qi = query_instance(qt, qm->link.query_instance_id); + rrdlabels_value_to_buffer_array_item_or_null(rrdinstance_acquired_labels(qi->ria), wb, label_key); + i++; } + buffer_json_array_close(wb); } - buffer_sprintf(wb, " %slatest_values%s: [" - , kq, kq); + buffer_json_object_close(wb); + + return i; +} + +static inline long query_target_metrics_latest_values(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options) { + QUERY_TARGET *qt = r->internal.qt; + const long query_used = qt->query.used; + long c, i; + + buffer_json_member_add_array(wb, key); for(c = 0, i = 0; c < query_used ;c++) { - QUERY_METRIC *qm = &qt->query.array[c]; + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; + + QUERY_METRIC *qm = query_metric(qt, c); + QUERY_DIMENSION *qd = query_dimension(qt, qm->link.query_dimension_id); + buffer_json_add_array_item_double(wb, rrdmetric_acquired_last_stored_value(qd->rma)); + i++; + } - if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + buffer_json_array_close(wb); + + return i; +} + +static inline size_t rrdr_dimension_view_latest_values(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options) { + buffer_json_member_add_array(wb, key); + + size_t c, i; + for(c = 0, i = 0; c < r->d ; c++) { + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; - if(i) buffer_strcat(wb, ", "); i++; - NETDATA_DOUBLE value = rrdmetric_acquired_last_stored_value(qm->link.rma); - if (NAN == value) - buffer_strcat(wb, "null"); + NETDATA_DOUBLE *cn = &r->v[ (rrdr_rows(r) - 1) * r->d ]; + RRDR_VALUE_FLAGS *co = &r->o[ (rrdr_rows(r) - 1) * r->d ]; + NETDATA_DOUBLE n = cn[c]; + + if(co[c] & RRDR_VALUE_EMPTY) { + if(options & RRDR_OPTION_NULL2ZERO) + buffer_json_add_array_item_double(wb, 0.0); + else + buffer_json_add_array_item_double(wb, NAN); + } else - buffer_rrd_value(wb, value); - } - if(!i) { - rows = 0; - buffer_strcat(wb, "null"); + buffer_json_add_array_item_double(wb, n); } - buffer_sprintf(wb, "],\n" - " %sview_latest_values%s: [" - , kq, kq); + buffer_json_array_close(wb); - i = 0; - if(rows) { - NETDATA_DOUBLE total = 1; + return i; +} - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { - total = 0; - for(c = 0; c < query_used ;c++) { - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; +static inline void rrdr_dimension_query_points_statistics(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options, bool dview) { + STORAGE_POINT *sp = (dview) ? r->dview : r->dqp; + NETDATA_DOUBLE anomaly_rate_multiplier = (dview) ? RRDR_DVIEW_ANOMALY_COUNT_MULTIPLIER : 1.0; - NETDATA_DOUBLE *cn = &r->v[ (rrdr_rows(r) - 1) * r->d ]; - NETDATA_DOUBLE n = cn[c]; + if(unlikely(!sp)) + return; - if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; + if(key) + buffer_json_member_add_object(wb, key); - total += n; - } - // prevent a division by zero - if(total == 0) total = 1; - } + buffer_json_member_add_array(wb, "min"); + for(size_t c = 0; c < r->d ; c++) { + if (!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; - for(c = 0, i = 0; c < query_used ;c++) { - if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + buffer_json_add_array_item_double(wb, sp[c].min); + } + buffer_json_array_close(wb); - if(i) buffer_strcat(wb, ", "); - i++; + buffer_json_member_add_array(wb, "max"); + for(size_t c = 0; c < r->d ; c++) { + if (!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; - NETDATA_DOUBLE *cn = &r->v[ (rrdr_rows(r) - 1) * r->d ]; - RRDR_VALUE_FLAGS *co = &r->o[ (rrdr_rows(r) - 1) * r->d ]; - NETDATA_DOUBLE n = cn[c]; + buffer_json_add_array_item_double(wb, sp[c].max); + } + buffer_json_array_close(wb); - if(co[c] & RRDR_VALUE_EMPTY) { - if(options & RRDR_OPTION_NULL2ZERO) - buffer_strcat(wb, "0"); - else - buffer_strcat(wb, "null"); - } - else { - if(unlikely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; + if(options & RRDR_OPTION_RETURN_RAW) { + buffer_json_member_add_array(wb, "sum"); + for(size_t c = 0; c < r->d ; c++) { + if (!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) - n = n * 100 / total; + buffer_json_add_array_item_double(wb, sp[c].sum); + } + buffer_json_array_close(wb); - buffer_rrd_value(wb, n); - } + buffer_json_member_add_array(wb, "cnt"); + for(size_t c = 0; c < r->d ; c++) { + if (!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; + + buffer_json_add_array_item_uint64(wb, sp[c].count); } + buffer_json_array_close(wb); + + buffer_json_member_add_array(wb, "arc"); + for(size_t c = 0; c < r->d ; c++) { + if (!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; + + buffer_json_add_array_item_uint64(wb, storage_point_anomaly_rate(sp[c]) / anomaly_rate_multiplier / 100.0 * sp[c].count); + } + buffer_json_array_close(wb); } - if(!i) { - rows = 0; - buffer_strcat(wb, "null"); - } + else { + NETDATA_DOUBLE sum = 0.0; + for(size_t c = 0; c < r->d ; c++) { + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; - buffer_sprintf(wb, "],\n" - " %sdimensions%s: %ld,\n" - " %spoints%s: %ld,\n" - " %sformat%s: %s" - , kq, kq, i - , kq, kq, rows - , kq, kq, sq - ); + sum += ABS(sp[c].sum); + } - rrdr_buffer_print_format(wb, format); + buffer_json_member_add_array(wb, "avg"); + for(size_t c = 0; c < r->d ; c++) { + if (!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; - buffer_sprintf(wb, "%s,\n" - " %sdb_points_per_tier%s: [ " - , sq - , kq, kq - ); + buffer_json_add_array_item_double(wb, storage_point_average_value(sp[c])); + } + buffer_json_array_close(wb); - for(size_t tier = 0; tier < storage_tiers ; tier++) - buffer_sprintf(wb, "%s%zu", tier>0?", ":"", r->internal.tier_points_read[tier]); + buffer_json_member_add_array(wb, "arp"); + for(size_t c = 0; c < r->d ; c++) { + if (!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; - buffer_strcat(wb, " ],"); + buffer_json_add_array_item_double(wb, storage_point_anomaly_rate(sp[c]) / anomaly_rate_multiplier); + } + buffer_json_array_close(wb); - if(options & RRDR_OPTION_SHOW_PLAN) - rrdr_show_plan(r, wb, kq, sq); + buffer_json_member_add_array(wb, "con"); + for(size_t c = 0; c < r->d ; c++) { + if (!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; - buffer_sprintf(wb, "\n %sresult%s: ", kq, kq); + NETDATA_DOUBLE con = (sum > 0.0) ? ABS(sp[c].sum) * 100.0 / sum : 0.0; + buffer_json_add_array_item_double(wb, con); + } + buffer_json_array_close(wb); + } - if(string_value) buffer_strcat(wb, sq); - //info("JSONWRAPPER(): %s: END", r->st->id); + if(key) + buffer_json_object_close(wb); } -void rrdr_json_wrapper_anomaly_rates(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value) { - (void)r; - (void)format; +void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb) { + QUERY_TARGET *qt = r->internal.qt; + DATASOURCE_FORMAT format = qt->request.format; + RRDR_OPTIONS options = qt->window.options; + + long rows = rrdr_rows(r); char kq[2] = "", // key quote - sq[2] = ""; // string quote + sq[2] = ""; // string quote if( options & RRDR_OPTION_GOOGLE_JSON ) { kq[0] = '\0'; @@ -447,31 +874,702 @@ void rrdr_json_wrapper_anomaly_rates(RRDR *r, BUFFER *wb, uint32_t format, uint3 sq[0] = '"'; } - if(string_value) buffer_strcat(wb, sq); + buffer_json_initialize(wb, kq, sq, 0, true, options & RRDR_OPTION_MINIFY); + + buffer_json_member_add_uint64(wb, "api", 1); + buffer_json_member_add_string(wb, "id", qt->id); + buffer_json_member_add_string(wb, "name", qt->id); + buffer_json_member_add_time_t(wb, "view_update_every", r->view.update_every); + buffer_json_member_add_time_t(wb, "update_every", qt->db.minimum_latest_update_every_s); + buffer_json_member_add_time_t(wb, "first_entry", qt->db.first_time_s); + buffer_json_member_add_time_t(wb, "last_entry", qt->db.last_time_s); + buffer_json_member_add_time_t(wb, "after", r->view.after); + buffer_json_member_add_time_t(wb, "before", r->view.before); + buffer_json_member_add_string(wb, "group", time_grouping_tostring(qt->request.time_group_method)); + web_client_api_request_v1_data_options_to_buffer_json_array(wb, "options", options); + + if(!rrdr_dimension_names(wb, "dimension_names", r, options)) + rows = 0; + + if(!rrdr_dimension_ids(wb, "dimension_ids", r, options)) + rows = 0; + + if (options & RRDR_OPTION_ALL_DIMENSIONS) { + query_target_summary_instances_v1(wb, qt, "full_chart_list"); + query_target_summary_dimensions_v12(wb, qt, "full_dimension_list", false, NULL); + query_target_summary_labels_v12(wb, qt, "full_chart_labels", false, NULL, NULL); + } + + query_target_functions(wb, "functions", r); + + if (!qt->request.st && !jsonwrap_v1_chart_ids(wb, "chart_ids", r, options)) + rows = 0; + + if (qt->instances.chart_label_key_pattern && !query_target_chart_labels_filter_v1(wb, "chart_labels", r, options)) + rows = 0; + + if(!query_target_metrics_latest_values(wb, "latest_values", r, options)) + rows = 0; + + size_t dimensions = rrdr_dimension_view_latest_values(wb, "view_latest_values", r, options); + if(!dimensions) + rows = 0; + + buffer_json_member_add_uint64(wb, "dimensions", dimensions); + buffer_json_member_add_uint64(wb, "points", rows); + buffer_json_member_add_string(wb, "format", rrdr_format_to_string(format)); + + buffer_json_member_add_array(wb, "db_points_per_tier"); + for(size_t tier = 0; tier < storage_tiers ; tier++) + buffer_json_add_array_item_uint64(wb, qt->db.tiers[tier].points); + buffer_json_array_close(wb); + + if(options & RRDR_OPTION_DEBUG) + jsonwrap_query_plan(r, wb); +} + +static void rrdset_rrdcalc_entries_v2(BUFFER *wb, RRDINSTANCE_ACQUIRED *ria) { + RRDSET *st = rrdinstance_acquired_rrdset(ria); + if(st) { + netdata_rwlock_rdlock(&st->alerts.rwlock); + if(st->alerts.base) { + buffer_json_member_add_object(wb, "alerts"); + for(RRDCALC *rc = st->alerts.base; rc ;rc = rc->next) { + if(rc->status < RRDCALC_STATUS_CLEAR) + continue; + + buffer_json_member_add_object(wb, string2str(rc->name)); + buffer_json_member_add_string(wb, "st", rrdcalc_status2string(rc->status)); + buffer_json_member_add_double(wb, "vl", rc->value); + buffer_json_member_add_string(wb, "un", string2str(rc->units)); + buffer_json_object_close(wb); + } + buffer_json_object_close(wb); + } + netdata_rwlock_unlock(&st->alerts.rwlock); + } +} + +static void query_target_combined_units_v2(BUFFER *wb, QUERY_TARGET *qt, size_t contexts, bool ignore_percentage) { + if(!ignore_percentage && query_target_has_percentage_units(qt)) { + buffer_json_member_add_string(wb, "units", "%"); + } + else if(contexts == 1) { + buffer_json_member_add_string(wb, "units", rrdcontext_acquired_units(qt->contexts.array[0].rca)); + } + else if(contexts > 1) { + DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE); + for(size_t c = 0; c < qt->contexts.used ;c++) + dictionary_set(dict, rrdcontext_acquired_units(qt->contexts.array[c].rca), NULL, 0); + + if(dictionary_entries(dict) == 1) + buffer_json_member_add_string(wb, "units", rrdcontext_acquired_units(qt->contexts.array[0].rca)); + else { + buffer_json_member_add_array(wb, "units"); + const char *s; + dfe_start_read(dict, s) + buffer_json_add_array_item_string(wb, s_dfe.name); + dfe_done(s); + buffer_json_array_close(wb); + } + dictionary_destroy(dict); + } +} - buffer_sprintf(wb, ",\n %sanomaly_rates%s: ", kq, kq); +static void query_target_combined_chart_type(BUFFER *wb, QUERY_TARGET *qt, size_t contexts) { + if(contexts >= 1) + buffer_json_member_add_string(wb, "chart_type", rrdset_type_name(rrdcontext_acquired_chart_type(qt->contexts.array[0].rca))); } -void rrdr_json_wrapper_end(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value) { - (void)format; +static void rrdr_grouped_by_array_v2(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options __maybe_unused) { + QUERY_TARGET *qt = r->internal.qt; - char kq[2] = "", // key quote - sq[2] = ""; // string quote + buffer_json_member_add_array(wb, key); - if( options & RRDR_OPTION_GOOGLE_JSON ) { + // find the deeper group-by + ssize_t g = 0; + for(g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) { + if(qt->request.group_by[g].group_by == RRDR_GROUP_BY_NONE) + break; + } + + if(g > 0) + g--; + + RRDR_GROUP_BY group_by = qt->request.group_by[g].group_by; + + if(group_by & RRDR_GROUP_BY_SELECTED) + buffer_json_add_array_item_string(wb, "selected"); + + else if(group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE) + buffer_json_add_array_item_string(wb, "percentage-of-instance"); + + else { + + if(group_by & RRDR_GROUP_BY_DIMENSION) + buffer_json_add_array_item_string(wb, "dimension"); + + if(group_by & RRDR_GROUP_BY_INSTANCE) + buffer_json_add_array_item_string(wb, "instance"); + + if(group_by & RRDR_GROUP_BY_LABEL) { + BUFFER *b = buffer_create(0, NULL); + for (size_t l = 0; l < qt->group_by[g].used; l++) { + buffer_flush(b); + buffer_fast_strcat(b, "label:", 6); + buffer_strcat(b, qt->group_by[g].label_keys[l]); + buffer_json_add_array_item_string(wb, buffer_tostring(b)); + } + buffer_free(b); + } + + if(group_by & RRDR_GROUP_BY_NODE) + buffer_json_add_array_item_string(wb, "node"); + + if(group_by & RRDR_GROUP_BY_CONTEXT) + buffer_json_add_array_item_string(wb, "context"); + + if(group_by & RRDR_GROUP_BY_UNITS) + buffer_json_add_array_item_string(wb, "units"); + } + + buffer_json_array_close(wb); // group_by_order +} + +static void rrdr_dimension_units_array_v2(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options, bool ignore_percentage) { + if(!r->du) + return; + + bool percentage = !ignore_percentage && query_target_has_percentage_units(r->internal.qt); + + buffer_json_member_add_array(wb, key); + for(size_t c = 0; c < r->d ; c++) { + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; + + if(percentage) + buffer_json_add_array_item_string(wb, "%"); + else + buffer_json_add_array_item_string(wb, string2str(r->du[c])); + } + buffer_json_array_close(wb); +} + +static void rrdr_dimension_priority_array_v2(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options) { + if(!r->dp) + return; + + buffer_json_member_add_array(wb, key); + for(size_t c = 0; c < r->d ; c++) { + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; + + buffer_json_add_array_item_uint64(wb, r->dp[c]); + } + buffer_json_array_close(wb); +} + +static void rrdr_dimension_aggregated_array_v2(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options) { + if(!r->dgbc) + return; + + buffer_json_member_add_array(wb, key); + for(size_t c = 0; c < r->d ;c++) { + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; + + buffer_json_add_array_item_uint64(wb, r->dgbc[c]); + } + buffer_json_array_close(wb); +} + +static void query_target_title(BUFFER *wb, QUERY_TARGET *qt, size_t contexts) { + if(contexts == 1) { + buffer_json_member_add_string(wb, "title", rrdcontext_acquired_title(qt->contexts.array[0].rca)); + } + else if(contexts > 1) { + BUFFER *t = buffer_create(0, NULL); + DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE); + + buffer_strcat(t, "Chart for contexts: "); + + size_t added = 0; + for(size_t c = 0; c < qt->contexts.used ;c++) { + bool *set = dictionary_set(dict, rrdcontext_acquired_id(qt->contexts.array[c].rca), NULL, sizeof(*set)); + if(!*set) { + *set = true; + if(added) + buffer_fast_strcat(t, ", ", 2); + + buffer_strcat(t, rrdcontext_acquired_id(qt->contexts.array[c].rca)); + added++; + } + } + buffer_json_member_add_string(wb, "title", buffer_tostring(t)); + dictionary_destroy(dict); + buffer_free(t); + } +} + +static void query_target_detailed_objects_tree(BUFFER *wb, RRDR *r, RRDR_OPTIONS options) { + QUERY_TARGET *qt = r->internal.qt; + buffer_json_member_add_object(wb, "nodes"); + + time_t now_s = now_realtime_sec(); + RRDHOST *last_host = NULL; + RRDCONTEXT_ACQUIRED *last_rca = NULL; + RRDINSTANCE_ACQUIRED *last_ria = NULL; + + size_t h = 0, c = 0, i = 0, m = 0, q = 0; + for(; h < qt->nodes.used ; h++) { + QUERY_NODE *qn = query_node(qt, h); + RRDHOST *host = qn->rrdhost; + + for( ;c < qt->contexts.used ;c++) { + QUERY_CONTEXT *qc = query_context(qt, c); + RRDCONTEXT_ACQUIRED *rca = qc->rca; + if(!rrdcontext_acquired_belongs_to_host(rca, host)) break; + + for( ;i < qt->instances.used ;i++) { + QUERY_INSTANCE *qi = query_instance(qt, i); + RRDINSTANCE_ACQUIRED *ria = qi->ria; + if(!rrdinstance_acquired_belongs_to_context(ria, rca)) break; + + for( ; m < qt->dimensions.used ; m++) { + QUERY_DIMENSION *qd = query_dimension(qt, m); + RRDMETRIC_ACQUIRED *rma = qd->rma; + if(!rrdmetric_acquired_belongs_to_instance(rma, ria)) break; + + QUERY_METRIC *qm = NULL; + bool queried = false; + for( ; q < qt->query.used ;q++) { + QUERY_METRIC *tqm = query_metric(qt, q); + QUERY_DIMENSION *tqd = query_dimension(qt, tqm->link.query_dimension_id); + if(tqd->rma != rma) break; + + queried = tqm->status & RRDR_DIMENSION_QUERIED; + qm = tqm; + } + + if(!queried & !(options & RRDR_OPTION_ALL_DIMENSIONS)) + continue; + + if(host != last_host) { + if(last_host) { + if(last_rca) { + if(last_ria) { + buffer_json_object_close(wb); // dimensions + buffer_json_object_close(wb); // instance + last_ria = NULL; + } + buffer_json_object_close(wb); // instances + buffer_json_object_close(wb); // context + last_rca = NULL; + } + buffer_json_object_close(wb); // contexts + buffer_json_object_close(wb); // host + last_host = NULL; + } + + buffer_json_member_add_object(wb, host->machine_guid); + if(qn->node_id[0]) + buffer_json_member_add_string(wb, "nd", qn->node_id); + buffer_json_member_add_uint64(wb, "ni", qn->slot); + buffer_json_member_add_string(wb, "nm", rrdhost_hostname(host)); + buffer_json_member_add_object(wb, "contexts"); + + last_host = host; + } + + if(rca != last_rca) { + if(last_rca) { + if(last_ria) { + buffer_json_object_close(wb); // dimensions + buffer_json_object_close(wb); // instance + last_ria = NULL; + } + buffer_json_object_close(wb); // instances + buffer_json_object_close(wb); // context + last_rca = NULL; + } + + buffer_json_member_add_object(wb, rrdcontext_acquired_id(rca)); + buffer_json_member_add_object(wb, "instances"); + + last_rca = rca; + } + + if(ria != last_ria) { + if(last_ria) { + buffer_json_object_close(wb); // dimensions + buffer_json_object_close(wb); // instance + last_ria = NULL; + } + + buffer_json_member_add_object(wb, rrdinstance_acquired_id(ria)); + buffer_json_member_add_string(wb, "nm", rrdinstance_acquired_name(ria)); + buffer_json_member_add_time_t(wb, "ue", rrdinstance_acquired_update_every(ria)); + DICTIONARY *labels = rrdinstance_acquired_labels(ria); + if(labels) { + buffer_json_member_add_object(wb, "labels"); + rrdlabels_to_buffer_json_members(labels, wb); + buffer_json_object_close(wb); + } + rrdset_rrdcalc_entries_v2(wb, ria); + buffer_json_member_add_object(wb, "dimensions"); + + last_ria = ria; + } + + buffer_json_member_add_object(wb, rrdmetric_acquired_id(rma)); + { + buffer_json_member_add_string(wb, "nm", rrdmetric_acquired_name(rma)); + buffer_json_member_add_uint64(wb, "qr", queried ? 1 : 0); + time_t first_entry_s = rrdmetric_acquired_first_entry(rma); + time_t last_entry_s = rrdmetric_acquired_last_entry(rma); + buffer_json_member_add_time_t(wb, "fe", first_entry_s); + buffer_json_member_add_time_t(wb, "le", last_entry_s ? last_entry_s : now_s); + + if(qm) { + if(qm->status & RRDR_DIMENSION_GROUPED) { + // buffer_json_member_add_string(wb, "grouped_as_id", string2str(qm->grouped_as.id)); + buffer_json_member_add_string(wb, "as", string2str(qm->grouped_as.name)); + } + + query_target_points_statistics(wb, qt, &qm->query_points); + + if(options & RRDR_OPTION_DEBUG) + jsonwrap_query_metric_plan(wb, qm); + } + } + buffer_json_object_close(wb); // metric + } + } + } + } + + if(last_host) { + if(last_rca) { + if(last_ria) { + buffer_json_object_close(wb); // dimensions + buffer_json_object_close(wb); // instance + last_ria = NULL; + } + buffer_json_object_close(wb); // instances + buffer_json_object_close(wb); // context + last_rca = NULL; + } + buffer_json_object_close(wb); // contexts + buffer_json_object_close(wb); // host + last_host = NULL; + } + buffer_json_object_close(wb); // hosts +} + +void version_hashes_api_v2(BUFFER *wb, struct query_versions *versions) { + buffer_json_member_add_object(wb, "versions"); + buffer_json_member_add_uint64(wb, "nodes_hard_hash", dictionary_version(rrdhost_root_index)); + buffer_json_member_add_uint64(wb, "contexts_hard_hash", versions->contexts_hard_hash); + buffer_json_member_add_uint64(wb, "contexts_soft_hash", versions->contexts_soft_hash); + buffer_json_member_add_uint64(wb, "alerts_hard_hash", versions->alerts_hard_hash); + buffer_json_member_add_uint64(wb, "alerts_soft_hash", versions->alerts_soft_hash); + buffer_json_object_close(wb); +} + +void rrdr_json_wrapper_begin2(RRDR *r, BUFFER *wb) { + QUERY_TARGET *qt = r->internal.qt; + RRDR_OPTIONS options = qt->window.options; + + char kq[2] = "\"", // key quote + sq[2] = "\""; // string quote + + if(unlikely(options & RRDR_OPTION_GOOGLE_JSON)) { kq[0] = '\0'; sq[0] = '\''; } - else { - kq[0] = '"'; - sq[0] = '"'; + + buffer_json_initialize(wb, kq, sq, 0, true, options & RRDR_OPTION_MINIFY); + buffer_json_member_add_uint64(wb, "api", 2); + + if(options & RRDR_OPTION_DEBUG) { + buffer_json_member_add_string(wb, "id", qt->id); + buffer_json_member_add_object(wb, "request"); + { + buffer_json_member_add_string(wb, "format", rrdr_format_to_string(qt->request.format)); + web_client_api_request_v1_data_options_to_buffer_json_array(wb, "options", qt->request.options); + + buffer_json_member_add_object(wb, "scope"); + buffer_json_member_add_string(wb, "scope_nodes", qt->request.scope_nodes); + buffer_json_member_add_string(wb, "scope_contexts", qt->request.scope_contexts); + buffer_json_object_close(wb); // scope + + buffer_json_member_add_object(wb, "selectors"); + if (qt->request.host) + buffer_json_member_add_string(wb, "nodes", rrdhost_hostname(qt->request.host)); + else + buffer_json_member_add_string(wb, "nodes", qt->request.nodes); + buffer_json_member_add_string(wb, "contexts", qt->request.contexts); + buffer_json_member_add_string(wb, "instances", qt->request.instances); + buffer_json_member_add_string(wb, "dimensions", qt->request.dimensions); + buffer_json_member_add_string(wb, "labels", qt->request.labels); + buffer_json_member_add_string(wb, "alerts", qt->request.alerts); + buffer_json_object_close(wb); // selectors + + buffer_json_member_add_object(wb, "window"); + buffer_json_member_add_time_t(wb, "after", qt->request.after); + buffer_json_member_add_time_t(wb, "before", qt->request.before); + buffer_json_member_add_uint64(wb, "points", qt->request.points); + if (qt->request.options & RRDR_OPTION_SELECTED_TIER) + buffer_json_member_add_uint64(wb, "tier", qt->request.tier); + else + buffer_json_member_add_string(wb, "tier", NULL); + buffer_json_object_close(wb); // window + + buffer_json_member_add_object(wb, "aggregations"); + { + buffer_json_member_add_object(wb, "time"); + buffer_json_member_add_string(wb, "time_group", time_grouping_tostring(qt->request.time_group_method)); + buffer_json_member_add_string(wb, "time_group_options", qt->request.time_group_options); + if (qt->request.resampling_time > 0) + buffer_json_member_add_time_t(wb, "time_resampling", qt->request.resampling_time); + else + buffer_json_member_add_string(wb, "time_resampling", NULL); + buffer_json_object_close(wb); // time + + buffer_json_member_add_array(wb, "metrics"); + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) { + if(qt->request.group_by[g].group_by == RRDR_GROUP_BY_NONE) + break; + + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_array(wb, "group_by"); + buffer_json_group_by_to_array(wb, qt->request.group_by[g].group_by); + buffer_json_array_close(wb); + + buffer_json_member_add_array(wb, "group_by_label"); + for (size_t l = 0; l < qt->group_by[g].used; l++) + buffer_json_add_array_item_string(wb, qt->group_by[g].label_keys[l]); + buffer_json_array_close(wb); + + buffer_json_member_add_string( + wb, "aggregation",group_by_aggregate_function_to_string(qt->request.group_by[g].aggregation)); + } + buffer_json_object_close(wb); + } + buffer_json_array_close(wb); // group_by + } + buffer_json_object_close(wb); // aggregations + + buffer_json_member_add_uint64(wb, "timeout", qt->request.timeout_ms); + } + buffer_json_object_close(wb); // request + } + + version_hashes_api_v2(wb, &qt->versions); + + buffer_json_member_add_object(wb, "summary"); + struct summary_total_counts + nodes_totals = { 0 }, + contexts_totals = { 0 }, + instances_totals = { 0 }, + metrics_totals = { 0 }, + label_key_totals = { 0 }, + label_key_value_totals = { 0 }; + { + query_target_summary_nodes_v2(wb, qt, "nodes", &nodes_totals); + r->internal.contexts = query_target_summary_contexts_v2(wb, qt, "contexts", &contexts_totals); + query_target_summary_instances_v2(wb, qt, "instances", &instances_totals); + query_target_summary_dimensions_v12(wb, qt, "dimensions", true, &metrics_totals); + query_target_summary_labels_v12(wb, qt, "labels", true, &label_key_totals, &label_key_value_totals); + query_target_summary_alerts_v2(wb, qt, "alerts"); + } + if(query_target_aggregatable(qt)) { + buffer_json_member_add_object(wb, "globals"); + query_target_points_statistics(wb, qt, &qt->query_points); + buffer_json_object_close(wb); // globals + } + buffer_json_object_close(wb); // summary + + buffer_json_member_add_object(wb, "totals"); + query_target_total_counts(wb, "nodes", &nodes_totals); + query_target_total_counts(wb, "contexts", &contexts_totals); + query_target_total_counts(wb, "instances", &instances_totals); + query_target_total_counts(wb, "dimensions", &metrics_totals); + query_target_total_counts(wb, "label_keys", &label_key_totals); + query_target_total_counts(wb, "label_key_values", &label_key_value_totals); + buffer_json_object_close(wb); // totals + + if(options & RRDR_OPTION_SHOW_DETAILS) { + buffer_json_member_add_object(wb, "detailed"); + query_target_detailed_objects_tree(wb, r, options); + buffer_json_object_close(wb); // detailed + } + + query_target_functions(wb, "functions", r); +} + +//static void annotations_range_for_value_flags(RRDR *r, BUFFER *wb, DATASOURCE_FORMAT format __maybe_unused, RRDR_OPTIONS options, RRDR_VALUE_FLAGS flags, const char *type) { +// const size_t dims = r->d, rows = r->rows; +// size_t next_d_idx = 0; +// for(size_t d = 0; d < dims ; d++) { +// if(!rrdr_dimension_should_be_exposed(r->od[d], options)) +// continue; +// +// size_t d_idx = next_d_idx++; +// +// size_t t = 0; +// while(t < rows) { +// +// // find the beginning +// time_t started = 0; +// for(; t < rows ;t++) { +// RRDR_VALUE_FLAGS o = r->o[t * r->d + d]; +// if(o & flags) { +// started = r->t[t]; +// break; +// } +// } +// +// if(started) { +// time_t ended = 0; +// for(; t < rows ;t++) { +// RRDR_VALUE_FLAGS o = r->o[t * r->d + d]; +// if(!(o & flags)) { +// ended = r->t[t]; +// break; +// } +// } +// +// if(!ended) +// ended = r->t[rows - 1]; +// +// buffer_json_add_array_item_object(wb); +// buffer_json_member_add_string(wb, "t", type); +// // buffer_json_member_add_string(wb, "d", string2str(r->dn[d])); +// buffer_json_member_add_uint64(wb, "d", d_idx); +// if(started == ended) { +// if(options & RRDR_OPTION_MILLISECONDS) +// buffer_json_member_add_time_t2ms(wb, "x", started); +// else +// buffer_json_member_add_time_t(wb, "x", started); +// } +// else { +// buffer_json_member_add_array(wb, "x"); +// if(options & RRDR_OPTION_MILLISECONDS) { +// buffer_json_add_array_item_time_t2ms(wb, started); +// buffer_json_add_array_item_time_t2ms(wb, ended); +// } +// else { +// buffer_json_add_array_item_time_t(wb, started); +// buffer_json_add_array_item_time_t(wb, ended); +// } +// buffer_json_array_close(wb); +// } +// buffer_json_object_close(wb); +// } +// } +// } +//} +// +//void rrdr_json_wrapper_annotations(RRDR *r, BUFFER *wb, DATASOURCE_FORMAT format __maybe_unused, RRDR_OPTIONS options) { +// buffer_json_member_add_array(wb, "annotations"); +// +// annotations_range_for_value_flags(r, wb, format, options, RRDR_VALUE_EMPTY, "G"); // Gap +// annotations_range_for_value_flags(r, wb, format, options, RRDR_VALUE_RESET, "O"); // Overflow +// annotations_range_for_value_flags(r, wb, format, options, RRDR_VALUE_PARTIAL, "P"); // Partial +// +// buffer_json_array_close(wb); // annotations +//} + +void rrdr_json_wrapper_end(RRDR *r, BUFFER *wb) { + buffer_json_member_add_double(wb, "min", r->view.min); + buffer_json_member_add_double(wb, "max", r->view.max); + + buffer_json_query_timings(wb, "timings", &r->internal.qt->timings); + buffer_json_finalize(wb); +} + +void rrdr_json_wrapper_end2(RRDR *r, BUFFER *wb) { + QUERY_TARGET *qt = r->internal.qt; + DATASOURCE_FORMAT format = qt->request.format; + RRDR_OPTIONS options = qt->window.options; + + buffer_json_member_add_object(wb, "db"); + { + buffer_json_member_add_uint64(wb, "tiers", storage_tiers); + buffer_json_member_add_time_t(wb, "update_every", qt->db.minimum_latest_update_every_s); + buffer_json_member_add_time_t(wb, "first_entry", qt->db.first_time_s); + buffer_json_member_add_time_t(wb, "last_entry", qt->db.last_time_s); + + query_target_combined_units_v2(wb, qt, r->internal.contexts, true); + buffer_json_member_add_object(wb, "dimensions"); + { + rrdr_dimension_ids(wb, "ids", r, options); + rrdr_dimension_units_array_v2(wb, "units", r, options, true); + rrdr_dimension_query_points_statistics(wb, "sts", r, options, false); + } + buffer_json_object_close(wb); // dimensions + + buffer_json_member_add_array(wb, "per_tier"); + for(size_t tier = 0; tier < storage_tiers ; tier++) { + buffer_json_add_array_item_object(wb); + buffer_json_member_add_uint64(wb, "tier", tier); + buffer_json_member_add_uint64(wb, "queries", qt->db.tiers[tier].queries); + buffer_json_member_add_uint64(wb, "points", qt->db.tiers[tier].points); + buffer_json_member_add_time_t(wb, "update_every", qt->db.tiers[tier].update_every); + buffer_json_member_add_time_t(wb, "first_entry", qt->db.tiers[tier].retention.first_time_s); + buffer_json_member_add_time_t(wb, "last_entry", qt->db.tiers[tier].retention.last_time_s); + buffer_json_object_close(wb); + } + buffer_json_array_close(wb); } + buffer_json_object_close(wb); - if(string_value) buffer_strcat(wb, sq); + buffer_json_member_add_object(wb, "view"); + { + query_target_title(wb, qt, r->internal.contexts); + buffer_json_member_add_time_t(wb, "update_every", r->view.update_every); + buffer_json_member_add_time_t(wb, "after", r->view.after); + buffer_json_member_add_time_t(wb, "before", r->view.before); + + if(options & RRDR_OPTION_DEBUG) { + buffer_json_member_add_string(wb, "format", rrdr_format_to_string(format)); + web_client_api_request_v1_data_options_to_buffer_json_array(wb, "options", options); + buffer_json_member_add_string(wb, "time_group", time_grouping_tostring(qt->request.time_group_method)); + } + + if(options & RRDR_OPTION_DEBUG) { + buffer_json_member_add_object(wb, "partial_data_trimming"); + buffer_json_member_add_time_t(wb, "max_update_every", r->partial_data_trimming.max_update_every); + buffer_json_member_add_time_t(wb, "expected_after", r->partial_data_trimming.expected_after); + buffer_json_member_add_time_t(wb, "trimmed_after", r->partial_data_trimming.trimmed_after); + buffer_json_object_close(wb); + } + + if(options & RRDR_OPTION_RETURN_RAW) + buffer_json_member_add_uint64(wb, "points", rrdr_rows(r)); + + query_target_combined_units_v2(wb, qt, r->internal.contexts, false); + query_target_combined_chart_type(wb, qt, r->internal.contexts); + buffer_json_member_add_object(wb, "dimensions"); + { + rrdr_grouped_by_array_v2(wb, "grouped_by", r, options); + rrdr_dimension_ids(wb, "ids", r, options); + rrdr_dimension_names(wb, "names", r, options); + rrdr_dimension_units_array_v2(wb, "units", r, options, false); + rrdr_dimension_priority_array_v2(wb, "priorities", r, options); + rrdr_dimension_aggregated_array_v2(wb, "aggregated", r, options); + rrdr_dimension_query_points_statistics(wb, "sts", r, options, true); + rrdr_json_group_by_labels(wb, "labels", r, options); + } + buffer_json_object_close(wb); // dimensions + buffer_json_member_add_double(wb, "min", r->view.min); + buffer_json_member_add_double(wb, "max", r->view.max); + } + buffer_json_object_close(wb); // view - buffer_sprintf(wb, ",\n %smin%s: ", kq, kq); - buffer_rrd_value(wb, r->min); - buffer_sprintf(wb, ",\n %smax%s: ", kq, kq); - buffer_rrd_value(wb, r->max); - buffer_strcat(wb, "\n}\n"); + buffer_json_agents_array_v2(wb, &r->internal.qt->timings, 0); + buffer_json_cloud_timings(wb, "timings", &r->internal.qt->timings); + buffer_json_finalize(wb); } diff --git a/web/api/formatters/json_wrapper.h b/web/api/formatters/json_wrapper.h index 91c1475c..a702f3a5 100644 --- a/web/api/formatters/json_wrapper.h +++ b/web/api/formatters/json_wrapper.h @@ -6,10 +6,16 @@ #include "rrd2json.h" #include "web/api/queries/query.h" +typedef void (*wrapper_begin_t)(RRDR *r, BUFFER *wb); +typedef void (*wrapper_end_t)(RRDR *r, BUFFER *wb); -void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb, uint32_t format, RRDR_OPTIONS options, int string_value, - RRDR_GROUPING group_method); -void rrdr_json_wrapper_anomaly_rates(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value); -void rrdr_json_wrapper_end(RRDR *r, BUFFER *wb, uint32_t format, uint32_t options, int string_value); +void rrdr_json_wrapper_begin(RRDR *r, BUFFER *wb); +void rrdr_json_wrapper_end(RRDR *r, BUFFER *wb); + +void rrdr_json_wrapper_begin2(RRDR *r, BUFFER *wb); +void rrdr_json_wrapper_end2(RRDR *r, BUFFER *wb); + +struct query_versions; +void version_hashes_api_v2(BUFFER *wb, struct query_versions *versions); #endif //NETDATA_API_FORMATTER_JSON_WRAPPER_H diff --git a/web/api/formatters/rrd2json.c b/web/api/formatters/rrd2json.c index 64cde5b2..139fa6ec 100644 --- a/web/api/formatters/rrd2json.c +++ b/web/api/formatters/rrd2json.c @@ -7,63 +7,55 @@ void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb) { rrdset2json(st, wb, NULL, NULL, 0); } -void rrdr_buffer_print_format(BUFFER *wb, uint32_t format) { +const char *rrdr_format_to_string(DATASOURCE_FORMAT format) { switch(format) { case DATASOURCE_JSON: - buffer_strcat(wb, DATASOURCE_FORMAT_JSON); - break; + return DATASOURCE_FORMAT_JSON; + + case DATASOURCE_JSON2: + return DATASOURCE_FORMAT_JSON2; case DATASOURCE_DATATABLE_JSON: - buffer_strcat(wb, DATASOURCE_FORMAT_DATATABLE_JSON); - break; + return DATASOURCE_FORMAT_DATATABLE_JSON; case DATASOURCE_DATATABLE_JSONP: - buffer_strcat(wb, DATASOURCE_FORMAT_DATATABLE_JSONP); - break; + return DATASOURCE_FORMAT_DATATABLE_JSONP; case DATASOURCE_JSONP: - buffer_strcat(wb, DATASOURCE_FORMAT_JSONP); - break; + return DATASOURCE_FORMAT_JSONP; case DATASOURCE_SSV: - buffer_strcat(wb, DATASOURCE_FORMAT_SSV); - break; + return DATASOURCE_FORMAT_SSV; case DATASOURCE_CSV: - buffer_strcat(wb, DATASOURCE_FORMAT_CSV); - break; + return DATASOURCE_FORMAT_CSV; case DATASOURCE_TSV: - buffer_strcat(wb, DATASOURCE_FORMAT_TSV); - break; + return DATASOURCE_FORMAT_TSV; case DATASOURCE_HTML: - buffer_strcat(wb, DATASOURCE_FORMAT_HTML); - break; + return DATASOURCE_FORMAT_HTML; case DATASOURCE_JS_ARRAY: - buffer_strcat(wb, DATASOURCE_FORMAT_JS_ARRAY); - break; + return DATASOURCE_FORMAT_JS_ARRAY; case DATASOURCE_SSV_COMMA: - buffer_strcat(wb, DATASOURCE_FORMAT_SSV_COMMA); - break; + return DATASOURCE_FORMAT_SSV_COMMA; default: - buffer_strcat(wb, "unknown"); - break; + return "unknown"; } } int rrdset2value_api_v1( - RRDSET *st + RRDSET *st , BUFFER *wb , NETDATA_DOUBLE *n , const char *dimensions , size_t points , time_t after , time_t before - , RRDR_GROUPING group_method + , RRDR_TIME_GROUPING group_method , const char *group_options , time_t resampling_time , uint32_t options @@ -105,15 +97,15 @@ int rrdset2value_api_v1( } if(db_points_read) - *db_points_read += r->internal.db_points_read; + *db_points_read += r->stats.db_points_read; if(db_points_per_tier) { for(size_t t = 0; t < storage_tiers ;t++) - db_points_per_tier[t] += r->internal.tier_points_read[t]; + db_points_per_tier[t] += r->internal.qt->db.tiers[t].points; } if(result_points_generated) - *result_points_generated += r->internal.result_points_generated; + *result_points_generated += r->stats.result_points_generated; if(rrdr_rows(r) == 0) { if(db_after) *db_after = 0; @@ -125,14 +117,14 @@ int rrdset2value_api_v1( } if(wb) { - if (r->result_options & RRDR_RESULT_OPTION_RELATIVE) + if (r->view.flags & RRDR_RESULT_FLAG_RELATIVE) buffer_no_cacheable(wb); - else if (r->result_options & RRDR_RESULT_OPTION_ABSOLUTE) + else if (r->view.flags & RRDR_RESULT_FLAG_ABSOLUTE) buffer_cacheable(wb); } - if(db_after) *db_after = r->after; - if(db_before) *db_before = r->before; + if(db_after) *db_after = r->view.after; + if(db_before) *db_before = r->view.before; long i = (!(options & RRDR_OPTION_REVERSED))?(long)rrdr_rows(r) - 1:0; *n = rrdr2value(r, i, options, value_is_null, anomaly_rate); @@ -144,108 +136,141 @@ cleanup: return ret; } +static inline void buffer_json_member_add_key_only(BUFFER *wb, const char *key) { + buffer_print_json_comma_newline_spacing(wb); + buffer_print_json_key(wb, key); + buffer_fast_strcat(wb, ":", 1); + wb->json.stack[wb->json.depth].count++; +} + +static inline void buffer_json_member_add_string_open(BUFFER *wb, const char *key) { + buffer_json_member_add_key_only(wb, key); + buffer_strcat(wb, wb->json.value_quote); +} + +static inline void buffer_json_member_add_string_close(BUFFER *wb) { + buffer_strcat(wb, wb->json.value_quote); +} + int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *latest_timestamp) { + wrapper_begin_t wrapper_begin = rrdr_json_wrapper_begin; + wrapper_end_t wrapper_end = rrdr_json_wrapper_end; + + if(qt->request.version == 2) { + wrapper_begin = rrdr_json_wrapper_begin2; + wrapper_end = rrdr_json_wrapper_end2; + } RRDR *r = rrd2rrdr(owa, qt); + if(!r) { buffer_strcat(wb, "Cannot generate output with these parameters on this chart."); return HTTP_RESP_INTERNAL_SERVER_ERROR; } - if (r->result_options & RRDR_RESULT_OPTION_CANCEL) { + if (r->view.flags & RRDR_RESULT_FLAG_CANCEL) { rrdr_free(owa, r); return HTTP_RESP_BACKEND_FETCH_FAILED; } - if(r->result_options & RRDR_RESULT_OPTION_RELATIVE) + if(r->view.flags & RRDR_RESULT_FLAG_RELATIVE) buffer_no_cacheable(wb); - else if(r->result_options & RRDR_RESULT_OPTION_ABSOLUTE) + else if(r->view.flags & RRDR_RESULT_FLAG_ABSOLUTE) buffer_cacheable(wb); if(latest_timestamp && rrdr_rows(r) > 0) - *latest_timestamp = r->before; + *latest_timestamp = r->view.before; DATASOURCE_FORMAT format = qt->request.format; - RRDR_OPTIONS options = qt->request.options; - RRDR_GROUPING group_method = qt->request.group_method; + RRDR_OPTIONS options = qt->window.options; switch(format) { case DATASOURCE_SSV: if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, group_method); + wb->content_type = CT_APPLICATION_JSON; + wrapper_begin(r, wb); + buffer_json_member_add_string_open(wb, "result"); rrdr2ssv(r, wb, options, "", " ", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); + buffer_json_member_add_string_close(wb); + wrapper_end(r, wb); } else { - wb->contenttype = CT_TEXT_PLAIN; + wb->content_type = CT_TEXT_PLAIN; rrdr2ssv(r, wb, options, "", " ", ""); } break; case DATASOURCE_SSV_COMMA: if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, group_method); + wb->content_type = CT_APPLICATION_JSON; + wrapper_begin(r, wb); + buffer_json_member_add_string_open(wb, "result"); rrdr2ssv(r, wb, options, "", ",", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); + buffer_json_member_add_string_close(wb); + wrapper_end(r, wb); } else { - wb->contenttype = CT_TEXT_PLAIN; + wb->content_type = CT_TEXT_PLAIN; rrdr2ssv(r, wb, options, "", ",", ""); } break; case DATASOURCE_JS_ARRAY: if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 0, group_method); - rrdr2ssv(r, wb, options, "[", ",", "]"); - rrdr_json_wrapper_end(r, wb, format, options, 0); + wb->content_type = CT_APPLICATION_JSON; + wrapper_begin(r, wb); + buffer_json_member_add_array(wb, "result"); + rrdr2ssv(r, wb, options, "", ",", ""); + buffer_json_array_close(wb); + wrapper_end(r, wb); } else { - wb->contenttype = CT_APPLICATION_JSON; + wb->content_type = CT_APPLICATION_JSON; rrdr2ssv(r, wb, options, "[", ",", "]"); } break; case DATASOURCE_CSV: if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, group_method); + wb->content_type = CT_APPLICATION_JSON; + wrapper_begin(r, wb); + buffer_json_member_add_string_open(wb, "result"); rrdr2csv(r, wb, format, options, "", ",", "\\n", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); + buffer_json_member_add_string_close(wb); + wrapper_end(r, wb); } else { - wb->contenttype = CT_TEXT_PLAIN; + wb->content_type = CT_TEXT_PLAIN; rrdr2csv(r, wb, format, options, "", ",", "\r\n", ""); } break; case DATASOURCE_CSV_MARKDOWN: if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, group_method); + wb->content_type = CT_APPLICATION_JSON; + wrapper_begin(r, wb); + buffer_json_member_add_string_open(wb, "result"); rrdr2csv(r, wb, format, options, "", "|", "\\n", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); + buffer_json_member_add_string_close(wb); + wrapper_end(r, wb); } else { - wb->contenttype = CT_TEXT_PLAIN; + wb->content_type = CT_TEXT_PLAIN; rrdr2csv(r, wb, format, options, "", "|", "\r\n", ""); } break; case DATASOURCE_CSV_JSON_ARRAY: - wb->contenttype = CT_APPLICATION_JSON; + wb->content_type = CT_APPLICATION_JSON; if(options & RRDR_OPTION_JSON_WRAP) { - rrdr_json_wrapper_begin(r, wb, format, options, 0, group_method); - buffer_strcat(wb, "[\n"); + wrapper_begin(r, wb); + buffer_json_member_add_array(wb, "result"); rrdr2csv(r, wb, format, options + RRDR_OPTION_LABEL_QUOTES, "[", ",", "]", ",\n"); - buffer_strcat(wb, "\n]"); - rrdr_json_wrapper_end(r, wb, format, options, 0); + buffer_json_array_close(wb); + wrapper_end(r, wb); } else { - wb->contenttype = CT_APPLICATION_JSON; + wb->content_type = CT_APPLICATION_JSON; buffer_strcat(wb, "[\n"); rrdr2csv(r, wb, format, options + RRDR_OPTION_LABEL_QUOTES, "[", ",", "]", ",\n"); buffer_strcat(wb, "\n]"); @@ -254,28 +279,32 @@ int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *l case DATASOURCE_TSV: if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, group_method); + wb->content_type = CT_APPLICATION_JSON; + wrapper_begin(r, wb); + buffer_json_member_add_string_open(wb, "result"); rrdr2csv(r, wb, format, options, "", "\t", "\\n", ""); - rrdr_json_wrapper_end(r, wb, format, options, 1); + buffer_json_member_add_string_close(wb); + wrapper_end(r, wb); } else { - wb->contenttype = CT_TEXT_PLAIN; + wb->content_type = CT_TEXT_PLAIN; rrdr2csv(r, wb, format, options, "", "\t", "\r\n", ""); } break; case DATASOURCE_HTML: if(options & RRDR_OPTION_JSON_WRAP) { - wb->contenttype = CT_APPLICATION_JSON; - rrdr_json_wrapper_begin(r, wb, format, options, 1, group_method); + wb->content_type = CT_APPLICATION_JSON; + wrapper_begin(r, wb); + buffer_json_member_add_string_open(wb, "result"); buffer_strcat(wb, "\\n
\\n\\n"); rrdr2csv(r, wb, format, options, "\\n", ""); buffer_strcat(wb, "
", "", "
\\n
\\n\\n"); - rrdr_json_wrapper_end(r, wb, format, options, 1); + buffer_json_member_add_string_close(wb); + wrapper_end(r, wb); } else { - wb->contenttype = CT_TEXT_HTML; + wb->content_type = CT_TEXT_HTML; buffer_strcat(wb, "\n
\n\n"); rrdr2csv(r, wb, format, options, "\n", ""); buffer_strcat(wb, "
", "", "
\n
\n\n"); @@ -283,57 +312,75 @@ int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, QUERY_TARGET *qt, time_t *l break; case DATASOURCE_DATATABLE_JSONP: - wb->contenttype = CT_APPLICATION_X_JAVASCRIPT; + wb->content_type = CT_APPLICATION_X_JAVASCRIPT; - if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0, group_method); + if(options & RRDR_OPTION_JSON_WRAP) { + wrapper_begin(r, wb); + buffer_json_member_add_key_only(wb, "result"); + } rrdr2json(r, wb, options, 1); if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_end(r, wb, format, options, 0); + wrapper_end(r, wb); + break; case DATASOURCE_DATATABLE_JSON: - wb->contenttype = CT_APPLICATION_JSON; + wb->content_type = CT_APPLICATION_JSON; - if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0, group_method); + if(options & RRDR_OPTION_JSON_WRAP) { + wrapper_begin(r, wb); + buffer_json_member_add_key_only(wb, "result"); + } rrdr2json(r, wb, options, 1); if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_end(r, wb, format, options, 0); + wrapper_end(r, wb); + break; case DATASOURCE_JSONP: - wb->contenttype = CT_APPLICATION_X_JAVASCRIPT; - if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0, group_method); + wb->content_type = CT_APPLICATION_X_JAVASCRIPT; + if(options & RRDR_OPTION_JSON_WRAP) { + wrapper_begin(r, wb); + buffer_json_member_add_key_only(wb, "result"); + } rrdr2json(r, wb, options, 0); if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_end(r, wb, format, options, 0); + wrapper_end(r, wb); + break; case DATASOURCE_JSON: default: - wb->contenttype = CT_APPLICATION_JSON; + wb->content_type = CT_APPLICATION_JSON; - if(options & RRDR_OPTION_JSON_WRAP) - rrdr_json_wrapper_begin(r, wb, format, options, 0, group_method); + if(options & RRDR_OPTION_JSON_WRAP) { + wrapper_begin(r, wb); + buffer_json_member_add_key_only(wb, "result"); + } rrdr2json(r, wb, options, 0); if(options & RRDR_OPTION_JSON_WRAP) { - if(options & RRDR_OPTION_RETURN_JWAR) { - rrdr_json_wrapper_anomaly_rates(r, wb, format, options, 0); - rrdr2json(r, wb, options | RRDR_OPTION_INTERNAL_AR, 0); + if (options & RRDR_OPTION_RETURN_JWAR) { + buffer_json_member_add_key_only(wb, "anomaly_rates"); + rrdr2json(r, wb, options | RRDR_OPTION_INTERNAL_AR, false); } - rrdr_json_wrapper_end(r, wb, format, options, 0); + wrapper_end(r, wb); } break; + + case DATASOURCE_JSON2: + wb->content_type = CT_APPLICATION_JSON; + wrapper_begin(r, wb); + rrdr2json_v2(r, wb); + wrapper_end(r, wb); + break; } rrdr_free(owa, r); diff --git a/web/api/formatters/rrd2json.h b/web/api/formatters/rrd2json.h index 88b9f773..def26c75 100644 --- a/web/api/formatters/rrd2json.h +++ b/web/api/formatters/rrd2json.h @@ -3,6 +3,23 @@ #ifndef NETDATA_RRD2JSON_H #define NETDATA_RRD2JSON_H 1 +// type of JSON generations +typedef enum { + DATASOURCE_JSON = 0, + DATASOURCE_DATATABLE_JSON = 1, + DATASOURCE_DATATABLE_JSONP = 2, + DATASOURCE_SSV = 3, + DATASOURCE_CSV = 4, + DATASOURCE_JSONP = 5, + DATASOURCE_TSV = 6, + DATASOURCE_HTML = 7, + DATASOURCE_JS_ARRAY = 8, + DATASOURCE_SSV_COMMA = 9, + DATASOURCE_CSV_JSON_ARRAY = 10, + DATASOURCE_CSV_MARKDOWN = 11, + DATASOURCE_JSON2 = 12, +} DATASOURCE_FORMAT; + #include "web/api/web_api_v1.h" #include "web/api/exporters/allmetrics.h" @@ -23,23 +40,8 @@ #define API_RELATIVE_TIME_MAX (3 * 365 * 86400) -// type of JSON generations -typedef enum { - DATASOURCE_JSON = 0, - DATASOURCE_DATATABLE_JSON = 1, - DATASOURCE_DATATABLE_JSONP = 2, - DATASOURCE_SSV = 3, - DATASOURCE_CSV = 4, - DATASOURCE_JSONP = 5, - DATASOURCE_TSV = 6, - DATASOURCE_HTML = 7, - DATASOURCE_JS_ARRAY = 8, - DATASOURCE_SSV_COMMA = 9, - DATASOURCE_CSV_JSON_ARRAY = 10, - DATASOURCE_CSV_MARKDOWN = 11, -} DATASOURCE_FORMAT; - #define DATASOURCE_FORMAT_JSON "json" +#define DATASOURCE_FORMAT_JSON2 "json2" #define DATASOURCE_FORMAT_DATATABLE_JSON "datatable" #define DATASOURCE_FORMAT_DATATABLE_JSONP "datasource" #define DATASOURCE_FORMAT_JSONP "jsonp" @@ -53,19 +55,21 @@ typedef enum { #define DATASOURCE_FORMAT_CSV_MARKDOWN "markdown" void rrd_stats_api_v1_chart(RRDSET *st, BUFFER *wb); -void rrdr_buffer_print_format(BUFFER *wb, uint32_t format); +const char *rrdr_format_to_string(DATASOURCE_FORMAT format); int data_query_execute(ONEWAYALLOC *owa, BUFFER *wb, struct query_target *qt, time_t *latest_timestamp); +void rrdr_json_group_by_labels(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options); + int rrdset2value_api_v1( - RRDSET *st + RRDSET *st , BUFFER *wb , NETDATA_DOUBLE *n , const char *dimensions , size_t points , time_t after , time_t before - , RRDR_GROUPING group_method + , RRDR_TIME_GROUPING group_method , const char *group_options , time_t resampling_time , uint32_t options @@ -82,4 +86,15 @@ int rrdset2value_api_v1( , STORAGE_PRIORITY priority ); +static inline bool rrdr_dimension_should_be_exposed(RRDR_DIMENSION_FLAGS rrdr_dim_flags, RRDR_OPTIONS options) { + if(unlikely(options & RRDR_OPTION_RETURN_RAW)) + return true; + + if(unlikely(rrdr_dim_flags & RRDR_DIMENSION_HIDDEN)) return false; + if(unlikely(!(rrdr_dim_flags & RRDR_DIMENSION_QUERIED))) return false; + if(unlikely((options & RRDR_OPTION_NONZERO) && !(rrdr_dim_flags & RRDR_DIMENSION_NONZERO))) return false; + + return true; +} + #endif /* NETDATA_RRD2JSON_H */ diff --git a/web/api/formatters/rrdset2json.c b/web/api/formatters/rrdset2json.c index 449d4ddf..156f4486 100644 --- a/web/api/formatters/rrdset2json.c +++ b/web/api/formatters/rrdset2json.c @@ -96,9 +96,9 @@ void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memor buffer_strcat(wb, ",\n\t\t\t\t\""); else buffer_strcat(wb, "\t\t\t\t\""); - buffer_strcat_jsonescape(wb, rrddim_id(rd)); + buffer_json_strcat(wb, rrddim_id(rd)); buffer_strcat(wb, "\": { \"name\": \""); - buffer_strcat_jsonescape(wb, rrddim_name(rd)); + buffer_json_strcat(wb, rrddim_name(rd)); buffer_strcat(wb, "\" }"); dimensions++; @@ -112,9 +112,9 @@ void rrdset2json(RRDSET *st, BUFFER *wb, size_t *dimensions_count, size_t *memor health_api_v1_chart_custom_variables2json(st, wb); buffer_strcat(wb, ",\n\t\t\t\"green\": "); - buffer_rrd_value(wb, st->green); + buffer_print_netdata_double(wb, st->green); buffer_strcat(wb, ",\n\t\t\t\"red\": "); - buffer_rrd_value(wb, st->red); + buffer_print_netdata_double(wb, st->red); if (likely(!skip_volatile)) { buffer_strcat(wb, ",\n\t\t\t\"alarms\": {\n"); diff --git a/web/api/formatters/ssv/README.md b/web/api/formatters/ssv/README.md index 4ca2a64c..434d5672 100644 --- a/web/api/formatters/ssv/README.md +++ b/web/api/formatters/ssv/README.md @@ -1,6 +1,10 @@ # SSV formatter diff --git a/web/api/formatters/ssv/ssv.c b/web/api/formatters/ssv/ssv.c index d561980d..65de0464 100644 --- a/web/api/formatters/ssv/ssv.c +++ b/web/api/formatters/ssv/ssv.c @@ -20,12 +20,12 @@ void rrdr2ssv(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, const char *prefix, con NETDATA_DOUBLE v = rrdr2value(r, i, options, &all_values_are_null, NULL); if(likely(i != start)) { - if(r->min > v) r->min = v; - if(r->max < v) r->max = v; + if(r->view.min > v) r->view.min = v; + if(r->view.max < v) r->view.max = v; } else { - r->min = v; - r->max = v; + r->view.min = v; + r->view.max = v; } if(likely(i != start)) @@ -38,7 +38,7 @@ void rrdr2ssv(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, const char *prefix, con buffer_strcat(wb, "null"); } else - buffer_rrd_value(wb, v); + buffer_print_netdata_double(wb, v); } buffer_strcat(wb, suffix); //info("RRD2SSV(): %s: END", r->st->id); diff --git a/web/api/formatters/value/README.md b/web/api/formatters/value/README.md index 5b75ded7..5631d820 100644 --- a/web/api/formatters/value/README.md +++ b/web/api/formatters/value/README.md @@ -1,6 +1,10 @@ # Value formatter diff --git a/web/api/formatters/value/value.c b/web/api/formatters/value/value.c index fd918805..1d07f62f 100644 --- a/web/api/formatters/value/value.c +++ b/web/api/formatters/value/value.c @@ -4,9 +4,7 @@ inline NETDATA_DOUBLE rrdr2value(RRDR *r, long i, RRDR_OPTIONS options, int *all_values_are_null, NETDATA_DOUBLE *anomaly_rate) { - QUERY_TARGET *qt = r->internal.qt; - long c; - const long used = qt->query.used; + size_t c; NETDATA_DOUBLE *cn = &r->v[ i * r->d ]; RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ]; @@ -15,49 +13,15 @@ inline NETDATA_DOUBLE rrdr2value(RRDR *r, long i, RRDR_OPTIONS options, int *all NETDATA_DOUBLE sum = 0, min = 0, max = 0, v; int all_null = 1, init = 1; - NETDATA_DOUBLE total = 1; NETDATA_DOUBLE total_anomaly_rate = 0; - int set_min_max = 0; - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { - total = 0; - for (c = 0; c < used; c++) { - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - NETDATA_DOUBLE n = cn[c]; - - if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; - - total += n; - } - // prevent a division by zero - if(total == 0) total = 1; - set_min_max = 1; - } - // for each dimension - for (c = 0; c < used; c++) { - if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if(unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - if(unlikely((options & RRDR_OPTION_NONZERO) && !(r->od[c] & RRDR_DIMENSION_NONZERO))) continue; + for (c = 0; c < r->d ; c++) { + if(!rrdr_dimension_should_be_exposed(r->od[c], options)) + continue; NETDATA_DOUBLE n = cn[c]; - if(likely((options & RRDR_OPTION_ABSOLUTE) && n < 0)) - n = -n; - - if(unlikely(options & RRDR_OPTION_PERCENTAGE)) { - n = n * 100 / total; - - if(unlikely(set_min_max)) { - r->min = r->max = n; - set_min_max = 0; - } - - if(n < r->min) r->min = n; - if(n > r->max) r->max = n; - } - if(unlikely(init)) { if(n > 0) { min = 0; @@ -107,10 +71,11 @@ inline NETDATA_DOUBLE rrdr2value(RRDR *r, long i, RRDR_OPTIONS options, int *all 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, + RRDR_OPTIONS options, RRDR_TIME_GROUPING time_group_method, const char *time_group_options, size_t tier, time_t timeout, QUERY_SOURCE query_source, STORAGE_PRIORITY priority ) { QUERY_TARGET_REQUEST qtr = { + .version = 1, .host = host, .rca = rca, .ria = ria, @@ -119,16 +84,17 @@ QUERY_VALUE rrdmetric2value(RRDHOST *host, .before = before, .points = 1, .options = options, - .group_method = group_method, - .group_options = group_options, + .time_group_method = time_group_method, + .time_group_options = time_group_options, .tier = tier, - .timeout = timeout, + .timeout_ms = timeout, .query_source = query_source, .priority = priority, }; ONEWAYALLOC *owa = onewayalloc_create(16 * 1024); - RRDR *r = rrd2rrdr(owa, query_target_create(&qtr)); + QUERY_TARGET *qt = query_target_create(&qtr); + RRDR *r = rrd2rrdr(owa, qt); QUERY_VALUE qv; @@ -136,18 +102,37 @@ QUERY_VALUE rrdmetric2value(RRDHOST *host, qv = (QUERY_VALUE) { .value = NAN, .anomaly_rate = NAN, + .sp = { + .count = 0, + .min = NAN, + .max = NAN, + .sum = NAN, + .anomaly_count = 0, + }, + .duration_ut = (r) ? r->internal.qt->timings.executed_ut - r->internal.qt->timings.received_ut : 0, }; } else { qv = (QUERY_VALUE) { - .after = r->after, - .before = r->before, - .points_read = r->internal.db_points_read, - .result_points = r->internal.result_points_generated, + .after = r->view.after, + .before = r->view.before, + .points_read = r->stats.db_points_read, + .result_points = r->stats.result_points_generated, + .sp = { + .count = 0, + }, + .duration_ut = r->internal.qt->timings.executed_ut - r->internal.qt->timings.received_ut, }; + for(size_t d = 0; d < r->internal.qt->query.used ;d++) { + if(!rrdr_dimension_should_be_exposed(r->internal.qt->query.array[d].status, options)) + continue; + + storage_point_merge_to(qv.sp, r->internal.qt->query.array[d].query_points); + } + for(size_t t = 0; t < storage_tiers ;t++) - qv.storage_points_per_tier[t] = r->internal.tier_points_read[t]; + qv.storage_points_per_tier[t] = r->internal.qt->db.tiers[t].points; long i = (!(options & RRDR_OPTION_REVERSED))?(long)rrdr_rows(r) - 1:0; int all_values_are_null = 0; @@ -159,6 +144,7 @@ QUERY_VALUE rrdmetric2value(RRDHOST *host, } rrdr_free(owa, r); + query_target_release(qt); onewayalloc_destroy(owa); return qv; diff --git a/web/api/formatters/value/value.h b/web/api/formatters/value/value.h index 3f7f51cc..072ca14f 100644 --- a/web/api/formatters/value/value.h +++ b/web/api/formatters/value/value.h @@ -13,6 +13,8 @@ typedef struct storage_value { size_t points_read; size_t storage_points_per_tier[RRD_STORAGE_TIERS]; size_t result_points; + STORAGE_POINT sp; + usec_t duration_ut; } QUERY_VALUE; struct rrdmetric_acquired; @@ -22,7 +24,7 @@ struct rrdcontext_acquired; 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, + RRDR_OPTIONS options, RRDR_TIME_GROUPING time_group_method, const char *time_group_options, size_t tier, time_t timeout, QUERY_SOURCE query_source, STORAGE_PRIORITY priority ); diff --git a/web/api/health/README.md b/web/api/health/README.md index bfdd0ac6..dd46854a 100644 --- a/web/api/health/README.md +++ b/web/api/health/README.md @@ -2,6 +2,10 @@ title: "Health API Calls" date: 2020-04-27 custom_edit_url: https://github.com/netdata/netdata/edit/master/web/api/health/README.md +sidebar_label: "Health API Calls" +learn_status: "Published" +learn_topic_type: "References" +learn_rel_path: "Developers/Web/Api" --> # Health API Calls diff --git a/web/api/health/health_cmdapi.c b/web/api/health/health_cmdapi.c index 7a939bc0..7c4869bd 100644 --- a/web/api/health/health_cmdapi.c +++ b/web/api/health/health_cmdapi.c @@ -121,7 +121,7 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c BUFFER *wb = w->response.data; buffer_flush(wb); - wb->contenttype = CT_TEXT_PLAIN; + wb->content_type = CT_TEXT_PLAIN; buffer_flush(w->response.data); @@ -139,10 +139,10 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c ret = HTTP_RESP_FORBIDDEN; } else { while (url) { - char *value = mystrsep(&url, "&"); + char *value = strsep_skip_consecutive_separators(&url, "&"); if (!value || !*value) continue; - char *key = mystrsep(&value, "="); + char *key = strsep_skip_consecutive_separators(&value, "="); if (!key || !*key) continue; if (!value || !*value) continue; @@ -171,7 +171,7 @@ int web_client_api_request_v1_mgmt_health(RRDHOST *host, struct web_client *w, c silencers->silencers = NULL; buffer_strcat(wb, HEALTH_CMDAPI_MSG_RESET); } else if (!strcmp(value, HEALTH_CMDAPI_CMD_LIST)) { - w->response.data->contenttype = CT_APPLICATION_JSON; + w->response.data->content_type = CT_APPLICATION_JSON; health_silencers2json(wb); config_changed=0; } diff --git a/web/api/netdata-swagger.json b/web/api/netdata-swagger.json index cb2b4809..16d8bfaf 100644 --- a/web/api/netdata-swagger.json +++ b/web/api/netdata-swagger.json @@ -3,12 +3,210 @@ "info": { "title": "Netdata API", "description": "Real-time performance and health monitoring.", - "version": "1.33.1" + "version": "1.38", + "contact": { + "name": "Netdata Agent API", + "email": "info@netdata.cloud", + "url": "https://netdata.cloud" + }, + "license": { + "name": "GPL v3+", + "url": "https://github.com/netdata/netdata/blob/master/LICENSE" + } }, + "servers": [ + { + "url": "https://registry.my-netdata.io" + }, + { + "url": "http://registry.my-netdata.io" + }, + { + "url": "http://localhost:19999" + } + ], + "tags": [ + { + "name": "nodes", + "description": "Everything related to monitored nodes" + }, + { + "name": "charts", + "description": "Everything related to chart instances - DO NOT USE IN NEW CODE - use contexts instead" + }, + { + "name": "contexts", + "description": "Everything related contexts - in new code, use this instead of charts" + }, + { + "name": "data", + "description": "Everything related to data queries" + }, + { + "name": "badges", + "description": "Everything related to dynamic badges based on metric data" + }, + { + "name": "weights", + "description": "Everything related to scoring / weighting metrics" + }, + { + "name": "functions", + "description": "Everything related to functions" + }, + { + "name": "alerts", + "description": "Everything related to alerts" + }, + { + "name": "management", + "description": "Everything related to managing netdata agents" + } + ], "paths": { - "/info": { + "/api/v2/nodes": { + "get": { + "operationId": "getNodes2", + "tags": [ + "nodes" + ], + "summary": "Nodes Info v2", + "description": "Get a list of all nodes hosted by this Netdata agent.\n", + "parameters": [ + { + "$ref": "#/components/parameters/scopeNodes" + }, + { + "$ref": "#/components/parameters/scopeContexts" + }, + { + "$ref": "#/components/parameters/filterNodes" + }, + { + "$ref": "#/components/parameters/filterContexts" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "description": "`/api/v2/nodes` response for all nodes hosted by a Netdata agent.\n", + "type": "object", + "properties": { + "api": { + "$ref": "#/components/schemas/api" + }, + "agents": { + "$ref": "#/components/schemas/agents" + }, + "versions": { + "$ref": "#/components/schemas/versions" + }, + "nodes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/nodeFull" + } + } + } + } + } + } + } + } + } + }, + "/api/v2/contexts": { + "get": { + "operationId": "getContexts2", + "tags": [ + "contexts" + ], + "summary": "Contexts Info v2", + "description": "Get a list of all contexts, across all nodes, hosted by this Netdata agent.\n", + "parameters": [ + { + "$ref": "#/components/parameters/scopeNodes" + }, + { + "$ref": "#/components/parameters/scopeContexts" + }, + { + "$ref": "#/components/parameters/filterNodes" + }, + { + "$ref": "#/components/parameters/filterContexts" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contexts2" + } + } + } + } + } + } + }, + "/api/v2/q": { + "get": { + "operationId": "q2", + "tags": [ + "contexts" + ], + "summary": "Full Text Search v2", + "description": "Get a list of contexts, across all nodes, hosted by this Netdata agent, matching a string expression\n", + "parameters": [ + { + "name": "q", + "in": "query", + "description": "The strings to search for, formatted as a simple pattern", + "required": true, + "schema": { + "type": "string", + "format": "simple pattern" + } + }, + { + "$ref": "#/components/parameters/scopeNodes" + }, + { + "$ref": "#/components/parameters/scopeContexts" + }, + { + "$ref": "#/components/parameters/filterNodes" + }, + { + "$ref": "#/components/parameters/filterContexts" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contexts2" + } + } + } + } + } + } + }, + "/api/v1/info": { "get": { - "summary": "Get netdata basic information", + "operationId": "getNodeInfo1", + "tags": [ + "nodes" + ], + "summary": "Node Info v1", "description": "The info endpoint returns basic information about netdata. It provides:\n* netdata version\n* netdata unique id\n* list of hosts mirrored (includes itself)\n* Operating System, Virtualization, K8s nodes and Container technology information\n* List of active collector plugins and modules\n* Streaming information\n* number of alarms in the host\n * number of alarms in normal state\n * number of alarms in warning state\n * number of alarms in critical state\n", "responses": { "200": { @@ -27,9 +225,13 @@ } } }, - "/charts": { + "/api/v1/charts": { "get": { - "summary": "Get a list of all charts available at the server", + "operationId": "getNodeCharts1", + "tags": [ + "charts" + ], + "summary": "List all charts v1 - EOL", "description": "The charts endpoint returns a summary about all charts stored in the netdata server.", "responses": { "200": { @@ -45,21 +247,17 @@ } } }, - "/chart": { + "/api/v1/chart": { "get": { - "summary": "Get info about a specific chart", + "operationId": "getNodeChart1", + "tags": [ + "charts" + ], + "summary": "Get one chart v1 - EOL", "description": "The chart endpoint returns detailed information about a chart.", "parameters": [ { - "name": "chart", - "in": "query", - "description": "The id of the chart as returned by the /charts call.", - "required": true, - "schema": { - "type": "string", - "format": "as returned by /charts", - "default": "system.cpu" - } + "$ref": "#/components/parameters/chart" } ], "responses": { @@ -82,88 +280,32 @@ } } }, - "/contexts": { + "/api/v1/contexts": { "get": { - "summary": "Get a list of all contexts available at the server", + "operationId": "getNodeContexts1", + "tags": [ + "contexts" + ], + "summary": "Get a list of all node contexts available v1", "description": "The contexts endpoint returns a summary about all contexts stored in the netdata server.", "parameters": [ { - "name": "options", - "in": "query", - "description": "Options that affect data generation.", - "required": false, - "allowEmptyValue": true, - "schema": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "full", - "all", - "charts", - "dimensions", - "labels", - "uuids", - "queue", - "flags", - "deleted", - "deepscan" - ] - }, - "default": [ - "full" - ] - } + "$ref": "#/components/parameters/dimensions" }, { - "name": "after", - "in": "query", - "description": "limit the results on context having data after this timestamp.", - "required": false, - "schema": { - "type": "number", - "format": "integer" - } + "$ref": "#/components/parameters/chart_label_key" }, { - "name": "before", - "in": "query", - "description": "limit the results on context having data before this timestamp.", - "required": false, - "schema": { - "type": "number", - "format": "integer" - } + "$ref": "#/components/parameters/chart_labels_filter" }, { - "name": "chart_label_key", - "in": "query", - "description": "a simple pattern matching charts label keys (use comma or pipe as separator)", - "required": false, - "allowEmptyValue": true, - "schema": { - "type": "string" - } + "$ref": "#/components/parameters/contextOptions1" }, { - "name": "chart_labels_filter", - "in": "query", - "description": "a simple pattern matching charts label key and values (use colon for equality, comma or pipe as separator)", - "required": false, - "allowEmptyValue": true, - "schema": { - "type": "string" - } + "$ref": "#/components/parameters/after" }, { - "name": "dimensions", - "in": "query", - "description": "a simple pattern matching dimensions (use comma or pipe as separator)", - "required": false, - "allowEmptyValue": true, - "schema": { - "type": "string" - } + "$ref": "#/components/parameters/before" } ], "responses": { @@ -180,99 +322,35 @@ } } }, - "/context": { + "/api/v1/context": { "get": { + "operationId": "getNodeContext1", + "tags": [ + "contexts" + ], "summary": "Get info about a specific context", - "description": "The context endpoint returns detailed information about a given context.", + "description": "The context endpoint returns detailed information about a given context.\nThe `context` parameter is required for this call.\n", "parameters": [ { - "name": "context", - "in": "query", - "description": "The id of the context as returned by the /contexts call.", - "required": true, - "schema": { - "type": "string", - "format": "as returned by /contexts", - "default": "system.cpu" - } + "$ref": "#/components/parameters/context" }, { - "name": "options", - "in": "query", - "description": "Options that affect data generation.", - "required": false, - "allowEmptyValue": true, - "schema": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "full", - "all", - "charts", - "dimensions", - "labels", - "uuids", - "queue", - "flags", - "deleted", - "deepscan" - ] - }, - "default": [ - "full" - ] - } + "$ref": "#/components/parameters/dimensions" }, { - "name": "after", - "in": "query", - "description": "limit the results on context having data after this timestamp.", - "required": false, - "schema": { - "type": "number", - "format": "integer" - } + "$ref": "#/components/parameters/chart_label_key" }, { - "name": "before", - "in": "query", - "description": "limit the results on context having data before this timestamp.", - "required": false, - "schema": { - "type": "number", - "format": "integer" - } + "$ref": "#/components/parameters/chart_labels_filter" }, { - "name": "chart_label_key", - "in": "query", - "description": "a simple pattern matching charts label keys (use comma or pipe as separator)", - "required": false, - "allowEmptyValue": true, - "schema": { - "type": "string" - } + "$ref": "#/components/parameters/contextOptions1" }, { - "name": "chart_labels_filter", - "in": "query", - "description": "a simple pattern matching charts label key and values (use colon for equality, comma or pipe as separator)", - "required": false, - "allowEmptyValue": true, - "schema": { - "type": "string" - } + "$ref": "#/components/parameters/after" }, { - "name": "dimensions", - "in": "query", - "description": "a simple pattern matching dimensions (use comma or pipe as separator)", - "required": false, - "allowEmptyValue": true, - "schema": { - "type": "string" - } + "$ref": "#/components/parameters/before" } ], "responses": { @@ -295,332 +373,273 @@ } } }, - "/alarm_variables": { + "/api/v2/data": { "get": { - "summary": "List variables available to configure alarms for a chart", - "description": "Returns the basic information of a chart and all the variables that can be used in alarm and template health configurations for the particular chart or family.", + "operationId": "dataQuery2", + "tags": [ + "data" + ], + "summary": "Data Query v2", + "description": "Multi-node, multi-context, multi-instance, multi-dimension data queries, with time and metric aggregation.\n", "parameters": [ { - "name": "chart", + "name": "group_by", "in": "query", - "description": "The id of the chart as returned by the /charts call.", - "required": true, + "description": "A comma separated list of the groupings required.\nAll possible values can be combined together, except `selected`. If `selected` is given in the list, all others are ignored.\nThe order they are placed in the list is currently ignored.\n", + "required": false, "schema": { - "type": "string", - "format": "as returned by /charts", - "default": "system.cpu" + "type": "array", + "items": { + "type": "string", + "enum": [ + "dimension", + "instance", + "percentage-of-instance", + "label", + "node", + "context", + "units", + "selected" + ] + }, + "default": [ + "dimension" + ] } - } - ], - "responses": { - "200": { - "description": "A javascript object with information about the chart and the available variables.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/alarm_variables" - } - } + }, + { + "name": "group_by_label", + "in": "query", + "description": "A comma separated list of the label keys to group by their values. The order of the labels in the list is respected.\n", + "required": false, + "schema": { + "type": "string", + "format": "comma separated list of label keys to group by", + "default": "" } }, - "400": { - "description": "Bad request - the body will include a message stating what is wrong." + { + "name": "aggregation", + "in": "query", + "description": "The aggregation function to apply when grouping metrics together.\nWhen option `raw` is given, `average` and `avg` behave like `sum` and the caller is expected to calculate the average.\n", + "required": false, + "schema": { + "type": "string", + "enum": [ + "min", + "max", + "avg", + "average", + "sum" + ], + "default": "average" + } }, - "404": { - "description": "No chart with the given id is found." + { + "$ref": "#/components/parameters/scopeNodes" + }, + { + "$ref": "#/components/parameters/scopeContexts" + }, + { + "$ref": "#/components/parameters/filterNodes" + }, + { + "$ref": "#/components/parameters/filterContexts" + }, + { + "$ref": "#/components/parameters/filterInstances" + }, + { + "$ref": "#/components/parameters/filterLabels" + }, + { + "$ref": "#/components/parameters/filterAlerts" + }, + { + "$ref": "#/components/parameters/filterDimensions" + }, + { + "$ref": "#/components/parameters/after" + }, + { + "$ref": "#/components/parameters/before" + }, + { + "$ref": "#/components/parameters/points" + }, + { + "$ref": "#/components/parameters/tier" + }, + { + "$ref": "#/components/parameters/dataQueryOptions" + }, + { + "$ref": "#/components/parameters/dataTimeGroup2" + }, + { + "$ref": "#/components/parameters/dataTimeGroupOptions2" + }, + { + "$ref": "#/components/parameters/dataTimeResampling2" + }, + { + "$ref": "#/components/parameters/dataFormat2" + }, + { + "$ref": "#/components/parameters/timeoutMS" + }, + { + "$ref": "#/components/parameters/callback" + }, + { + "$ref": "#/components/parameters/filename" + }, + { + "$ref": "#/components/parameters/tqx" + } + ], + "responses": { + "200": { + "description": "The call was successful. The response includes the data in the format requested.\n", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/jsonwrap2" + }, + { + "$ref": "#/components/schemas/data_json_formats2" + } + ] + } + }, + "text/plain": { + "schema": { + "type": "string", + "format": "according to the format requested." + } + }, + "text/html": { + "schema": { + "type": "string", + "format": "html" + } + }, + "application/x-javascript": { + "schema": { + "type": "string", + "format": "javascript" + } + } + } + }, + "400": { + "description": "Bad request - the body will include a message stating what is wrong.\n" }, "500": { - "description": "Internal server error. This usually means the server is out of memory." + "description": "Internal server error. This usually means the server is out of memory.\n" } } } }, - "/data": { + "/api/v1/data": { "get": { - "summary": "Get collected data for a specific chart", - "description": "The data endpoint returns data stored in the round robin database of a chart.", + "operationId": "dataQuery1", + "tags": [ + "data" + ], + "summary": "Data Query v1 - Single node, single chart or context queries. without group-by.", + "description": "Query metric data of a chart or context of a node and return a dataset having time-series data for all dimensions available.\nFor group-by functionality, use `/api/v2/data`.\nAt least a `chart` or a `context` have to be given for the data query to be executed.\n", "parameters": [ { - "name": "chart", - "in": "query", - "description": "The id of the chart as returned by the /charts call. Note chart or context must be specified", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "string", - "format": "as returned by /charts", - "default": "system.cpu" - } + "$ref": "#/components/parameters/chart" }, { - "name": "context", - "in": "query", - "description": "The context of the chart as returned by the /charts call. Note chart or context must be specified", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "string", - "format": "as returned by /charts" - } + "$ref": "#/components/parameters/context" }, { - "name": "dimension", - "in": "query", - "description": "Zero, one or more dimension ids or names, as returned by the /chart call, separated with comma or pipe. Netdata simple patterns are supported.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "as returned by /charts" - } - } + "$ref": "#/components/parameters/dimension" }, { - "name": "after", - "in": "query", - "description": "This parameter can either be an absolute timestamp specifying the starting point of the data to be returned, or a relative number of seconds (negative, relative to parameter: before). Netdata will assume it is a relative number if it is less that 3 years (in seconds). If not specified the default is -600 seconds. Netdata will adapt this parameter to the boundaries of the round robin database unless the allow_past option is specified.", - "required": true, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer", - "default": -600 - } + "$ref": "#/components/parameters/chart_label_key" }, { - "name": "before", - "in": "query", - "description": "This parameter can either be an absolute timestamp specifying the ending point of the data to be returned, or a relative number of seconds (negative), relative to the last collected timestamp. Netdata will assume it is a relative number if it is less than 3 years (in seconds). Netdata will adapt this parameter to the boundaries of the round robin database. The default is zero (i.e. the timestamp of the last value collected).", - "required": false, - "schema": { - "type": "number", - "format": "integer", - "default": 0 - } + "$ref": "#/components/parameters/chart_labels_filter" }, { - "name": "points", - "in": "query", - "description": "The number of points to be returned. If not given, or it is <= 0, or it is bigger than the points stored in the round robin database for this chart for the given duration, all the available collected values for the given duration will be returned.", - "required": true, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer", - "default": 20 - } + "$ref": "#/components/parameters/after" }, { - "name": "chart_label_key", - "in": "query", - "description": "Specify the chart label keys that need to match for context queries as comma separated values. At least one matching key is needed to match the corresponding chart.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "string", - "format": "key1,key2,key3" - } + "$ref": "#/components/parameters/before" }, { - "name": "chart_labels_filter", - "in": "query", - "description": "Specify the chart label keys and values to match for context queries. All keys/values need to match for the chart to be included in the query. The labels are specified as key1:value1,key2:value2", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "string", - "format": "key1:value1,key2:value2,key3:value3" - } + "$ref": "#/components/parameters/points" }, { - "name": "group", - "in": "query", - "description": "The grouping method. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. methods supported \"min\", \"max\", \"average\", \"sum\", \"incremental-sum\". \"max\" is actually calculated on the absolute value collected (so it works for both positive and negative dimensions to return the most extreme value in either direction).", - "required": true, - "allowEmptyValue": false, - "schema": { - "type": "string", - "enum": [ - "min", - "max", - "average", - "median", - "stddev", - "sum", - "incremental-sum", - "ses", - "des", - "cv", - "countif", - "percentile", - "percentile25", - "percentile50", - "percentile75", - "percentile80", - "percentile90", - "percentile95", - "percentile97", - "percentile98", - "percentile99", - "trimmed-mean", - "trimmed-mean1", - "trimmed-mean2", - "trimmed-mean3", - "trimmed-mean5", - "trimmed-mean10", - "trimmed-mean15", - "trimmed-mean20", - "trimmed-mean25", - "trimmed-median", - "trimmed-median1", - "trimmed-median2", - "trimmed-median3", - "trimmed-median5", - "trimmed-median10", - "trimmed-median15", - "trimmed-median20", - "trimmed-median25" - ], - "default": "average" - } + "$ref": "#/components/parameters/tier" }, { - "name": "group_options", - "in": "query", - "description": "When the group function supports additional parameters, this field can be used to pass them to it. Currently only \"countif\" supports this.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "string" - } + "$ref": "#/components/parameters/dataQueryOptions" }, { - "name": "gtime", - "in": "query", - "description": "The grouping number of seconds. This is used in conjunction with group=average to change the units of metrics (ie when the data is per-second, setting gtime=60 will turn them to per-minute).", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer", - "default": 0 - } + "$ref": "#/components/parameters/dataFormat1" }, { - "name": "timeout", - "in": "query", - "description": "Specify a timeout value in milliseconds after which the agent will abort the query and return a 503 error. A value of 0 indicates no timeout.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer", - "default": 0 - } + "$ref": "#/components/parameters/dataTimeGroup1" }, { - "name": "format", - "in": "query", - "description": "The format of the data to be returned.", - "required": true, - "allowEmptyValue": false, - "schema": { - "type": "string", - "enum": [ - "json", - "jsonp", - "csv", - "tsv", - "tsv-excel", - "ssv", - "ssvcomma", - "datatable", - "datasource", - "html", - "markdown", - "array", - "csvjsonarray" - ], - "default": "json" - } + "$ref": "#/components/parameters/dataTimeGroupOptions1" }, { - "name": "options", - "in": "query", - "description": "Options that affect data generation.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "nonzero", - "flip", - "jsonwrap", - "min2max", - "seconds", - "milliseconds", - "abs", - "absolute", - "absolute-sum", - "null2zero", - "objectrows", - "google_json", - "percentage", - "unaligned", - "match-ids", - "match-names", - "allow_past", - "anomaly-bit" - ] - }, - "default": [ - "seconds", - "jsonwrap" - ] - } + "$ref": "#/components/parameters/dataTimeResampling1" }, { - "name": "callback", - "in": "query", - "description": "For JSONP responses, the callback function name.", - "required": false, - "allowEmptyValue": true, - "schema": { - "type": "string" - } + "$ref": "#/components/parameters/timeoutMS" }, { - "name": "filename", - "in": "query", - "description": "Add Content-Disposition: attachment; filename= header to the response, that will instruct the browser to save the response with the given filename.", - "required": false, - "allowEmptyValue": true, - "schema": { - "type": "string" - } + "$ref": "#/components/parameters/callback" }, { - "name": "tqx", - "in": "query", - "description": "[Google Visualization API](https://developers.google.com/chart/interactive/docs/dev/implementing_data_source?hl=en) formatted parameter.", - "required": false, - "allowEmptyValue": true, - "schema": { - "type": "string" - } + "$ref": "#/components/parameters/filename" + }, + { + "$ref": "#/components/parameters/tqx" } ], "responses": { "200": { - "description": "The call was successful. The response includes the data in the format requested. Swagger2.0 does not process the discriminator field to show polymorphism. The response will be one of the sub-types of the data-schema according to the chosen format, e.g. json -> data_json.", + "description": "The call was successful. The response includes the data in the format requested.\n", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/data" + "oneOf": [ + { + "$ref": "#/components/schemas/jsonwrap1" + }, + { + "$ref": "#/components/schemas/data_json_formats1" + } + ] + } + }, + "text/plain": { + "schema": { + "type": "string", + "format": "according to the format requested." + } + }, + "text/html": { + "schema": { + "type": "string", + "format": "html" + } + }, + "application/x-javascript": { + "schema": { + "type": "string", + "format": "javascript" } } } @@ -637,147 +656,221 @@ } } }, - "/badge.svg": { + "/api/v1/allmetrics": { "get": { - "summary": "Generate a badge in form of SVG image for a chart (or dimension)", - "description": "Successful responses are SVG images.", + "operationId": "allMetrics1", + "tags": [ + "data" + ], + "summary": "All Metrics v1 - Fetch latest value for all metrics", + "description": "The `allmetrics` endpoint returns the latest value of all metrics maintained for a netdata node.\n", "parameters": [ { - "name": "chart", + "name": "format", "in": "query", - "description": "The id of the chart as returned by the /charts call.", + "description": "The format of the response to be returned.", "required": true, - "allowEmptyValue": false, "schema": { "type": "string", - "format": "as returned by /charts", - "default": "system.cpu" + "enum": [ + "shell", + "prometheus", + "prometheus_all_hosts", + "json" + ], + "default": "shell" } }, { - "name": "alarm", + "name": "filter", "in": "query", - "description": "The name of an alarm linked to the chart.", + "description": "Allows to filter charts out using simple patterns.", "required": false, - "allowEmptyValue": true, "schema": { "type": "string", "format": "any text" } }, { - "name": "dimension", + "name": "variables", "in": "query", - "description": "Zero, one or more dimension ids, as returned by the /chart call.", + "description": "When enabled, netdata will expose various system configuration variables.\n", "required": false, - "allowEmptyValue": false, "schema": { - "type": "array", - "items": { - "type": "string", - "format": "as returned by /charts" - } - } - }, - { - "name": "after", + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "no" + } + }, + { + "name": "help", "in": "query", - "description": "This parameter can either be an absolute timestamp specifying the starting point of the data to be returned, or a relative number of seconds, to the last collected timestamp. Netdata will assume it is a relative number if it is smaller than the duration of the round robin database for this chart. So, if the round robin database is 3600 seconds, any value from -3600 to 3600 will trigger relative arithmetics. Netdata will adapt this parameter to the boundaries of the round robin database.", - "required": true, - "allowEmptyValue": false, + "description": "Enable or disable HELP lines in prometheus output.\n", + "required": false, "schema": { - "type": "number", - "format": "integer", - "default": -600 + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "no" } }, { - "name": "before", + "name": "types", "in": "query", - "description": "This parameter can either be an absolute timestamp specifying the ending point of the data to be returned, or a relative number of seconds, to the last collected timestamp. Netdata will assume it is a relative number if it is smaller than the duration of the round robin database for this chart. So, if the round robin database is 3600 seconds, any value from -3600 to 3600 will trigger relative arithmetics. Netdata will adapt this parameter to the boundaries of the round robin database.", + "description": "Enable or disable TYPE lines in prometheus output.\n", "required": false, "schema": { - "type": "number", - "format": "integer", - "default": 0 + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "no" } }, { - "name": "group", + "name": "timestamps", "in": "query", - "description": "The grouping method. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. methods are supported \"min\", \"max\", \"average\", \"sum\", \"incremental-sum\". \"max\" is actually calculated on the absolute value collected (so it works for both positive and negative dimensions to return the most extreme value in either direction).", - "required": true, - "allowEmptyValue": false, + "description": "Enable or disable timestamps in prometheus output.\n", + "required": false, "schema": { "type": "string", "enum": [ - "min", - "max", + "yes", + "no" + ], + "default": "yes" + } + }, + { + "name": "names", + "in": "query", + "description": "When enabled netdata will report dimension names. When disabled netdata will report dimension IDs. The default is controlled in netdata.conf.\n", + "required": false, + "schema": { + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "yes" + } + }, + { + "name": "oldunits", + "in": "query", + "description": "When enabled, netdata will show metric names for the default `source=average` as they appeared before 1.12, by using the legacy unit naming conventions.\n", + "required": false, + "schema": { + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "yes" + } + }, + { + "name": "hideunits", + "in": "query", + "description": "When enabled, netdata will not include the units in the metric names, for the default `source=average`.\n", + "required": false, + "schema": { + "type": "string", + "enum": [ + "yes", + "no" + ], + "default": "yes" + } + }, + { + "name": "server", + "in": "query", + "description": "Set a distinct name of the client querying prometheus metrics. Netdata will use the client IP if this is not set.\n", + "required": false, + "schema": { + "type": "string", + "format": "any text" + } + }, + { + "name": "prefix", + "in": "query", + "description": "Prefix all prometheus metrics with this string.\n", + "required": false, + "schema": { + "type": "string", + "format": "any text" + } + }, + { + "name": "data", + "in": "query", + "description": "Select the prometheus response data source. There is a setting in netdata.conf for the default.\n", + "required": false, + "schema": { + "type": "string", + "enum": [ + "as-collected", "average", - "median", - "stddev", - "sum", - "incremental-sum", - "ses", - "des", - "cv", - "countif", - "percentile", - "percentile25", - "percentile50", - "percentile75", - "percentile80", - "percentile90", - "percentile95", - "percentile97", - "percentile98", - "percentile99", - "trimmed-mean", - "trimmed-mean1", - "trimmed-mean2", - "trimmed-mean3", - "trimmed-mean5", - "trimmed-mean10", - "trimmed-mean15", - "trimmed-mean20", - "trimmed-mean25", - "trimmed-median", - "trimmed-median1", - "trimmed-median2", - "trimmed-median3", - "trimmed-median5", - "trimmed-median10", - "trimmed-median15", - "trimmed-median20", - "trimmed-median25" + "sum" ], "default": "average" } + } + ], + "responses": { + "200": { + "description": "All the metrics returned in the format requested." + }, + "400": { + "description": "The format requested is not supported." + } + } + } + }, + "/api/v1/badge.svg": { + "get": { + "operationId": "badge1", + "tags": [ + "badges" + ], + "summary": "Generate a badge in form of SVG image for a chart (or dimension)", + "description": "Successful responses are SVG images.", + "parameters": [ + { + "$ref": "#/components/parameters/chart" + }, + { + "$ref": "#/components/parameters/dimension" + }, + { + "$ref": "#/components/parameters/after" + }, + { + "$ref": "#/components/parameters/before" + }, + { + "$ref": "#/components/parameters/dataTimeGroup1" + }, + { + "$ref": "#/components/parameters/dataQueryOptions" }, { - "name": "options", + "name": "alarm", "in": "query", - "description": "Options that affect data generation.", + "description": "The name of an alarm linked to the chart.", "required": false, "allowEmptyValue": true, "schema": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "abs", - "absolute", - "display-absolute", - "absolute-sum", - "null2zero", - "percentage", - "unaligned", - "anomaly-bit" - ] - }, - "default": [ - "absolute" - ] + "type": "string", + "format": "any text" } }, { @@ -805,7 +898,7 @@ { "name": "label_color", "in": "query", - "description": "A color to be used for the background of the label side(left side) of the badge. One of predefined colors or specific color in hex `RGB` or `RRGGBB` format (without preceding `#` character). If value wrong or not given default color will be used.", + "description": "A color to be used for the background of the label side(left side) of the badge. One of predefined colors or specific color in hex `RGB` or `RRGGBB` format (without preceding `#` character). If value wrong or not given default color will be used.\n", "required": false, "allowEmptyValue": true, "schema": { @@ -836,7 +929,7 @@ { "name": "value_color", "in": "query", - "description": "A color to be used for the background of the value *(right)* part of badge. You can set multiple using a pipe with a condition each, like this: `color, <, >=, <=, =, :null (to check if no value exists). Each color can be specified in same manner as for `label_color` parameter. Currently only integers are supported as values.", + "description": "A color to be used for the background of the value *(right)* part of badge. You can set multiple using a pipe with a condition each, like this: `color, <, >=, <=, =, :null (to check if no value exists). Each color can be specified in same manner as for `label_color` parameter. Currently only integers are supported as values.\n", "required": false, "allowEmptyValue": true, "schema": { @@ -847,7 +940,7 @@ { "name": "text_color_lbl", "in": "query", - "description": "Font color for label *(left)* part of the badge. One of predefined colors or as HTML hexadecimal color without preceding `#` character. Formats allowed `RGB` or `RRGGBB`. If no or wrong value given default color will be used.", + "description": "Font color for label *(left)* part of the badge. One of predefined colors or as HTML hexadecimal color without preceding `#` character. Formats allowed `RGB` or `RRGGBB`. If no or wrong value given default color will be used.\n", "required": false, "allowEmptyValue": true, "schema": { @@ -878,7 +971,7 @@ { "name": "text_color_val", "in": "query", - "description": "Font color for value *(right)* part of the badge. One of predefined colors or as HTML hexadecimal color without preceding `#` character. Formats allowed `RGB` or `RRGGBB`. If no or wrong value given default color will be used.", + "description": "Font color for value *(right)* part of the badge. One of predefined colors or as HTML hexadecimal color without preceding `#` character. Formats allowed `RGB` or `RRGGBB`. If no or wrong value given default color will be used.\n", "required": false, "allowEmptyValue": true, "schema": { @@ -942,7 +1035,7 @@ { "name": "fixed_width_lbl", "in": "query", - "description": "This parameter overrides auto-sizing of badge and creates it with fixed width. This parameter determines the size of the label's left side *(label/name)*. You must set this parameter together with `fixed_width_val` otherwise it will be ignored. You should set the label/value widths wide enough to provide space for all the possible values/contents of the badge you're requesting. In case the text cannot fit the space given it will be clipped. The `scale` parameter still applies on the values you give to `fixed_width_lbl` and `fixed_width_val`.", + "description": "This parameter overrides auto-sizing of badge and creates it with fixed width. This parameter determines the size of the label's left side *(label/name)*. You must set this parameter together with `fixed_width_val` otherwise it will be ignored. You should set the label/value widths wide enough to provide space for all the possible values/contents of the badge you're requesting. In case the text cannot fit the space given it will be clipped. The `scale` parameter still applies on the values you give to `fixed_width_lbl` and `fixed_width_val`.\n", "required": false, "allowEmptyValue": false, "schema": { @@ -953,7 +1046,7 @@ { "name": "fixed_width_val", "in": "query", - "description": "This parameter overrides auto-sizing of badge and creates it with fixed width. This parameter determines the size of the label's right side *(value)*. You must set this parameter together with `fixed_width_lbl` otherwise it will be ignored. You should set the label/value widths wide enough to provide space for all the possible values/contents of the badge you're requesting. In case the text cannot fit the space given it will be clipped. The `scale` parameter still applies on the values you give to `fixed_width_lbl` and `fixed_width_val`.", + "description": "This parameter overrides auto-sizing of badge and creates it with fixed width. This parameter determines the size of the label's right side *(value)*. You must set this parameter together with `fixed_width_lbl` otherwise it will be ignored. You should set the label/value widths wide enough to provide space for all the possible values/contents of the badge you're requesting. In case the text cannot fit the space given it will be clipped. The `scale` parameter still applies on the values you give to `fixed_width_lbl` and `fixed_width_val`.\n", "required": false, "allowEmptyValue": false, "schema": { @@ -978,185 +1071,310 @@ } } }, - "/allmetrics": { + "/api/v2/weights": { "get": { - "summary": "Get a value of all the metrics maintained by netdata", - "description": "The allmetrics endpoint returns the latest value of all charts and dimensions stored in the netdata server.", + "operationId": "weights2", + "tags": [ + "weights" + ], + "summary": "Score or weight all or some of the metrics, across all nodes, according to various algorithms.", + "description": "This endpoint goes through all metrics and scores them according to an algorithm.\n", "parameters": [ { - "name": "format", - "in": "query", - "description": "The format of the response to be returned.", - "required": true, - "schema": { - "type": "string", - "enum": [ - "shell", - "prometheus", - "prometheus_all_hosts", - "json" - ], - "default": "shell" - } + "$ref": "#/components/parameters/weightMethods" }, { - "name": "filter", - "in": "query", - "description": "Allows to filter charts out using simple patterns.", - "required": false, - "schema": { - "type": "string", - "format": "any text" - } + "$ref": "#/components/parameters/scopeNodes" }, { - "name": "variables", - "in": "query", - "description": "When enabled, netdata will expose various system configuration metrics.", - "required": false, - "schema": { - "type": "string", - "enum": [ - "yes", - "no" - ], - "default": "no" - } + "$ref": "#/components/parameters/scopeContexts" }, { - "name": "help", - "in": "query", - "description": "Enable or disable HELP lines in prometheus output.", - "required": false, - "schema": { - "type": "string", - "enum": [ - "yes", - "no" - ], - "default": "no" - } + "$ref": "#/components/parameters/filterNodes" }, { - "name": "types", - "in": "query", - "description": "Enable or disable TYPE lines in prometheus output.", - "required": false, - "schema": { - "type": "string", - "enum": [ - "yes", - "no" - ], - "default": "no" - } + "$ref": "#/components/parameters/filterContexts" }, { - "name": "timestamps", - "in": "query", - "description": "Enable or disable timestamps in prometheus output.", - "required": false, - "schema": { - "type": "string", - "enum": [ - "yes", - "no" - ], - "default": "yes" - } + "$ref": "#/components/parameters/filterInstances" }, { - "name": "names", - "in": "query", - "description": "When enabled netdata will report dimension names. When disabled netdata will report dimension IDs. The default is controlled in netdata.conf.", - "required": false, - "schema": { - "type": "string", - "enum": [ - "yes", - "no" - ], - "default": "yes" - } + "$ref": "#/components/parameters/filterLabels" }, { - "name": "oldunits", - "in": "query", - "description": "When enabled, netdata will show metric names for the default source=average as they appeared before 1.12, by using the legacy unit naming conventions.", - "required": false, - "schema": { - "type": "string", - "enum": [ - "yes", - "no" - ], - "default": "yes" - } + "$ref": "#/components/parameters/filterAlerts" }, { - "name": "hideunits", - "in": "query", - "description": "When enabled, netdata will not include the units in the metric names, for the default source=average.", - "required": false, - "schema": { - "type": "string", - "enum": [ - "yes", - "no" - ], - "default": "yes" - } + "$ref": "#/components/parameters/filterDimensions" }, { - "name": "server", - "in": "query", - "description": "Set a distinct name of the client querying prometheus metrics. Netdata will use the client IP if this is not set.", - "required": false, - "schema": { - "type": "string", - "format": "any text" - } + "$ref": "#/components/parameters/baselineAfter" }, { - "name": "prefix", - "in": "query", - "description": "Prefix all prometheus metrics with this string.", - "required": false, - "schema": { - "type": "string", - "format": "any text" + "$ref": "#/components/parameters/baselineBefore" + }, + { + "$ref": "#/components/parameters/after" + }, + { + "$ref": "#/components/parameters/before" + }, + { + "$ref": "#/components/parameters/tier" + }, + { + "$ref": "#/components/parameters/points" + }, + { + "$ref": "#/components/parameters/timeoutMS" + }, + { + "$ref": "#/components/parameters/dataQueryOptions" + }, + { + "$ref": "#/components/parameters/dataTimeGroup2" + }, + { + "$ref": "#/components/parameters/dataTimeGroupOptions2" + } + ], + "responses": { + "200": { + "description": "JSON object with weights for each context, chart and dimension.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/weights2" + } + } } }, + "400": { + "description": "The given parameters are invalid." + }, + "403": { + "description": "metrics correlations are not enabled on this Netdata Agent." + }, + "404": { + "description": "No charts could be found, or the method that correlated the metrics did not produce any result.\n" + }, + "504": { + "description": "Timeout - the query took too long and has been cancelled." + } + } + } + }, + "/api/v1/weights": { + "get": { + "operationId": "weights1", + "tags": [ + "weights" + ], + "summary": "Score or weight all or some of the metrics of a single node, according to various algorithms.", + "description": "This endpoint goes through all metrics and scores them according to an algorithm.\n", + "parameters": [ { - "name": "data", + "$ref": "#/components/parameters/weightMethods" + }, + { + "$ref": "#/components/parameters/context" + }, + { + "$ref": "#/components/parameters/baselineAfter" + }, + { + "$ref": "#/components/parameters/baselineBefore" + }, + { + "$ref": "#/components/parameters/after" + }, + { + "$ref": "#/components/parameters/before" + }, + { + "$ref": "#/components/parameters/tier" + }, + { + "$ref": "#/components/parameters/points" + }, + { + "$ref": "#/components/parameters/timeoutMS" + }, + { + "$ref": "#/components/parameters/dataQueryOptions" + }, + { + "$ref": "#/components/parameters/dataTimeGroup1" + }, + { + "$ref": "#/components/parameters/dataTimeGroupOptions1" + } + ], + "responses": { + "200": { + "description": "JSON object with weights for each context, chart and dimension.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/weights" + } + } + } + }, + "400": { + "description": "The given parameters are invalid." + }, + "403": { + "description": "metrics correlations are not enabled on this Netdata Agent." + }, + "404": { + "description": "No charts could be found, or the method that correlated the metrics did not produce any result." + }, + "504": { + "description": "Timeout - the query took too long and has been cancelled." + } + } + } + }, + "/api/v1/metric_correlations": { + "get": { + "operationId": "metricCorrelations1", + "tags": [ + "weights" + ], + "summary": "Analyze all the metrics to find their correlations - EOL", + "description": "THIS ENDPOINT IS OBSOLETE. Use the /weights endpoint. Given two time-windows (baseline, highlight), it goes through all the available metrics, querying both windows and tries to find how these two windows relate to each other. It supports multiple algorithms to do so. The result is a list of all metrics evaluated, weighted for 0.0 (the two windows are more different) to 1.0 (the two windows are similar). The algorithm adjusts automatically the baseline window to be a power of two multiple of the highlighted (1, 2, 4, 8, etc).\n", + "parameters": [ + { + "$ref": "#/components/parameters/weightMethods" + }, + { + "$ref": "#/components/parameters/baselineAfter" + }, + { + "$ref": "#/components/parameters/baselineBefore" + }, + { + "$ref": "#/components/parameters/after" + }, + { + "$ref": "#/components/parameters/before" + }, + { + "$ref": "#/components/parameters/points" + }, + { + "$ref": "#/components/parameters/tier" + }, + { + "$ref": "#/components/parameters/timeoutMS" + }, + { + "$ref": "#/components/parameters/dataQueryOptions" + }, + { + "$ref": "#/components/parameters/dataTimeGroup1" + }, + { + "$ref": "#/components/parameters/dataTimeGroupOptions1" + } + ], + "responses": { + "200": { + "description": "JSON object with weights for each chart and dimension.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/metric_correlations" + } + } + } + }, + "400": { + "description": "The given parameters are invalid." + }, + "403": { + "description": "metrics correlations are not enabled on this Netdata Agent." + }, + "404": { + "description": "No charts could be found, or the method that correlated the metrics did not produce any result." + }, + "504": { + "description": "Timeout - the query took too long and has been cancelled." + } + } + } + }, + "/api/v1/function": { + "get": { + "operationId": "function1", + "tags": [ + "functions" + ], + "description": "Execute a collector function.", + "parameters": [ + { + "name": "function", "in": "query", - "description": "Select the prometheus response data source. There is a setting in netdata.conf for the default.", - "required": false, + "description": "The name of the function, as returned by the collector.", + "required": true, + "allowEmptyValue": false, "schema": { - "type": "string", - "enum": [ - "as-collected", - "average", - "sum" - ], - "default": "average" + "type": "string" } + }, + { + "$ref": "#/components/parameters/timeoutSecs" } ], "responses": { "200": { - "description": "All the metrics returned in the format requested." + "description": "The collector function has been executed successfully. Each collector may return a different type of content." }, "400": { - "description": "The format requested is not supported." + "description": "The request was rejected by the collector." + }, + "404": { + "description": "The requested function is not found." + }, + "500": { + "description": "Other internal error, getting this error means there is a bug in Netdata." + }, + "503": { + "description": "The collector to execute the function is not currently available." + }, + "504": { + "description": "Timeout while waiting for the collector to execute the function." + }, + "591": { + "description": "The collector sent a response, but it was invalid or corrupted." + } + } + } + }, + "/api/v1/functions": { + "get": { + "operationId": "functions1", + "tags": [ + "functions" + ], + "summary": "Get a list of all registered collector functions.", + "description": "Collector functions are programs that can be executed on demand.", + "responses": { + "200": { + "description": "A JSON object containing one object per supported function." } } } }, - "/alarms": { + "/api/v1/alarms": { "get": { + "operationId": "alerts1", + "tags": [ + "alerts" + ], "summary": "Get a list of active or raised alarms on the server", - "description": "The alarms endpoint returns the list of all raised or enabled alarms on the netdata server. Called without any parameters, the raised alarms in state WARNING or CRITICAL are returned. By passing \"?all\", all the enabled alarms are returned.", + "description": "The alarms endpoint returns the list of all raised or enabled alarms on the netdata server. Called without any parameters, the raised alarms in state WARNING or CRITICAL are returned. By passing \"?all\", all the enabled alarms are returned.\n", "parameters": [ { "name": "all", @@ -1193,10 +1411,14 @@ } } }, - "/alarms_values": { + "/api/v1/alarms_values": { "get": { + "operationId": "alertValues1", + "tags": [ + "alerts" + ], "summary": "Get a list of active or raised alarms on the server", - "description": "The alarms_values endpoint returns the list of all raised or enabled alarms on the netdata server. Called without any parameters, the raised alarms in state WARNING or CRITICAL are returned. By passing '?all', all the enabled alarms are returned. This option output differs from `/alarms` in the number of variables delivered. This endpoint gives to user `id`, `value`, `last_updated` time, and alarm `status`.", + "description": "The alarms_values endpoint returns the list of all raised or enabled alarms on the netdata server. Called without any parameters, the raised alarms in state WARNING or CRITICAL are returned. By passing '?all', all the enabled alarms are returned. This option output differs from `/alarms` in the number of variables delivered. This endpoint gives to user `id`, `value`, `last_updated` time, and alarm `status`.\n", "parameters": [ { "name": "all", @@ -1233,15 +1455,19 @@ } } }, - "/alarm_log": { + "/api/v1/alarm_log": { "get": { + "operationId": "alertsLog1", + "tags": [ + "alerts" + ], "summary": "Retrieves the entries of the alarm log", - "description": "Returns an array of alarm_log entries, with historical information on raised and cleared alarms.", + "description": "Returns an array of alarm_log entries, with historical information on raised and cleared alarms.\n", "parameters": [ { "name": "after", "in": "query", - "description": "Passing the parameter after=UNIQUEID returns all the events in the alarm log that occurred after UNIQUEID. An automated series of calls would call the interface once without after=, store the last UNIQUEID of the returned set, and give it back to get incrementally the next events.", + "description": "Passing the parameter after=UNIQUEID returns all the events in the alarm log that occurred after UNIQUEID. An automated series of calls would call the interface once without after=, store the last UNIQUEID of the returned set, and give it back to get incrementally the next events.\n", "required": false, "schema": { "type": "integer" @@ -1265,30 +1491,21 @@ } } }, - "/alarm_count": { + "/api/v1/alarm_count": { "get": { + "operationId": "alertsCount1", + "tags": [ + "alerts" + ], "summary": "Get an overall status of the chart", - "description": "Checks multiple charts with the same context and counts number of alarms with given status.", + "description": "Checks multiple charts with the same context and counts number of alarms with given status.\n", "parameters": [ { - "in": "query", - "name": "context", - "description": "Specify context which should be checked.", - "required": false, - "allowEmptyValue": true, - "schema": { - "type": "array", - "items": { - "type": "string" - }, - "default": [ - "system.cpu" - ] - } + "$ref": "#/components/parameters/context" }, { - "in": "query", "name": "status", + "in": "query", "description": "Specify alarm status to count.", "required": false, "allowEmptyValue": true, @@ -1327,40 +1544,88 @@ } } }, - "/manage/health": { + "/api/v1/alarm_variables": { "get": { - "summary": "Accesses the health management API to control health checks and notifications at runtime.", - "description": "Available from Netdata v1.12 and above, protected via bearer authorization. Especially useful for maintenance periods, the API allows you to disable health checks completely, silence alarm notifications, or Disable/Silence specific alarms that match selectors on alarm/template name, chart, context, host and family. For the simple disable/silence all scenarios, only the cmd parameter is required. The other parameters are used to define alarm selectors. For more information and examples, refer to the netdata documentation.", + "operationId": "getNodeAlertVariables1", + "tags": [ + "alerts" + ], + "summary": "List variables available to configure alarms for a chart", + "description": "Returns the basic information of a chart and all the variables that can be used in alarm and template health configurations for the particular chart or family.\n", "parameters": [ { - "name": "cmd", + "name": "chart", "in": "query", - "description": "DISABLE ALL: No alarm criteria are evaluated, nothing is written in the alarm log. SILENCE ALL: No notifications are sent. RESET: Return to the default state. DISABLE/SILENCE: Set the mode to be used for the alarms matching the criteria of the alarm selectors. LIST: Show active configuration.", - "required": false, + "description": "The id of the chart as returned by the /charts call.", + "required": true, "schema": { "type": "string", - "enum": [ - "DISABLE ALL", - "SILENCE ALL", - "DISABLE", - "SILENCE", - "RESET", - "LIST" - ] + "format": "as returned by /charts", + "default": "system.cpu" } - }, - { - "name": "alarm", - "in": "query", - "description": "The expression provided will match both `alarm` and `template` names.", - "schema": { - "type": "string" + } + ], + "responses": { + "200": { + "description": "A javascript object with information about the chart and the available variables.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/alarm_variables" + } + } } }, - { - "name": "chart", - "in": "query", - "description": "Chart ids/names, as shown on the dashboard. These will match the `on` entry of a configured `alarm`.", + "400": { + "description": "Bad request - the body will include a message stating what is wrong." + }, + "404": { + "description": "No chart with the given id is found." + }, + "500": { + "description": "Internal server error. This usually means the server is out of memory." + } + } + } + }, + "/api/v1/manage/health": { + "get": { + "operationId": "health1", + "tags": [ + "management" + ], + "summary": "Accesses the health management API to control health checks and notifications at runtime.\n", + "description": "Available from Netdata v1.12 and above, protected via bearer authorization. Especially useful for maintenance periods, the API allows you to disable health checks completely, silence alarm notifications, or Disable/Silence specific alarms that match selectors on alarm/template name, chart, context, host and family. For the simple disable/silence all scenarios, only the cmd parameter is required. The other parameters are used to define alarm selectors. For more information and examples, refer to the netdata documentation.\n", + "parameters": [ + { + "name": "cmd", + "in": "query", + "description": "DISABLE ALL: No alarm criteria are evaluated, nothing is written in the alarm log. SILENCE ALL: No notifications are sent. RESET: Return to the default state. DISABLE/SILENCE: Set the mode to be used for the alarms matching the criteria of the alarm selectors. LIST: Show active configuration.\n", + "required": false, + "schema": { + "type": "string", + "enum": [ + "DISABLE ALL", + "SILENCE ALL", + "DISABLE", + "SILENCE", + "RESET", + "LIST" + ] + } + }, + { + "name": "alarm", + "in": "query", + "description": "The expression provided will match both `alarm` and `template` names.", + "schema": { + "type": "string" + } + }, + { + "name": "chart", + "in": "query", + "description": "Chart ids/names, as shown on the dashboard. These will match the `on` entry of a configured `alarm`.", "schema": { "type": "string" } @@ -1400,10 +1665,14 @@ } } }, - "/aclk": { + "/api/v1/aclk": { "get": { + "operationId": "aclk1", + "tags": [ + "management" + ], "summary": "Get information about current ACLK state", - "description": "ACLK endpoint returns detailed information about current state of ACLK (Agent to Cloud communication).", + "description": "ACLK endpoint returns detailed information about current state of ACLK (Agent to Cloud communication).\n", "responses": { "200": { "description": "JSON object with ACLK information.", @@ -1417,1013 +1686,1858 @@ } } } + } + }, + "components": { + "parameters": { + "scopeNodes": { + "name": "scope_nodes", + "in": "query", + "description": "A simple pattern limiting the nodes scope of the query. The scope controls both data and metadata response. The simple pattern is checked against the nodes' machine guid, node id and hostname. The default nodes scope is all nodes for which this agent has data for. Usually the nodes scope is used to slice the entire dashboard (e.g. the Global Nodes Selector at the Netdata Cloud overview dashboard). Both positive and negative simple pattern expressions are supported.\n", + "required": false, + "schema": { + "type": "string", + "format": "simple pattern", + "default": "*" + } + }, + "scopeContexts": { + "name": "scope_contexts", + "in": "query", + "description": "A simple pattern limiting the contexts scope of the query. The scope controls both data and metadata response. The default contexts scope is all contexts for which this agent has data for. Usually the contexts scope is used to slice data on the dashboard (e.g. each context based chart has its own contexts scope, limiting the chart to all the instances of the selected context). Both positive and negative simple pattern expressions are supported.\n", + "required": false, + "schema": { + "type": "string", + "format": "simple pattern", + "default": "*" + } + }, + "filterNodes": { + "name": "nodes", + "in": "query", + "description": "A simple pattern matching the nodes to be queried. This only controls the data response, not the metadata. The simple pattern is checked against the nodes' machine guid, node id, hostname. The default nodes selector is all the nodes matched by the nodes scope. Both positive and negative simple pattern expressions are supported.\n", + "required": false, + "schema": { + "type": "string", + "format": "simple pattern", + "default": "*" + } + }, + "filterContexts": { + "name": "contexts", + "in": "query", + "description": "A simple pattern matching the contexts to be queried. This only controls the data response, not the metadata. Both positive and negative simple pattern expressions are supported.\n", + "required": false, + "schema": { + "type": "string", + "format": "simple pattern", + "default": "*" + } + }, + "filterInstances": { + "name": "instances", + "in": "query", + "description": "A simple pattern matching the instances to be queried. The simple pattern is checked against the instance `id`, the instance `name`, the fully qualified name of the instance `id` and `name`, like `instance@machine_guid`, where `instance` is either its `id` or `name`. Both positive and negative simple pattern expressions are supported.\n", + "required": false, + "schema": { + "type": "string", + "format": "simple pattern", + "default": "*" + } + }, + "filterLabels": { + "name": "labels", + "in": "query", + "description": "A simple pattern matching the labels to be queried. The simple pattern is checked against `name:value` of all the labels of all the eligible instances (as filtered by all the above: scope nodes, scope contexts, nodes, contexts and instances). Negative simple patterns should not be used in this filter.\n", + "required": false, + "schema": { + "type": "string", + "format": "simple pattern", + "default": "*" + } + }, + "filterAlerts": { + "name": "alerts", + "in": "query", + "description": "A simple pattern matching the alerts to be queried. The simple pattern is checked against the `name` of alerts and the combination of `name:status`, when status is one of `CLEAR`, `WARNING`, `CRITICAL`, `REMOVED`, `UNDEFINED`, `UNINITIALIZED`, of all the alerts of all the eligible instances (as filtered by all the above). A negative simple pattern will exclude the instances having the labels matched.\n", + "required": false, + "schema": { + "type": "string", + "format": "simple pattern", + "default": "*" + } + }, + "filterDimensions": { + "name": "dimensions", + "in": "query", + "description": "A simple patterns matching the dimensions to be queried. The simple pattern is checked against and `id` and the `name` of the dimensions of the eligible instances (as filtered by all the above). Both positive and negative simple pattern expressions are supported.\n", + "required": false, + "schema": { + "type": "string", + "format": "simple pattern", + "default": "*" + } + }, + "dataFormat1": { + "name": "format", + "in": "query", + "description": "The format of the data to be returned.", + "allowEmptyValue": false, + "schema": { + "type": "string", + "enum": [ + "json", + "jsonp", + "csv", + "tsv", + "tsv-excel", + "ssv", + "ssvcomma", + "datatable", + "datasource", + "html", + "markdown", + "array", + "csvjsonarray" + ], + "default": "json" + } + }, + "dataFormat2": { + "name": "format", + "in": "query", + "description": "The format of the data to be returned.", + "allowEmptyValue": false, + "schema": { + "type": "string", + "enum": [ + "json", + "json2", + "jsonp", + "csv", + "tsv", + "tsv-excel", + "ssv", + "ssvcomma", + "datatable", + "datasource", + "html", + "markdown", + "array", + "csvjsonarray" + ], + "default": "json2" + } + }, + "dataQueryOptions": { + "name": "options", + "in": "query", + "description": "Options that affect data generation.\n* `jsonwrap` - Wrap the output in a JSON object with metadata about the query.\n* `raw` - change the output so that it is aggregatable across multiple such queries. Supported by `/api/v2` data queries and `json2` format.\n* `minify` - Remove unnecessary spaces and newlines from the output.\n* `debug` - Provide additional information in `jsonwrap` output to help tracing issues.\n* `nonzero` - Do not return dimensions that all their values are zero, to improve the visual appearance of charts. They will still be returned if all the dimensions are entirely zero.\n* `null2zero` - Replace `null` values with `0`.\n* `absolute` or `abs` - Traditionally Netdata returns select dimensions negative to improve visual appearance. This option turns this feature off.\n* `display-absolute` - Only used by badges, to do color calculation using the signed value, but render the value without a sign.\n* `flip` or `reversed` - Order the timestamps array in reverse order (newest to oldest).\n* `min2max` - When flattening multi-dimensional data into a single metric format, use `max - min` instead of `sum`. This is EOL - use `/api/v2` to control aggregation across dimensions.\n* `percentage` - Convert all values into a percentage vs the row total. When enabled, Netdata will query all dimensions, even the ones that have not been selected or are hidden, to find the row total, in order to calculate the percentage of each dimension selected.\n* `seconds` - Output timestamps in seconds instead of dates.\n* `milliseconds` or `ms` - Output timestamps in milliseconds instead of dates.\n* `unaligned` - by default queries are aligned to the the view, so that as time passes past data returned do not change. When a data query will not be used for visualization, `unaligned` can be given to avoid aligning the query time-frame for visual precision.\n* `match-ids`, `match-names`. By default filters match both IDs and names when they are available. Setting either of the two options will disable the other.\n* `anomaly-bit` - query the anomaly information instead of metric values. This is EOL, use `/api/v2` and `json2` format which always returns this information and many more.\n* `jw-anomaly-rates` - return anomaly rates as a separate result set in the same `json` format response. This is EOL, use `/api/v2` and `json2` format which always returns information and many more. \n* `details` - `/api/v2/data` returns in `jsonwrap` the full tree of dimensions that have been matched by the query.\n* `group-by-labels` - `/api/v2/data` returns in `jsonwrap` flattened labels per output dimension. These are used to identify the instances that have been aggregated into each dimension, making it possible to provide a map, like Netdata does for Kubernetes.\n* `natural-points` - return timestamps as found in the database. The result is again fixed-step, but the query engine attempts to align them with the timestamps found in the database.\n* `virtual-points` - return timestamps independent of the database alignment. This is needed aggregating data across multiple Netdata agents, to ensure that their outputs do not need to be interpolated to be merged.\n* `selected-tier` - use data exclusively from the selected tier given with the `tier` parameter. This option is set automatically when the `tier` parameter is set.\n* `all-dimensions` - In `/api/v1` `jsonwrap` include metadata for all candidate metrics examined. In `/api/v2` this is standard behavior and no option is needed.\n* `label-quotes` - In `csv` output format, enclose each header label in quotes.\n* `objectrows` - Each row of value should be an object, not an array (only for `json` format).\n* `google_json` - Comply with google JSON/JSONP specs (only for `json` format).\n", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "jsonwrap", + "raw", + "minify", + "debug", + "nonzero", + "null2zero", + "abs", + "absolute", + "display-absolute", + "flip", + "reversed", + "min2max", + "percentage", + "seconds", + "ms", + "milliseconds", + "unaligned", + "match-ids", + "match-names", + "anomaly-bit", + "jw-anomaly-rates", + "details", + "group-by-labels", + "natural-points", + "virtual-points", + "selected-tier", + "all-dimensions", + "label-quotes", + "objectrows", + "google_json" + ] + }, + "default": [ + "seconds", + "jsonwrap" + ] + } + }, + "dataTimeGroup1": { + "name": "group", + "in": "query", + "description": "Time aggregation function. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. If the `absolute` option is set, the values are turned positive before applying this calculation.\n", + "required": false, + "schema": { + "type": "string", + "enum": [ + "min", + "max", + "avg", + "average", + "median", + "stddev", + "sum", + "incremental-sum", + "ses", + "des", + "cv", + "countif", + "percentile", + "percentile25", + "percentile50", + "percentile75", + "percentile80", + "percentile90", + "percentile95", + "percentile97", + "percentile98", + "percentile99", + "trimmed-mean", + "trimmed-mean1", + "trimmed-mean2", + "trimmed-mean3", + "trimmed-mean5", + "trimmed-mean10", + "trimmed-mean15", + "trimmed-mean20", + "trimmed-mean25", + "trimmed-median", + "trimmed-median1", + "trimmed-median2", + "trimmed-median3", + "trimmed-median5", + "trimmed-median10", + "trimmed-median15", + "trimmed-median20", + "trimmed-median25" + ], + "default": "average" + } + }, + "dataTimeGroup2": { + "name": "time_group", + "in": "query", + "description": "Time aggregation function. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. If the `absolute` option is set, the values are turned positive before applying this calculation.\n", + "required": false, + "schema": { + "type": "string", + "enum": [ + "min", + "max", + "avg", + "average", + "median", + "stddev", + "sum", + "incremental-sum", + "ses", + "des", + "cv", + "countif", + "percentile", + "percentile25", + "percentile50", + "percentile75", + "percentile80", + "percentile90", + "percentile95", + "percentile97", + "percentile98", + "percentile99", + "trimmed-mean", + "trimmed-mean1", + "trimmed-mean2", + "trimmed-mean3", + "trimmed-mean5", + "trimmed-mean10", + "trimmed-mean15", + "trimmed-mean20", + "trimmed-mean25", + "trimmed-median", + "trimmed-median1", + "trimmed-median2", + "trimmed-median3", + "trimmed-median5", + "trimmed-median10", + "trimmed-median15", + "trimmed-median20", + "trimmed-median25" + ], + "default": "average" + } + }, + "dataTimeGroupOptions1": { + "name": "group_options", + "in": "query", + "description": "When the time grouping function supports additional parameters, this field can be used to pass them to it. Currently `countif`, `trimmed-mean`, `trimmed-median` and `percentile` support this. For `countif` the string may start with `<`, `<=`, `<:`, `<>`, `!=`, `>`, `>=`, `>:`. For all others just a number is expected.\n", + "required": false, + "schema": { + "type": "string" + } + }, + "dataTimeGroupOptions2": { + "name": "time_group_options", + "in": "query", + "description": "When the time grouping function supports additional parameters, this field can be used to pass them to it. Currently `countif`, `trimmed-mean`, `trimmed-median` and `percentile` support this. For `countif` the string may start with `<`, `<=`, `<:`, `<>`, `!=`, `>`, `>=`, `>:`. For all others just a number is expected.\n", + "required": false, + "schema": { + "type": "string" + } + }, + "dataTimeResampling1": { + "name": "gtime", + "in": "query", + "description": "The grouping number of seconds. This is used in conjunction with group=average to change the units of metrics (ie when the data is per-second, setting gtime=60 will turn them to per-minute).\n", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "number", + "format": "integer", + "default": 0 + } + }, + "dataTimeResampling2": { + "name": "time_resampling", + "in": "query", + "description": "For incremental values that are \"per second\", this value is used to resample them to \"per minute` (60) or \"per hour\" (3600). It can only be used in conjunction with group=average.\n", + "required": false, + "schema": { + "type": "number", + "format": "integer", + "default": 0 + } + }, + "timeoutMS": { + "name": "timeout", + "in": "query", + "description": "Specify a timeout value in milliseconds after which the agent will abort the query and return a 503 error. A value of 0 indicates no timeout.\n", + "required": false, + "schema": { + "type": "number", + "format": "integer", + "default": 0 + } + }, + "timeoutSecs": { + "name": "timeout", + "in": "query", + "description": "Specify a timeout value in seconds after which the agent will abort the query and return a 504 error. A value of 0 indicates no timeout, but some endpoints, like `weights`, do not accept infinite timeouts (they have a predefined default), so to disable the timeout it must be set to a really high value.\n", + "required": false, + "schema": { + "type": "number", + "format": "integer", + "default": 0 + } + }, + "before": { + "name": "before", + "in": "query", + "description": "`after` and `before` define the time-frame of a query. `before` can be a negative number of seconds, up to 3 years (-94608000), relative to current clock. If not set, it is assumed to be the current clock time. When `before` is positive, it is assumed to be a unix epoch timestamp. When non-data endpoints support the `after` and `before`, they use the time-frame to limit their response for objects having data retention within the time-frame given.\n", + "required": false, + "schema": { + "type": "integer", + "default": 0 + } + }, + "after": { + "name": "after", + "in": "query", + "description": "`after` and `before` define the time-frame of a query. `after` can be a negative number of seconds, up to 3 years (-94608000), relative to `before`. If not set, it is usually assumed to be -600. When non-data endpoints support the `after` and `before`, they use the time-frame to limit their response for objects having data retention within the time-frame given.\n", + "required": false, + "schema": { + "type": "integer", + "default": -600 + } + }, + "baselineBefore": { + "name": "baseline_before", + "in": "query", + "description": "`baseline_after` and `baseline_before` define the baseline time-frame of a comparative query. `baseline_before` can be a negative number of seconds, up to 3 years (-94608000), relative to current clock. If not set, it is assumed to be the current clock time. When `baseline_before` is positive, it is assumed to be a unix epoch timestamp.\n", + "required": false, + "schema": { + "type": "integer", + "default": 0 + } + }, + "baselineAfter": { + "name": "baseline_after", + "in": "query", + "description": "`baseline_after` and `baseline_before` define the baseline time-frame of a comparative query. `baseline_after` can be a negative number of seconds, up to 3 years (-94608000), relative to `baseline_before`. If not set, it is usually assumed to be -300.\n", + "required": false, + "schema": { + "type": "integer", + "default": -600 + } + }, + "points": { + "name": "points", + "in": "query", + "description": "The number of points to be returned. If not given, or it is <= 0, or it is bigger than the points stored in the database for the given duration, all the available collected values for the given duration will be returned. For `weights` endpoints that do statistical analysis, the `points` define the detail of this analysis (the default is 500).\n", + "required": false, + "schema": { + "type": "number", + "format": "integer", + "default": 0 + } + }, + "tier": { + "name": "tier", + "in": "query", + "description": "Use only the given dbengine tier for executing the query. Setting this parameters automatically sets the option `selected-tier` for the query.\n", + "required": false, + "schema": { + "type": "number", + "format": "integer" + } + }, + "callback": { + "name": "callback", + "in": "query", + "description": "For JSONP responses, the callback function name.\n", + "required": false, + "schema": { + "type": "string" + } + }, + "filename": { + "name": "filename", + "in": "query", + "description": "Add `Content-Disposition: attachment; filename=` header to the response, that will instruct the browser to save the response with the given filename.\"\n", + "required": false, + "schema": { + "type": "string" + } + }, + "tqx": { + "name": "tqx", + "in": "query", + "description": "[Google Visualization API](https://developers.google.com/chart/interactive/docs/dev/implementing_data_source?hl=en) formatted parameter.\n", + "required": false, + "schema": { + "type": "string" + } + }, + "contextOptions1": { + "name": "options", + "in": "query", + "description": "Options that affect data generation.", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "full", + "all", + "charts", + "dimensions", + "labels", + "uuids", + "queue", + "flags", + "deleted", + "deepscan" + ] + } + } + }, + "chart": { + "name": "chart", + "in": "query", + "description": "The id of the chart as returned by the `/api/v1/charts` call.", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "format": "as returned by `/api/v1/charts`" + } + }, + "context": { + "name": "context", + "in": "query", + "description": "The context of the chart as returned by the /charts call.", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "format": "as returned by /charts" + } + }, + "dimension": { + "name": "dimension", + "in": "query", + "description": "Zero, one or more dimension ids or names, as returned by the /chart call, separated with comma or pipe. Netdata simple patterns are supported.", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "as returned by /charts" + } + } + }, + "dimensions": { + "name": "dimensions", + "in": "query", + "description": "a simple pattern matching dimensions (use comma or pipe as separator)", + "required": false, + "allowEmptyValue": true, + "schema": { + "type": "string" + } + }, + "chart_label_key": { + "name": "chart_label_key", + "in": "query", + "description": "Specify the chart label keys that need to match for context queries as comma separated values. At least one matching key is needed to match the corresponding chart.\n", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "format": "key1,key2,key3" + } + }, + "chart_labels_filter": { + "name": "chart_labels_filter", + "in": "query", + "description": "Specify the chart label keys and values to match for context queries. All keys/values need to match for the chart to be included in the query. The labels are specified as key1:value1,key2:value2\n", + "required": false, + "allowEmptyValue": false, + "schema": { + "type": "string", + "format": "key1:value1,key2:value2,key3:value3" + } + }, + "weightMethods": { + "name": "method", + "in": "query", + "description": "The weighting / scoring algorithm.", + "required": false, + "schema": { + "type": "string", + "enum": [ + "ks2", + "volume", + "anomaly-rate", + "value" + ] + } + } }, - "/metric_correlations": { - "get": { - "summary": "Analyze all the metrics to find their correlations", - "description": "THIS ENDPOINT IS OBSOLETE. Use the /weights endpoint. Given two time-windows (baseline, highlight), it goes through all the available metrics, querying both windows and tries to find how these two windows relate to each other. It supports multiple algorithms to do so. The result is a list of all metrics evaluated, weighted for 0.0 (the two windows are more different) to 1.0 (the two windows are similar). The algorithm adjusts automatically the baseline window to be a power of two multiple of the highlighted (1, 2, 4, 8, etc).", - "parameters": [ - { - "name": "baseline_after", - "in": "query", - "description": "This parameter can either be an absolute timestamp specifying the starting point of baseline window, or a relative number of seconds (negative, relative to parameter baseline_before). Netdata will assume it is a relative number if it is less that 3 years (in seconds).", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer", - "default": -300 - } + "schemas": { + "info": { + "type": "object", + "properties": { + "version": { + "type": "string", + "description": "netdata version of the server.", + "example": "1.11.1_rolling" }, - { - "name": "baseline_before", - "in": "query", - "description": "This parameter can either be an absolute timestamp specifying the ending point of the baseline window, or a relative number of seconds (negative), relative to the last collected timestamp. Netdata will assume it is a relative number if it is less than 3 years (in seconds).", - "required": false, - "schema": { - "type": "number", - "format": "integer", - "default": -60 - } + "uid": { + "type": "string", + "description": "netdata unique id of the server.", + "example": "24e9fe3c-f2ac-11e8-bafc-0242ac110002" }, - { - "name": "after", - "in": "query", - "description": "This parameter can either be an absolute timestamp specifying the starting point of highlighted window, or a relative number of seconds (negative, relative to parameter highlight_before). Netdata will assume it is a relative number if it is less that 3 years (in seconds).", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer", - "default": -60 - } + "mirrored_hosts": { + "type": "array", + "description": "List of hosts mirrored of the server (include itself).", + "items": { + "type": "string" + }, + "example": [ + "host1.example.com", + "host2.example.com" + ] }, - { - "name": "before", - "in": "query", - "description": "This parameter can either be an absolute timestamp specifying the ending point of the highlighted window, or a relative number of seconds (negative), relative to the last collected timestamp. Netdata will assume it is a relative number if it is less than 3 years (in seconds).", - "required": false, - "schema": { - "type": "number", - "format": "integer", - "default": 0 + "mirrored_hosts_status": { + "type": "array", + "description": "List of details of hosts mirrored to this served (including self). Indexes correspond to indexes in \"mirrored_hosts\".", + "items": { + "type": "object", + "description": "Host data", + "properties": { + "guid": { + "type": "string", + "format": "uuid", + "nullable": false, + "description": "Host unique GUID from `netdata.public.unique.id`.", + "example": "245e4bff-3b34-47c1-a6e5-5c535a9abfb2" + }, + "reachable": { + "type": "boolean", + "nullable": false, + "description": "Current state of streaming. Always true for localhost/self." + }, + "claim_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Cloud GUID/identifier in case the host is claimed. If child status unknown or unclaimed this field is set to `null`", + "example": "c3b2a66a-3052-498c-ac52-7fe9e8cccb0c" + } + } } }, - { - "name": "points", - "in": "query", - "description": "The number of points to be evaluated for the highlighted window. The baseline window will be adjusted automatically to receive a proportional amount of points.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer", - "default": 500 - } + "os_name": { + "type": "string", + "description": "Operating System Name.", + "example": "Manjaro Linux" }, - { - "name": "method", - "in": "query", - "description": "the algorithm to run", - "required": false, - "schema": { - "type": "string", - "enum": [ - "ks2", - "volume" - ], - "default": "ks2" - } + "os_id": { + "type": "string", + "description": "Operating System ID.", + "example": "manjaro" }, - { - "name": "timeout", - "in": "query", - "description": "Cancel the query if to takes more that this amount of milliseconds.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer", - "default": 60000 - } + "os_id_like": { + "type": "string", + "description": "Known OS similar to this OS.", + "example": "arch" }, - { - "name": "options", - "in": "query", - "description": "Options that affect data generation.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "min2max", - "abs", - "absolute", - "absolute-sum", - "null2zero", - "percentage", - "unaligned", - "allow_past", - "nonzero", - "anomaly-bit", - "raw" - ] - }, - "default": [ - "null2zero", - "allow_past", - "nonzero", - "unaligned" - ] - } - }, - { - "name": "group", - "in": "query", - "description": "The grouping method. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. methods supported \"min\", \"max\", \"average\", \"sum\", \"incremental-sum\". \"max\" is actually calculated on the absolute value collected (so it works for both positive and negative dimensions to return the most extreme value in either direction).", - "required": true, - "allowEmptyValue": false, - "schema": { - "type": "string", - "enum": [ - "min", - "max", - "average", - "median", - "stddev", - "sum", - "incremental-sum", - "ses", - "des", - "cv", - "countif", - "percentile", - "percentile25", - "percentile50", - "percentile75", - "percentile80", - "percentile90", - "percentile95", - "percentile97", - "percentile98", - "percentile99", - "trimmed-mean", - "trimmed-mean1", - "trimmed-mean2", - "trimmed-mean3", - "trimmed-mean5", - "trimmed-mean10", - "trimmed-mean15", - "trimmed-mean20", - "trimmed-mean25", - "trimmed-median", - "trimmed-median1", - "trimmed-median2", - "trimmed-median3", - "trimmed-median5", - "trimmed-median10", - "trimmed-median15", - "trimmed-median20", - "trimmed-median25" - ], - "default": "average" - } - }, - { - "name": "group_options", - "in": "query", - "description": "When the group function supports additional parameters, this field can be used to pass them to it. Currently only \"countif\" supports this.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "JSON object with weights for each chart and dimension.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/metric_correlations" - } - } - } + "os_version": { + "type": "string", + "description": "Operating System Version.", + "example": "18.0.4" }, - "400": { - "description": "The given parameters are invalid." + "os_version_id": { + "type": "string", + "description": "Operating System Version ID.", + "example": "unknown" }, - "403": { - "description": "metrics correlations are not enabled on this Netdata Agent." + "os_detection": { + "type": "string", + "description": "OS parameters detection method.", + "example": "Mixed" }, - "404": { - "description": "No charts could be found, or the method that correlated the metrics did not produce any result." + "kernel_name": { + "type": "string", + "description": "Kernel Name.", + "example": "Linux" }, - "504": { - "description": "Timeout - the query took too long and has been cancelled." - } - } - } - }, - "/function": { - "get": { - "summary": "Execute a collector function.", - "parameters": [ - { - "name": "function", - "in": "query", - "description": "The name of the function, as returned by the collector.", - "required": true, - "allowEmptyValue": false, - "schema": { - "type": "string" - } + "kernel_version": { + "type": "string", + "description": "Kernel Version.", + "example": "4.19.32-1-MANJARO" }, - { - "name": "timeout", - "in": "query", - "description": "The timeout in seconds to wait for the function to complete.", - "required": false, - "schema": { - "type": "number", - "format": "integer", - "default": 10 - } - } - ], - "responses": { - "200": { - "description": "The collector function has been executed successfully. Each collector may return a different type of content." + "is_k8s_node": { + "type": "boolean", + "description": "Netdata is running on a K8s node.", + "example": false }, - "400": { - "description": "The request was rejected by the collector." + "architecture": { + "type": "string", + "description": "Kernel architecture.", + "example": "x86_64" }, - "404": { - "description": "The requested function is not found." + "virtualization": { + "type": "string", + "description": "Virtualization Type.", + "example": "kvm" }, - "500": { - "description": "Other internal error, getting this error means there is a bug in Netdata." + "virt_detection": { + "type": "string", + "description": "Virtualization detection method.", + "example": "systemd-detect-virt" }, - "503": { - "description": "The collector to execute the function is not currently available." + "container": { + "type": "string", + "description": "Container technology.", + "example": "docker" }, - "504": { - "description": "Timeout while waiting for the collector to execute the function." + "container_detection": { + "type": "string", + "description": "Container technology detection method.", + "example": "dockerenv" }, - "591": { - "description": "The collector sent a response, but it was invalid or corrupted." - } - } - } - }, - "/functions": { - "get": { - "summary": "Get a list of all registered collector functions.", - "description": "Collector functions are programs that can be executed on demand.", - "responses": { - "200": { - "description": "A JSON object containing one object per supported function." - } - } - } - }, - "/weights": { - "get": { - "summary": "Analyze all the metrics using an algorithm and score them accordingly", - "description": "This endpoint goes through all metrics and scores them according to an algorithm.", - "parameters": [ - { - "name": "baseline_after", - "in": "query", - "description": "This parameter can either be an absolute timestamp specifying the starting point of baseline window, or a relative number of seconds (negative, relative to parameter baseline_before). Netdata will assume it is a relative number if it is less that 3 years (in seconds). This parameter is used in KS2 and VOLUME algorithms.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer", - "default": -300 - } + "stream_compression": { + "type": "boolean", + "description": "Stream transmission compression method.", + "example": true }, - { - "name": "baseline_before", - "in": "query", - "description": "This parameter can either be an absolute timestamp specifying the ending point of the baseline window, or a relative number of seconds (negative), relative to the last collected timestamp. Netdata will assume it is a relative number if it is less than 3 years (in seconds). This parameter is used in KS2 and VOLUME algorithms.", - "required": false, - "schema": { - "type": "number", - "format": "integer", - "default": -60 + "labels": { + "type": "object", + "description": "List of host labels.", + "properties": { + "app": { + "type": "string", + "description": "Host label.", + "example": "netdata" + } } }, - { - "name": "after", - "in": "query", - "description": "This parameter can either be an absolute timestamp specifying the starting point of highlighted window, or a relative number of seconds (negative, relative to parameter highlight_before). Netdata will assume it is a relative number if it is less that 3 years (in seconds).", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer", - "default": -60 + "collectors": { + "type": "array", + "items": { + "type": "object", + "description": "Array of collector plugins and modules.", + "properties": { + "plugin": { + "type": "string", + "description": "Collector plugin.", + "example": "python.d.plugin" + }, + "module": { + "type": "string", + "description": "Module of the collector plugin.", + "example": "dockerd" + } + } } }, - { - "name": "before", - "in": "query", - "description": "This parameter can either be an absolute timestamp specifying the ending point of the highlighted window, or a relative number of seconds (negative), relative to the last collected timestamp. Netdata will assume it is a relative number if it is less than 3 years (in seconds).", - "required": false, - "schema": { - "type": "number", - "format": "integer", - "default": 0 + "alarms": { + "type": "object", + "description": "Number of alarms in the server.", + "properties": { + "normal": { + "type": "integer", + "description": "Number of alarms in normal state." + }, + "warning": { + "type": "integer", + "description": "Number of alarms in warning state." + }, + "critical": { + "type": "integer", + "description": "Number of alarms in critical state." + } } + } + } + }, + "chart_summary": { + "type": "object", + "properties": { + "hostname": { + "type": "string", + "description": "The hostname of the netdata server." }, - { - "name": "context", - "in": "query", - "description": "A simple pattern matching the contexts to evaluate.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "string" - } + "version": { + "type": "string", + "description": "netdata version of the server." }, - { - "name": "points", - "in": "query", - "description": "The number of points to be evaluated for the highlighted window. The baseline window will be adjusted automatically to receive a proportional amount of points. This parameter is only used by the KS2 algorithm.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer", - "default": 500 - } + "release_channel": { + "type": "string", + "description": "The release channel of the build on the server.", + "example": "nightly" }, - { - "name": "method", - "in": "query", - "description": "the algorithm to run", - "required": false, - "schema": { - "type": "string", - "enum": [ - "ks2", - "volume", - "anomaly-rate" - ], - "default": "anomaly-rate" - } + "timezone": { + "type": "string", + "description": "The current timezone on the server." }, - { - "name": "tier", - "in": "query", - "description": "Use the specified database tier", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer" - } + "os": { + "type": "string", + "description": "The netdata server host operating system.", + "enum": [ + "macos", + "linux", + "freebsd" + ] }, - { - "name": "timeout", - "in": "query", - "description": "Cancel the query if to takes more that this amount of milliseconds.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "number", - "format": "integer", - "default": 60000 - } + "history": { + "type": "number", + "description": "The duration, in seconds, of the round robin database maintained by netdata." }, - { - "name": "options", - "in": "query", - "description": "Options that affect data generation.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "min2max", - "abs", - "absolute", - "absolute-sum", - "null2zero", - "percentage", - "unaligned", - "nonzero", - "anomaly-bit", - "raw" - ] - }, - "default": [ - "null2zero", - "nonzero", - "unaligned" - ] - } + "memory_mode": { + "type": "string", + "description": "The name of the database memory mode on the server." }, - { - "name": "group", - "in": "query", - "description": "The grouping method. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. methods supported \"min\", \"max\", \"average\", \"sum\", \"incremental-sum\". \"max\" is actually calculated on the absolute value collected (so it works for both positive and negative dimensions to return the most extreme value in either direction).", - "required": true, - "allowEmptyValue": false, - "schema": { - "type": "string", - "enum": [ - "min", - "max", - "average", - "median", - "stddev", - "sum", - "incremental-sum", - "ses", - "des", - "cv", - "countif", - "percentile", - "percentile25", - "percentile50", - "percentile75", - "percentile80", - "percentile90", - "percentile95", - "percentile97", - "percentile98", - "percentile99", - "trimmed-mean", - "trimmed-mean1", - "trimmed-mean2", - "trimmed-mean3", - "trimmed-mean5", - "trimmed-mean10", - "trimmed-mean15", - "trimmed-mean20", - "trimmed-mean25", - "trimmed-median", - "trimmed-median1", - "trimmed-median2", - "trimmed-median3", - "trimmed-median5", - "trimmed-median10", - "trimmed-median15", - "trimmed-median20", - "trimmed-median25" - ], - "default": "average" - } + "update_every": { + "type": "number", + "description": "The default update frequency of the netdata server. All charts have an update frequency equal or bigger than this." }, - { - "name": "group_options", - "in": "query", - "description": "When the group function supports additional parameters, this field can be used to pass them to it. Currently only \"countif\" supports this.", - "required": false, - "allowEmptyValue": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "JSON object with weights for each context, chart and dimension.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/weights" - } - } + "charts": { + "type": "object", + "description": "An object containing all the chart objects available at the netdata server. This is used as an indexed array. The key of each chart object is the id of the chart.", + "additionalProperties": { + "$ref": "#/components/schemas/chart" } }, - "400": { - "description": "The given parameters are invalid." + "charts_count": { + "type": "number", + "description": "The number of charts." }, - "403": { - "description": "metrics correlations are not enabled on this Netdata Agent." + "dimensions_count": { + "type": "number", + "description": "The total number of dimensions." }, - "404": { - "description": "No charts could be found, or the method that correlated the metrics did not produce any result." + "alarms_count": { + "type": "number", + "description": "The number of alarms." }, - "504": { - "description": "Timeout - the query took too long and has been cancelled." + "rrd_memory_bytes": { + "type": "number", + "description": "The size of the round robin database in bytes." } } - } - } - }, - "servers": [ - { - "url": "https://registry.my-netdata.io/api/v1" - }, - { - "url": "http://registry.my-netdata.io/api/v1" - } - ], - "components": { - "schemas": { - "info": { + }, + "chart": { "type": "object", "properties": { - "version": { - "type": "string", - "description": "netdata version of the server.", - "example": "1.11.1_rolling" - }, - "uid": { - "type": "string", - "description": "netdata unique id of the server.", - "example": "24e9fe3c-f2ac-11e8-bafc-0242ac110002" - }, - "mirrored_hosts": { - "type": "array", - "description": "List of hosts mirrored of the server (include itself).", - "items": { - "type": "string" - }, - "example": [ - "host1.example.com", - "host2.example.com" - ] - }, - "mirrored_hosts_status": { - "type": "array", - "description": "List of details of hosts mirrored to this served (including self). Indexes correspond to indexes in \"mirrored_hosts\".", - "items": { - "type": "object", - "description": "Host data", - "properties": { - "guid": { - "type": "string", - "format": "uuid", - "nullable": false, - "description": "Host unique GUID from `netdata.public.unique.id`.", - "example": "245e4bff-3b34-47c1-a6e5-5c535a9abfb2" - }, - "reachable": { - "type": "boolean", - "nullable": false, - "description": "Current state of streaming. Always true for localhost/self." - }, - "claim_id": { - "type": "string", - "format": "uuid", - "nullable": true, - "description": "Cloud GUID/identifier in case the host is claimed. If child status unknown or unclaimed this field is set to `null`", - "example": "c3b2a66a-3052-498c-ac52-7fe9e8cccb0c" - } - } - } - }, - "os_name": { - "type": "string", - "description": "Operating System Name.", - "example": "Manjaro Linux" - }, - "os_id": { - "type": "string", - "description": "Operating System ID.", - "example": "manjaro" - }, - "os_id_like": { + "id": { "type": "string", - "description": "Known OS similar to this OS.", - "example": "arch" + "description": "The unique id of the chart." }, - "os_version": { + "name": { "type": "string", - "description": "Operating System Version.", - "example": "18.0.4" + "description": "The name of the chart." }, - "os_version_id": { + "type": { "type": "string", - "description": "Operating System Version ID.", - "example": "unknown" + "description": "The type of the chart. Types are not handled by netdata. You can use this field for anything you like." }, - "os_detection": { + "family": { "type": "string", - "description": "OS parameters detection method.", - "example": "Mixed" + "description": "The family of the chart. Families are not handled by netdata. You can use this field for anything you like." }, - "kernel_name": { + "title": { "type": "string", - "description": "Kernel Name.", - "example": "Linux" + "description": "The title of the chart." }, - "kernel_version": { - "type": "string", - "description": "Kernel Version.", - "example": "4.19.32-1-MANJARO" + "priority": { + "type": "number", + "description": "The relative priority of the chart. Netdata does not care about priorities. This is just an indication of importance for the chart viewers to sort charts of higher priority (lower number) closer to the top. Priority sorting should only be used among charts of the same type or family." }, - "is_k8s_node": { + "enabled": { "type": "boolean", - "description": "Netdata is running on a K8s node.", - "example": false + "description": "True when the chart is enabled. Disabled charts do not currently collect values, but they may have historical values available." }, - "architecture": { + "units": { "type": "string", - "description": "Kernel architecture.", - "example": "x86_64" + "description": "The unit of measurement for the values of all dimensions of the chart." }, - "virtualization": { + "data_url": { "type": "string", - "description": "Virtualization Type.", - "example": "kvm" + "description": "The absolute path to get data values for this chart. You are expected to use this path as the base when constructing the URL to fetch data values for this chart." }, - "virt_detection": { + "chart_type": { "type": "string", - "description": "Virtualization detection method.", - "example": "systemd-detect-virt" + "description": "The chart type.", + "enum": [ + "line", + "area", + "stacked" + ] }, - "container": { - "type": "string", - "description": "Container technology.", - "example": "docker" + "duration": { + "type": "number", + "description": "The duration, in seconds, of the round robin database maintained by netdata." }, - "container_detection": { - "type": "string", - "description": "Container technology detection method.", - "example": "dockerenv" + "first_entry": { + "type": "number", + "description": "The UNIX timestamp of the first entry (the oldest) in the round robin database." }, - "stream_compression": { - "type": "boolean", - "description": "Stream transmission compression method.", - "example": true + "last_entry": { + "type": "number", + "description": "The UNIX timestamp of the latest entry in the round robin database." }, - "labels": { + "update_every": { + "type": "number", + "description": "The update frequency of this chart, in seconds. One value every this amount of time is kept in the round robin database." + }, + "dimensions": { + "type": "object", + "description": "An object containing all the chart dimensions available for the chart. This is used as an indexed array. For each pair in the dictionary: the key is the id of the dimension and the value is a dictionary containing the name.\"\n", + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the dimension" + } + } + } + }, + "chart_variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/chart_variables" + } + }, + "green": { + "type": "number", + "nullable": true, + "description": "Chart health green threshold." + }, + "red": { + "type": "number", + "nullable": true, + "description": "Chart health red threshold." + } + } + }, + "context_summary": { + "type": "object", + "properties": { + "hostname": { + "type": "string", + "description": "The hostname of the netdata server." + }, + "machine_guid": { + "type": "string", + "description": "The unique installation id of this netdata server." + }, + "node_id": { + "type": "string", + "description": "The unique node id of this netdata server at the hub.", + "example": "nightly" + }, + "claim_id": { + "type": "string", + "description": "The unique handshake id of this netdata server and the hub." + }, + "host_labels": { + "type": "object", + "description": "The host labels associated with this netdata server." + }, + "context": { + "type": "object", + "description": "An object containing all the context objects available at the netdata server. This is used as an indexed array. The key of each context object is the id of the context.", + "additionalProperties": { + "$ref": "#/components/schemas/context" + } + } + } + }, + "context": { + "type": "object", + "properties": { + "version": { + "type": "string", + "description": "The version of this context. The number are not sequential, but bigger numbers depict a newer object." + }, + "hub_version": { + "type": "string", + "description": "The version of this context, as known by hub." + }, + "family": { + "type": "string", + "description": "The family of the context. When multiple charts of a context have different families, the netdata server replaces the different parts with [x], so that the context can have only one family." + }, + "title": { + "type": "string", + "description": "The title of the context. When multiple charts of a context have different titles, the netdata server replaces the different parts with [x], so that the context can have only one title." + }, + "priority": { + "type": "number", + "description": "The relative priority of the context. When multiple contexts have different priorities, the minimum among them is selected as the priority of the context." + }, + "units": { + "type": "string", + "description": "The unit of measurement for the values of all dimensions of the context. If multiple charts of context have different units, the latest collected is selected." + }, + "chart_type": { + "type": "string", + "description": "The chart type.", + "enum": [ + "line", + "area", + "stacked" + ] + }, + "first_time_t": { + "type": "number", + "description": "The UNIX timestamp of the first entry (the oldest) in the database." + }, + "last_time_t": { + "type": "number", + "description": "The UNIX timestamp of the latest entry in the database." + }, + "charts": { + "type": "object", + "description": "An object containing all the charts available for the chart. This is used as an indexed array. For each pair in the dictionary, the key is the id of the chart and the value provides all details about the chart." + } + } + }, + "alarm_variables": { + "type": "object", + "properties": { + "chart": { + "type": "string", + "description": "The unique id of the chart." + }, + "chart_name": { + "type": "string", + "description": "The name of the chart." + }, + "cnart_context": { + "type": "string", + "description": "The context of the chart. It is shared across multiple monitored software or hardware instances and used in alarm templates." + }, + "family": { + "type": "string", + "description": "The family of the chart." + }, + "host": { + "type": "string", + "description": "The host containing the chart." + }, + "chart_variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/chart_variables" + } + }, + "family_variables": { "type": "object", - "description": "List of host labels.", "properties": { - "app": { - "type": "string", - "description": "Host label.", - "example": "netdata" + "varname1": { + "type": "number", + "format": "float" + }, + "varname2": { + "type": "number", + "format": "float" } } }, - "collectors": { + "host_variables": { + "type": "object", + "properties": { + "varname1": { + "type": "number", + "format": "float" + }, + "varname2": { + "type": "number", + "format": "float" + } + } + } + } + }, + "chart_variables": { + "type": "object", + "properties": { + "varname1": { + "type": "number", + "format": "float" + }, + "varname2": { + "type": "number", + "format": "float" + } + } + }, + "jsonwrap2": { + "description": "Data response with `format=json2`\n", + "type": "object", + "properties": { + "api": { + "$ref": "#/components/schemas/api" + }, + "agents": { + "$ref": "#/components/schemas/agents" + }, + "versions": { + "$ref": "#/components/schemas/versions" + }, + "summary": { + "description": "Summarized information about nodes, contexts, instances, labels, alerts, and dimensions. The items returned are determined by the scope of the query only, however the statistical data in them are influenced by the filters of the query. Using this information the dashboard allows users to slice and dice the data by filtering and grouping.\n", + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/nodeWithDataStatistics" + } + }, + "contexts": { + "type": "array", + "items": { + "type": "object", + "description": "An object describing a unique context. `is` stands for instances, `ds` for dimensions, `al` for alerts, `sts` for statistics.\n", + "properties": { + "id": { + "description": "the context id.", + "type": "string" + }, + "is": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + }, + "ds": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + }, + "al": { + "$ref": "#/components/schemas/jsonwrap2_alerts_count" + }, + "sts": { + "oneOf": [ + { + "$ref": "#/components/schemas/jsonwrap2_sts" + }, + { + "$ref": "#/components/schemas/jsonwrap2_sts_raw" + } + ] + } + } + } + }, + "instances": { + "type": "array", + "items": { + "type": "object", + "description": "An object describing an instance. `ds` stands for dimensions, `al` for alerts, `sts` for statistics.\n", + "properties": { + "id": { + "description": "the id of the instance.", + "type": "string" + }, + "nm": { + "description": "the name of the instance (may be absent when it is the same with the id)", + "type": "string" + }, + "ni": { + "description": "the node index id this instance belongs to. The UI uses this to compone the fully qualified name of the instance, using the node hostname to present it to users and its machine guid to add it to filters." + }, + "ds": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + }, + "al": { + "$ref": "#/components/schemas/jsonwrap2_alerts_count" + }, + "sts": { + "oneOf": [ + { + "$ref": "#/components/schemas/jsonwrap2_sts" + }, + { + "$ref": "#/components/schemas/jsonwrap2_sts_raw" + } + ] + } + } + } + }, + "dimensions": { + "type": "array", + "items": { + "type": "object", + "description": "An object describing a unique dimension. `ds` stands for `dimensions`, `sts` for statistics.\n", + "properties": { + "id": { + "description": "the id of the dimension.", + "type": "string" + }, + "nm": { + "description": "the name of the dimension (may be absent when it is the same with the id)", + "type": "string" + }, + "ds": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + }, + "sts": { + "oneOf": [ + { + "$ref": "#/components/schemas/jsonwrap2_sts" + }, + { + "$ref": "#/components/schemas/jsonwrap2_sts_raw" + } + ] + } + } + } + }, + "labels": { + "type": "array", + "items": { + "type": "object", + "description": "An object describing a label key. `ds` stands for `dimensions`, `sts` for statistics.\n", + "properties": { + "id": { + "description": "the key of the label.", + "type": "string" + }, + "ds": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + }, + "sts": { + "oneOf": [ + { + "$ref": "#/components/schemas/jsonwrap2_sts" + }, + { + "$ref": "#/components/schemas/jsonwrap2_sts_raw" + } + ] + }, + "vl": { + "description": "An array of values for this key.\n", + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "description": "The value string", + "type": "string" + }, + "ds": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + }, + "sts": { + "oneOf": [ + { + "$ref": "#/components/schemas/jsonwrap2_sts" + }, + { + "$ref": "#/components/schemas/jsonwrap2_sts_raw" + } + ] + } + } + } + } + } + } + }, + "alerts": { + "description": "An array of all the unique alerts running, grouped by alert name (`nm` is available here)\n", + "type": "array", + "items": { + "$ref": "#/components/schemas/jsonwrap2_alerts_count" + } + } + } + }, + "totals": { + "type": "object", + "properties": { + "nodes": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + }, + "contexts": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + }, + "instances": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + }, + "dimensions": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + }, + "label_keys": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + }, + "label_key_values": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + } + } + }, + "functions": { "type": "array", "items": { - "type": "object", - "description": "Array of collector plugins and modules.", - "properties": { - "plugin": { - "type": "string", - "description": "Collector plugin.", - "example": "python.d.plugin" - }, - "module": { - "type": "string", - "description": "Module of the collector plugin.", - "example": "dockerd" + "type": "string" + } + }, + "db": { + "type": "object", + "properties": { + "tiers": { + "description": "The number of tiers this server is using.\n", + "type": "integer" + }, + "update_every": { + "description": "The minimum update every, in seconds, for all tiers and all metrics aggregated into this query.\n", + "type": "integer" + }, + "first_entry": { + "description": "The minimum unix epoch timestamp of the retention across all tiers for all metrics aggregated into this query.\n", + "type": "integer" + }, + "last_entry": { + "description": "The maximum unix epoch timestamp of the retention across all tier for all metrics aggregated into this query.\n", + "type": "integer" + }, + "per_tier": { + "description": "An array with information for each of the tiers available, related to this query.\n", + "type": "array", + "items": { + "type": "object", + "properties": { + "tier": { + "description": "The tier number of this tier, starting at 0.\n", + "type": "integer" + }, + "queries": { + "description": "The number of queries executed on this tier. Usually one query per metric is made, but the query may cross multiple tier, in which case more than one query per metric is made.\n", + "type": "integer" + }, + "points": { + "description": "The number of points read from this tier.\n", + "type": "integer" + }, + "update_every": { + "description": "The minimum resolution of all metrics queried on this tier.\n", + "type": "integer" + }, + "first_entry": { + "description": "The minimum unix epoch timestamp available across all metrics that used this tier. This reflects the oldest timestamp of the tier's retention.\n", + "type": "integer" + }, + "last_entry": { + "description": "The maximum unix epoch timestamp available across all metrics that used this tier. This reflects the newest timestamp of the tier's retention.\n" + } + } + } + }, + "units": { + "description": "The units of the database data\n", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "dimensions": { + "type": "object", + "properties": { + "ids": { + "description": "An array with the dimension ids that uniquely identify the dimensions for this query. It is the same with `view.dimensions.ids`.\n", + "type": "array", + "items": { + "type": "string" + } + }, + "units": { + "description": "An array with the units each dimension has in the database (independent of group-by aggregation that may override the units).\n", + "type": "array", + "items": { + "type": "string" + } + }, + "sts": { + "description": "Statistics about the data collection points used for each dimension.\n", + "oneOf": [ + { + "$ref": "#/components/schemas/jsonwrap2_sts" + }, + { + "$ref": "#/components/schemas/jsonwrap2_sts_raw" + } + ] + } } } } }, - "alarms": { + "view": { "type": "object", - "description": "Number of alarms in the server.", "properties": { - "normal": { - "type": "integer", - "description": "Number of alarms in normal state." + "title": { + "description": "The title the chart should have.\n", + "type": "string" + }, + "format": { + "description": "The format the `result` top level member has. Available on when `debug` flag is set.\n", + "type": "string" + }, + "options": { + "description": "An array presenting all the options given to the query. Available on when `debug` flag is set.\n", + "type": "array", + "items": { + "type": "string" + } + }, + "time_group": { + "description": "The same as the parameter `time_group`. Available on when `debug` flag is set.\n", + "type": "string" + }, + "after": { + "description": "The oldest unix epoch timestamp of the data returned in the `result`.\n", + "type": "integer" + }, + "before": { + "description": "The newest unix epoch timestamp of the data returned in the `result`.\n", + "type": "integer" + }, + "partial_data_trimming": { + "description": "Information related to trimming of the last few points of the `result`, that was required to remove (increasing) partial data.\nTrimming is disabled when the `raw` option is given to the query.\nThis object is available only when the `debug` flag is set.\n", + "type": "object", + "properties": { + "max_update_every": { + "description": "The maximum `update_every` for all metrics aggregated into the query.\nTrimming is by default enabled at `view.before - max_update_every`, but only when `view.before >= now - max_update_every`.\n", + "type": "integer" + }, + "expected_after": { + "description": "The timestamp at which trimming can be enabled.\nIf this timestamp is greater or equal to `view.before`, there is no trimming.\n", + "type": "integer" + }, + "trimmed_after": { + "description": "The timestamp at which trimming has been applied.\nIf this timestamp is greater or equal to `view.before`, there is no trimming.\n" + } + } + }, + "points": { + "description": "The number of points in `result`. Available only when `raw` is given.\n", + "type": "integer" + }, + "units": { + "description": "The units of the query.\n", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "chart_type": { + "description": "The default chart type of the query.\n", + "type": "string", + "enum": [ + "line", + "area", + "stacked" + ] + }, + "dimensions": { + "description": "Detailed information about the chart dimensions included in the `result`.\n", + "type": "object", + "properties": { + "grouped_by": { + "description": "An array with the order of the groupings performed.\n", + "type": "array", + "items": { + "type": "string", + "enum": [ + "selected", + "dimension", + "instance", + "node", + "context", + "units", + "label:key1", + "label:key2", + "label:keyN" + ] + } + }, + "ids": { + "description": "An array with the dimension ids that uniquely identify the dimensions for this query.\n", + "type": "array", + "items": { + "type": "string" + } + }, + "names": { + "description": "An array with the dimension names to be presented to users. Names may be overlapping, but IDs are not.\n", + "type": "array", + "items": { + "type": "string" + } + }, + "priorities": { + "description": "An array with the relative priorities of the dimensions.\nNumbers may not be sequential or unique. The application is expected to order by this and then by name.\n", + "type": "array", + "items": { + "type": "integer" + } + }, + "aggregated": { + "description": "An array with the number of source metrics aggregated into each dimension.\n", + "type": "array", + "items": { + "type": "integer" + } + }, + "units": { + "description": "An array with the units each dimension has.\n", + "type": "array", + "items": { + "type": "string" + } + }, + "sts": { + "description": "Statistics about the view points for each dimension.\n", + "oneOf": [ + { + "$ref": "#/components/schemas/jsonwrap2_sts" + }, + { + "$ref": "#/components/schemas/jsonwrap2_sts_raw" + } + ] + }, + "labels": { + "description": "The labels associated with each dimension in the query.\nThis object is only available when the `group-by-labels` option is given to the query.\n", + "type": "object", + "properties": { + "label_key1": { + "description": "An array having one entry for each of the dimensions of the query.\n", + "type": "array", + "items": { + "description": "An array having one entry for each of the values this label key has for the given dimension.\n", + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } }, - "warning": { - "type": "integer", - "description": "Number of alarms in warning state." + "min": { + "description": "The minimum value of all points included in the `result`.\n", + "type": "number" }, - "critical": { - "type": "integer", - "description": "Number of alarms in critical state." + "max": { + "description": "The maximum value of all points included in the `result`.\n", + "type": "number" } } + }, + "result": { + "$ref": "#/components/schemas/data_json_formats2" + }, + "timings": { + "type": "object" } } }, - "chart_summary": { + "jsonwrap2_sts": { + "description": "Statistical values\n", "type": "object", "properties": { - "hostname": { - "type": "string", - "description": "The hostname of the netdata server." - }, - "version": { - "type": "string", - "description": "netdata version of the server." - }, - "release_channel": { - "type": "string", - "description": "The release channel of the build on the server.", - "example": "nightly" - }, - "timezone": { - "type": "string", - "description": "The current timezone on the server." - }, - "os": { - "type": "string", - "description": "The netdata server host operating system.", - "enum": [ - "macos", - "linux", - "freebsd" - ] - }, - "history": { - "type": "number", - "description": "The duration, in seconds, of the round robin database maintained by netdata." - }, - "memory_mode": { - "type": "string", - "description": "The name of the database memory mode on the server." - }, - "update_every": { - "type": "number", - "description": "The default update frequency of the netdata server. All charts have an update frequency equal or bigger than this." - }, - "charts": { - "type": "object", - "description": "An object containing all the chart objects available at the netdata server. This is used as an indexed array. The key of each chart object is the id of the chart.", - "additionalProperties": { - "$ref": "#/components/schemas/chart" - } + "min": { + "description": "The minimum value of all metrics aggregated", + "type": "number" }, - "charts_count": { - "type": "number", - "description": "The number of charts." + "max": { + "description": "The maximum value of all metrics aggregated", + "type": "number" }, - "dimensions_count": { - "type": "number", - "description": "The total number of dimensions." + "avg": { + "description": "The average value of all metrics aggregated", + "type": "number" }, - "alarms_count": { - "type": "number", - "description": "The number of alarms." + "arp": { + "description": "The average anomaly rate of all metrics aggregated", + "type": "number" }, - "rrd_memory_bytes": { - "type": "number", - "description": "The size of the round robin database in bytes." + "con": { + "description": "The contribution percentage of all the metrics aggregated", + "type": "number" } } }, - "chart": { + "jsonwrap2_sts_raw": { + "description": "Statistical values when `raw` option is given.\n", "type": "object", "properties": { - "id": { - "type": "string", - "description": "The unique id of the chart." - }, - "name": { - "type": "string", - "description": "The name of the chart." - }, - "type": { - "type": "string", - "description": "The type of the chart. Types are not handled by netdata. You can use this field for anything you like." - }, - "family": { - "type": "string", - "description": "The family of the chart. Families are not handled by netdata. You can use this field for anything you like." - }, - "title": { - "type": "string", - "description": "The title of the chart." - }, - "priority": { - "type": "number", - "description": "The relative priority of the chart. Netdata does not care about priorities. This is just an indication of importance for the chart viewers to sort charts of higher priority (lower number) closer to the top. Priority sorting should only be used among charts of the same type or family." - }, - "enabled": { - "type": "boolean", - "description": "True when the chart is enabled. Disabled charts do not currently collect values, but they may have historical values available." - }, - "units": { - "type": "string", - "description": "The unit of measurement for the values of all dimensions of the chart." - }, - "data_url": { - "type": "string", - "description": "The absolute path to get data values for this chart. You are expected to use this path as the base when constructing the URL to fetch data values for this chart." - }, - "chart_type": { - "type": "string", - "description": "The chart type.", - "enum": [ - "line", - "area", - "stacked" - ] - }, - "duration": { - "type": "number", - "description": "The duration, in seconds, of the round robin database maintained by netdata." - }, - "first_entry": { - "type": "number", - "description": "The UNIX timestamp of the first entry (the oldest) in the round robin database." - }, - "last_entry": { - "type": "number", - "description": "The UNIX timestamp of the latest entry in the round robin database." + "min": { + "description": "The minimum value of all metrics aggregated", + "type": "number" }, - "update_every": { - "type": "number", - "description": "The update frequency of this chart, in seconds. One value every this amount of time is kept in the round robin database." + "max": { + "description": "The maximum value of all metrics aggregated", + "type": "number" }, - "dimensions": { - "type": "object", - "description": "An object containing all the chart dimensions available for the chart. This is used as an indexed array. For each pair in the dictionary: the key is the id of the dimension and the value is a dictionary containing the name.", - "additionalProperties": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the dimension" - } - } - } + "sum": { + "description": "The sum value of all metrics aggregated", + "type": "number" }, - "chart_variables": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/chart_variables" - } + "ars": { + "description": "The sum anomaly rate of all metrics aggregated", + "type": "number" }, - "green": { - "type": "number", - "nullable": true, - "description": "Chart health green threshold." + "vol": { + "description": "The volume of all the metrics aggregated", + "type": "number" }, - "red": { - "type": "number", - "nullable": true, - "description": "Chart health red threshold." + "cnt": { + "description": "The count of all metrics aggregated", + "type": "integer" } } }, - "context_summary": { + "jsonwrap2_items_count": { + "description": "Depending on the placement of this object, `items` may be `nodes`, `contexts`, `instances`, `dimensions`, `label keys`, `label key-value pairs`. Furthermore, if the whole object is missing it should be assumed that all its members are zero.\n", "type": "object", "properties": { - "hostname": { - "type": "string", - "description": "The hostname of the netdata server." - }, - "machine_guid": { - "type": "string", - "description": "The unique installation id of this netdata server." - }, - "node_id": { - "type": "string", - "description": "The unique node id of this netdata server at the hub.", - "example": "nightly" + "sl": { + "description": "The number of items `selected` to query. If absent it is zero.", + "type": "integer" }, - "claim_id": { - "type": "string", - "description": "The unique handshake id of this netdata server and the hub." + "ex": { + "description": "The number of items `excluded` from querying. If absent it is zero.", + "type": "integer" }, - "host_labels": { - "type": "object", - "description": "The host labels associated with this netdata server." + "qr": { + "description": "The number of items (out of `selected`) the query successfully `queried`. If absent it is zero.", + "type": "integer" }, - "context": { - "type": "object", - "description": "An object containing all the context objects available at the netdata server. This is used as an indexed array. The key of each context object is the id of the context.", - "additionalProperties": { - "$ref": "#/components/schemas/context" - } + "fl": { + "description": "The number of items (from `selected`) that `failed` to be queried. If absent it is zero.", + "type": "integer" } } }, - "context": { + "jsonwrap2_alerts_count": { + "description": "Counters about alert statuses. If this object is missing, it is assumed that all its members are zero.\n", "type": "object", "properties": { - "version": { - "type": "string", - "description": "The version of this context. The number are not sequential, but bigger numbers depict a newer object." - }, - "hub_version": { - "type": "string", - "description": "The version of this context, as known by hub." - }, - "family": { - "type": "string", - "description": "The family of the context. When multiple charts of a context have different families, the netdata server replaces the different parts with [x], so that the context can have only one family." + "nm": { + "description": "The name of the alert. Can be absent when the counters refer to more than one alert instances.", + "type": "string" }, - "title": { - "type": "string", - "description": "The title of the context. When multiple charts of a context have different titles, the netdata server replaces the different parts with [x], so that the context can have only one title." + "cl": { + "description": "The number of CLEAR alerts. If absent, it is zero.", + "type": "integer" }, - "priority": { - "type": "number", - "description": "The relative priority of the context. When multiple contexts have different priorities, the minimum among them is selected as the priority of the context." + "wr": { + "description": "The number of WARNING alerts. If absent, it is zero.", + "type": "integer" }, - "units": { - "type": "string", - "description": "The unit of measurement for the values of all dimensions of the context. If multiple charts of context have different units, the latest collected is selected." + "cr": { + "description": "The number of CRITICAL alerts. If absent, it is zero.", + "type": "integer" }, - "chart_type": { - "type": "string", - "description": "The chart type.", - "enum": [ - "line", - "area", - "stacked" - ] + "ot": { + "description": "The number of alerts that are not CLEAR, WARNING, CRITICAL (so, they are \"other\"). If absent, it is zero.\n", + "type": "integer" + } + } + }, + "api": { + "description": "The version of the API used.", + "type": "integer" + }, + "agents": { + "description": "An array of agent definitions consulted to compose this response.\n", + "type": "array", + "items": { + "type": "object", + "properties": { + "mg": { + "description": "The agent machine GUID.", + "type": "string", + "format": "uuid" + }, + "nd": { + "description": "The agent cloud node ID.", + "type": "string", + "format": "uuid" + }, + "nm": { + "description": "The agent hostname.", + "type": "string" + }, + "ai": { + "description": "The agent index ID for this agent, in this response.", + "type": "integer" + }, + "now": { + "description": "The current unix epoch timestamp of this agent.", + "type": "integer" + } + } + } + }, + "versions": { + "description": "Hashes that allow the caller to detect important database changes of Netdata agents.\n", + "type": "object", + "properties": { + "nodes_hard_hash": { + "description": "An auto-increment value that reflects the number of changes to the number of nodes maintained by the server. Everytime a node is added or removed, this number gets incremented.\n", + "type": "integer" }, - "first_time_t": { - "type": "number", - "description": "The UNIX timestamp of the first entry (the oldest) in the database." + "contexts_hard_hash": { + "description": "An auto-increment value that reflects the number of changes to the number of contexts maintained by the server. Everytime a context is added or removed, this number gets incremented.\n", + "type": "integer" }, - "last_time_t": { - "type": "number", - "description": "The UNIX timestamp of the latest entry in the database." + "contexts_soft_hash": { + "description": "An auto-increment value that reflects the number of changes to the queue that sends contexts updates to Netdata Cloud. Everytime the contents of a context are updated, this number gets incremented.\n", + "type": "integer" }, - "charts": { - "type": "object", - "description": "An object containing all the charts available for the chart. This is used as an indexed array. For each pair in the dictionary, the key is the id of the chart and the value provides all details about the chart." + "alerts_hard_hash": { + "description": "An auto-increment value that reflects the number of changes to the number of alerts. Everytime an alert is added or removed, this number gets incremented.\n", + "type": "integer" + }, + "alerts_soft_hash": { + "description": "An auto-increment value that reflects the number of alerts transitions. Everytime an alert transitions to a new state, this number gets incremented.\n", + "type": "integer" } } }, - "alarm_variables": { + "nodeBasic": { "type": "object", + "description": "Basic information about a node.", + "required": [ + "ni", + "st" + ], "properties": { - "chart": { - "type": "string", - "description": "The unique id of the chart." - }, - "chart_name": { + "mg": { + "description": "The machine guid of the node. May not be available if the request is served by the Netdata Cloud.", "type": "string", - "description": "The name of the chart." + "format": "UUID" }, - "cnart_context": { + "nd": { + "description": "The node id of the node. May not be available if the node is not registered to Netdata Cloud.", "type": "string", - "description": "The context of the chart. It is shared across multiple monitored software or hardware instances and used in alarm templates." + "format": "UUID" }, - "family": { - "type": "string", - "description": "The family of the chart." + "nm": { + "description": "The name (hostname) of the node.", + "type": "string" }, - "host": { - "type": "string", - "description": "The host containing the chart." + "ni": { + "description": "The node index id, a number that uniquely identifies this node for this query.", + "type": "integer" }, - "chart_variables": { + "st": { + "description": "Status information about the communication with this node.", "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/chart_variables" + "properties": { + "ai": { + "description": "The agent index id that has been contacted for this node.", + "type": "integer" + }, + "code": { + "description": "The HTTP response code of the response for this node. When working directly with an agent, this is always 200. If the `code` is missing, it should be assumed to be 200.", + "type": "integer" + }, + "msg": { + "description": "A human readable description of the error, if any. If `msg` is missing, or is the empty string `\"\"` or is `null`, there is no description associated with the current status.", + "type": "string" + }, + "ms": { + "description": "The time in milliseconds this node took to respond, or if the local agent responded for this node, the time it needed to execute the query. If `ms` is missing, the time that was required to query this node is unknown.", + "type": "number" + } } + } + } + }, + "nodeWithDataStatistics": { + "allOf": [ + { + "$ref": "#/components/schemas/nodeBasic" }, - "family_variables": { + { "type": "object", + "description": "`is` stands for instances, `ds` for dimensions, `al` for alerts, `sts` for statistics.\n", "properties": { - "varname1": { - "type": "number", - "format": "float" + "is": { + "$ref": "#/components/schemas/jsonwrap2_items_count" }, - "varname2": { - "type": "number", - "format": "float" + "ds": { + "$ref": "#/components/schemas/jsonwrap2_items_count" + }, + "al": { + "$ref": "#/components/schemas/jsonwrap2_alerts_count" + }, + "sts": { + "oneOf": [ + { + "$ref": "#/components/schemas/jsonwrap2_sts" + }, + { + "$ref": "#/components/schemas/jsonwrap2_sts_raw" + } + ] } } + } + ] + }, + "nodeFull": { + "allOf": [ + { + "$ref": "#/components/schemas/nodeBasic" }, - "host_variables": { + { "type": "object", "properties": { - "varname1": { - "type": "number", - "format": "float" + "version": { + "description": "The version of the Netdata Agent the node runs.", + "type": "string" }, - "varname2": { - "type": "number", - "format": "float" + "hops": { + "description": "How many hops away from the origin node, the queried one is. 0 means the agent itself is the origin node.", + "type": "integer" + }, + "state": { + "description": "The current state of the node on this agent.", + "type": "string", + "enum": [ + "reachable", + "stale", + "offline" + ] } } } + ] + }, + "context2Basic": { + "type": "object", + "properties": { + "family": { + "type": "string" + }, + "priority": { + "type": "integer" + }, + "first_entry": { + "type": "integer" + }, + "last_entry": { + "type": "integer" + }, + "live": { + "type": "boolean" + } } }, - "chart_variables": { + "contexts2": { + "description": "`/api/v2/contexts` and `/api/v2/q` response about multi-node contexts hosted by a Netdata agent.\n", "type": "object", "properties": { - "varname1": { - "type": "number", - "format": "float" + "api": { + "$ref": "#/components/schemas/api" }, - "varname2": { - "type": "number", - "format": "float" + "agents": { + "$ref": "#/components/schemas/agents" + }, + "versions": { + "$ref": "#/components/schemas/versions" + }, + "contexts": { + "additionalProperties": { + "$ref": "#/components/schemas/context2Basic" + } } } }, - "data": { + "jsonwrap1": { "type": "object", "discriminator": { "propertyName": "format" @@ -2432,7 +3546,7 @@ "properties": { "api": { "type": "number", - "description": "The API version this conforms to, currently 1." + "description": "The API version this conforms to." }, "id": { "type": "string", @@ -2519,149 +3633,173 @@ "additionalProperties": { "$ref": "#/components/schemas/chart_variables" } + }, + "result": { + "$ref": "#/components/schemas/data_json_formats1" } } }, - "data_json": { - "description": "Data response in json format.", - "allOf": [ + "data_json_formats1": { + "description": "Depending on the `format` given to a data query, any of the following may be returned.\n", + "oneOf": [ { - "$ref": "#/components/schemas/data" + "$ref": "#/components/schemas/data_json" }, { - "properties": { - "result": { - "type": "object", - "properties": { - "labels": { - "description": "The dimensions retrieved from the chart.", - "type": "array", - "items": { - "type": "string" - } - }, - "data": { - "description": "The data requested, one element per sample with each element containing the values of the dimensions described in the labels value.", - "type": "array", - "items": { - "type": "number" - } - } - }, - "description": "The result requested, in the format requested." - } - } + "$ref": "#/components/schemas/data_datatable" + }, + { + "$ref": "#/components/schemas/data_csvjsonarray" + }, + { + "$ref": "#/components/schemas/data_array" + }, + { + "$ref": "#/components/schemas/data_txt" } ] }, - "data_flat": { - "description": "Data response in csv / tsv / tsv-excel / ssv / ssv-comma / markdown / html formats.", - "allOf": [ + "data_json_formats2": { + "description": "Depending on the `format` given to a data query, any of the following may be returned.\n", + "oneOf": [ { - "$ref": "#/components/schemas/data" + "$ref": "#/components/schemas/data_json2" }, { - "properties": { - "result": { - "type": "string" - } - } + "$ref": "#/components/schemas/data_json_formats1" } ] }, - "data_array": { - "description": "Data response in array format.", - "allOf": [ - { - "$ref": "#/components/schemas/data" + "data_json2": { + "type": "object", + "properties": { + "labels": { + "description": "The IDs of the dimensions returned. The first is always `time`.\n", + "type": "array", + "items": { + "type": "string" + } }, - { + "point": { + "description": "The format of each point returned.\n", + "type": "object", "properties": { - "result": { - "type": "array", - "items": { - "type": "number" - } + "value": { + "description": "The index of the value in each point.\n", + "type": "integer" + }, + "arp": { + "description": "The index of the anomaly rate in each point.\n", + "type": "integer" + }, + "pa": { + "description": "The index of the point annotations in each point.\nThis is a bitmap. `EMPTY = 1`, `RESET = 2`, `PARTIAL = 4`.\n`EMPTY` means the point has no value.\n`RESET` means that at least one metric aggregated experienced an overflow (a counter that wrapped).\n`PARTIAL` means that this point should have more metrics aggregated into it, but not all metrics had data.\n", + "type": "integer" + }, + "count": { + "description": "The number of metrics aggregated into this point. This exists only when the option `raw` is given to the query.\n", + "type": "integer" } } + }, + "data": { + "type": "array", + "items": { + "allOf": [ + { + "type": "integer" + }, + { + "type": "array" + } + ] + } } - ] + } }, - "data_csvjsonarray": { - "description": "Data response in csvjsonarray format.", - "allOf": [ - { - "$ref": "#/components/schemas/data" + "data_json": { + "description": "Data response in `json` format.", + "type": "object", + "properties": { + "labels": { + "description": "The dimensions retrieved from the chart.", + "type": "array", + "items": { + "type": "string" + } }, - { - "properties": { - "result": { - "description": "The first inner array contains strings showing the labels of each column, each subsequent array contains the values for each point in time.", - "type": "array", - "items": { - "type": "array", - "items": {} - } - } + "data": { + "description": "The data requested, one element per sample with each element containing the values of the dimensions described in the labels value.\n", + "type": "array", + "items": { + "type": "number" } } - ] + } + }, + "data_txt": { + "description": "Data response in `csv`, `tsv`, `tsv-excel`, `ssv`, `ssv-comma`, `markdown`, `html` formats.\n", + "type": "string" + }, + "data_array": { + "description": "Data response in `array` format.", + "type": "array", + "items": { + "type": "number" + } + }, + "data_csvjsonarray": { + "description": "The first inner array contains strings showing the labels of each column, each subsequent array contains the values for each point in time.\n", + "type": "array", + "items": { + "type": "array", + "items": {} + } }, "data_datatable": { - "description": "Data response in datatable / datasource formats (suitable for Google Charts).", - "allOf": [ - { - "$ref": "#/components/schemas/data" + "description": "Data response in datatable / datasource formats (suitable for Google Charts).\n", + "type": "object", + "properties": { + "cols": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "description": "Always empty - for future use." + }, + "label": { + "description": "The dimension returned from the chart." + }, + "pattern": { + "description": "Always empty - for future use." + }, + "type": { + "description": "The type of data in the column / chart-dimension." + }, + "p": { + "description": "Contains any annotations for the column." + } + }, + "required": [ + "id", + "label", + "pattern", + "type" + ] + } }, - { - "properties": { - "result": { - "type": "object", - "properties": { - "cols": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "description": "Always empty - for future use." - }, - "label": { - "description": "The dimension returned from the chart." - }, - "pattern": { - "description": "Always empty - for future use." - }, - "type": { - "description": "The type of data in the column / chart-dimension." - }, - "p": { - "description": "Contains any annotations for the column." - } - }, - "required": [ - "id", - "label", - "pattern", - "type" - ] - } - }, - "rows": { - "type": "array", - "items": { - "type": "object", - "properties": { - "c": { - "type": "array", - "items": { - "properties": { - "v": { - "description": "Each value in the row is represented by an object named `c` with five v fields: data, null, null, 0, the value. This format is fixed by the Google Charts API." - } - } - } - } + "rows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "c": { + "type": "array", + "items": { + "properties": { + "v": { + "description": "Each value in the row is represented by an object named `c` with five v fields: data, null, null, 0, the value. This format is fixed by the Google Charts API.\"\n" } } } @@ -2669,7 +3807,7 @@ } } } - ] + } }, "alarms": { "type": "object", @@ -3009,7 +4147,7 @@ "properties": { "aclk-available": { "type": "string", - "description": "Describes whether this agent is capable of connection to the Cloud. False means agent has been built without ACLK component either on purpose (user choice) or due to missing dependency." + "description": "Describes whether this agent is capable of connection to the Cloud. False means agent has been built without ACLK component either on purpose (user choice) or due to missing dependency.\n" }, "aclk-version": { "type": "integer", @@ -3165,6 +4303,9 @@ } } }, + "weights2": { + "type": "object" + }, "weights": { "type": "object", "properties": { diff --git a/web/api/netdata-swagger.yaml b/web/api/netdata-swagger.yaml index fced6544..c25f0b71 100644 --- a/web/api/netdata-swagger.yaml +++ b/web/api/netdata-swagger.yaml @@ -2,11 +2,124 @@ openapi: 3.0.0 info: title: Netdata API description: Real-time performance and health monitoring. - version: 1.33.1 + version: "1.38" + contact: + name: Netdata Agent API + email: info@netdata.cloud + url: https://netdata.cloud + license: + name: GPL v3+ + url: https://github.com/netdata/netdata/blob/master/LICENSE +servers: + - url: https://registry.my-netdata.io + - url: http://registry.my-netdata.io + - url: http://localhost:19999 +tags: + - name: nodes + description: Everything related to monitored nodes + - name: charts + description: Everything related to chart instances - DO NOT USE IN NEW CODE - use contexts instead + - name: contexts + description: Everything related contexts - in new code, use this instead of charts + - name: data + description: Everything related to data queries + - name: badges + description: Everything related to dynamic badges based on metric data + - name: weights + description: Everything related to scoring / weighting metrics + - name: functions + description: Everything related to functions + - name: alerts + description: Everything related to alerts + - name: management + description: Everything related to managing netdata agents paths: - /info: + /api/v2/nodes: + get: + operationId: getNodes2 + tags: + - nodes + summary: Nodes Info v2 + description: | + Get a list of all nodes hosted by this Netdata agent. + parameters: + - $ref: '#/components/parameters/scopeNodes' + - $ref: '#/components/parameters/scopeContexts' + - $ref: '#/components/parameters/filterNodes' + - $ref: '#/components/parameters/filterContexts' + responses: + "200": + description: OK + content: + application/json: + schema: + description: | + `/api/v2/nodes` response for all nodes hosted by a Netdata agent. + type: object + properties: + api: + $ref: '#/components/schemas/api' + agents: + $ref: '#/components/schemas/agents' + versions: + $ref: '#/components/schemas/versions' + nodes: + type: array + items: + $ref: '#/components/schemas/nodeFull' + /api/v2/contexts: + get: + operationId: getContexts2 + tags: + - contexts + summary: Contexts Info v2 + description: | + Get a list of all contexts, across all nodes, hosted by this Netdata agent. + parameters: + - $ref: '#/components/parameters/scopeNodes' + - $ref: '#/components/parameters/scopeContexts' + - $ref: '#/components/parameters/filterNodes' + - $ref: '#/components/parameters/filterContexts' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/contexts2' + /api/v2/q: + get: + operationId: q2 + tags: + - contexts + summary: Full Text Search v2 + description: | + Get a list of contexts, across all nodes, hosted by this Netdata agent, matching a string expression + parameters: + - name: q + in: query + description: The strings to search for, formatted as a simple pattern + required: true + schema: + type: string + format: simple pattern + - $ref: '#/components/parameters/scopeNodes' + - $ref: '#/components/parameters/scopeContexts' + - $ref: '#/components/parameters/filterNodes' + - $ref: '#/components/parameters/filterContexts' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/contexts2' + /api/v1/info: get: - summary: Get netdata basic information + operationId: getNodeInfo1 + tags: + - nodes + summary: Node Info v1 description: | The info endpoint returns basic information about netdata. It provides: * netdata version @@ -28,9 +141,12 @@ paths: $ref: "#/components/schemas/info" "503": description: netdata daemon not ready (used for health checks). - /charts: + /api/v1/charts: get: - summary: Get a list of all charts available at the server + operationId: getNodeCharts1 + tags: + - charts + summary: List all charts v1 - EOL description: The charts endpoint returns a summary about all charts stored in the netdata server. responses: @@ -40,19 +156,15 @@ paths: application/json: schema: $ref: "#/components/schemas/chart_summary" - /chart: + /api/v1/chart: get: - summary: Get info about a specific chart + operationId: getNodeChart1 + tags: + - charts + summary: Get one chart v1 - EOL description: The chart endpoint returns detailed information about a chart. parameters: - - name: chart - in: query - description: The id of the chart as returned by the /charts call. - required: true - schema: - type: string - format: as returned by /charts - default: system.cpu + - $ref: '#/components/parameters/chart' responses: "200": description: A javascript object with detailed information about the chart. @@ -64,70 +176,21 @@ paths: description: No chart id was supplied in the request. "404": description: No chart with the given id is found. - /contexts: + /api/v1/contexts: get: - summary: Get a list of all contexts available at the server + operationId: getNodeContexts1 + tags: + - contexts + summary: Get a list of all node contexts available v1 description: The contexts endpoint returns a summary about all contexts stored in the netdata server. parameters: - - name: options - in: query - description: Options that affect data generation. - required: false - allowEmptyValue: true - schema: - type: array - items: - type: string - enum: - - full - - all - - charts - - dimensions - - labels - - uuids - - queue - - flags - - deleted - - deepscan - default: - - full - - name: after - in: query - description: limit the results on context having data after this timestamp. - required: false - schema: - type: number - format: integer - - name: before - in: query - description: limit the results on context having data before this timestamp. - required: false - schema: - type: number - format: integer - - name: chart_label_key - in: query - description: a simple pattern matching charts label keys (use comma or pipe as separator) - required: false - allowEmptyValue: true - schema: - type: string - - name: chart_labels_filter - in: query - description: "a simple pattern matching charts label key and values (use colon for equality, comma or pipe - as separator)" - required: false - allowEmptyValue: true - schema: - type: string - - name: dimensions - in: query - description: a simple pattern matching dimensions (use comma or pipe as separator) - required: false - allowEmptyValue: true - schema: - type: string + - $ref: '#/components/parameters/dimensions' + - $ref: '#/components/parameters/chart_label_key' + - $ref: '#/components/parameters/chart_labels_filter' + - $ref: '#/components/parameters/contextOptions1' + - $ref: '#/components/parameters/after' + - $ref: '#/components/parameters/before' responses: "200": description: An array of contexts. @@ -135,404 +198,348 @@ paths: application/json: schema: $ref: "#/components/schemas/context_summary" - /context: + /api/v1/context: get: + operationId: getNodeContext1 + tags: + - contexts summary: Get info about a specific context - description: The context endpoint returns detailed information about a given context. + description: | + The context endpoint returns detailed information about a given context. + The `context` parameter is required for this call. parameters: - - name: context - in: query - description: The id of the context as returned by the /contexts call. - required: true - schema: - type: string - format: as returned by /contexts - default: system.cpu - - name: options + - $ref: '#/components/parameters/context' + - $ref: '#/components/parameters/dimensions' + - $ref: '#/components/parameters/chart_label_key' + - $ref: '#/components/parameters/chart_labels_filter' + - $ref: '#/components/parameters/contextOptions1' + - $ref: '#/components/parameters/after' + - $ref: '#/components/parameters/before' + responses: + "200": + description: A javascript object with detailed information about the context. + content: + application/json: + schema: + $ref: "#/components/schemas/context" + "400": + description: No context id was supplied in the request. + "404": + description: No context with the given id is found. + /api/v2/data: + get: + operationId: dataQuery2 + tags: + - data + summary: Data Query v2 + description: | + Multi-node, multi-context, multi-instance, multi-dimension data queries, with time and metric aggregation. + parameters: + - name: group_by in: query - description: Options that affect data generation. + description: | + A comma separated list of the groupings required. + All possible values can be combined together, except `selected`. If `selected` is given in the list, all others are ignored. + The order they are placed in the list is currently ignored. required: false - allowEmptyValue: true schema: type: array items: type: string enum: - - full - - all - - charts - - dimensions - - labels - - uuids - - queue - - flags - - deleted - - deepscan + - dimension + - instance + - percentage-of-instance + - label + - node + - context + - units + - selected default: - - full - - name: after - in: query - description: limit the results on context having data after this timestamp. - required: false - schema: - type: number - format: integer - - name: before - in: query - description: limit the results on context having data before this timestamp. - required: false - schema: - type: number - format: integer - - name: chart_label_key - in: query - description: a simple pattern matching charts label keys (use comma or pipe as separator) - required: false - allowEmptyValue: true - schema: - type: string - - name: chart_labels_filter + - dimension + - name: group_by_label in: query - description: "a simple pattern matching charts label key and values (use colon for equality, comma or pipe - as separator)" + description: | + A comma separated list of the label keys to group by their values. The order of the labels in the list is respected. required: false - allowEmptyValue: true schema: type: string - - name: dimensions + format: comma separated list of label keys to group by + default: "" + - name: aggregation in: query - description: a simple pattern matching dimensions (use comma or pipe as separator) + description: | + The aggregation function to apply when grouping metrics together. + When option `raw` is given, `average` and `avg` behave like `sum` and the caller is expected to calculate the average. required: false - allowEmptyValue: true schema: type: string + enum: + - min + - max + - avg + - average + - sum + default: average + - $ref: '#/components/parameters/scopeNodes' + - $ref: '#/components/parameters/scopeContexts' + - $ref: '#/components/parameters/filterNodes' + - $ref: '#/components/parameters/filterContexts' + - $ref: '#/components/parameters/filterInstances' + - $ref: '#/components/parameters/filterLabels' + - $ref: '#/components/parameters/filterAlerts' + - $ref: '#/components/parameters/filterDimensions' + - $ref: '#/components/parameters/after' + - $ref: '#/components/parameters/before' + - $ref: '#/components/parameters/points' + - $ref: '#/components/parameters/tier' + - $ref: '#/components/parameters/dataQueryOptions' + - $ref: '#/components/parameters/dataTimeGroup2' + - $ref: '#/components/parameters/dataTimeGroupOptions2' + - $ref: '#/components/parameters/dataTimeResampling2' + - $ref: '#/components/parameters/dataFormat2' + - $ref: '#/components/parameters/timeoutMS' + - $ref: '#/components/parameters/callback' + - $ref: '#/components/parameters/filename' + - $ref: '#/components/parameters/tqx' responses: "200": - description: A javascript object with detailed information about the context. + description: | + The call was successful. The response includes the data in the format requested. content: application/json: schema: - $ref: "#/components/schemas/context" + oneOf: + - $ref: '#/components/schemas/jsonwrap2' + - $ref: '#/components/schemas/data_json_formats2' + text/plain: + schema: + type: string + format: according to the format requested. + text/html: + schema: + type: string + format: html + application/x-javascript: + schema: + type: string + format: javascript "400": - description: No context id was supplied in the request. - "404": - description: No context with the given id is found. - /alarm_variables: + description: | + Bad request - the body will include a message stating what is wrong. + "500": + description: | + Internal server error. This usually means the server is out of memory. + /api/v1/data: get: - summary: List variables available to configure alarms for a chart - description: Returns the basic information of a chart and all the variables that can - be used in alarm and template health configurations for the particular - chart or family. + operationId: dataQuery1 + tags: + - data + summary: Data Query v1 - Single node, single chart or context queries. without group-by. + description: | + Query metric data of a chart or context of a node and return a dataset having time-series data for all dimensions available. + For group-by functionality, use `/api/v2/data`. + At least a `chart` or a `context` have to be given for the data query to be executed. parameters: - - name: chart - in: query - description: The id of the chart as returned by the /charts call. - required: true - schema: - type: string - format: as returned by /charts - default: system.cpu + - $ref: '#/components/parameters/chart' + - $ref: '#/components/parameters/context' + - $ref: '#/components/parameters/dimension' + - $ref: '#/components/parameters/chart_label_key' + - $ref: '#/components/parameters/chart_labels_filter' + - $ref: '#/components/parameters/after' + - $ref: '#/components/parameters/before' + - $ref: '#/components/parameters/points' + - $ref: '#/components/parameters/tier' + - $ref: '#/components/parameters/dataQueryOptions' + - $ref: '#/components/parameters/dataFormat1' + - $ref: '#/components/parameters/dataTimeGroup1' + - $ref: '#/components/parameters/dataTimeGroupOptions1' + - $ref: '#/components/parameters/dataTimeResampling1' + - $ref: '#/components/parameters/timeoutMS' + - $ref: '#/components/parameters/callback' + - $ref: '#/components/parameters/filename' + - $ref: '#/components/parameters/tqx' responses: "200": - description: A javascript object with information about the chart and the - available variables. + description: | + The call was successful. The response includes the data in the format requested. content: application/json: schema: - $ref: "#/components/schemas/alarm_variables" + oneOf: + - $ref: '#/components/schemas/jsonwrap1' + - $ref: '#/components/schemas/data_json_formats1' + text/plain: + schema: + type: string + format: according to the format requested. + text/html: + schema: + type: string + format: html + application/x-javascript: + schema: + type: string + format: javascript "400": description: Bad request - the body will include a message stating what is wrong. "404": - description: No chart with the given id is found. + description: Chart or context is not found. The supplied chart or context will be reported. "500": description: Internal server error. This usually means the server is out of memory. - /data: + /api/v1/allmetrics: get: - summary: Get collected data for a specific chart - description: The data endpoint returns data stored in the round robin database of a - chart. + operationId: allMetrics1 + tags: + - data + summary: All Metrics v1 - Fetch latest value for all metrics + description: | + The `allmetrics` endpoint returns the latest value of all metrics maintained for a netdata node. parameters: - - name: chart + - name: format in: query - description: The id of the chart as returned by the /charts call. Note chart or context must be specified - required: false - allowEmptyValue: false + description: The format of the response to be returned. + required: true schema: type: string - format: as returned by /charts - default: system.cpu - - name: context + enum: + - shell + - prometheus + - prometheus_all_hosts + - json + default: shell + - name: filter in: query - description: The context of the chart as returned by the /charts call. Note chart or context must be specified + description: Allows to filter charts out using simple patterns. required: false - allowEmptyValue: false schema: type: string - format: as returned by /charts - - name: dimension + format: any text + - name: variables in: query - description: Zero, one or more dimension ids or names, as returned by the /chart - call, separated with comma or pipe. Netdata simple patterns are - supported. + description: | + When enabled, netdata will expose various system configuration variables. required: false - allowEmptyValue: false - schema: - type: array - items: - type: string - format: as returned by /charts - - name: after - in: query - description: "This parameter can either be an absolute timestamp specifying the - starting point of the data to be returned, or a relative number of - seconds (negative, relative to parameter: before). Netdata will - assume it is a relative number if it is less that 3 years (in seconds). - If not specified the default is -600 seconds. Netdata will adapt this - parameter to the boundaries of the round robin database unless the allow_past - option is specified." - required: true - allowEmptyValue: false schema: - type: number - format: integer - default: -600 - - name: before + type: string + enum: + - yes + - no + default: no + - name: help in: query - description: This parameter can either be an absolute timestamp specifying the - ending point of the data to be returned, or a relative number of - seconds (negative), relative to the last collected timestamp. - Netdata will assume it is a relative number if it is less than 3 - years (in seconds). Netdata will adapt this parameter to the - boundaries of the round robin database. The default is zero (i.e. - the timestamp of the last value collected). + description: | + Enable or disable HELP lines in prometheus output. required: false schema: - type: number - format: integer - default: 0 - - name: points + type: string + enum: + - yes + - no + default: no + - name: types in: query - description: The number of points to be returned. If not given, or it is <= 0, or - it is bigger than the points stored in the round robin database for - this chart for the given duration, all the available collected - values for the given duration will be returned. - required: true - allowEmptyValue: false + description: | + Enable or disable TYPE lines in prometheus output. + required: false schema: - type: number - format: integer - default: 20 - - name: chart_label_key + type: string + enum: + - yes + - no + default: no + - name: timestamps in: query - description: Specify the chart label keys that need to match for context queries as comma separated values. - At least one matching key is needed to match the corresponding chart. + description: | + Enable or disable timestamps in prometheus output. required: false - allowEmptyValue: false schema: type: string - format: key1,key2,key3 - - name: chart_labels_filter + enum: + - yes + - no + default: yes + - name: names in: query - description: Specify the chart label keys and values to match for context queries. All keys/values need to - match for the chart to be included in the query. The labels are specified as key1:value1,key2:value2 + description: | + When enabled netdata will report dimension names. When disabled netdata will report dimension IDs. The default is controlled in netdata.conf. required: false - allowEmptyValue: false schema: type: string - format: key1:value1,key2:value2,key3:value3 - - name: group + enum: + - yes + - no + default: yes + - name: oldunits in: query - description: The grouping method. If multiple collected values are to be grouped - in order to return fewer points, this parameters defines the method - of grouping. methods supported "min", "max", "average", "sum", - "incremental-sum". "max" is actually calculated on the absolute - value collected (so it works for both positive and negative - dimensions to return the most extreme value in either direction). - required: true - allowEmptyValue: false + description: | + When enabled, netdata will show metric names for the default `source=average` as they appeared before 1.12, by using the legacy unit naming conventions. + required: false schema: type: string enum: - - min - - max - - average - - median - - stddev - - sum - - incremental-sum - - ses - - des - - cv - - countif - - percentile - - percentile25 - - percentile50 - - percentile75 - - percentile80 - - percentile90 - - percentile95 - - percentile97 - - percentile98 - - percentile99 - - trimmed-mean - - trimmed-mean1 - - trimmed-mean2 - - trimmed-mean3 - - trimmed-mean5 - - trimmed-mean10 - - trimmed-mean15 - - trimmed-mean20 - - trimmed-mean25 - - trimmed-median - - trimmed-median1 - - trimmed-median2 - - trimmed-median3 - - trimmed-median5 - - trimmed-median10 - - trimmed-median15 - - trimmed-median20 - - trimmed-median25 - default: average - - name: group_options + - yes + - no + default: yes + - name: hideunits in: query - description: When the group function supports additional parameters, this field - can be used to pass them to it. Currently only "countif" supports this. + description: | + When enabled, netdata will not include the units in the metric names, for the default `source=average`. required: false - allowEmptyValue: false schema: type: string - - name: gtime + enum: + - yes + - no + default: yes + - name: server in: query - description: The grouping number of seconds. This is used in conjunction with - group=average to change the units of metrics (ie when the data is - per-second, setting gtime=60 will turn them to per-minute). + description: | + Set a distinct name of the client querying prometheus metrics. Netdata will use the client IP if this is not set. required: false - allowEmptyValue: false schema: - type: number - format: integer - default: 0 - - name: timeout + type: string + format: any text + - name: prefix in: query - description: Specify a timeout value in milliseconds after which the agent will - abort the query and return a 503 error. A value of 0 indicates no timeout. + description: | + Prefix all prometheus metrics with this string. required: false - allowEmptyValue: false schema: - type: number - format: integer - default: 0 - - name: format + type: string + format: any text + - name: data in: query - description: The format of the data to be returned. - required: true - allowEmptyValue: false + description: | + Select the prometheus response data source. There is a setting in netdata.conf for the default. + required: false schema: type: string enum: - - json - - jsonp - - csv - - tsv - - tsv-excel - - ssv - - ssvcomma - - datatable - - datasource - - html - - markdown - - array - - csvjsonarray - default: json - - name: options - in: query - description: Options that affect data generation. - required: false - allowEmptyValue: false - schema: - type: array - items: - type: string - enum: - - nonzero - - flip - - jsonwrap - - min2max - - seconds - - milliseconds - - abs - - absolute - - absolute-sum - - null2zero - - objectrows - - google_json - - percentage - - unaligned - - match-ids - - match-names - - allow_past - - anomaly-bit - default: - - seconds - - jsonwrap - - name: callback - in: query - description: For JSONP responses, the callback function name. - required: false - allowEmptyValue: true - schema: - type: string - - name: filename - in: query - description: "Add Content-Disposition: attachment; filename= header to - the response, that will instruct the browser to save the response - with the given filename." - required: false - allowEmptyValue: true - schema: - type: string - - name: tqx - in: query - description: "[Google Visualization - API](https://developers.google.com/chart/interactive/docs/dev/imple\ - menting_data_source?hl=en) formatted parameter." - required: false - allowEmptyValue: true - schema: - type: string + - as-collected + - average + - sum + default: average responses: "200": - description: The call was successful. The response includes the data in the - format requested. Swagger2.0 does not process the discriminator - field to show polymorphism. The response will be one of the - sub-types of the data-schema according to the chosen format, e.g. - json -> data_json. - content: - application/json: - schema: - $ref: "#/components/schemas/data" + description: All the metrics returned in the format requested. "400": - description: Bad request - the body will include a message stating what is wrong. - "404": - description: Chart or context is not found. The supplied chart or context will be reported. - "500": - description: Internal server error. This usually means the server is out of - memory. - /badge.svg: + description: The format requested is not supported. + /api/v1/badge.svg: get: + operationId: badge1 + tags: + - badges summary: Generate a badge in form of SVG image for a chart (or dimension) description: Successful responses are SVG images. parameters: - - name: chart - in: query - description: The id of the chart as returned by the /charts call. - required: true - allowEmptyValue: false - schema: - type: string - format: as returned by /charts - default: system.cpu + - $ref: '#/components/parameters/chart' + - $ref: '#/components/parameters/dimension' + - $ref: '#/components/parameters/after' + - $ref: '#/components/parameters/before' + - $ref: '#/components/parameters/dataTimeGroup1' + - $ref: '#/components/parameters/dataQueryOptions' - name: alarm in: query description: The name of an alarm linked to the chart. @@ -541,120 +548,6 @@ paths: schema: type: string format: any text - - name: dimension - in: query - description: Zero, one or more dimension ids, as returned by the /chart call. - required: false - allowEmptyValue: false - schema: - type: array - items: - type: string - format: as returned by /charts - - name: after - in: query - description: This parameter can either be an absolute timestamp specifying the - starting point of the data to be returned, or a relative number of - seconds, to the last collected timestamp. Netdata will assume it is - a relative number if it is smaller than the duration of the round - robin database for this chart. So, if the round robin database is - 3600 seconds, any value from -3600 to 3600 will trigger relative - arithmetics. Netdata will adapt this parameter to the boundaries of - the round robin database. - required: true - allowEmptyValue: false - schema: - type: number - format: integer - default: -600 - - name: before - in: query - description: This parameter can either be an absolute timestamp specifying the - ending point of the data to be returned, or a relative number of - seconds, to the last collected timestamp. Netdata will assume it is - a relative number if it is smaller than the duration of the round - robin database for this chart. So, if the round robin database is - 3600 seconds, any value from -3600 to 3600 will trigger relative - arithmetics. Netdata will adapt this parameter to the boundaries of - the round robin database. - required: false - schema: - type: number - format: integer - default: 0 - - name: group - in: query - description: The grouping method. If multiple collected values are to be grouped - in order to return fewer points, this parameters defines the method - of grouping. methods are supported "min", "max", "average", "sum", - "incremental-sum". "max" is actually calculated on the absolute - value collected (so it works for both positive and negative - dimensions to return the most extreme value in either direction). - required: true - allowEmptyValue: false - schema: - type: string - enum: - - min - - max - - average - - median - - stddev - - sum - - incremental-sum - - ses - - des - - cv - - countif - - percentile - - percentile25 - - percentile50 - - percentile75 - - percentile80 - - percentile90 - - percentile95 - - percentile97 - - percentile98 - - percentile99 - - trimmed-mean - - trimmed-mean1 - - trimmed-mean2 - - trimmed-mean3 - - trimmed-mean5 - - trimmed-mean10 - - trimmed-mean15 - - trimmed-mean20 - - trimmed-mean25 - - trimmed-median - - trimmed-median1 - - trimmed-median2 - - trimmed-median3 - - trimmed-median5 - - trimmed-median10 - - trimmed-median15 - - trimmed-median20 - - trimmed-median25 - default: average - - name: options - in: query - description: Options that affect data generation. - required: false - allowEmptyValue: true - schema: - type: array - items: - type: string - enum: - - abs - - absolute - - display-absolute - - absolute-sum - - null2zero - - percentage - - unaligned - - anomaly-bit - default: - - absolute - name: label in: query description: A text to be used as the label. @@ -673,9 +566,8 @@ paths: format: any text - name: label_color in: query - description: "A color to be used for the background of the label side(left side) of the badge. - One of predefined colors or specific color in hex `RGB` or `RRGGBB` format (without preceding `#` character). - If value wrong or not given default color will be used." + description: | + A color to be used for the background of the label side(left side) of the badge. One of predefined colors or specific color in hex `RGB` or `RRGGBB` format (without preceding `#` character). If value wrong or not given default color will be used. required: false allowEmptyValue: true schema: @@ -697,12 +589,8 @@ paths: format: ^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ - name: value_color in: query - description: "A color to be used for the background of the value *(right)* part of badge. You can set - multiple using a pipe with a condition each, like this: - `color, <, >=, <=, =, :null (to check if no value exists). - Each color can be specified in same manner as for `label_color` parameter. - Currently only integers are supported as values." + description: | + A color to be used for the background of the value *(right)* part of badge. You can set multiple using a pipe with a condition each, like this: `color, <, >=, <=, =, :null (to check if no value exists). Each color can be specified in same manner as for `label_color` parameter. Currently only integers are supported as values. required: false allowEmptyValue: true schema: @@ -710,9 +598,8 @@ paths: format: any text - name: text_color_lbl in: query - description: "Font color for label *(left)* part of the badge. One of predefined colors or as HTML hexadecimal - color without preceding `#` character. Formats allowed `RGB` or `RRGGBB`. If no or wrong value given default - color will be used." + description: | + Font color for label *(left)* part of the badge. One of predefined colors or as HTML hexadecimal color without preceding `#` character. Formats allowed `RGB` or `RRGGBB`. If no or wrong value given default color will be used. required: false allowEmptyValue: true schema: @@ -734,9 +621,8 @@ paths: format: ^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ - name: text_color_val in: query - description: "Font color for value *(right)* part of the badge. One of predefined colors or as HTML - hexadecimal color without preceding `#` character. Formats allowed `RGB` or `RRGGBB`. If no or wrong value - given default color will be used." + description: | + Font color for value *(right)* part of the badge. One of predefined colors or as HTML hexadecimal color without preceding `#` character. Formats allowed `RGB` or `RRGGBB`. If no or wrong value given default color will be used. required: false allowEmptyValue: true schema: @@ -784,12 +670,8 @@ paths: format: integer - name: fixed_width_lbl in: query - description: "This parameter overrides auto-sizing of badge and creates it with fixed width. - This parameter determines the size of the label's left side *(label/name)*. - You must set this parameter together with `fixed_width_val` otherwise it will be ignored. - You should set the label/value widths wide enough to provide space for all the possible values/contents of - the badge you're requesting. In case the text cannot fit the space given it will be clipped. - The `scale` parameter still applies on the values you give to `fixed_width_lbl` and `fixed_width_val`." + description: | + This parameter overrides auto-sizing of badge and creates it with fixed width. This parameter determines the size of the label's left side *(label/name)*. You must set this parameter together with `fixed_width_val` otherwise it will be ignored. You should set the label/value widths wide enough to provide space for all the possible values/contents of the badge you're requesting. In case the text cannot fit the space given it will be clipped. The `scale` parameter still applies on the values you give to `fixed_width_lbl` and `fixed_width_val`. required: false allowEmptyValue: false schema: @@ -797,12 +679,8 @@ paths: format: integer - name: fixed_width_val in: query - description: "This parameter overrides auto-sizing of badge and creates it with fixed width. This parameter - determines the size of the label's right side *(value)*. You must set this parameter together with - `fixed_width_lbl` otherwise it will be ignored. You should set the label/value widths wide enough to - provide space for all the possible values/contents of the badge you're requesting. In case the text cannot - fit the space given it will be clipped. The `scale` parameter still applies on the values you give to - `fixed_width_lbl` and `fixed_width_val`." + description: | + This parameter overrides auto-sizing of badge and creates it with fixed width. This parameter determines the size of the label's right side *(value)*. You must set this parameter together with `fixed_width_lbl` otherwise it will be ignored. You should set the label/value widths wide enough to provide space for all the possible values/contents of the badge you're requesting. In case the text cannot fit the space given it will be clipped. The `scale` parameter still applies on the values you give to `fixed_width_lbl` and `fixed_width_val`. required: false allowEmptyValue: false schema: @@ -818,212 +696,236 @@ paths: "500": description: Internal server error. This usually means the server is out of memory. - /allmetrics: + /api/v2/weights: get: - summary: Get a value of all the metrics maintained by netdata - description: The allmetrics endpoint returns the latest value of all charts and - dimensions stored in the netdata server. + operationId: weights2 + tags: + - weights + summary: Score or weight all or some of the metrics, across all nodes, according to various algorithms. + description: | + This endpoint goes through all metrics and scores them according to an algorithm. parameters: - - name: format + - $ref: '#/components/parameters/weightMethods' + - $ref: '#/components/parameters/scopeNodes' + - $ref: '#/components/parameters/scopeContexts' + - $ref: '#/components/parameters/filterNodes' + - $ref: '#/components/parameters/filterContexts' + - $ref: '#/components/parameters/filterInstances' + - $ref: '#/components/parameters/filterLabels' + - $ref: '#/components/parameters/filterAlerts' + - $ref: '#/components/parameters/filterDimensions' + - $ref: '#/components/parameters/baselineAfter' + - $ref: '#/components/parameters/baselineBefore' + - $ref: '#/components/parameters/after' + - $ref: '#/components/parameters/before' + - $ref: '#/components/parameters/tier' + - $ref: '#/components/parameters/points' + - $ref: '#/components/parameters/timeoutMS' + - $ref: '#/components/parameters/dataQueryOptions' + - $ref: '#/components/parameters/dataTimeGroup2' + - $ref: '#/components/parameters/dataTimeGroupOptions2' + responses: + "200": + description: JSON object with weights for each context, chart and dimension. + content: + application/json: + schema: + $ref: "#/components/schemas/weights2" + "400": + description: The given parameters are invalid. + "403": + description: metrics correlations are not enabled on this Netdata Agent. + "404": + description: | + No charts could be found, or the method that correlated the metrics did not produce any result. + "504": + description: Timeout - the query took too long and has been cancelled. + /api/v1/weights: + get: + operationId: weights1 + tags: + - weights + summary: Score or weight all or some of the metrics of a single node, according to various algorithms. + description: | + This endpoint goes through all metrics and scores them according to an algorithm. + parameters: + - $ref: '#/components/parameters/weightMethods' + - $ref: '#/components/parameters/context' + - $ref: '#/components/parameters/baselineAfter' + - $ref: '#/components/parameters/baselineBefore' + - $ref: '#/components/parameters/after' + - $ref: '#/components/parameters/before' + - $ref: '#/components/parameters/tier' + - $ref: '#/components/parameters/points' + - $ref: '#/components/parameters/timeoutMS' + - $ref: '#/components/parameters/dataQueryOptions' + - $ref: '#/components/parameters/dataTimeGroup1' + - $ref: '#/components/parameters/dataTimeGroupOptions1' + responses: + "200": + description: JSON object with weights for each context, chart and dimension. + content: + application/json: + schema: + $ref: "#/components/schemas/weights" + "400": + description: The given parameters are invalid. + "403": + description: metrics correlations are not enabled on this Netdata Agent. + "404": + description: No charts could be found, or the method + that correlated the metrics did not produce any result. + "504": + description: Timeout - the query took too long and has been cancelled. + /api/v1/metric_correlations: + get: + operationId: metricCorrelations1 + tags: + - weights + summary: Analyze all the metrics to find their correlations - EOL + description: | + THIS ENDPOINT IS OBSOLETE. Use the /weights endpoint. Given two time-windows (baseline, highlight), it goes through all the available metrics, querying both windows and tries to find how these two windows relate to each other. It supports multiple algorithms to do so. The result is a list of all metrics evaluated, weighted for 0.0 (the two windows are more different) to 1.0 (the two windows are similar). The algorithm adjusts automatically the baseline window to be a power of two multiple of the highlighted (1, 2, 4, 8, etc). + parameters: + - $ref: '#/components/parameters/weightMethods' + - $ref: '#/components/parameters/baselineAfter' + - $ref: '#/components/parameters/baselineBefore' + - $ref: '#/components/parameters/after' + - $ref: '#/components/parameters/before' + - $ref: '#/components/parameters/points' + - $ref: '#/components/parameters/tier' + - $ref: '#/components/parameters/timeoutMS' + - $ref: '#/components/parameters/dataQueryOptions' + - $ref: '#/components/parameters/dataTimeGroup1' + - $ref: '#/components/parameters/dataTimeGroupOptions1' + responses: + "200": + description: JSON object with weights for each chart and dimension. + content: + application/json: + schema: + $ref: "#/components/schemas/metric_correlations" + "400": + description: The given parameters are invalid. + "403": + description: metrics correlations are not enabled on this Netdata Agent. + "404": + description: No charts could be found, or the method + that correlated the metrics did not produce any result. + "504": + description: Timeout - the query took too long and has been cancelled. + /api/v1/function: + get: + operationId: function1 + tags: + - functions + description: "Execute a collector function." + parameters: + - name: function in: query - description: The format of the response to be returned. + description: The name of the function, as returned by the collector. required: true + allowEmptyValue: false schema: type: string - enum: - - shell - - prometheus - - prometheus_all_hosts - - json - default: shell - - name: filter - in: query - description: Allows to filter charts out using simple patterns. - required: false - schema: - type: string - format: any text - - name: variables + - $ref: '#/components/parameters/timeoutSecs' + responses: + "200": + description: The collector function has been executed successfully. Each collector may return a different type of content. + "400": + description: The request was rejected by the collector. + "404": + description: The requested function is not found. + "500": + description: Other internal error, getting this error means there is a bug in Netdata. + "503": + description: The collector to execute the function is not currently available. + "504": + description: Timeout while waiting for the collector to execute the function. + "591": + description: The collector sent a response, but it was invalid or corrupted. + /api/v1/functions: + get: + operationId: functions1 + tags: + - functions + summary: Get a list of all registered collector functions. + description: Collector functions are programs that can be executed on demand. + responses: + "200": + description: A JSON object containing one object per supported function. + /api/v1/alarms: + get: + operationId: alerts1 + tags: + - alerts + summary: Get a list of active or raised alarms on the server + description: | + The alarms endpoint returns the list of all raised or enabled alarms on the netdata server. Called without any parameters, the raised alarms in state WARNING or CRITICAL are returned. By passing "?all", all the enabled alarms are returned. + parameters: + - name: all in: query - description: When enabled, netdata will expose various system - configuration metrics. + description: If passed, all enabled alarms are returned. required: false + allowEmptyValue: true schema: - type: string - enum: - - yes - - no - default: no - - name: help + type: boolean + - name: active in: query - description: Enable or disable HELP lines in prometheus output. + description: If passed, the raised alarms in state WARNING or CRITICAL are returned. required: false + allowEmptyValue: true schema: - type: string - enum: - - yes - - no - default: no - - name: types + type: boolean + responses: + "200": + description: An object containing general info and a linked list of alarms. + content: + application/json: + schema: + $ref: "#/components/schemas/alarms" + /api/v1/alarms_values: + get: + operationId: alertValues1 + tags: + - alerts + summary: Get a list of active or raised alarms on the server + description: | + The alarms_values endpoint returns the list of all raised or enabled alarms on the netdata server. Called without any parameters, the raised alarms in state WARNING or CRITICAL are returned. By passing '?all', all the enabled alarms are returned. This option output differs from `/alarms` in the number of variables delivered. This endpoint gives to user `id`, `value`, `last_updated` time, and alarm `status`. + parameters: + - name: all in: query - description: Enable or disable TYPE lines in prometheus output. + description: If passed, all enabled alarms are returned. required: false + allowEmptyValue: true schema: - type: string - enum: - - yes - - no - default: no - - name: timestamps + type: boolean + - name: active in: query - description: Enable or disable timestamps in prometheus output. + description: If passed, the raised alarms in state WARNING or CRITICAL are returned. required: false + allowEmptyValue: true schema: - type: string - enum: - - yes - - no - default: yes - - name: names + type: boolean + responses: + "200": + description: An object containing general info and a linked list of alarms. + content: + application/json: + schema: + $ref: "#/components/schemas/alarms_values" + /api/v1/alarm_log: + get: + operationId: alertsLog1 + tags: + - alerts + summary: Retrieves the entries of the alarm log + description: | + Returns an array of alarm_log entries, with historical information on raised and cleared alarms. + parameters: + - name: after in: query - description: When enabled netdata will report dimension names. When disabled - netdata will report dimension IDs. The default is controlled in - netdata.conf. - required: false - schema: - type: string - enum: - - yes - - no - default: yes - - name: oldunits - in: query - description: When enabled, netdata will show metric names for the default - source=average as they appeared before 1.12, by using the legacy - unit naming conventions. - required: false - schema: - type: string - enum: - - yes - - no - default: yes - - name: hideunits - in: query - description: When enabled, netdata will not include the units in the metric - names, for the default source=average. - required: false - schema: - type: string - enum: - - yes - - no - default: yes - - name: server - in: query - description: Set a distinct name of the client querying prometheus metrics. - Netdata will use the client IP if this is not set. - required: false - schema: - type: string - format: any text - - name: prefix - in: query - description: Prefix all prometheus metrics with this string. - required: false - schema: - type: string - format: any text - - name: data - in: query - description: Select the prometheus response data source. There is a setting in - netdata.conf for the default. - required: false - schema: - type: string - enum: - - as-collected - - average - - sum - default: average - responses: - "200": - description: All the metrics returned in the format requested. - "400": - description: The format requested is not supported. - /alarms: - get: - summary: Get a list of active or raised alarms on the server - description: The alarms endpoint returns the list of all raised or enabled alarms on - the netdata server. Called without any parameters, the raised alarms in - state WARNING or CRITICAL are returned. By passing "?all", all the - enabled alarms are returned. - parameters: - - name: all - in: query - description: If passed, all enabled alarms are returned. - required: false - allowEmptyValue: true - schema: - type: boolean - - name: active - in: query - description: If passed, the raised alarms in state WARNING or CRITICAL are returned. - required: false - allowEmptyValue: true - schema: - type: boolean - responses: - "200": - description: An object containing general info and a linked list of alarms. - content: - application/json: - schema: - $ref: "#/components/schemas/alarms" - /alarms_values: - get: - summary: Get a list of active or raised alarms on the server - description: "The alarms_values endpoint returns the list of all raised or enabled alarms on - the netdata server. Called without any parameters, the raised alarms in - state WARNING or CRITICAL are returned. By passing '?all', all the - enabled alarms are returned. - This option output differs from `/alarms` in the number of variables delivered. This endpoint gives - to user `id`, `value`, `last_updated` time, and alarm `status`." - parameters: - - name: all - in: query - description: If passed, all enabled alarms are returned. - required: false - allowEmptyValue: true - schema: - type: boolean - - name: active - in: query - description: If passed, the raised alarms in state WARNING or CRITICAL are returned. - required: false - allowEmptyValue: true - schema: - type: boolean - responses: - "200": - description: An object containing general info and a linked list of alarms. - content: - application/json: - schema: - $ref: "#/components/schemas/alarms_values" - /alarm_log: - get: - summary: Retrieves the entries of the alarm log - description: Returns an array of alarm_log entries, with historical information on - raised and cleared alarms. - parameters: - - name: after - in: query - description: Passing the parameter after=UNIQUEID returns all the events in the - alarm log that occurred after UNIQUEID. An automated series of calls - would call the interface once without after=, store the last - UNIQUEID of the returned set, and give it back to get incrementally - the next events. + description: | + Passing the parameter after=UNIQUEID returns all the events in the alarm log that occurred after UNIQUEID. An automated series of calls would call the interface once without after=, store the last UNIQUEID of the returned set, and give it back to get incrementally the next events. required: false schema: type: integer @@ -1036,25 +938,18 @@ paths: type: array items: $ref: "#/components/schemas/alarm_log_entry" - /alarm_count: + /api/v1/alarm_count: get: + operationId: alertsCount1 + tags: + - alerts summary: Get an overall status of the chart - description: Checks multiple charts with the same context and counts number of alarms - with given status. + description: | + Checks multiple charts with the same context and counts number of alarms with given status. parameters: - - in: query - name: context - description: Specify context which should be checked. - required: false - allowEmptyValue: true - schema: - type: array - items: - type: string - default: - - system.cpu - - in: query - name: status + - $ref: '#/components/parameters/context' + - name: status + in: query description: Specify alarm status to count. required: false allowEmptyValue: true @@ -1082,26 +977,52 @@ paths: "500": description: Internal server error. This usually means the server is out of memory. - /manage/health: + /api/v1/alarm_variables: + get: + operationId: getNodeAlertVariables1 + tags: + - alerts + summary: List variables available to configure alarms for a chart + description: | + Returns the basic information of a chart and all the variables that can be used in alarm and template health configurations for the particular chart or family. + parameters: + - name: chart + in: query + description: The id of the chart as returned by the /charts call. + required: true + schema: + type: string + format: as returned by /charts + default: system.cpu + responses: + "200": + description: A javascript object with information about the chart and the + available variables. + content: + application/json: + schema: + $ref: "#/components/schemas/alarm_variables" + "400": + description: Bad request - the body will include a message stating what is wrong. + "404": + description: No chart with the given id is found. + "500": + description: Internal server error. This usually means the server is out of + memory. + /api/v1/manage/health: get: - summary: "Accesses the health management API to control health checks and - notifications at runtime." - description: "Available from Netdata v1.12 and above, protected via bearer - authorization. Especially useful for maintenance periods, the API allows - you to disable health checks completely, silence alarm notifications, or - Disable/Silence specific alarms that match selectors on alarm/template - name, chart, context, host and family. For the simple disable/silence - all scenarios, only the cmd parameter is required. The other parameters - are used to define alarm selectors. For more information and examples, - refer to the netdata documentation." + operationId: health1 + tags: + - management + summary: | + Accesses the health management API to control health checks and notifications at runtime. + description: | + Available from Netdata v1.12 and above, protected via bearer authorization. Especially useful for maintenance periods, the API allows you to disable health checks completely, silence alarm notifications, or Disable/Silence specific alarms that match selectors on alarm/template name, chart, context, host and family. For the simple disable/silence all scenarios, only the cmd parameter is required. The other parameters are used to define alarm selectors. For more information and examples, refer to the netdata documentation. parameters: - name: cmd in: query - description: "DISABLE ALL: No alarm criteria are evaluated, nothing is written in - the alarm log. SILENCE ALL: No notifications are sent. RESET: Return - to the default state. DISABLE/SILENCE: Set the mode to be used for - the alarms matching the criteria of the alarm selectors. LIST: Show - active configuration." + description: | + DISABLE ALL: No alarm criteria are evaluated, nothing is written in the alarm log. SILENCE ALL: No notifications are sent. RESET: Return to the default state. DISABLE/SILENCE: Set the mode to be used for the alarms matching the criteria of the alarm selectors. LIST: Show active configuration. required: false schema: type: string @@ -1144,11 +1065,14 @@ paths: description: A plain text response based on the result of the command. "403": description: Bearer authentication error. - /aclk: + /api/v1/aclk: get: + operationId: aclk1 + tags: + - management summary: Get information about current ACLK state - description: "ACLK endpoint returns detailed information - about current state of ACLK (Agent to Cloud communication)." + description: | + ACLK endpoint returns detailed information about current state of ACLK (Agent to Cloud communication). responses: "200": description: JSON object with ACLK information. @@ -1156,448 +1080,532 @@ paths: application/json: schema: $ref: "#/components/schemas/aclk_state" - /metric_correlations: - get: - summary: "Analyze all the metrics to find their correlations" - description: "THIS ENDPOINT IS OBSOLETE. Use the /weights endpoint. - Given two time-windows (baseline, highlight), it goes - through all the available metrics, querying both windows and tries to find - how these two windows relate to each other. It supports - multiple algorithms to do so. The result is a list of all - metrics evaluated, weighted for 0.0 (the two windows are - more different) to 1.0 (the two windows are similar). - The algorithm adjusts automatically the baseline window to be - a power of two multiple of the highlighted (1, 2, 4, 8, etc)." - parameters: - - name: baseline_after - in: query - description: This parameter can either be an absolute timestamp specifying the - starting point of baseline window, or a relative number of - seconds (negative, relative to parameter baseline_before). Netdata will - assume it is a relative number if it is less that 3 years (in seconds). - required: false - allowEmptyValue: false - schema: - type: number - format: integer - default: -300 - - name: baseline_before - in: query - description: This parameter can either be an absolute timestamp specifying the - ending point of the baseline window, or a relative number of - seconds (negative), relative to the last collected timestamp. - Netdata will assume it is a relative number if it is less than 3 - years (in seconds). - required: false - schema: - type: number - format: integer - default: -60 - - name: after - in: query - description: This parameter can either be an absolute timestamp specifying the - starting point of highlighted window, or a relative number of - seconds (negative, relative to parameter highlight_before). Netdata will - assume it is a relative number if it is less that 3 years (in seconds). - required: false - allowEmptyValue: false - schema: - type: number - format: integer - default: -60 - - name: before - in: query - description: This parameter can either be an absolute timestamp specifying the - ending point of the highlighted window, or a relative number of - seconds (negative), relative to the last collected timestamp. - Netdata will assume it is a relative number if it is less than 3 - years (in seconds). - required: false - schema: - type: number - format: integer - default: 0 - - name: points - in: query - description: The number of points to be evaluated for the highlighted window. - The baseline window will be adjusted automatically to receive a proportional - amount of points. - required: false - allowEmptyValue: false - schema: - type: number - format: integer - default: 500 - - name: method - in: query - description: the algorithm to run - required: false - schema: - type: string - enum: - - ks2 - - volume - default: ks2 - - name: timeout - in: query - description: Cancel the query if to takes more that this amount of milliseconds. - required: false - allowEmptyValue: false - schema: - type: number - format: integer - default: 60000 - - name: options - in: query - description: Options that affect data generation. - required: false - allowEmptyValue: false - schema: - type: array - items: - type: string - enum: - - min2max - - abs - - absolute - - absolute-sum - - null2zero - - percentage - - unaligned - - allow_past - - nonzero - - anomaly-bit - - raw - default: - - null2zero - - allow_past - - nonzero - - unaligned - - name: group - in: query - description: The grouping method. If multiple collected values are to be grouped - in order to return fewer points, this parameters defines the method - of grouping. methods supported "min", "max", "average", "sum", - "incremental-sum". "max" is actually calculated on the absolute - value collected (so it works for both positive and negative - dimensions to return the most extreme value in either direction). - required: true - allowEmptyValue: false - schema: - type: string - enum: - - min - - max - - average - - median - - stddev - - sum - - incremental-sum - - ses - - des - - cv - - countif - - percentile - - percentile25 - - percentile50 - - percentile75 - - percentile80 - - percentile90 - - percentile95 - - percentile97 - - percentile98 - - percentile99 - - trimmed-mean - - trimmed-mean1 - - trimmed-mean2 - - trimmed-mean3 - - trimmed-mean5 - - trimmed-mean10 - - trimmed-mean15 - - trimmed-mean20 - - trimmed-mean25 - - trimmed-median - - trimmed-median1 - - trimmed-median2 - - trimmed-median3 - - trimmed-median5 - - trimmed-median10 - - trimmed-median15 - - trimmed-median20 - - trimmed-median25 - default: average - - name: group_options - in: query - description: When the group function supports additional parameters, this field - can be used to pass them to it. Currently only "countif" supports this. - required: false - allowEmptyValue: false - schema: - type: string - responses: - "200": - description: JSON object with weights for each chart and dimension. - content: - application/json: - schema: - $ref: "#/components/schemas/metric_correlations" - "400": - description: The given parameters are invalid. - "403": - description: metrics correlations are not enabled on this Netdata Agent. - "404": - description: No charts could be found, or the method - that correlated the metrics did not produce any result. - "504": - description: Timeout - the query took too long and has been cancelled. - /function: - get: - summary: "Execute a collector function." - parameters: - - name: function - in: query - description: The name of the function, as returned by the collector. - required: true - allowEmptyValue: false - schema: - type: string - - name: timeout - in: query - description: The timeout in seconds to wait for the function to complete. - required: false - schema: - type: number - format: integer - default: 10 - responses: - "200": - description: The collector function has been executed successfully. Each collector may return a different type of content. - "400": - description: The request was rejected by the collector. - "404": - description: The requested function is not found. - "500": - description: Other internal error, getting this error means there is a bug in Netdata. - "503": - description: The collector to execute the function is not currently available. - "504": - description: Timeout while waiting for the collector to execute the function. - "591": - description: The collector sent a response, but it was invalid or corrupted. - /functions: - get: - summary: Get a list of all registered collector functions. - description: Collector functions are programs that can be executed on demand. - responses: - "200": - description: A JSON object containing one object per supported function. - /weights: - get: - summary: "Analyze all the metrics using an algorithm and score them accordingly" - description: "This endpoint goes through all metrics and scores them according to an algorithm." - parameters: - - name: baseline_after - in: query - description: This parameter can either be an absolute timestamp specifying the - starting point of baseline window, or a relative number of - seconds (negative, relative to parameter baseline_before). Netdata will - assume it is a relative number if it is less that 3 years (in seconds). - This parameter is used in KS2 and VOLUME algorithms. - required: false - allowEmptyValue: false - schema: - type: number - format: integer - default: -300 - - name: baseline_before - in: query - description: This parameter can either be an absolute timestamp specifying the - ending point of the baseline window, or a relative number of - seconds (negative), relative to the last collected timestamp. - Netdata will assume it is a relative number if it is less than 3 - years (in seconds). - This parameter is used in KS2 and VOLUME algorithms. - required: false - schema: - type: number - format: integer - default: -60 - - name: after - in: query - description: This parameter can either be an absolute timestamp specifying the - starting point of highlighted window, or a relative number of - seconds (negative, relative to parameter highlight_before). Netdata will - assume it is a relative number if it is less that 3 years (in seconds). - required: false - allowEmptyValue: false - schema: - type: number - format: integer - default: -60 - - name: before - in: query - description: This parameter can either be an absolute timestamp specifying the - ending point of the highlighted window, or a relative number of - seconds (negative), relative to the last collected timestamp. - Netdata will assume it is a relative number if it is less than 3 - years (in seconds). - required: false - schema: - type: number - format: integer - default: 0 - - name: context - in: query - description: A simple pattern matching the contexts to evaluate. - required: false - allowEmptyValue: false - schema: - type: string - - name: points - in: query - description: The number of points to be evaluated for the highlighted window. - The baseline window will be adjusted automatically to receive a proportional - amount of points. - This parameter is only used by the KS2 algorithm. - required: false - allowEmptyValue: false - schema: - type: number - format: integer - default: 500 - - name: method - in: query - description: the algorithm to run - required: false - schema: - type: string - enum: - - ks2 - - volume - - anomaly-rate - default: anomaly-rate - - name: tier - in: query - description: Use the specified database tier - required: false - allowEmptyValue: false - schema: - type: number - format: integer - - name: timeout - in: query - description: Cancel the query if to takes more that this amount of milliseconds. - required: false - allowEmptyValue: false - schema: - type: number - format: integer - default: 60000 - - name: options - in: query - description: Options that affect data generation. - required: false - allowEmptyValue: false - schema: - type: array - items: - type: string - enum: - - min2max - - abs - - absolute - - absolute-sum - - null2zero - - percentage - - unaligned - - nonzero - - anomaly-bit - - raw - default: - - null2zero - - nonzero - - unaligned - - name: group - in: query - description: The grouping method. If multiple collected values are to be grouped - in order to return fewer points, this parameters defines the method - of grouping. methods supported "min", "max", "average", "sum", - "incremental-sum". "max" is actually calculated on the absolute - value collected (so it works for both positive and negative - dimensions to return the most extreme value in either direction). - required: true - allowEmptyValue: false - schema: - type: string - enum: - - min - - max - - average - - median - - stddev - - sum - - incremental-sum - - ses - - des - - cv - - countif - - percentile - - percentile25 - - percentile50 - - percentile75 - - percentile80 - - percentile90 - - percentile95 - - percentile97 - - percentile98 - - percentile99 - - trimmed-mean - - trimmed-mean1 - - trimmed-mean2 - - trimmed-mean3 - - trimmed-mean5 - - trimmed-mean10 - - trimmed-mean15 - - trimmed-mean20 - - trimmed-mean25 - - trimmed-median - - trimmed-median1 - - trimmed-median2 - - trimmed-median3 - - trimmed-median5 - - trimmed-median10 - - trimmed-median15 - - trimmed-median20 - - trimmed-median25 - default: average - - name: group_options - in: query - description: When the group function supports additional parameters, this field - can be used to pass them to it. Currently only "countif" supports this. - required: false - allowEmptyValue: false - schema: - type: string - responses: - "200": - description: JSON object with weights for each context, chart and dimension. - content: - application/json: - schema: - $ref: "#/components/schemas/weights" - "400": - description: The given parameters are invalid. - "403": - description: metrics correlations are not enabled on this Netdata Agent. - "404": - description: No charts could be found, or the method - that correlated the metrics did not produce any result. - "504": - description: Timeout - the query took too long and has been cancelled. -servers: - - url: https://registry.my-netdata.io/api/v1 - - url: http://registry.my-netdata.io/api/v1 components: + parameters: + scopeNodes: + name: scope_nodes + in: query + description: | + A simple pattern limiting the nodes scope of the query. The scope controls both data and metadata response. The simple pattern is checked against the nodes' machine guid, node id and hostname. The default nodes scope is all nodes for which this agent has data for. Usually the nodes scope is used to slice the entire dashboard (e.g. the Global Nodes Selector at the Netdata Cloud overview dashboard). Both positive and negative simple pattern expressions are supported. + required: false + schema: + type: string + format: simple pattern + default: "*" + scopeContexts: + name: scope_contexts + in: query + description: | + A simple pattern limiting the contexts scope of the query. The scope controls both data and metadata response. The default contexts scope is all contexts for which this agent has data for. Usually the contexts scope is used to slice data on the dashboard (e.g. each context based chart has its own contexts scope, limiting the chart to all the instances of the selected context). Both positive and negative simple pattern expressions are supported. + required: false + schema: + type: string + format: simple pattern + default: "*" + filterNodes: + name: nodes + in: query + description: | + A simple pattern matching the nodes to be queried. This only controls the data response, not the metadata. The simple pattern is checked against the nodes' machine guid, node id, hostname. The default nodes selector is all the nodes matched by the nodes scope. Both positive and negative simple pattern expressions are supported. + required: false + schema: + type: string + format: simple pattern + default: "*" + filterContexts: + name: contexts + in: query + description: | + A simple pattern matching the contexts to be queried. This only controls the data response, not the metadata. Both positive and negative simple pattern expressions are supported. + required: false + schema: + type: string + format: simple pattern + default: "*" + filterInstances: + name: instances + in: query + description: | + A simple pattern matching the instances to be queried. The simple pattern is checked against the instance `id`, the instance `name`, the fully qualified name of the instance `id` and `name`, like `instance@machine_guid`, where `instance` is either its `id` or `name`. Both positive and negative simple pattern expressions are supported. + required: false + schema: + type: string + format: simple pattern + default: "*" + filterLabels: + name: labels + in: query + description: | + A simple pattern matching the labels to be queried. The simple pattern is checked against `name:value` of all the labels of all the eligible instances (as filtered by all the above: scope nodes, scope contexts, nodes, contexts and instances). Negative simple patterns should not be used in this filter. + required: false + schema: + type: string + format: simple pattern + default: "*" + filterAlerts: + name: alerts + in: query + description: | + A simple pattern matching the alerts to be queried. The simple pattern is checked against the `name` of alerts and the combination of `name:status`, when status is one of `CLEAR`, `WARNING`, `CRITICAL`, `REMOVED`, `UNDEFINED`, `UNINITIALIZED`, of all the alerts of all the eligible instances (as filtered by all the above). A negative simple pattern will exclude the instances having the labels matched. + required: false + schema: + type: string + format: simple pattern + default: "*" + filterDimensions: + name: dimensions + in: query + description: | + A simple patterns matching the dimensions to be queried. The simple pattern is checked against and `id` and the `name` of the dimensions of the eligible instances (as filtered by all the above). Both positive and negative simple pattern expressions are supported. + required: false + schema: + type: string + format: simple pattern + default: "*" + + dataFormat1: + name: format + in: query + description: The format of the data to be returned. + allowEmptyValue: false + schema: + type: string + enum: + - json + - jsonp + - csv + - tsv + - tsv-excel + - ssv + - ssvcomma + - datatable + - datasource + - html + - markdown + - array + - csvjsonarray + default: json + dataFormat2: + name: format + in: query + description: The format of the data to be returned. + allowEmptyValue: false + schema: + type: string + enum: + - json + - json2 + - jsonp + - csv + - tsv + - tsv-excel + - ssv + - ssvcomma + - datatable + - datasource + - html + - markdown + - array + - csvjsonarray + default: json2 + dataQueryOptions: + name: options + in: query + description: | + Options that affect data generation. + * `jsonwrap` - Wrap the output in a JSON object with metadata about the query. + * `raw` - change the output so that it is aggregatable across multiple such queries. Supported by `/api/v2` data queries and `json2` format. + * `minify` - Remove unnecessary spaces and newlines from the output. + * `debug` - Provide additional information in `jsonwrap` output to help tracing issues. + * `nonzero` - Do not return dimensions that all their values are zero, to improve the visual appearance of charts. They will still be returned if all the dimensions are entirely zero. + * `null2zero` - Replace `null` values with `0`. + * `absolute` or `abs` - Traditionally Netdata returns select dimensions negative to improve visual appearance. This option turns this feature off. + * `display-absolute` - Only used by badges, to do color calculation using the signed value, but render the value without a sign. + * `flip` or `reversed` - Order the timestamps array in reverse order (newest to oldest). + * `min2max` - When flattening multi-dimensional data into a single metric format, use `max - min` instead of `sum`. This is EOL - use `/api/v2` to control aggregation across dimensions. + * `percentage` - Convert all values into a percentage vs the row total. When enabled, Netdata will query all dimensions, even the ones that have not been selected or are hidden, to find the row total, in order to calculate the percentage of each dimension selected. + * `seconds` - Output timestamps in seconds instead of dates. + * `milliseconds` or `ms` - Output timestamps in milliseconds instead of dates. + * `unaligned` - by default queries are aligned to the the view, so that as time passes past data returned do not change. When a data query will not be used for visualization, `unaligned` can be given to avoid aligning the query time-frame for visual precision. + * `match-ids`, `match-names`. By default filters match both IDs and names when they are available. Setting either of the two options will disable the other. + * `anomaly-bit` - query the anomaly information instead of metric values. This is EOL, use `/api/v2` and `json2` format which always returns this information and many more. + * `jw-anomaly-rates` - return anomaly rates as a separate result set in the same `json` format response. This is EOL, use `/api/v2` and `json2` format which always returns information and many more. + * `details` - `/api/v2/data` returns in `jsonwrap` the full tree of dimensions that have been matched by the query. + * `group-by-labels` - `/api/v2/data` returns in `jsonwrap` flattened labels per output dimension. These are used to identify the instances that have been aggregated into each dimension, making it possible to provide a map, like Netdata does for Kubernetes. + * `natural-points` - return timestamps as found in the database. The result is again fixed-step, but the query engine attempts to align them with the timestamps found in the database. + * `virtual-points` - return timestamps independent of the database alignment. This is needed aggregating data across multiple Netdata agents, to ensure that their outputs do not need to be interpolated to be merged. + * `selected-tier` - use data exclusively from the selected tier given with the `tier` parameter. This option is set automatically when the `tier` parameter is set. + * `all-dimensions` - In `/api/v1` `jsonwrap` include metadata for all candidate metrics examined. In `/api/v2` this is standard behavior and no option is needed. + * `label-quotes` - In `csv` output format, enclose each header label in quotes. + * `objectrows` - Each row of value should be an object, not an array (only for `json` format). + * `google_json` - Comply with google JSON/JSONP specs (only for `json` format). + required: false + allowEmptyValue: false + schema: + type: array + items: + type: string + enum: + - jsonwrap + - raw + - minify + - debug + - nonzero + - null2zero + - abs + - absolute + - display-absolute + - flip + - reversed + - min2max + - percentage + - seconds + - ms + - milliseconds + - unaligned + - match-ids + - match-names + - anomaly-bit + - jw-anomaly-rates + - details + - group-by-labels + - natural-points + - virtual-points + - selected-tier + - all-dimensions + - label-quotes + - objectrows + - google_json + default: + - seconds + - jsonwrap + dataTimeGroup1: + name: group + in: query + description: | + Time aggregation function. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. If the `absolute` option is set, the values are turned positive before applying this calculation. + required: false + schema: + type: string + enum: + - min + - max + - avg + - average + - median + - stddev + - sum + - incremental-sum + - ses + - des + - cv + - countif + - percentile + - percentile25 + - percentile50 + - percentile75 + - percentile80 + - percentile90 + - percentile95 + - percentile97 + - percentile98 + - percentile99 + - trimmed-mean + - trimmed-mean1 + - trimmed-mean2 + - trimmed-mean3 + - trimmed-mean5 + - trimmed-mean10 + - trimmed-mean15 + - trimmed-mean20 + - trimmed-mean25 + - trimmed-median + - trimmed-median1 + - trimmed-median2 + - trimmed-median3 + - trimmed-median5 + - trimmed-median10 + - trimmed-median15 + - trimmed-median20 + - trimmed-median25 + default: average + dataTimeGroup2: + name: time_group + in: query + description: | + Time aggregation function. If multiple collected values are to be grouped in order to return fewer points, this parameters defines the method of grouping. If the `absolute` option is set, the values are turned positive before applying this calculation. + required: false + schema: + type: string + enum: + - min + - max + - avg + - average + - median + - stddev + - sum + - incremental-sum + - ses + - des + - cv + - countif + - percentile + - percentile25 + - percentile50 + - percentile75 + - percentile80 + - percentile90 + - percentile95 + - percentile97 + - percentile98 + - percentile99 + - trimmed-mean + - trimmed-mean1 + - trimmed-mean2 + - trimmed-mean3 + - trimmed-mean5 + - trimmed-mean10 + - trimmed-mean15 + - trimmed-mean20 + - trimmed-mean25 + - trimmed-median + - trimmed-median1 + - trimmed-median2 + - trimmed-median3 + - trimmed-median5 + - trimmed-median10 + - trimmed-median15 + - trimmed-median20 + - trimmed-median25 + default: average + dataTimeGroupOptions1: + name: group_options + in: query + description: | + When the time grouping function supports additional parameters, this field can be used to pass them to it. Currently `countif`, `trimmed-mean`, `trimmed-median` and `percentile` support this. For `countif` the string may start with `<`, `<=`, `<:`, `<>`, `!=`, `>`, `>=`, `>:`. For all others just a number is expected. + required: false + schema: + type: string + dataTimeGroupOptions2: + name: time_group_options + in: query + description: | + When the time grouping function supports additional parameters, this field can be used to pass them to it. Currently `countif`, `trimmed-mean`, `trimmed-median` and `percentile` support this. For `countif` the string may start with `<`, `<=`, `<:`, `<>`, `!=`, `>`, `>=`, `>:`. For all others just a number is expected. + required: false + schema: + type: string + dataTimeResampling1: + name: gtime + in: query + description: | + The grouping number of seconds. This is used in conjunction with group=average to change the units of metrics (ie when the data is per-second, setting gtime=60 will turn them to per-minute). + required: false + allowEmptyValue: false + schema: + type: number + format: integer + default: 0 + dataTimeResampling2: + name: time_resampling + in: query + description: | + For incremental values that are "per second", this value is used to resample them to "per minute` (60) or "per hour" (3600). It can only be used in conjunction with group=average. + required: false + schema: + type: number + format: integer + default: 0 + timeoutMS: + name: timeout + in: query + description: | + Specify a timeout value in milliseconds after which the agent will abort the query and return a 503 error. A value of 0 indicates no timeout. + required: false + schema: + type: number + format: integer + default: 0 + timeoutSecs: + name: timeout + in: query + description: | + Specify a timeout value in seconds after which the agent will abort the query and return a 504 error. A value of 0 indicates no timeout, but some endpoints, like `weights`, do not accept infinite timeouts (they have a predefined default), so to disable the timeout it must be set to a really high value. + required: false + schema: + type: number + format: integer + default: 0 + before: + name: before + in: query + description: | + `after` and `before` define the time-frame of a query. `before` can be a negative number of seconds, up to 3 years (-94608000), relative to current clock. If not set, it is assumed to be the current clock time. When `before` is positive, it is assumed to be a unix epoch timestamp. When non-data endpoints support the `after` and `before`, they use the time-frame to limit their response for objects having data retention within the time-frame given. + required: false + schema: + type: integer + default: 0 + after: + name: after + in: query + description: | + `after` and `before` define the time-frame of a query. `after` can be a negative number of seconds, up to 3 years (-94608000), relative to `before`. If not set, it is usually assumed to be -600. When non-data endpoints support the `after` and `before`, they use the time-frame to limit their response for objects having data retention within the time-frame given. + required: false + schema: + type: integer + default: -600 + baselineBefore: + name: baseline_before + in: query + description: | + `baseline_after` and `baseline_before` define the baseline time-frame of a comparative query. `baseline_before` can be a negative number of seconds, up to 3 years (-94608000), relative to current clock. If not set, it is assumed to be the current clock time. When `baseline_before` is positive, it is assumed to be a unix epoch timestamp. + required: false + schema: + type: integer + default: 0 + baselineAfter: + name: baseline_after + in: query + description: | + `baseline_after` and `baseline_before` define the baseline time-frame of a comparative query. `baseline_after` can be a negative number of seconds, up to 3 years (-94608000), relative to `baseline_before`. If not set, it is usually assumed to be -300. + required: false + schema: + type: integer + default: -600 + points: + name: points + in: query + description: | + The number of points to be returned. If not given, or it is <= 0, or it is bigger than the points stored in the database for the given duration, all the available collected values for the given duration will be returned. For `weights` endpoints that do statistical analysis, the `points` define the detail of this analysis (the default is 500). + required: false + schema: + type: number + format: integer + default: 0 + tier: + name: tier + in: query + description: | + Use only the given dbengine tier for executing the query. Setting this parameters automatically sets the option `selected-tier` for the query. + required: false + schema: + type: number + format: integer + callback: + name: callback + in: query + description: | + For JSONP responses, the callback function name. + required: false + schema: + type: string + filename: + name: filename + in: query + description: | + Add `Content-Disposition: attachment; filename=` header to the response, that will instruct the browser to save the response with the given filename." + required: false + schema: + type: string + tqx: + name: tqx + in: query + description: | + [Google Visualization API](https://developers.google.com/chart/interactive/docs/dev/implementing_data_source?hl=en) formatted parameter. + required: false + schema: + type: string + contextOptions1: + name: options + in: query + description: Options that affect data generation. + required: false + schema: + type: array + items: + type: string + enum: + - full + - all + - charts + - dimensions + - labels + - uuids + - queue + - flags + - deleted + - deepscan + chart: + name: chart + in: query + description: The id of the chart as returned by the `/api/v1/charts` call. + required: false + allowEmptyValue: false + schema: + type: string + format: as returned by `/api/v1/charts` + context: + name: context + in: query + description: The context of the chart as returned by the /charts call. + required: false + allowEmptyValue: false + schema: + type: string + format: as returned by /charts + dimension: + name: dimension + in: query + description: Zero, one or more dimension ids or names, as returned by the /chart + call, separated with comma or pipe. Netdata simple patterns are + supported. + required: false + allowEmptyValue: false + schema: + type: array + items: + type: string + format: as returned by /charts + dimensions: + name: dimensions + in: query + description: a simple pattern matching dimensions (use comma or pipe as separator) + required: false + allowEmptyValue: true + schema: + type: string + chart_label_key: + name: chart_label_key + in: query + description: | + Specify the chart label keys that need to match for context queries as comma separated values. At least one matching key is needed to match the corresponding chart. + required: false + allowEmptyValue: false + schema: + type: string + format: key1,key2,key3 + chart_labels_filter: + name: chart_labels_filter + in: query + description: | + Specify the chart label keys and values to match for context queries. All keys/values need to match for the chart to be included in the query. The labels are specified as key1:value1,key2:value2 + required: false + allowEmptyValue: false + schema: + type: string + format: key1:value1,key2:value2,key3:value3 + weightMethods: + name: method + in: query + description: The weighting / scoring algorithm. + required: false + schema: + type: string + enum: + - ks2 + - volume + - anomaly-rate + - value schemas: info: type: object @@ -1857,10 +1865,8 @@ components: amount of time is kept in the round robin database. dimensions: type: object - description: "An object containing all the chart dimensions available for the - chart. This is used as an indexed array. For each pair in the - dictionary: the key is the id of the dimension and the value is a - dictionary containing the name." + description: | + An object containing all the chart dimensions available for the chart. This is used as an indexed array. For each pair in the dictionary: the key is the id of the dimension and the value is a dictionary containing the name." additionalProperties: type: object properties: @@ -1998,7 +2004,612 @@ components: varname2: type: number format: float - data: + jsonwrap2: + description: | + Data response with `format=json2` + type: object + properties: + api: + $ref: '#/components/schemas/api' + agents: + $ref: '#/components/schemas/agents' + versions: + $ref: '#/components/schemas/versions' + summary: + description: | + Summarized information about nodes, contexts, instances, labels, alerts, and dimensions. The items returned are determined by the scope of the query only, however the statistical data in them are influenced by the filters of the query. Using this information the dashboard allows users to slice and dice the data by filtering and grouping. + type: object + properties: + nodes: + type: array + items: + $ref: '#/components/schemas/nodeWithDataStatistics' + contexts: + type: array + items: + type: object + description: | + An object describing a unique context. `is` stands for instances, `ds` for dimensions, `al` for alerts, `sts` for statistics. + properties: + id: + description: the context id. + type: string + is: + $ref: "#/components/schemas/jsonwrap2_items_count" + ds: + $ref: "#/components/schemas/jsonwrap2_items_count" + al: + $ref: "#/components/schemas/jsonwrap2_alerts_count" + sts: + oneOf: + - $ref: "#/components/schemas/jsonwrap2_sts" + - $ref: "#/components/schemas/jsonwrap2_sts_raw" + instances: + type: array + items: + type: object + description: | + An object describing an instance. `ds` stands for dimensions, `al` for alerts, `sts` for statistics. + properties: + id: + description: the id of the instance. + type: string + nm: + description: the name of the instance (may be absent when it is the same with the id) + type: string + ni: + description: the node index id this instance belongs to. The UI uses this to compone the fully qualified name of the instance, using the node hostname to present it to users and its machine guid to add it to filters. + ds: + $ref: "#/components/schemas/jsonwrap2_items_count" + al: + $ref: "#/components/schemas/jsonwrap2_alerts_count" + sts: + oneOf: + - $ref: "#/components/schemas/jsonwrap2_sts" + - $ref: "#/components/schemas/jsonwrap2_sts_raw" + dimensions: + type: array + items: + type: object + description: | + An object describing a unique dimension. `ds` stands for `dimensions`, `sts` for statistics. + properties: + id: + description: the id of the dimension. + type: string + nm: + description: the name of the dimension (may be absent when it is the same with the id) + type: string + ds: + $ref: "#/components/schemas/jsonwrap2_items_count" + sts: + oneOf: + - $ref: "#/components/schemas/jsonwrap2_sts" + - $ref: "#/components/schemas/jsonwrap2_sts_raw" + labels: + type: array + items: + type: object + description: | + An object describing a label key. `ds` stands for `dimensions`, `sts` for statistics. + properties: + id: + description: the key of the label. + type: string + ds: + $ref: "#/components/schemas/jsonwrap2_items_count" + sts: + oneOf: + - $ref: "#/components/schemas/jsonwrap2_sts" + - $ref: "#/components/schemas/jsonwrap2_sts_raw" + vl: + description: | + An array of values for this key. + type: array + items: + type: object + properties: + id: + description: The value string + type: string + ds: + $ref: "#/components/schemas/jsonwrap2_items_count" + sts: + oneOf: + - $ref: "#/components/schemas/jsonwrap2_sts" + - $ref: "#/components/schemas/jsonwrap2_sts_raw" + alerts: + description: | + An array of all the unique alerts running, grouped by alert name (`nm` is available here) + type: array + items: + $ref: "#/components/schemas/jsonwrap2_alerts_count" + totals: + type: object + properties: + nodes: + $ref: "#/components/schemas/jsonwrap2_items_count" + contexts: + $ref: "#/components/schemas/jsonwrap2_items_count" + instances: + $ref: "#/components/schemas/jsonwrap2_items_count" + dimensions: + $ref: "#/components/schemas/jsonwrap2_items_count" + label_keys: + $ref: "#/components/schemas/jsonwrap2_items_count" + label_key_values: + $ref: "#/components/schemas/jsonwrap2_items_count" + functions: + type: array + items: + type: string + db: + type: object + properties: + tiers: + description: | + The number of tiers this server is using. + type: integer + update_every: + description: | + The minimum update every, in seconds, for all tiers and all metrics aggregated into this query. + type: integer + first_entry: + description: | + The minimum unix epoch timestamp of the retention across all tiers for all metrics aggregated into this query. + type: integer + last_entry: + description: | + The maximum unix epoch timestamp of the retention across all tier for all metrics aggregated into this query. + type: integer + per_tier: + description: | + An array with information for each of the tiers available, related to this query. + type: array + items: + type: object + properties: + tier: + description: | + The tier number of this tier, starting at 0. + type: integer + queries: + description: | + The number of queries executed on this tier. Usually one query per metric is made, but the query may cross multiple tier, in which case more than one query per metric is made. + type: integer + points: + description: | + The number of points read from this tier. + type: integer + update_every: + description: | + The minimum resolution of all metrics queried on this tier. + type: integer + first_entry: + description: | + The minimum unix epoch timestamp available across all metrics that used this tier. This reflects the oldest timestamp of the tier's retention. + type: integer + last_entry: + description: | + The maximum unix epoch timestamp available across all metrics that used this tier. This reflects the newest timestamp of the tier's retention. + units: + description: | + The units of the database data + oneOf: + - type: string + - type: array + items: + type: string + dimensions: + type: object + properties: + ids: + description: | + An array with the dimension ids that uniquely identify the dimensions for this query. It is the same with `view.dimensions.ids`. + type: array + items: + type: string + units: + description: | + An array with the units each dimension has in the database (independent of group-by aggregation that may override the units). + type: array + items: + type: string + sts: + description: | + Statistics about the data collection points used for each dimension. + oneOf: + - $ref: "#/components/schemas/jsonwrap2_sts" + - $ref: "#/components/schemas/jsonwrap2_sts_raw" + view: + type: object + properties: + title: + description: | + The title the chart should have. + type: string + format: + description: | + The format the `result` top level member has. Available on when `debug` flag is set. + type: string + options: + description: | + An array presenting all the options given to the query. Available on when `debug` flag is set. + type: array + items: + type: string + time_group: + description: | + The same as the parameter `time_group`. Available on when `debug` flag is set. + type: string + after: + description: | + The oldest unix epoch timestamp of the data returned in the `result`. + type: integer + before: + description: | + The newest unix epoch timestamp of the data returned in the `result`. + type: integer + partial_data_trimming: + description: | + Information related to trimming of the last few points of the `result`, that was required to remove (increasing) partial data. + Trimming is disabled when the `raw` option is given to the query. + This object is available only when the `debug` flag is set. + type: object + properties: + max_update_every: + description: | + The maximum `update_every` for all metrics aggregated into the query. + Trimming is by default enabled at `view.before - max_update_every`, but only when `view.before >= now - max_update_every`. + type: integer + expected_after: + description: | + The timestamp at which trimming can be enabled. + If this timestamp is greater or equal to `view.before`, there is no trimming. + type: integer + trimmed_after: + description: | + The timestamp at which trimming has been applied. + If this timestamp is greater or equal to `view.before`, there is no trimming. + points: + description: | + The number of points in `result`. Available only when `raw` is given. + type: integer + units: + description: | + The units of the query. + oneOf: + - type: string + - type: array + items: + type: string + chart_type: + description: | + The default chart type of the query. + type: string + enum: + - line + - area + - stacked + dimensions: + description: | + Detailed information about the chart dimensions included in the `result`. + type: object + properties: + grouped_by: + description: | + An array with the order of the groupings performed. + type: array + items: + type: string + enum: + - selected + - dimension + - instance + - node + - context + - units + - "label:key1" + - "label:key2" + - "label:keyN" + ids: + description: | + An array with the dimension ids that uniquely identify the dimensions for this query. + type: array + items: + type: string + names: + description: | + An array with the dimension names to be presented to users. Names may be overlapping, but IDs are not. + type: array + items: + type: string + priorities: + description: | + An array with the relative priorities of the dimensions. + Numbers may not be sequential or unique. The application is expected to order by this and then by name. + type: array + items: + type: integer + aggregated: + description: | + An array with the number of source metrics aggregated into each dimension. + type: array + items: + type: integer + units: + description: | + An array with the units each dimension has. + type: array + items: + type: string + sts: + description: | + Statistics about the view points for each dimension. + oneOf: + - $ref: "#/components/schemas/jsonwrap2_sts" + - $ref: "#/components/schemas/jsonwrap2_sts_raw" + labels: + description: | + The labels associated with each dimension in the query. + This object is only available when the `group-by-labels` option is given to the query. + type: object + properties: + label_key1: + description: | + An array having one entry for each of the dimensions of the query. + type: array + items: + description: | + An array having one entry for each of the values this label key has for the given dimension. + type: array + items: + type: string + min: + description: | + The minimum value of all points included in the `result`. + type: number + max: + description: | + The maximum value of all points included in the `result`. + type: number + result: + $ref: '#/components/schemas/data_json_formats2' + timings: + type: object + jsonwrap2_sts: + description: | + Statistical values + type: object + properties: + min: + description: The minimum value of all metrics aggregated + type: number + max: + description: The maximum value of all metrics aggregated + type: number + avg: + description: The average value of all metrics aggregated + type: number + arp: + description: The average anomaly rate of all metrics aggregated + type: number + con: + description: The contribution percentage of all the metrics aggregated + type: number + jsonwrap2_sts_raw: + description: | + Statistical values when `raw` option is given. + type: object + properties: + min: + description: The minimum value of all metrics aggregated + type: number + max: + description: The maximum value of all metrics aggregated + type: number + sum: + description: The sum value of all metrics aggregated + type: number + ars: + description: The sum anomaly rate of all metrics aggregated + type: number + vol: + description: The volume of all the metrics aggregated + type: number + cnt: + description: The count of all metrics aggregated + type: integer + jsonwrap2_items_count: + description: | + Depending on the placement of this object, `items` may be `nodes`, `contexts`, `instances`, `dimensions`, `label keys`, `label key-value pairs`. Furthermore, if the whole object is missing it should be assumed that all its members are zero. + type: object + properties: + sl: + description: The number of items `selected` to query. If absent it is zero. + type: integer + ex: + description: The number of items `excluded` from querying. If absent it is zero. + type: integer + qr: + description: The number of items (out of `selected`) the query successfully `queried`. If absent it is zero. + type: integer + fl: + description: The number of items (from `selected`) that `failed` to be queried. If absent it is zero. + type: integer + jsonwrap2_alerts_count: + description: | + Counters about alert statuses. If this object is missing, it is assumed that all its members are zero. + type: object + properties: + nm: + description: The name of the alert. Can be absent when the counters refer to more than one alert instances. + type: string + cl: + description: The number of CLEAR alerts. If absent, it is zero. + type: integer + wr: + description: The number of WARNING alerts. If absent, it is zero. + type: integer + cr: + description: The number of CRITICAL alerts. If absent, it is zero. + type: integer + ot: + description: | + The number of alerts that are not CLEAR, WARNING, CRITICAL (so, they are "other"). If absent, it is zero. + type: integer + api: + description: The version of the API used. + type: integer + agents: + description: | + An array of agent definitions consulted to compose this response. + type: array + items: + type: object + properties: + mg: + description: The agent machine GUID. + type: string + format: uuid + nd: + description: The agent cloud node ID. + type: string + format: uuid + nm: + description: The agent hostname. + type: string + ai: + description: The agent index ID for this agent, in this response. + type: integer + now: + description: The current unix epoch timestamp of this agent. + type: integer + versions: + description: | + Hashes that allow the caller to detect important database changes of Netdata agents. + type: object + properties: + nodes_hard_hash: + description: | + An auto-increment value that reflects the number of changes to the number of nodes maintained by the server. Everytime a node is added or removed, this number gets incremented. + type: integer + contexts_hard_hash: + description: | + An auto-increment value that reflects the number of changes to the number of contexts maintained by the server. Everytime a context is added or removed, this number gets incremented. + type: integer + contexts_soft_hash: + description: | + An auto-increment value that reflects the number of changes to the queue that sends contexts updates to Netdata Cloud. Everytime the contents of a context are updated, this number gets incremented. + type: integer + alerts_hard_hash: + description: | + An auto-increment value that reflects the number of changes to the number of alerts. Everytime an alert is added or removed, this number gets incremented. + type: integer + alerts_soft_hash: + description: | + An auto-increment value that reflects the number of alerts transitions. Everytime an alert transitions to a new state, this number gets incremented. + type: integer + nodeBasic: + type: object + description: Basic information about a node. + required: + - ni + - st + properties: + mg: + description: The machine guid of the node. May not be available if the request is served by the Netdata Cloud. + type: string + format: UUID + nd: + description: The node id of the node. May not be available if the node is not registered to Netdata Cloud. + type: string + format: UUID + nm: + description: The name (hostname) of the node. + type: string + ni: + description: The node index id, a number that uniquely identifies this node for this query. + type: integer + st: + description: Status information about the communication with this node. + type: object + properties: + ai: + description: The agent index id that has been contacted for this node. + type: integer + code: + description: The HTTP response code of the response for this node. When working directly with an agent, this is always 200. If the `code` is missing, it should be assumed to be 200. + type: integer + msg: + description: A human readable description of the error, if any. If `msg` is missing, or is the empty string `""` or is `null`, there is no description associated with the current status. + type: string + ms: + description: The time in milliseconds this node took to respond, or if the local agent responded for this node, the time it needed to execute the query. If `ms` is missing, the time that was required to query this node is unknown. + type: number + nodeWithDataStatistics: + allOf: + - $ref: '#/components/schemas/nodeBasic' + - type: object + description: | + `is` stands for instances, `ds` for dimensions, `al` for alerts, `sts` for statistics. + properties: + is: + $ref: "#/components/schemas/jsonwrap2_items_count" + ds: + $ref: "#/components/schemas/jsonwrap2_items_count" + al: + $ref: "#/components/schemas/jsonwrap2_alerts_count" + sts: + oneOf: + - $ref: "#/components/schemas/jsonwrap2_sts" + - $ref: "#/components/schemas/jsonwrap2_sts_raw" + nodeFull: + allOf: + - $ref: '#/components/schemas/nodeBasic' + - type: object + properties: + version: + description: The version of the Netdata Agent the node runs. + type: string + hops: + description: How many hops away from the origin node, the queried one is. 0 means the agent itself is the origin node. + type: integer + state: + description: The current state of the node on this agent. + type: string + enum: + - reachable + - stale + - offline + context2Basic: + type: object + properties: + family: + type: string + priority: + type: integer + first_entry: + type: integer + last_entry: + type: integer + live: + type: boolean + contexts2: + description: | + `/api/v2/contexts` and `/api/v2/q` response about multi-node contexts hosted by a Netdata agent. + type: object + properties: + api: + $ref: '#/components/schemas/api' + agents: + $ref: '#/components/schemas/agents' + versions: + $ref: '#/components/schemas/versions' + contexts: + additionalProperties: + $ref: '#/components/schemas/context2Basic' + jsonwrap1: type: object discriminator: propertyName: format @@ -2007,7 +2618,7 @@ components: properties: api: type: number - description: The API version this conforms to, currently 1. + description: The API version this conforms to. id: type: string description: The unique id of the chart. @@ -2080,101 +2691,132 @@ components: chart_variables: type: object additionalProperties: - $ref: "#/components/schemas/chart_variables" + $ref: '#/components/schemas/chart_variables' + result: + $ref: '#/components/schemas/data_json_formats1' + data_json_formats1: + description: | + Depending on the `format` given to a data query, any of the following may be returned. + oneOf: + - $ref: '#/components/schemas/data_json' + - $ref: '#/components/schemas/data_datatable' + - $ref: '#/components/schemas/data_csvjsonarray' + - $ref: '#/components/schemas/data_array' + - $ref: '#/components/schemas/data_txt' + data_json_formats2: + description: | + Depending on the `format` given to a data query, any of the following may be returned. + oneOf: + - $ref: '#/components/schemas/data_json2' + - $ref: '#/components/schemas/data_json_formats1' + data_json2: + type: object + properties: + labels: + description: | + The IDs of the dimensions returned. The first is always `time`. + type: array + items: + type: string + point: + description: | + The format of each point returned. + type: object + properties: + value: + description: | + The index of the value in each point. + type: integer + arp: + description: | + The index of the anomaly rate in each point. + type: integer + pa: + description: | + The index of the point annotations in each point. + This is a bitmap. `EMPTY = 1`, `RESET = 2`, `PARTIAL = 4`. + `EMPTY` means the point has no value. + `RESET` means that at least one metric aggregated experienced an overflow (a counter that wrapped). + `PARTIAL` means that this point should have more metrics aggregated into it, but not all metrics had data. + type: integer + count: + description: | + The number of metrics aggregated into this point. This exists only when the option `raw` is given to the query. + type: integer + data: + type: array + items: + allOf: + - type: integer + - type: array data_json: - description: Data response in json format. - allOf: - - $ref: "#/components/schemas/data" - - properties: - result: - type: object - properties: - labels: - description: The dimensions retrieved from the chart. - type: array - items: - type: string - data: - description: The data requested, one element per sample with each element - containing the values of the dimensions described in the - labels value. - type: array - items: - type: number - description: The result requested, in the format requested. - data_flat: - description: Data response in csv / tsv / tsv-excel / ssv / ssv-comma / markdown / - html formats. - allOf: - - $ref: "#/components/schemas/data" - - properties: - result: - type: string + description: Data response in `json` format. + type: object + properties: + labels: + description: The dimensions retrieved from the chart. + type: array + items: + type: string + data: + description: | + The data requested, one element per sample with each element containing the values of the dimensions described in the labels value. + type: array + items: + type: number + data_txt: + description: | + Data response in `csv`, `tsv`, `tsv-excel`, `ssv`, `ssv-comma`, `markdown`, `html` formats. + type: string data_array: - description: Data response in array format. - allOf: - - $ref: "#/components/schemas/data" - - properties: - result: - type: array - items: - type: number + description: Data response in `array` format. + type: array + items: + type: number data_csvjsonarray: - description: Data response in csvjsonarray format. - allOf: - - $ref: "#/components/schemas/data" - - properties: - result: - description: The first inner array contains strings showing the labels of - each column, each subsequent array contains the values for each - point in time. - type: array - items: - type: array - items: {} + description: | + The first inner array contains strings showing the labels of each column, each subsequent array contains the values for each point in time. + type: array + items: + type: array + items: {} data_datatable: - description: Data response in datatable / datasource formats (suitable for Google - Charts). - allOf: - - $ref: "#/components/schemas/data" - - properties: - result: - type: object - properties: - cols: - type: array - items: - type: object - properties: - id: - description: Always empty - for future use. - label: - description: The dimension returned from the chart. - pattern: - description: Always empty - for future use. - type: - description: The type of data in the column / chart-dimension. - p: - description: Contains any annotations for the column. - required: - - id - - label - - pattern - - type - rows: - type: array - items: - type: object - properties: - c: - type: array - items: - properties: - v: - description: "Each value in the row is represented by an - object named `c` with five v fields: data, null, - null, 0, the value. This format is fixed by the - Google Charts API." + description: | + Data response in datatable / datasource formats (suitable for Google Charts). + type: object + properties: + cols: + type: array + items: + type: object + properties: + id: + description: Always empty - for future use. + label: + description: The dimension returned from the chart. + pattern: + description: Always empty - for future use. + type: + description: The type of data in the column / chart-dimension. + p: + description: Contains any annotations for the column. + required: + - id + - label + - pattern + - type + rows: + type: array + items: + type: object + properties: + c: + type: array + items: + properties: + v: + description: | + Each value in the row is represented by an object named `c` with five v fields: data, null, null, 0, the value. This format is fixed by the Google Charts API." alarms: type: object properties: @@ -2419,9 +3061,8 @@ components: properties: aclk-available: type: string - description: "Describes whether this agent is capable of connection to the Cloud. - False means agent has been built without ACLK component either on purpose (user choice) - or due to missing dependency." + description: | + Describes whether this agent is capable of connection to the Cloud. False means agent has been built without ACLK component either on purpose (user choice) or due to missing dependency. aclk-version: type: integer description: Describes which ACLK version is currently used. @@ -2529,6 +3170,8 @@ components: type: number dimension2-name: type: number + weights2: + type: object weights: type: object properties: diff --git a/web/api/queries/README.md b/web/api/queries/README.md index 2a17ac78..dacd2900 100644 --- a/web/api/queries/README.md +++ b/web/api/queries/README.md @@ -1,11 +1,10 @@ - +# Database queries/lookup -# Database Queries +This document explains in detail the options available to retrieve data from the Netdata timeseries database in order to configure alerts, create badges or +create custom charts. -Netdata database can be queried with `/api/v1/data` and `/api/v1/badge.svg` REST API methods. +The Netdata database can be queried with the `/api/v1/data` and `/api/v1/badge.svg` REST API methods. The database is also queried from the `lookup` line +in an [alert configuration](https://github.com/netdata/netdata/blob/master/health/REFERENCE.md). Every data query accepts the following parameters: @@ -104,18 +103,24 @@ For each value it calls the **grouping method** given with the `&group=` query p The following grouping methods are supported. These are given all the values in the time-frame and they group the values every `group points`. -- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=min&after=-60&label=min&value_color=blue) finds the minimum value -- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=max&after=-60&label=max&value_color=lightblue) finds the maximum value -- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=average&after=-60&label=average&value_color=yellow) finds the average value -- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=sum&after=-60&label=sum&units=requests&value_color=orange) adds all the values and returns the sum -- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=median&after=-60&label=median&value_color=red) sorts the values and returns the value in the middle of the list -- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=stddev&after=-60&label=stddev&value_color=green) finds the standard deviation of the values -- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=cv&after=-60&label=cv&units=pcent&value_color=yellow) finds the relative standard deviation (coefficient of variation) of the values -- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=ses&after=-60&label=ses&value_color=brown) finds the exponential weighted moving average of the values -- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=des&after=-60&label=des&value_color=blue) applies Holt-Winters double exponential smoothing -- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=web_log_nginx.response_statuses&options=unaligned&dimensions=success&group=incremental_sum&after=-60&label=incremental_sum&value_color=red) finds the difference of the last vs the first value - -The examples shown above, are live information from the `successful` web requests of the global Netdata registry. +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=net.eth0&options=unaligned&dimensions=received&group=min&after=-60&label=min&value_color=blue) finds the minimum value +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=net.eth0&options=unaligned&dimensions=received&group=max&after=-60&label=max&value_color=lightblue) finds the maximum value +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=net.eth0&options=unaligned&dimensions=received&group=average&after=-60&label=average&value_color=yellow) finds the average value +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=net.eth0&options=unaligned&dimensions=received&group=sum&units=kilobits&after=-60&label=sum&value_color=orange) adds all the values and returns the sum +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=net.eth0&options=unaligned&dimensions=received&group=median&after=-60&label=median&value_color=red) sorts the values and returns the value in the middle of the list +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=net.eth0&options=unaligned&dimensions=received&group=stddev&after=-60&label=stddev&value_color=green) finds the standard deviation of the values +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=net.eth0&options=unaligned&dimensions=received&group=cv&after=-60&label=cv&units=pcent&value_color=yellow) finds the relative standard deviation (coefficient of variation) of the values +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=net.eth0&options=unaligned&dimensions=received&group=ses&after=-60&label=ses&value_color=brown) finds the exponential weighted moving average of the values +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=net.eth0&options=unaligned&dimensions=received&group=des&after=-60&label=des&value_color=blue) applies Holt-Winters double exponential smoothing +- ![](https://registry.my-netdata.io/api/v1/badge.svg?chart=net.eth0&options=unaligned&dimensions=received&group=incremental_sum&after=-60&label=incremental_sum&value_color=red) finds the difference of the last vs the first value + +The examples shown above show live information from the `received` traffic on the `eth0` interface of the global Netdata registry. +Inspect any of the badges to see the parameters provided. You can directly issue the request to the registry server's API yourself, e.g. by +passing the following to get the value shown on the badge for the sum of the values within the period: + +``` +https://registry.my-netdata.io/api/v1/data?chart=net.eth0&options=unaligned&dimensions=received&group=sum&units=kilobits&after=-60&label=sum&points=1 +``` ## Further processing diff --git a/web/api/queries/average/README.md b/web/api/queries/average/README.md index b8d4ba7e..c9aa402c 100644 --- a/web/api/queries/average/README.md +++ b/web/api/queries/average/README.md @@ -1,6 +1,10 @@ # Average or Mean diff --git a/web/api/queries/average/average.c b/web/api/queries/average/average.c index 0719d57f..f54dcb24 100644 --- a/web/api/queries/average/average.c +++ b/web/api/queries/average/average.c @@ -2,58 +2,3 @@ #include "average.h" -// ---------------------------------------------------------------------------- -// average - -struct grouping_average { - NETDATA_DOUBLE sum; - size_t count; -}; - -void grouping_create_average(RRDR *r, const char *options __maybe_unused) { - r->internal.grouping_data = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct grouping_average)); -} - -// resets when switches dimensions -// so, clear everything to restart -void grouping_reset_average(RRDR *r) { - struct grouping_average *g = (struct grouping_average *)r->internal.grouping_data; - g->sum = 0; - g->count = 0; -} - -void grouping_free_average(RRDR *r) { - onewayalloc_freez(r->internal.owa, r->internal.grouping_data); - r->internal.grouping_data = NULL; -} - -void grouping_add_average(RRDR *r, NETDATA_DOUBLE value) { - struct grouping_average *g = (struct grouping_average *)r->internal.grouping_data; - g->sum += value; - g->count++; -} - -NETDATA_DOUBLE grouping_flush_average(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_average *g = (struct grouping_average *)r->internal.grouping_data; - - NETDATA_DOUBLE value; - - if(unlikely(!g->count)) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - else { - if(unlikely(r->internal.resampling_group != 1)) { - if (unlikely(r->result_options & RRDR_RESULT_OPTION_VARIABLE_STEP)) - value = g->sum / g->count / r->internal.resampling_divisor; - else - value = g->sum / r->internal.resampling_divisor; - } else - value = g->sum / g->count; - } - - g->sum = 0.0; - g->count = 0; - - return value; -} diff --git a/web/api/queries/average/average.h b/web/api/queries/average/average.h index b3196688..2d77cc57 100644 --- a/web/api/queries/average/average.h +++ b/web/api/queries/average/average.h @@ -6,10 +6,57 @@ #include "../query.h" #include "../rrdr.h" -void grouping_create_average(RRDR *r, const char *options __maybe_unused); -void grouping_reset_average(RRDR *r); -void grouping_free_average(RRDR *r); -void grouping_add_average(RRDR *r, NETDATA_DOUBLE value); -NETDATA_DOUBLE grouping_flush_average(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +// ---------------------------------------------------------------------------- +// average + +struct tg_average { + NETDATA_DOUBLE sum; + size_t count; +}; + +static inline void tg_average_create(RRDR *r, const char *options __maybe_unused) { + r->time_grouping.data = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct tg_average)); +} + +// resets when switches dimensions +// so, clear everything to restart +static inline void tg_average_reset(RRDR *r) { + struct tg_average *g = (struct tg_average *)r->time_grouping.data; + g->sum = 0; + g->count = 0; +} + +static inline void tg_average_free(RRDR *r) { + onewayalloc_freez(r->internal.owa, r->time_grouping.data); + r->time_grouping.data = NULL; +} + +static inline void tg_average_add(RRDR *r, NETDATA_DOUBLE value) { + struct tg_average *g = (struct tg_average *)r->time_grouping.data; + g->sum += value; + g->count++; +} + +static inline NETDATA_DOUBLE tg_average_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_average *g = (struct tg_average *)r->time_grouping.data; + + NETDATA_DOUBLE value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + if(unlikely(r->time_grouping.resampling_group != 1)) + value = g->sum / r->time_grouping.resampling_divisor; + else + value = g->sum / g->count; + } + + g->sum = 0.0; + g->count = 0; + + return value; +} #endif //NETDATA_API_QUERY_AVERAGE_H diff --git a/web/api/queries/countif/README.md b/web/api/queries/countif/README.md index 200a4c9e..37b3f642 100644 --- a/web/api/queries/countif/README.md +++ b/web/api/queries/countif/README.md @@ -1,6 +1,10 @@ # CountIf diff --git a/web/api/queries/countif/countif.c b/web/api/queries/countif/countif.c index 369b20be..8a3a1f50 100644 --- a/web/api/queries/countif/countif.c +++ b/web/api/queries/countif/countif.c @@ -5,132 +5,3 @@ // ---------------------------------------------------------------------------- // countif -struct grouping_countif { - size_t (*comparison)(NETDATA_DOUBLE, NETDATA_DOUBLE); - NETDATA_DOUBLE target; - size_t count; - size_t matched; -}; - -static size_t countif_equal(NETDATA_DOUBLE v, NETDATA_DOUBLE target) { - return (v == target); -} - -static size_t countif_notequal(NETDATA_DOUBLE v, NETDATA_DOUBLE target) { - return (v != target); -} - -static size_t countif_less(NETDATA_DOUBLE v, NETDATA_DOUBLE target) { - return (v < target); -} - -static size_t countif_lessequal(NETDATA_DOUBLE v, NETDATA_DOUBLE target) { - return (v <= target); -} - -static size_t countif_greater(NETDATA_DOUBLE v, NETDATA_DOUBLE target) { - return (v > target); -} - -static size_t countif_greaterequal(NETDATA_DOUBLE v, NETDATA_DOUBLE target) { - return (v >= target); -} - -void grouping_create_countif(RRDR *r, const char *options __maybe_unused) { - struct grouping_countif *g = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct grouping_countif)); - r->internal.grouping_data = g; - - if(options && *options) { - // skip any leading spaces - while(isspace(*options)) options++; - - // find the comparison function - switch(*options) { - case '!': - options++; - if(*options != '=' && *options != ':') - options--; - g->comparison = countif_notequal; - break; - - case '>': - options++; - if(*options == '=' || *options == ':') { - g->comparison = countif_greaterequal; - } - else { - options--; - g->comparison = countif_greater; - } - break; - - case '<': - options++; - if(*options == '>') { - g->comparison = countif_notequal; - } - else if(*options == '=' || *options == ':') { - g->comparison = countif_lessequal; - } - else { - options--; - g->comparison = countif_less; - } - break; - - default: - case '=': - case ':': - g->comparison = countif_equal; - break; - } - if(*options) options++; - - // skip everything up to the first digit - while(isspace(*options)) options++; - - g->target = str2ndd(options, NULL); - } - else { - g->target = 0.0; - g->comparison = countif_equal; - } -} - -// resets when switches dimensions -// so, clear everything to restart -void grouping_reset_countif(RRDR *r) { - struct grouping_countif *g = (struct grouping_countif *)r->internal.grouping_data; - g->matched = 0; - g->count = 0; -} - -void grouping_free_countif(RRDR *r) { - onewayalloc_freez(r->internal.owa, r->internal.grouping_data); - r->internal.grouping_data = NULL; -} - -void grouping_add_countif(RRDR *r, NETDATA_DOUBLE value) { - struct grouping_countif *g = (struct grouping_countif *)r->internal.grouping_data; - g->matched += g->comparison(value, g->target); - g->count++; -} - -NETDATA_DOUBLE grouping_flush_countif(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_countif *g = (struct grouping_countif *)r->internal.grouping_data; - - NETDATA_DOUBLE value; - - if(unlikely(!g->count)) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - else { - value = (NETDATA_DOUBLE)g->matched * 100 / (NETDATA_DOUBLE)g->count; - } - - g->matched = 0; - g->count = 0; - - return value; -} diff --git a/web/api/queries/countif/countif.h b/web/api/queries/countif/countif.h index dfe80565..896b9d87 100644 --- a/web/api/queries/countif/countif.h +++ b/web/api/queries/countif/countif.h @@ -6,10 +6,143 @@ #include "../query.h" #include "../rrdr.h" -void grouping_create_countif(RRDR *r, const char *options __maybe_unused); -void grouping_reset_countif(RRDR *r); -void grouping_free_countif(RRDR *r); -void grouping_add_countif(RRDR *r, NETDATA_DOUBLE value); -NETDATA_DOUBLE grouping_flush_countif(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +enum tg_countif_cmp { + TG_COUNTIF_EQUAL, + TG_COUNTIF_NOTEQUAL, + TG_COUNTIF_LESS, + TG_COUNTIF_LESSEQUAL, + TG_COUNTIF_GREATER, + TG_COUNTIF_GREATEREQUAL, +}; + +struct tg_countif { + enum tg_countif_cmp comparison; + NETDATA_DOUBLE target; + size_t count; + size_t matched; +}; + +static inline void tg_countif_create(RRDR *r, const char *options __maybe_unused) { + struct tg_countif *g = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct tg_countif)); + r->time_grouping.data = g; + + if(options && *options) { + // skip any leading spaces + while(isspace(*options)) options++; + + // find the comparison function + switch(*options) { + case '!': + options++; + if(*options != '=' && *options != ':') + options--; + g->comparison = TG_COUNTIF_NOTEQUAL; + break; + + case '>': + options++; + if(*options == '=' || *options == ':') { + g->comparison = TG_COUNTIF_GREATEREQUAL; + } + else { + options--; + g->comparison = TG_COUNTIF_GREATER; + } + break; + + case '<': + options++; + if(*options == '>') { + g->comparison = TG_COUNTIF_NOTEQUAL; + } + else if(*options == '=' || *options == ':') { + g->comparison = TG_COUNTIF_LESSEQUAL; + } + else { + options--; + g->comparison = TG_COUNTIF_LESS; + } + break; + + default: + case '=': + case ':': + g->comparison = TG_COUNTIF_EQUAL; + break; + } + if(*options) options++; + + // skip everything up to the first digit + while(isspace(*options)) options++; + + g->target = str2ndd(options, NULL); + } + else { + g->target = 0.0; + g->comparison = TG_COUNTIF_EQUAL; + } +} + +// resets when switches dimensions +// so, clear everything to restart +static inline void tg_countif_reset(RRDR *r) { + struct tg_countif *g = (struct tg_countif *)r->time_grouping.data; + g->matched = 0; + g->count = 0; +} + +static inline void tg_countif_free(RRDR *r) { + onewayalloc_freez(r->internal.owa, r->time_grouping.data); + r->time_grouping.data = NULL; +} + +static inline void tg_countif_add(RRDR *r, NETDATA_DOUBLE value) { + struct tg_countif *g = (struct tg_countif *)r->time_grouping.data; + switch(g->comparison) { + case TG_COUNTIF_GREATER: + if(value > g->target) g->matched++; + break; + + case TG_COUNTIF_GREATEREQUAL: + if(value >= g->target) g->matched++; + break; + + case TG_COUNTIF_LESS: + if(value < g->target) g->matched++; + break; + + case TG_COUNTIF_LESSEQUAL: + if(value <= g->target) g->matched++; + break; + + case TG_COUNTIF_EQUAL: + if(value == g->target) g->matched++; + break; + + case TG_COUNTIF_NOTEQUAL: + if(value != g->target) g->matched++; + break; + } + g->count++; +} + +static inline NETDATA_DOUBLE tg_countif_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_countif *g = (struct tg_countif *)r->time_grouping.data; + + NETDATA_DOUBLE value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + value = (NETDATA_DOUBLE)g->matched * 100 / (NETDATA_DOUBLE)g->count; + } + + g->matched = 0; + g->count = 0; + + return value; +} #endif //NETDATA_API_QUERY_COUNTIF_H diff --git a/web/api/queries/des/README.md b/web/api/queries/des/README.md index 33c5f1a0..b12751a4 100644 --- a/web/api/queries/des/README.md +++ b/web/api/queries/des/README.md @@ -1,6 +1,10 @@ # double exponential smoothing diff --git a/web/api/queries/des/des.c b/web/api/queries/des/des.c index a6c4e405..d0e234e2 100644 --- a/web/api/queries/des/des.c +++ b/web/api/queries/des/des.c @@ -6,132 +6,3 @@ // ---------------------------------------------------------------------------- // single exponential smoothing - -struct grouping_des { - NETDATA_DOUBLE alpha; - NETDATA_DOUBLE alpha_other; - NETDATA_DOUBLE beta; - NETDATA_DOUBLE beta_other; - - NETDATA_DOUBLE level; - NETDATA_DOUBLE trend; - - size_t count; -}; - -static size_t max_window_size = 15; - -void grouping_init_des(void) { - long long ret = config_get_number(CONFIG_SECTION_WEB, "des max window", (long long)max_window_size); - if(ret <= 1) { - config_set_number(CONFIG_SECTION_WEB, "des max window", (long long)max_window_size); - } - else { - max_window_size = (size_t) ret; - } -} - -static inline NETDATA_DOUBLE window(RRDR *r, struct grouping_des *g) { - (void)g; - - NETDATA_DOUBLE points; - if(r->group == 1) { - // provide a running DES - points = (NETDATA_DOUBLE)r->internal.points_wanted; - } - else { - // provide a SES with flush points - points = (NETDATA_DOUBLE)r->group; - } - - // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average - // A commonly used value for alpha is 2 / (N + 1) - return (points > (NETDATA_DOUBLE)max_window_size) ? (NETDATA_DOUBLE)max_window_size : points; -} - -static inline void set_alpha(RRDR *r, struct grouping_des *g) { - // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average - // A commonly used value for alpha is 2 / (N + 1) - - g->alpha = 2.0 / (window(r, g) + 1.0); - g->alpha_other = 1.0 - g->alpha; - - //info("alpha for chart '%s' is " CALCULATED_NUMBER_FORMAT, r->st->name, g->alpha); -} - -static inline void set_beta(RRDR *r, struct grouping_des *g) { - // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average - // A commonly used value for alpha is 2 / (N + 1) - - g->beta = 2.0 / (window(r, g) + 1.0); - g->beta_other = 1.0 - g->beta; - - //info("beta for chart '%s' is " CALCULATED_NUMBER_FORMAT, r->st->name, g->beta); -} - -void grouping_create_des(RRDR *r, const char *options __maybe_unused) { - struct grouping_des *g = (struct grouping_des *)onewayalloc_mallocz(r->internal.owa, sizeof(struct grouping_des)); - set_alpha(r, g); - set_beta(r, g); - g->level = 0.0; - g->trend = 0.0; - g->count = 0; - r->internal.grouping_data = g; -} - -// resets when switches dimensions -// so, clear everything to restart -void grouping_reset_des(RRDR *r) { - struct grouping_des *g = (struct grouping_des *)r->internal.grouping_data; - g->level = 0.0; - g->trend = 0.0; - g->count = 0; - - // fprintf(stderr, "\nDES: "); - -} - -void grouping_free_des(RRDR *r) { - onewayalloc_freez(r->internal.owa, r->internal.grouping_data); - r->internal.grouping_data = NULL; -} - -void grouping_add_des(RRDR *r, NETDATA_DOUBLE value) { - struct grouping_des *g = (struct grouping_des *)r->internal.grouping_data; - - if(likely(g->count > 0)) { - // we have at least a number so far - - if(unlikely(g->count == 1)) { - // the second value we got - g->trend = value - g->trend; - g->level = value; - } - - // for the values, except the first - NETDATA_DOUBLE last_level = g->level; - g->level = (g->alpha * value) + (g->alpha_other * (g->level + g->trend)); - g->trend = (g->beta * (g->level - last_level)) + (g->beta_other * g->trend); - } - else { - // the first value we got - g->level = g->trend = value; - } - - g->count++; - - //fprintf(stderr, "value: " CALCULATED_NUMBER_FORMAT ", level: " CALCULATED_NUMBER_FORMAT ", trend: " CALCULATED_NUMBER_FORMAT "\n", value, g->level, g->trend); -} - -NETDATA_DOUBLE grouping_flush_des(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_des *g = (struct grouping_des *)r->internal.grouping_data; - - if(unlikely(!g->count || !netdata_double_isnumber(g->level))) { - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - return 0.0; - } - - //fprintf(stderr, " RESULT for %zu values = " CALCULATED_NUMBER_FORMAT " \n", g->count, g->level); - - return g->level; -} diff --git a/web/api/queries/des/des.h b/web/api/queries/des/des.h index 05fa01b3..3153d497 100644 --- a/web/api/queries/des/des.h +++ b/web/api/queries/des/des.h @@ -6,12 +6,133 @@ #include "../query.h" #include "../rrdr.h" -void grouping_init_des(void); +struct tg_des { + NETDATA_DOUBLE alpha; + NETDATA_DOUBLE alpha_other; + NETDATA_DOUBLE beta; + NETDATA_DOUBLE beta_other; -void grouping_create_des(RRDR *r, const char *options __maybe_unused); -void grouping_reset_des(RRDR *r); -void grouping_free_des(RRDR *r); -void grouping_add_des(RRDR *r, NETDATA_DOUBLE value); -NETDATA_DOUBLE grouping_flush_des(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + NETDATA_DOUBLE level; + NETDATA_DOUBLE trend; + + size_t count; +}; + +static size_t tg_des_max_window_size = 15; + +static inline void tg_des_init(void) { + long long ret = config_get_number(CONFIG_SECTION_WEB, "des max tg_des_window", (long long)tg_des_max_window_size); + if(ret <= 1) { + config_set_number(CONFIG_SECTION_WEB, "des max tg_des_window", (long long)tg_des_max_window_size); + } + else { + tg_des_max_window_size = (size_t) ret; + } +} + +static inline NETDATA_DOUBLE tg_des_window(RRDR *r, struct tg_des *g) { + (void)g; + + NETDATA_DOUBLE points; + if(r->view.group == 1) { + // provide a running DES + points = (NETDATA_DOUBLE)r->time_grouping.points_wanted; + } + else { + // provide a SES with flush points + points = (NETDATA_DOUBLE)r->view.group; + } + + // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + // A commonly used value for alpha is 2 / (N + 1) + return (points > (NETDATA_DOUBLE)tg_des_max_window_size) ? (NETDATA_DOUBLE)tg_des_max_window_size : points; +} + +static inline void tg_des_set_alpha(RRDR *r, struct tg_des *g) { + // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + // A commonly used value for alpha is 2 / (N + 1) + + g->alpha = 2.0 / (tg_des_window(r, g) + 1.0); + g->alpha_other = 1.0 - g->alpha; + + //info("alpha for chart '%s' is " CALCULATED_NUMBER_FORMAT, r->st->name, g->alpha); +} + +static inline void tg_des_set_beta(RRDR *r, struct tg_des *g) { + // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + // A commonly used value for alpha is 2 / (N + 1) + + g->beta = 2.0 / (tg_des_window(r, g) + 1.0); + g->beta_other = 1.0 - g->beta; + + //info("beta for chart '%s' is " CALCULATED_NUMBER_FORMAT, r->st->name, g->beta); +} + +static inline void tg_des_create(RRDR *r, const char *options __maybe_unused) { + struct tg_des *g = (struct tg_des *)onewayalloc_mallocz(r->internal.owa, sizeof(struct tg_des)); + tg_des_set_alpha(r, g); + tg_des_set_beta(r, g); + g->level = 0.0; + g->trend = 0.0; + g->count = 0; + r->time_grouping.data = g; +} + +// resets when switches dimensions +// so, clear everything to restart +static inline void tg_des_reset(RRDR *r) { + struct tg_des *g = (struct tg_des *)r->time_grouping.data; + g->level = 0.0; + g->trend = 0.0; + g->count = 0; + + // fprintf(stderr, "\nDES: "); + +} + +static inline void tg_des_free(RRDR *r) { + onewayalloc_freez(r->internal.owa, r->time_grouping.data); + r->time_grouping.data = NULL; +} + +static inline void tg_des_add(RRDR *r, NETDATA_DOUBLE value) { + struct tg_des *g = (struct tg_des *)r->time_grouping.data; + + if(likely(g->count > 0)) { + // we have at least a number so far + + if(unlikely(g->count == 1)) { + // the second value we got + g->trend = value - g->trend; + g->level = value; + } + + // for the values, except the first + NETDATA_DOUBLE last_level = g->level; + g->level = (g->alpha * value) + (g->alpha_other * (g->level + g->trend)); + g->trend = (g->beta * (g->level - last_level)) + (g->beta_other * g->trend); + } + else { + // the first value we got + g->level = g->trend = value; + } + + g->count++; + + //fprintf(stderr, "value: " CALCULATED_NUMBER_FORMAT ", level: " CALCULATED_NUMBER_FORMAT ", trend: " CALCULATED_NUMBER_FORMAT "\n", value, g->level, g->trend); +} + +static inline NETDATA_DOUBLE tg_des_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_des *g = (struct tg_des *)r->time_grouping.data; + + if(unlikely(!g->count || !netdata_double_isnumber(g->level))) { + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + return 0.0; + } + + //fprintf(stderr, " RESULT for %zu values = " CALCULATED_NUMBER_FORMAT " \n", g->count, g->level); + + return g->level; +} #endif //NETDATA_API_QUERIES_DES_H diff --git a/web/api/queries/incremental_sum/README.md b/web/api/queries/incremental_sum/README.md index 44301172..9b89f318 100644 --- a/web/api/queries/incremental_sum/README.md +++ b/web/api/queries/incremental_sum/README.md @@ -1,6 +1,10 @@ # Incremental Sum (`incremental_sum`) diff --git a/web/api/queries/incremental_sum/incremental_sum.c b/web/api/queries/incremental_sum/incremental_sum.c index afca530c..88072f29 100644 --- a/web/api/queries/incremental_sum/incremental_sum.c +++ b/web/api/queries/incremental_sum/incremental_sum.c @@ -5,62 +5,3 @@ // ---------------------------------------------------------------------------- // incremental sum -struct grouping_incremental_sum { - NETDATA_DOUBLE first; - NETDATA_DOUBLE last; - size_t count; -}; - -void grouping_create_incremental_sum(RRDR *r, const char *options __maybe_unused) { - r->internal.grouping_data = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct grouping_incremental_sum)); -} - -// resets when switches dimensions -// so, clear everything to restart -void grouping_reset_incremental_sum(RRDR *r) { - struct grouping_incremental_sum *g = (struct grouping_incremental_sum *)r->internal.grouping_data; - g->first = 0; - g->last = 0; - g->count = 0; -} - -void grouping_free_incremental_sum(RRDR *r) { - onewayalloc_freez(r->internal.owa, r->internal.grouping_data); - r->internal.grouping_data = NULL; -} - -void grouping_add_incremental_sum(RRDR *r, NETDATA_DOUBLE value) { - struct grouping_incremental_sum *g = (struct grouping_incremental_sum *)r->internal.grouping_data; - - if(unlikely(!g->count)) { - g->first = value; - g->count++; - } - else { - g->last = value; - g->count++; - } -} - -NETDATA_DOUBLE grouping_flush_incremental_sum(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_incremental_sum *g = (struct grouping_incremental_sum *)r->internal.grouping_data; - - NETDATA_DOUBLE value; - - if(unlikely(!g->count)) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - else if(unlikely(g->count == 1)) { - value = 0.0; - } - else { - value = g->last - g->first; - } - - g->first = 0.0; - g->last = 0.0; - g->count = 0; - - return value; -} diff --git a/web/api/queries/incremental_sum/incremental_sum.h b/web/api/queries/incremental_sum/incremental_sum.h index c24507fc..dd6483b2 100644 --- a/web/api/queries/incremental_sum/incremental_sum.h +++ b/web/api/queries/incremental_sum/incremental_sum.h @@ -6,10 +6,64 @@ #include "../query.h" #include "../rrdr.h" -void grouping_create_incremental_sum(RRDR *r, const char *options __maybe_unused); -void grouping_reset_incremental_sum(RRDR *r); -void grouping_free_incremental_sum(RRDR *r); -void grouping_add_incremental_sum(RRDR *r, NETDATA_DOUBLE value); -NETDATA_DOUBLE grouping_flush_incremental_sum(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +struct tg_incremental_sum { + NETDATA_DOUBLE first; + NETDATA_DOUBLE last; + size_t count; +}; + +static inline void tg_incremental_sum_create(RRDR *r, const char *options __maybe_unused) { + r->time_grouping.data = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct tg_incremental_sum)); +} + +// resets when switches dimensions +// so, clear everything to restart +static inline void tg_incremental_sum_reset(RRDR *r) { + struct tg_incremental_sum *g = (struct tg_incremental_sum *)r->time_grouping.data; + g->first = 0; + g->last = 0; + g->count = 0; +} + +static inline void tg_incremental_sum_free(RRDR *r) { + onewayalloc_freez(r->internal.owa, r->time_grouping.data); + r->time_grouping.data = NULL; +} + +static inline void tg_incremental_sum_add(RRDR *r, NETDATA_DOUBLE value) { + struct tg_incremental_sum *g = (struct tg_incremental_sum *)r->time_grouping.data; + + if(unlikely(!g->count)) { + g->first = value; + g->count++; + } + else { + g->last = value; + g->count++; + } +} + +static inline NETDATA_DOUBLE tg_incremental_sum_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_incremental_sum *g = (struct tg_incremental_sum *)r->time_grouping.data; + + NETDATA_DOUBLE value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else if(unlikely(g->count == 1)) { + value = 0.0; + } + else { + value = g->last - g->first; + } + + g->first = 0.0; + g->last = 0.0; + g->count = 0; + + return value; +} #endif //NETDATA_API_QUERY_INCREMENTAL_SUM_H diff --git a/web/api/queries/max/README.md b/web/api/queries/max/README.md index 48da7cf0..82749c4a 100644 --- a/web/api/queries/max/README.md +++ b/web/api/queries/max/README.md @@ -1,6 +1,10 @@ # Max diff --git a/web/api/queries/max/max.c b/web/api/queries/max/max.c index 73cf9fa6..cc5999a2 100644 --- a/web/api/queries/max/max.c +++ b/web/api/queries/max/max.c @@ -5,53 +5,3 @@ // ---------------------------------------------------------------------------- // max -struct grouping_max { - NETDATA_DOUBLE max; - size_t count; -}; - -void grouping_create_max(RRDR *r, const char *options __maybe_unused) { - r->internal.grouping_data = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct grouping_max)); -} - -// resets when switches dimensions -// so, clear everything to restart -void grouping_reset_max(RRDR *r) { - struct grouping_max *g = (struct grouping_max *)r->internal.grouping_data; - g->max = 0; - g->count = 0; -} - -void grouping_free_max(RRDR *r) { - onewayalloc_freez(r->internal.owa, r->internal.grouping_data); - r->internal.grouping_data = NULL; -} - -void grouping_add_max(RRDR *r, NETDATA_DOUBLE value) { - struct grouping_max *g = (struct grouping_max *)r->internal.grouping_data; - - if(!g->count || fabsndd(value) > fabsndd(g->max)) { - g->max = value; - g->count++; - } -} - -NETDATA_DOUBLE grouping_flush_max(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_max *g = (struct grouping_max *)r->internal.grouping_data; - - NETDATA_DOUBLE value; - - if(unlikely(!g->count)) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - else { - value = g->max; - } - - g->max = 0.0; - g->count = 0; - - return value; -} - diff --git a/web/api/queries/max/max.h b/web/api/queries/max/max.h index e2427d26..c26bb79a 100644 --- a/web/api/queries/max/max.h +++ b/web/api/queries/max/max.h @@ -6,10 +6,54 @@ #include "../query.h" #include "../rrdr.h" -void grouping_create_max(RRDR *r, const char *options __maybe_unused); -void grouping_reset_max(RRDR *r); -void grouping_free_max(RRDR *r); -void grouping_add_max(RRDR *r, NETDATA_DOUBLE value); -NETDATA_DOUBLE grouping_flush_max(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +struct tg_max { + NETDATA_DOUBLE max; + size_t count; +}; + +static inline void tg_max_create(RRDR *r, const char *options __maybe_unused) { + r->time_grouping.data = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct tg_max)); +} + +// resets when switches dimensions +// so, clear everything to restart +static inline void tg_max_reset(RRDR *r) { + struct tg_max *g = (struct tg_max *)r->time_grouping.data; + g->max = 0; + g->count = 0; +} + +static inline void tg_max_free(RRDR *r) { + onewayalloc_freez(r->internal.owa, r->time_grouping.data); + r->time_grouping.data = NULL; +} + +static inline void tg_max_add(RRDR *r, NETDATA_DOUBLE value) { + struct tg_max *g = (struct tg_max *)r->time_grouping.data; + + if(!g->count || fabsndd(value) > fabsndd(g->max)) { + g->max = value; + g->count++; + } +} + +static inline NETDATA_DOUBLE tg_max_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_max *g = (struct tg_max *)r->time_grouping.data; + + NETDATA_DOUBLE value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + value = g->max; + } + + g->max = 0.0; + g->count = 0; + + return value; +} #endif //NETDATA_API_QUERY_MAX_H diff --git a/web/api/queries/median/README.md b/web/api/queries/median/README.md index 5600284c..15549b3b 100644 --- a/web/api/queries/median/README.md +++ b/web/api/queries/median/README.md @@ -1,7 +1,11 @@ # Median diff --git a/web/api/queries/median/median.c b/web/api/queries/median/median.c index 40fd4ec3..9865b485 100644 --- a/web/api/queries/median/median.c +++ b/web/api/queries/median/median.c @@ -4,137 +4,3 @@ // ---------------------------------------------------------------------------- // median - -struct grouping_median { - size_t series_size; - size_t next_pos; - NETDATA_DOUBLE percent; - - NETDATA_DOUBLE *series; -}; - -void grouping_create_median_internal(RRDR *r, const char *options, NETDATA_DOUBLE def) { - long entries = r->group; - if(entries < 10) entries = 10; - - struct grouping_median *g = (struct grouping_median *)onewayalloc_callocz(r->internal.owa, 1, sizeof(struct grouping_median)); - g->series = onewayalloc_mallocz(r->internal.owa, entries * sizeof(NETDATA_DOUBLE)); - g->series_size = (size_t)entries; - - g->percent = def; - if(options && *options) { - g->percent = str2ndd(options, NULL); - if(!netdata_double_isnumber(g->percent)) g->percent = 0.0; - if(g->percent < 0.0) g->percent = 0.0; - if(g->percent > 50.0) g->percent = 50.0; - } - - g->percent = g->percent / 100.0; - r->internal.grouping_data = g; -} - -void grouping_create_median(RRDR *r, const char *options) { - grouping_create_median_internal(r, options, 0.0); -} -void grouping_create_trimmed_median1(RRDR *r, const char *options) { - grouping_create_median_internal(r, options, 1.0); -} -void grouping_create_trimmed_median2(RRDR *r, const char *options) { - grouping_create_median_internal(r, options, 2.0); -} -void grouping_create_trimmed_median3(RRDR *r, const char *options) { - grouping_create_median_internal(r, options, 3.0); -} -void grouping_create_trimmed_median5(RRDR *r, const char *options) { - grouping_create_median_internal(r, options, 5.0); -} -void grouping_create_trimmed_median10(RRDR *r, const char *options) { - grouping_create_median_internal(r, options, 10.0); -} -void grouping_create_trimmed_median15(RRDR *r, const char *options) { - grouping_create_median_internal(r, options, 15.0); -} -void grouping_create_trimmed_median20(RRDR *r, const char *options) { - grouping_create_median_internal(r, options, 20.0); -} -void grouping_create_trimmed_median25(RRDR *r, const char *options) { - grouping_create_median_internal(r, options, 25.0); -} - -// resets when switches dimensions -// so, clear everything to restart -void grouping_reset_median(RRDR *r) { - struct grouping_median *g = (struct grouping_median *)r->internal.grouping_data; - g->next_pos = 0; -} - -void grouping_free_median(RRDR *r) { - struct grouping_median *g = (struct grouping_median *)r->internal.grouping_data; - if(g) onewayalloc_freez(r->internal.owa, g->series); - - onewayalloc_freez(r->internal.owa, r->internal.grouping_data); - r->internal.grouping_data = NULL; -} - -void grouping_add_median(RRDR *r, NETDATA_DOUBLE value) { - struct grouping_median *g = (struct grouping_median *)r->internal.grouping_data; - - if(unlikely(g->next_pos >= g->series_size)) { - g->series = onewayalloc_doublesize( r->internal.owa, g->series, g->series_size * sizeof(NETDATA_DOUBLE)); - g->series_size *= 2; - } - - g->series[g->next_pos++] = value; -} - -NETDATA_DOUBLE grouping_flush_median(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_median *g = (struct grouping_median *)r->internal.grouping_data; - - size_t available_slots = g->next_pos; - NETDATA_DOUBLE value; - - if(unlikely(!available_slots)) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - else if(available_slots == 1) { - value = g->series[0]; - } - else { - sort_series(g->series, available_slots); - - size_t start_slot = 0; - size_t end_slot = available_slots - 1; - - if(g->percent > 0.0) { - NETDATA_DOUBLE min = g->series[0]; - NETDATA_DOUBLE max = g->series[available_slots - 1]; - NETDATA_DOUBLE delta = (max - min) * g->percent; - - NETDATA_DOUBLE wanted_min = min + delta; - NETDATA_DOUBLE wanted_max = max - delta; - - for (start_slot = 0; start_slot < available_slots; start_slot++) - if (g->series[start_slot] >= wanted_min) break; - - for (end_slot = available_slots - 1; end_slot > start_slot; end_slot--) - if (g->series[end_slot] <= wanted_max) break; - } - - if(start_slot == end_slot) - value = g->series[start_slot]; - else - value = median_on_sorted_series(&g->series[start_slot], end_slot - start_slot + 1); - } - - if(unlikely(!netdata_double_isnumber(value))) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - - //log_series_to_stderr(g->series, g->next_pos, value, "median"); - - g->next_pos = 0; - - return value; -} diff --git a/web/api/queries/median/median.h b/web/api/queries/median/median.h index 9fc159db..3d6d3592 100644 --- a/web/api/queries/median/median.h +++ b/web/api/queries/median/median.h @@ -6,18 +6,138 @@ #include "../query.h" #include "../rrdr.h" -void grouping_create_median(RRDR *r, const char *options); -void grouping_create_trimmed_median1(RRDR *r, const char *options); -void grouping_create_trimmed_median2(RRDR *r, const char *options); -void grouping_create_trimmed_median3(RRDR *r, const char *options); -void grouping_create_trimmed_median5(RRDR *r, const char *options); -void grouping_create_trimmed_median10(RRDR *r, const char *options); -void grouping_create_trimmed_median15(RRDR *r, const char *options); -void grouping_create_trimmed_median20(RRDR *r, const char *options); -void grouping_create_trimmed_median25(RRDR *r, const char *options); -void grouping_reset_median(RRDR *r); -void grouping_free_median(RRDR *r); -void grouping_add_median(RRDR *r, NETDATA_DOUBLE value); -NETDATA_DOUBLE grouping_flush_median(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +struct tg_median { + size_t series_size; + size_t next_pos; + NETDATA_DOUBLE percent; + + NETDATA_DOUBLE *series; +}; + +static inline void tg_median_create_internal(RRDR *r, const char *options, NETDATA_DOUBLE def) { + long entries = r->view.group; + if(entries < 10) entries = 10; + + struct tg_median *g = (struct tg_median *)onewayalloc_callocz(r->internal.owa, 1, sizeof(struct tg_median)); + g->series = onewayalloc_mallocz(r->internal.owa, entries * sizeof(NETDATA_DOUBLE)); + g->series_size = (size_t)entries; + + g->percent = def; + if(options && *options) { + g->percent = str2ndd(options, NULL); + if(!netdata_double_isnumber(g->percent)) g->percent = 0.0; + if(g->percent < 0.0) g->percent = 0.0; + if(g->percent > 50.0) g->percent = 50.0; + } + + g->percent = g->percent / 100.0; + r->time_grouping.data = g; +} + +static inline void tg_median_create(RRDR *r, const char *options) { + tg_median_create_internal(r, options, 0.0); +} +static inline void tg_median_create_trimmed_1(RRDR *r, const char *options) { + tg_median_create_internal(r, options, 1.0); +} +static inline void tg_median_create_trimmed_2(RRDR *r, const char *options) { + tg_median_create_internal(r, options, 2.0); +} +static inline void tg_median_create_trimmed_3(RRDR *r, const char *options) { + tg_median_create_internal(r, options, 3.0); +} +static inline void tg_median_create_trimmed_5(RRDR *r, const char *options) { + tg_median_create_internal(r, options, 5.0); +} +static inline void tg_median_create_trimmed_10(RRDR *r, const char *options) { + tg_median_create_internal(r, options, 10.0); +} +static inline void tg_median_create_trimmed_15(RRDR *r, const char *options) { + tg_median_create_internal(r, options, 15.0); +} +static inline void tg_median_create_trimmed_20(RRDR *r, const char *options) { + tg_median_create_internal(r, options, 20.0); +} +static inline void tg_median_create_trimmed_25(RRDR *r, const char *options) { + tg_median_create_internal(r, options, 25.0); +} + +// resets when switches dimensions +// so, clear everything to restart +static inline void tg_median_reset(RRDR *r) { + struct tg_median *g = (struct tg_median *)r->time_grouping.data; + g->next_pos = 0; +} + +static inline void tg_median_free(RRDR *r) { + struct tg_median *g = (struct tg_median *)r->time_grouping.data; + if(g) onewayalloc_freez(r->internal.owa, g->series); + + onewayalloc_freez(r->internal.owa, r->time_grouping.data); + r->time_grouping.data = NULL; +} + +static inline void tg_median_add(RRDR *r, NETDATA_DOUBLE value) { + struct tg_median *g = (struct tg_median *)r->time_grouping.data; + + if(unlikely(g->next_pos >= g->series_size)) { + g->series = onewayalloc_doublesize( r->internal.owa, g->series, g->series_size * sizeof(NETDATA_DOUBLE)); + g->series_size *= 2; + } + + g->series[g->next_pos++] = value; +} + +static inline NETDATA_DOUBLE tg_median_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_median *g = (struct tg_median *)r->time_grouping.data; + + size_t available_slots = g->next_pos; + NETDATA_DOUBLE value; + + if(unlikely(!available_slots)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else if(available_slots == 1) { + value = g->series[0]; + } + else { + sort_series(g->series, available_slots); + + size_t start_slot = 0; + size_t end_slot = available_slots - 1; + + if(g->percent > 0.0) { + NETDATA_DOUBLE min = g->series[0]; + NETDATA_DOUBLE max = g->series[available_slots - 1]; + NETDATA_DOUBLE delta = (max - min) * g->percent; + + NETDATA_DOUBLE wanted_min = min + delta; + NETDATA_DOUBLE wanted_max = max - delta; + + for (start_slot = 0; start_slot < available_slots; start_slot++) + if (g->series[start_slot] >= wanted_min) break; + + for (end_slot = available_slots - 1; end_slot > start_slot; end_slot--) + if (g->series[end_slot] <= wanted_max) break; + } + + if(start_slot == end_slot) + value = g->series[start_slot]; + else + value = median_on_sorted_series(&g->series[start_slot], end_slot - start_slot + 1); + } + + if(unlikely(!netdata_double_isnumber(value))) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + + //log_series_to_stderr(g->series, g->next_pos, value, "median"); + + g->next_pos = 0; + + return value; +} #endif //NETDATA_API_QUERIES_MEDIAN_H diff --git a/web/api/queries/min/README.md b/web/api/queries/min/README.md index 495523c0..cf63aaa0 100644 --- a/web/api/queries/min/README.md +++ b/web/api/queries/min/README.md @@ -1,6 +1,10 @@ # Min diff --git a/web/api/queries/min/min.c b/web/api/queries/min/min.c index 1752e9e0..cefa7cf3 100644 --- a/web/api/queries/min/min.c +++ b/web/api/queries/min/min.c @@ -5,53 +5,3 @@ // ---------------------------------------------------------------------------- // min -struct grouping_min { - NETDATA_DOUBLE min; - size_t count; -}; - -void grouping_create_min(RRDR *r, const char *options __maybe_unused) { - r->internal.grouping_data = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct grouping_min)); -} - -// resets when switches dimensions -// so, clear everything to restart -void grouping_reset_min(RRDR *r) { - struct grouping_min *g = (struct grouping_min *)r->internal.grouping_data; - g->min = 0; - g->count = 0; -} - -void grouping_free_min(RRDR *r) { - onewayalloc_freez(r->internal.owa, r->internal.grouping_data); - r->internal.grouping_data = NULL; -} - -void grouping_add_min(RRDR *r, NETDATA_DOUBLE value) { - struct grouping_min *g = (struct grouping_min *)r->internal.grouping_data; - - if(!g->count || fabsndd(value) < fabsndd(g->min)) { - g->min = value; - g->count++; - } -} - -NETDATA_DOUBLE grouping_flush_min(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_min *g = (struct grouping_min *)r->internal.grouping_data; - - NETDATA_DOUBLE value; - - if(unlikely(!g->count)) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - else { - value = g->min; - } - - g->min = 0.0; - g->count = 0; - - return value; -} - diff --git a/web/api/queries/min/min.h b/web/api/queries/min/min.h index dcdfe252..3c53dfd1 100644 --- a/web/api/queries/min/min.h +++ b/web/api/queries/min/min.h @@ -6,10 +6,54 @@ #include "../query.h" #include "../rrdr.h" -void grouping_create_min(RRDR *r, const char *options __maybe_unused); -void grouping_reset_min(RRDR *r); -void grouping_free_min(RRDR *r); -void grouping_add_min(RRDR *r, NETDATA_DOUBLE value); -NETDATA_DOUBLE grouping_flush_min(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +struct tg_min { + NETDATA_DOUBLE min; + size_t count; +}; + +static inline void tg_min_create(RRDR *r, const char *options __maybe_unused) { + r->time_grouping.data = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct tg_min)); +} + +// resets when switches dimensions +// so, clear everything to restart +static inline void tg_min_reset(RRDR *r) { + struct tg_min *g = (struct tg_min *)r->time_grouping.data; + g->min = 0; + g->count = 0; +} + +static inline void tg_min_free(RRDR *r) { + onewayalloc_freez(r->internal.owa, r->time_grouping.data); + r->time_grouping.data = NULL; +} + +static inline void tg_min_add(RRDR *r, NETDATA_DOUBLE value) { + struct tg_min *g = (struct tg_min *)r->time_grouping.data; + + if(!g->count || fabsndd(value) < fabsndd(g->min)) { + g->min = value; + g->count++; + } +} + +static inline NETDATA_DOUBLE tg_min_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_min *g = (struct tg_min *)r->time_grouping.data; + + NETDATA_DOUBLE value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + value = g->min; + } + + g->min = 0.0; + g->count = 0; + + return value; +} #endif //NETDATA_API_QUERY_MIN_H diff --git a/web/api/queries/percentile/README.md b/web/api/queries/percentile/README.md index 70afc742..19ec81ed 100644 --- a/web/api/queries/percentile/README.md +++ b/web/api/queries/percentile/README.md @@ -1,7 +1,11 @@ # Percentile diff --git a/web/api/queries/percentile/percentile.c b/web/api/queries/percentile/percentile.c index 88f8600d..da3b3269 100644 --- a/web/api/queries/percentile/percentile.c +++ b/web/api/queries/percentile/percentile.c @@ -4,166 +4,3 @@ // ---------------------------------------------------------------------------- // median - -struct grouping_percentile { - size_t series_size; - size_t next_pos; - NETDATA_DOUBLE percent; - - NETDATA_DOUBLE *series; -}; - -static void grouping_create_percentile_internal(RRDR *r, const char *options, NETDATA_DOUBLE def) { - long entries = r->group; - if(entries < 10) entries = 10; - - struct grouping_percentile *g = (struct grouping_percentile *)onewayalloc_callocz(r->internal.owa, 1, sizeof(struct grouping_percentile)); - g->series = onewayalloc_mallocz(r->internal.owa, entries * sizeof(NETDATA_DOUBLE)); - g->series_size = (size_t)entries; - - g->percent = def; - if(options && *options) { - g->percent = str2ndd(options, NULL); - if(!netdata_double_isnumber(g->percent)) g->percent = 0.0; - if(g->percent < 0.0) g->percent = 0.0; - if(g->percent > 100.0) g->percent = 100.0; - } - - g->percent = g->percent / 100.0; - r->internal.grouping_data = g; -} - -void grouping_create_percentile25(RRDR *r, const char *options) { - grouping_create_percentile_internal(r, options, 25.0); -} -void grouping_create_percentile50(RRDR *r, const char *options) { - grouping_create_percentile_internal(r, options, 50.0); -} -void grouping_create_percentile75(RRDR *r, const char *options) { - grouping_create_percentile_internal(r, options, 75.0); -} -void grouping_create_percentile80(RRDR *r, const char *options) { - grouping_create_percentile_internal(r, options, 80.0); -} -void grouping_create_percentile90(RRDR *r, const char *options) { - grouping_create_percentile_internal(r, options, 90.0); -} -void grouping_create_percentile95(RRDR *r, const char *options) { - grouping_create_percentile_internal(r, options, 95.0); -} -void grouping_create_percentile97(RRDR *r, const char *options) { - grouping_create_percentile_internal(r, options, 97.0); -} -void grouping_create_percentile98(RRDR *r, const char *options) { - grouping_create_percentile_internal(r, options, 98.0); -} -void grouping_create_percentile99(RRDR *r, const char *options) { - grouping_create_percentile_internal(r, options, 99.0); -} - -// resets when switches dimensions -// so, clear everything to restart -void grouping_reset_percentile(RRDR *r) { - struct grouping_percentile *g = (struct grouping_percentile *)r->internal.grouping_data; - g->next_pos = 0; -} - -void grouping_free_percentile(RRDR *r) { - struct grouping_percentile *g = (struct grouping_percentile *)r->internal.grouping_data; - if(g) onewayalloc_freez(r->internal.owa, g->series); - - onewayalloc_freez(r->internal.owa, r->internal.grouping_data); - r->internal.grouping_data = NULL; -} - -void grouping_add_percentile(RRDR *r, NETDATA_DOUBLE value) { - struct grouping_percentile *g = (struct grouping_percentile *)r->internal.grouping_data; - - if(unlikely(g->next_pos >= g->series_size)) { - g->series = onewayalloc_doublesize( r->internal.owa, g->series, g->series_size * sizeof(NETDATA_DOUBLE)); - g->series_size *= 2; - } - - g->series[g->next_pos++] = value; -} - -NETDATA_DOUBLE grouping_flush_percentile(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_percentile *g = (struct grouping_percentile *)r->internal.grouping_data; - - NETDATA_DOUBLE value; - size_t available_slots = g->next_pos; - - if(unlikely(!available_slots)) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - else if(available_slots == 1) { - value = g->series[0]; - } - else { - sort_series(g->series, available_slots); - - NETDATA_DOUBLE min = g->series[0]; - NETDATA_DOUBLE max = g->series[available_slots - 1]; - - if (min != max) { - size_t slots_to_use = (size_t)((NETDATA_DOUBLE)available_slots * g->percent); - if(!slots_to_use) slots_to_use = 1; - - NETDATA_DOUBLE percent_to_use = (NETDATA_DOUBLE)slots_to_use / (NETDATA_DOUBLE)available_slots; - NETDATA_DOUBLE percent_delta = g->percent - percent_to_use; - - NETDATA_DOUBLE percent_interpolation_slot = 0.0; - NETDATA_DOUBLE percent_last_slot = 0.0; - if(percent_delta > 0.0) { - NETDATA_DOUBLE percent_to_use_plus_1_slot = (NETDATA_DOUBLE)(slots_to_use + 1) / (NETDATA_DOUBLE)available_slots; - NETDATA_DOUBLE percent_1slot = percent_to_use_plus_1_slot - percent_to_use; - - percent_interpolation_slot = percent_delta / percent_1slot; - percent_last_slot = 1 - percent_interpolation_slot; - } - - int start_slot, stop_slot, step, last_slot, interpolation_slot; - if(min >= 0.0 && max >= 0.0) { - start_slot = 0; - stop_slot = start_slot + (int)slots_to_use; - last_slot = stop_slot - 1; - interpolation_slot = stop_slot; - step = 1; - } - else { - start_slot = (int)available_slots - 1; - stop_slot = start_slot - (int)slots_to_use; - last_slot = stop_slot + 1; - interpolation_slot = stop_slot; - step = -1; - } - - value = 0.0; - for(int slot = start_slot; slot != stop_slot ; slot += step) - value += g->series[slot]; - - size_t counted = slots_to_use; - if(percent_interpolation_slot > 0.0 && interpolation_slot >= 0 && interpolation_slot < (int)available_slots) { - value += g->series[interpolation_slot] * percent_interpolation_slot; - value += g->series[last_slot] * percent_last_slot; - counted++; - } - - value = value / (NETDATA_DOUBLE)counted; - } - else - value = min; - } - - if(unlikely(!netdata_double_isnumber(value))) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - - //log_series_to_stderr(g->series, g->next_pos, value, "percentile"); - - g->next_pos = 0; - - return value; -} diff --git a/web/api/queries/percentile/percentile.h b/web/api/queries/percentile/percentile.h index 65e335c1..0532f9d3 100644 --- a/web/api/queries/percentile/percentile.h +++ b/web/api/queries/percentile/percentile.h @@ -6,18 +6,167 @@ #include "../query.h" #include "../rrdr.h" -void grouping_create_percentile25(RRDR *r, const char *options); -void grouping_create_percentile50(RRDR *r, const char *options); -void grouping_create_percentile75(RRDR *r, const char *options); -void grouping_create_percentile80(RRDR *r, const char *options); -void grouping_create_percentile90(RRDR *r, const char *options); -void grouping_create_percentile95(RRDR *r, const char *options); -void grouping_create_percentile97(RRDR *r, const char *options); -void grouping_create_percentile98(RRDR *r, const char *options); -void grouping_create_percentile99(RRDR *r, const char *options ); -void grouping_reset_percentile(RRDR *r); -void grouping_free_percentile(RRDR *r); -void grouping_add_percentile(RRDR *r, NETDATA_DOUBLE value); -NETDATA_DOUBLE grouping_flush_percentile(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +struct tg_percentile { + size_t series_size; + size_t next_pos; + NETDATA_DOUBLE percent; + + NETDATA_DOUBLE *series; +}; + +static inline void tg_percentile_create_internal(RRDR *r, const char *options, NETDATA_DOUBLE def) { + long entries = r->view.group; + if(entries < 10) entries = 10; + + struct tg_percentile *g = (struct tg_percentile *)onewayalloc_callocz(r->internal.owa, 1, sizeof(struct tg_percentile)); + g->series = onewayalloc_mallocz(r->internal.owa, entries * sizeof(NETDATA_DOUBLE)); + g->series_size = (size_t)entries; + + g->percent = def; + if(options && *options) { + g->percent = str2ndd(options, NULL); + if(!netdata_double_isnumber(g->percent)) g->percent = 0.0; + if(g->percent < 0.0) g->percent = 0.0; + if(g->percent > 100.0) g->percent = 100.0; + } + + g->percent = g->percent / 100.0; + r->time_grouping.data = g; +} + +static inline void tg_percentile_create_25(RRDR *r, const char *options) { + tg_percentile_create_internal(r, options, 25.0); +} +static inline void tg_percentile_create_50(RRDR *r, const char *options) { + tg_percentile_create_internal(r, options, 50.0); +} +static inline void tg_percentile_create_75(RRDR *r, const char *options) { + tg_percentile_create_internal(r, options, 75.0); +} +static inline void tg_percentile_create_80(RRDR *r, const char *options) { + tg_percentile_create_internal(r, options, 80.0); +} +static inline void tg_percentile_create_90(RRDR *r, const char *options) { + tg_percentile_create_internal(r, options, 90.0); +} +static inline void tg_percentile_create_95(RRDR *r, const char *options) { + tg_percentile_create_internal(r, options, 95.0); +} +static inline void tg_percentile_create_97(RRDR *r, const char *options) { + tg_percentile_create_internal(r, options, 97.0); +} +static inline void tg_percentile_create_98(RRDR *r, const char *options) { + tg_percentile_create_internal(r, options, 98.0); +} +static inline void tg_percentile_create_99(RRDR *r, const char *options) { + tg_percentile_create_internal(r, options, 99.0); +} + +// resets when switches dimensions +// so, clear everything to restart +static inline void tg_percentile_reset(RRDR *r) { + struct tg_percentile *g = (struct tg_percentile *)r->time_grouping.data; + g->next_pos = 0; +} + +static inline void tg_percentile_free(RRDR *r) { + struct tg_percentile *g = (struct tg_percentile *)r->time_grouping.data; + if(g) onewayalloc_freez(r->internal.owa, g->series); + + onewayalloc_freez(r->internal.owa, r->time_grouping.data); + r->time_grouping.data = NULL; +} + +static inline void tg_percentile_add(RRDR *r, NETDATA_DOUBLE value) { + struct tg_percentile *g = (struct tg_percentile *)r->time_grouping.data; + + if(unlikely(g->next_pos >= g->series_size)) { + g->series = onewayalloc_doublesize( r->internal.owa, g->series, g->series_size * sizeof(NETDATA_DOUBLE)); + g->series_size *= 2; + } + + g->series[g->next_pos++] = value; +} + +static inline NETDATA_DOUBLE tg_percentile_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_percentile *g = (struct tg_percentile *)r->time_grouping.data; + + NETDATA_DOUBLE value; + size_t available_slots = g->next_pos; + + if(unlikely(!available_slots)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else if(available_slots == 1) { + value = g->series[0]; + } + else { + sort_series(g->series, available_slots); + + NETDATA_DOUBLE min = g->series[0]; + NETDATA_DOUBLE max = g->series[available_slots - 1]; + + if (min != max) { + size_t slots_to_use = (size_t)((NETDATA_DOUBLE)available_slots * g->percent); + if(!slots_to_use) slots_to_use = 1; + + NETDATA_DOUBLE percent_to_use = (NETDATA_DOUBLE)slots_to_use / (NETDATA_DOUBLE)available_slots; + NETDATA_DOUBLE percent_delta = g->percent - percent_to_use; + + NETDATA_DOUBLE percent_interpolation_slot = 0.0; + NETDATA_DOUBLE percent_last_slot = 0.0; + if(percent_delta > 0.0) { + NETDATA_DOUBLE percent_to_use_plus_1_slot = (NETDATA_DOUBLE)(slots_to_use + 1) / (NETDATA_DOUBLE)available_slots; + NETDATA_DOUBLE percent_1slot = percent_to_use_plus_1_slot - percent_to_use; + + percent_interpolation_slot = percent_delta / percent_1slot; + percent_last_slot = 1 - percent_interpolation_slot; + } + + int start_slot, stop_slot, step, last_slot, interpolation_slot; + if(min >= 0.0 && max >= 0.0) { + start_slot = 0; + stop_slot = start_slot + (int)slots_to_use; + last_slot = stop_slot - 1; + interpolation_slot = stop_slot; + step = 1; + } + else { + start_slot = (int)available_slots - 1; + stop_slot = start_slot - (int)slots_to_use; + last_slot = stop_slot + 1; + interpolation_slot = stop_slot; + step = -1; + } + + value = 0.0; + for(int slot = start_slot; slot != stop_slot ; slot += step) + value += g->series[slot]; + + size_t counted = slots_to_use; + if(percent_interpolation_slot > 0.0 && interpolation_slot >= 0 && interpolation_slot < (int)available_slots) { + value += g->series[interpolation_slot] * percent_interpolation_slot; + value += g->series[last_slot] * percent_last_slot; + counted++; + } + + value = value / (NETDATA_DOUBLE)counted; + } + else + value = min; + } + + if(unlikely(!netdata_double_isnumber(value))) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + + //log_series_to_stderr(g->series, g->next_pos, value, "percentile"); + + g->next_pos = 0; + + return value; +} #endif //NETDATA_API_QUERIES_PERCENTILE_H diff --git a/web/api/queries/query.c b/web/api/queries/query.c index df7e0979..3770d477 100644 --- a/web/api/queries/query.c +++ b/web/api/queries/query.c @@ -24,7 +24,8 @@ static struct { const char *name; uint32_t hash; - RRDR_GROUPING value; + RRDR_TIME_GROUPING value; + RRDR_TIME_GROUPING add_flush; // One time initialization for the module. // This is called once, when netdata starts. @@ -59,397 +60,445 @@ static struct { {.name = "average", .hash = 0, .value = RRDR_GROUPING_AVERAGE, + .add_flush = RRDR_GROUPING_AVERAGE, .init = NULL, - .create= grouping_create_average, - .reset = grouping_reset_average, - .free = grouping_free_average, - .add = grouping_add_average, - .flush = grouping_flush_average, + .create= tg_average_create, + .reset = tg_average_reset, + .free = tg_average_free, + .add = tg_average_add, + .flush = tg_average_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, - {.name = "mean", // alias on 'average' + {.name = "avg", // alias on 'average' .hash = 0, .value = RRDR_GROUPING_AVERAGE, + .add_flush = RRDR_GROUPING_AVERAGE, .init = NULL, - .create= grouping_create_average, - .reset = grouping_reset_average, - .free = grouping_free_average, - .add = grouping_add_average, - .flush = grouping_flush_average, + .create= tg_average_create, + .reset = tg_average_reset, + .free = tg_average_free, + .add = tg_average_add, + .flush = tg_average_flush, + .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE + }, + {.name = "mean", // alias on 'average' + .hash = 0, + .value = RRDR_GROUPING_AVERAGE, + .add_flush = RRDR_GROUPING_AVERAGE, + .init = NULL, + .create= tg_average_create, + .reset = tg_average_reset, + .free = tg_average_free, + .add = tg_average_add, + .flush = tg_average_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-mean1", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEAN1, + .add_flush = RRDR_GROUPING_TRIMMED_MEAN, .init = NULL, - .create= grouping_create_trimmed_mean1, - .reset = grouping_reset_trimmed_mean, - .free = grouping_free_trimmed_mean, - .add = grouping_add_trimmed_mean, - .flush = grouping_flush_trimmed_mean, + .create= tg_trimmed_mean_create_1, + .reset = tg_trimmed_mean_reset, + .free = tg_trimmed_mean_free, + .add = tg_trimmed_mean_add, + .flush = tg_trimmed_mean_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-mean2", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEAN2, + .add_flush = RRDR_GROUPING_TRIMMED_MEAN, .init = NULL, - .create= grouping_create_trimmed_mean2, - .reset = grouping_reset_trimmed_mean, - .free = grouping_free_trimmed_mean, - .add = grouping_add_trimmed_mean, - .flush = grouping_flush_trimmed_mean, + .create= tg_trimmed_mean_create_2, + .reset = tg_trimmed_mean_reset, + .free = tg_trimmed_mean_free, + .add = tg_trimmed_mean_add, + .flush = tg_trimmed_mean_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-mean3", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEAN3, + .add_flush = RRDR_GROUPING_TRIMMED_MEAN, .init = NULL, - .create= grouping_create_trimmed_mean3, - .reset = grouping_reset_trimmed_mean, - .free = grouping_free_trimmed_mean, - .add = grouping_add_trimmed_mean, - .flush = grouping_flush_trimmed_mean, + .create= tg_trimmed_mean_create_3, + .reset = tg_trimmed_mean_reset, + .free = tg_trimmed_mean_free, + .add = tg_trimmed_mean_add, + .flush = tg_trimmed_mean_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-mean5", .hash = 0, - .value = RRDR_GROUPING_TRIMMED_MEAN5, + .value = RRDR_GROUPING_TRIMMED_MEAN, + .add_flush = RRDR_GROUPING_TRIMMED_MEAN, .init = NULL, - .create= grouping_create_trimmed_mean5, - .reset = grouping_reset_trimmed_mean, - .free = grouping_free_trimmed_mean, - .add = grouping_add_trimmed_mean, - .flush = grouping_flush_trimmed_mean, + .create= tg_trimmed_mean_create_5, + .reset = tg_trimmed_mean_reset, + .free = tg_trimmed_mean_free, + .add = tg_trimmed_mean_add, + .flush = tg_trimmed_mean_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-mean10", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEAN10, + .add_flush = RRDR_GROUPING_TRIMMED_MEAN, .init = NULL, - .create= grouping_create_trimmed_mean10, - .reset = grouping_reset_trimmed_mean, - .free = grouping_free_trimmed_mean, - .add = grouping_add_trimmed_mean, - .flush = grouping_flush_trimmed_mean, + .create= tg_trimmed_mean_create_10, + .reset = tg_trimmed_mean_reset, + .free = tg_trimmed_mean_free, + .add = tg_trimmed_mean_add, + .flush = tg_trimmed_mean_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-mean15", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEAN15, + .add_flush = RRDR_GROUPING_TRIMMED_MEAN, .init = NULL, - .create= grouping_create_trimmed_mean15, - .reset = grouping_reset_trimmed_mean, - .free = grouping_free_trimmed_mean, - .add = grouping_add_trimmed_mean, - .flush = grouping_flush_trimmed_mean, + .create= tg_trimmed_mean_create_15, + .reset = tg_trimmed_mean_reset, + .free = tg_trimmed_mean_free, + .add = tg_trimmed_mean_add, + .flush = tg_trimmed_mean_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-mean20", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEAN20, + .add_flush = RRDR_GROUPING_TRIMMED_MEAN, .init = NULL, - .create= grouping_create_trimmed_mean20, - .reset = grouping_reset_trimmed_mean, - .free = grouping_free_trimmed_mean, - .add = grouping_add_trimmed_mean, - .flush = grouping_flush_trimmed_mean, + .create= tg_trimmed_mean_create_20, + .reset = tg_trimmed_mean_reset, + .free = tg_trimmed_mean_free, + .add = tg_trimmed_mean_add, + .flush = tg_trimmed_mean_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-mean25", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEAN25, + .add_flush = RRDR_GROUPING_TRIMMED_MEAN, .init = NULL, - .create= grouping_create_trimmed_mean25, - .reset = grouping_reset_trimmed_mean, - .free = grouping_free_trimmed_mean, - .add = grouping_add_trimmed_mean, - .flush = grouping_flush_trimmed_mean, + .create= tg_trimmed_mean_create_25, + .reset = tg_trimmed_mean_reset, + .free = tg_trimmed_mean_free, + .add = tg_trimmed_mean_add, + .flush = tg_trimmed_mean_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-mean", .hash = 0, - .value = RRDR_GROUPING_TRIMMED_MEAN5, + .value = RRDR_GROUPING_TRIMMED_MEAN, + .add_flush = RRDR_GROUPING_TRIMMED_MEAN, .init = NULL, - .create= grouping_create_trimmed_mean5, - .reset = grouping_reset_trimmed_mean, - .free = grouping_free_trimmed_mean, - .add = grouping_add_trimmed_mean, - .flush = grouping_flush_trimmed_mean, + .create= tg_trimmed_mean_create_5, + .reset = tg_trimmed_mean_reset, + .free = tg_trimmed_mean_free, + .add = tg_trimmed_mean_add, + .flush = tg_trimmed_mean_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "incremental_sum", .hash = 0, .value = RRDR_GROUPING_INCREMENTAL_SUM, + .add_flush = RRDR_GROUPING_INCREMENTAL_SUM, .init = NULL, - .create= grouping_create_incremental_sum, - .reset = grouping_reset_incremental_sum, - .free = grouping_free_incremental_sum, - .add = grouping_add_incremental_sum, - .flush = grouping_flush_incremental_sum, + .create= tg_incremental_sum_create, + .reset = tg_incremental_sum_reset, + .free = tg_incremental_sum_free, + .add = tg_incremental_sum_add, + .flush = tg_incremental_sum_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "incremental-sum", .hash = 0, .value = RRDR_GROUPING_INCREMENTAL_SUM, + .add_flush = RRDR_GROUPING_INCREMENTAL_SUM, .init = NULL, - .create= grouping_create_incremental_sum, - .reset = grouping_reset_incremental_sum, - .free = grouping_free_incremental_sum, - .add = grouping_add_incremental_sum, - .flush = grouping_flush_incremental_sum, + .create= tg_incremental_sum_create, + .reset = tg_incremental_sum_reset, + .free = tg_incremental_sum_free, + .add = tg_incremental_sum_add, + .flush = tg_incremental_sum_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "median", .hash = 0, .value = RRDR_GROUPING_MEDIAN, + .add_flush = RRDR_GROUPING_MEDIAN, .init = NULL, - .create= grouping_create_median, - .reset = grouping_reset_median, - .free = grouping_free_median, - .add = grouping_add_median, - .flush = grouping_flush_median, + .create= tg_median_create, + .reset = tg_median_reset, + .free = tg_median_free, + .add = tg_median_add, + .flush = tg_median_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-median1", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEDIAN1, + .add_flush = RRDR_GROUPING_MEDIAN, .init = NULL, - .create= grouping_create_trimmed_median1, - .reset = grouping_reset_median, - .free = grouping_free_median, - .add = grouping_add_median, - .flush = grouping_flush_median, + .create= tg_median_create_trimmed_1, + .reset = tg_median_reset, + .free = tg_median_free, + .add = tg_median_add, + .flush = tg_median_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-median2", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEDIAN2, + .add_flush = RRDR_GROUPING_MEDIAN, .init = NULL, - .create= grouping_create_trimmed_median2, - .reset = grouping_reset_median, - .free = grouping_free_median, - .add = grouping_add_median, - .flush = grouping_flush_median, + .create= tg_median_create_trimmed_2, + .reset = tg_median_reset, + .free = tg_median_free, + .add = tg_median_add, + .flush = tg_median_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-median3", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEDIAN3, + .add_flush = RRDR_GROUPING_MEDIAN, .init = NULL, - .create= grouping_create_trimmed_median3, - .reset = grouping_reset_median, - .free = grouping_free_median, - .add = grouping_add_median, - .flush = grouping_flush_median, + .create= tg_median_create_trimmed_3, + .reset = tg_median_reset, + .free = tg_median_free, + .add = tg_median_add, + .flush = tg_median_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-median5", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEDIAN5, + .add_flush = RRDR_GROUPING_MEDIAN, .init = NULL, - .create= grouping_create_trimmed_median5, - .reset = grouping_reset_median, - .free = grouping_free_median, - .add = grouping_add_median, - .flush = grouping_flush_median, + .create= tg_median_create_trimmed_5, + .reset = tg_median_reset, + .free = tg_median_free, + .add = tg_median_add, + .flush = tg_median_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-median10", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEDIAN10, + .add_flush = RRDR_GROUPING_MEDIAN, .init = NULL, - .create= grouping_create_trimmed_median10, - .reset = grouping_reset_median, - .free = grouping_free_median, - .add = grouping_add_median, - .flush = grouping_flush_median, + .create= tg_median_create_trimmed_10, + .reset = tg_median_reset, + .free = tg_median_free, + .add = tg_median_add, + .flush = tg_median_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-median15", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEDIAN15, + .add_flush = RRDR_GROUPING_MEDIAN, .init = NULL, - .create= grouping_create_trimmed_median15, - .reset = grouping_reset_median, - .free = grouping_free_median, - .add = grouping_add_median, - .flush = grouping_flush_median, + .create= tg_median_create_trimmed_15, + .reset = tg_median_reset, + .free = tg_median_free, + .add = tg_median_add, + .flush = tg_median_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-median20", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEDIAN20, + .add_flush = RRDR_GROUPING_MEDIAN, .init = NULL, - .create= grouping_create_trimmed_median20, - .reset = grouping_reset_median, - .free = grouping_free_median, - .add = grouping_add_median, - .flush = grouping_flush_median, + .create= tg_median_create_trimmed_20, + .reset = tg_median_reset, + .free = tg_median_free, + .add = tg_median_add, + .flush = tg_median_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-median25", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEDIAN25, + .add_flush = RRDR_GROUPING_MEDIAN, .init = NULL, - .create= grouping_create_trimmed_median25, - .reset = grouping_reset_median, - .free = grouping_free_median, - .add = grouping_add_median, - .flush = grouping_flush_median, + .create= tg_median_create_trimmed_25, + .reset = tg_median_reset, + .free = tg_median_free, + .add = tg_median_add, + .flush = tg_median_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "trimmed-median", .hash = 0, .value = RRDR_GROUPING_TRIMMED_MEDIAN5, + .add_flush = RRDR_GROUPING_MEDIAN, .init = NULL, - .create= grouping_create_trimmed_median5, - .reset = grouping_reset_median, - .free = grouping_free_median, - .add = grouping_add_median, - .flush = grouping_flush_median, + .create= tg_median_create_trimmed_5, + .reset = tg_median_reset, + .free = tg_median_free, + .add = tg_median_add, + .flush = tg_median_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "percentile25", .hash = 0, .value = RRDR_GROUPING_PERCENTILE25, + .add_flush = RRDR_GROUPING_PERCENTILE, .init = NULL, - .create= grouping_create_percentile25, - .reset = grouping_reset_percentile, - .free = grouping_free_percentile, - .add = grouping_add_percentile, - .flush = grouping_flush_percentile, + .create= tg_percentile_create_25, + .reset = tg_percentile_reset, + .free = tg_percentile_free, + .add = tg_percentile_add, + .flush = tg_percentile_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "percentile50", .hash = 0, .value = RRDR_GROUPING_PERCENTILE50, + .add_flush = RRDR_GROUPING_PERCENTILE, .init = NULL, - .create= grouping_create_percentile50, - .reset = grouping_reset_percentile, - .free = grouping_free_percentile, - .add = grouping_add_percentile, - .flush = grouping_flush_percentile, + .create= tg_percentile_create_50, + .reset = tg_percentile_reset, + .free = tg_percentile_free, + .add = tg_percentile_add, + .flush = tg_percentile_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "percentile75", .hash = 0, .value = RRDR_GROUPING_PERCENTILE75, + .add_flush = RRDR_GROUPING_PERCENTILE, .init = NULL, - .create= grouping_create_percentile75, - .reset = grouping_reset_percentile, - .free = grouping_free_percentile, - .add = grouping_add_percentile, - .flush = grouping_flush_percentile, + .create= tg_percentile_create_75, + .reset = tg_percentile_reset, + .free = tg_percentile_free, + .add = tg_percentile_add, + .flush = tg_percentile_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "percentile80", .hash = 0, .value = RRDR_GROUPING_PERCENTILE80, + .add_flush = RRDR_GROUPING_PERCENTILE, .init = NULL, - .create= grouping_create_percentile80, - .reset = grouping_reset_percentile, - .free = grouping_free_percentile, - .add = grouping_add_percentile, - .flush = grouping_flush_percentile, + .create= tg_percentile_create_80, + .reset = tg_percentile_reset, + .free = tg_percentile_free, + .add = tg_percentile_add, + .flush = tg_percentile_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "percentile90", .hash = 0, .value = RRDR_GROUPING_PERCENTILE90, + .add_flush = RRDR_GROUPING_PERCENTILE, .init = NULL, - .create= grouping_create_percentile90, - .reset = grouping_reset_percentile, - .free = grouping_free_percentile, - .add = grouping_add_percentile, - .flush = grouping_flush_percentile, + .create= tg_percentile_create_90, + .reset = tg_percentile_reset, + .free = tg_percentile_free, + .add = tg_percentile_add, + .flush = tg_percentile_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "percentile95", .hash = 0, - .value = RRDR_GROUPING_PERCENTILE95, + .value = RRDR_GROUPING_PERCENTILE, + .add_flush = RRDR_GROUPING_PERCENTILE, .init = NULL, - .create= grouping_create_percentile95, - .reset = grouping_reset_percentile, - .free = grouping_free_percentile, - .add = grouping_add_percentile, - .flush = grouping_flush_percentile, + .create= tg_percentile_create_95, + .reset = tg_percentile_reset, + .free = tg_percentile_free, + .add = tg_percentile_add, + .flush = tg_percentile_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "percentile97", .hash = 0, .value = RRDR_GROUPING_PERCENTILE97, + .add_flush = RRDR_GROUPING_PERCENTILE, .init = NULL, - .create= grouping_create_percentile97, - .reset = grouping_reset_percentile, - .free = grouping_free_percentile, - .add = grouping_add_percentile, - .flush = grouping_flush_percentile, + .create= tg_percentile_create_97, + .reset = tg_percentile_reset, + .free = tg_percentile_free, + .add = tg_percentile_add, + .flush = tg_percentile_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "percentile98", .hash = 0, .value = RRDR_GROUPING_PERCENTILE98, + .add_flush = RRDR_GROUPING_PERCENTILE, .init = NULL, - .create= grouping_create_percentile98, - .reset = grouping_reset_percentile, - .free = grouping_free_percentile, - .add = grouping_add_percentile, - .flush = grouping_flush_percentile, + .create= tg_percentile_create_98, + .reset = tg_percentile_reset, + .free = tg_percentile_free, + .add = tg_percentile_add, + .flush = tg_percentile_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "percentile99", .hash = 0, .value = RRDR_GROUPING_PERCENTILE99, + .add_flush = RRDR_GROUPING_PERCENTILE, .init = NULL, - .create= grouping_create_percentile99, - .reset = grouping_reset_percentile, - .free = grouping_free_percentile, - .add = grouping_add_percentile, - .flush = grouping_flush_percentile, + .create= tg_percentile_create_99, + .reset = tg_percentile_reset, + .free = tg_percentile_free, + .add = tg_percentile_add, + .flush = tg_percentile_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "percentile", .hash = 0, - .value = RRDR_GROUPING_PERCENTILE95, + .value = RRDR_GROUPING_PERCENTILE, + .add_flush = RRDR_GROUPING_PERCENTILE, .init = NULL, - .create= grouping_create_percentile95, - .reset = grouping_reset_percentile, - .free = grouping_free_percentile, - .add = grouping_add_percentile, - .flush = grouping_flush_percentile, + .create= tg_percentile_create_95, + .reset = tg_percentile_reset, + .free = tg_percentile_free, + .add = tg_percentile_add, + .flush = tg_percentile_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "min", .hash = 0, .value = RRDR_GROUPING_MIN, + .add_flush = RRDR_GROUPING_MIN, .init = NULL, - .create= grouping_create_min, - .reset = grouping_reset_min, - .free = grouping_free_min, - .add = grouping_add_min, - .flush = grouping_flush_min, + .create= tg_min_create, + .reset = tg_min_reset, + .free = tg_min_free, + .add = tg_min_add, + .flush = tg_min_flush, .tier_query_fetch = TIER_QUERY_FETCH_MIN }, {.name = "max", .hash = 0, .value = RRDR_GROUPING_MAX, + .add_flush = RRDR_GROUPING_MAX, .init = NULL, - .create= grouping_create_max, - .reset = grouping_reset_max, - .free = grouping_free_max, - .add = grouping_add_max, - .flush = grouping_flush_max, + .create= tg_max_create, + .reset = tg_max_reset, + .free = tg_max_free, + .add = tg_max_add, + .flush = tg_max_flush, .tier_query_fetch = TIER_QUERY_FETCH_MAX }, {.name = "sum", .hash = 0, .value = RRDR_GROUPING_SUM, + .add_flush = RRDR_GROUPING_SUM, .init = NULL, - .create= grouping_create_sum, - .reset = grouping_reset_sum, - .free = grouping_free_sum, - .add = grouping_add_sum, - .flush = grouping_flush_sum, + .create= tg_sum_create, + .reset = tg_sum_reset, + .free = tg_sum_free, + .add = tg_sum_add, + .flush = tg_sum_flush, .tier_query_fetch = TIER_QUERY_FETCH_SUM }, @@ -457,97 +506,75 @@ static struct { {.name = "stddev", .hash = 0, .value = RRDR_GROUPING_STDDEV, + .add_flush = RRDR_GROUPING_STDDEV, .init = NULL, - .create= grouping_create_stddev, - .reset = grouping_reset_stddev, - .free = grouping_free_stddev, - .add = grouping_add_stddev, - .flush = grouping_flush_stddev, + .create= tg_stddev_create, + .reset = tg_stddev_reset, + .free = tg_stddev_free, + .add = tg_stddev_add, + .flush = tg_stddev_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "cv", // coefficient variation is calculated by stddev .hash = 0, .value = RRDR_GROUPING_CV, + .add_flush = RRDR_GROUPING_CV, .init = NULL, - .create= grouping_create_stddev, // not an error, stddev calculates this too - .reset = grouping_reset_stddev, // not an error, stddev calculates this too - .free = grouping_free_stddev, // not an error, stddev calculates this too - .add = grouping_add_stddev, // not an error, stddev calculates this too - .flush = grouping_flush_coefficient_of_variation, + .create= tg_stddev_create, // not an error, stddev calculates this too + .reset = tg_stddev_reset, // not an error, stddev calculates this too + .free = tg_stddev_free, // not an error, stddev calculates this too + .add = tg_stddev_add, // not an error, stddev calculates this too + .flush = tg_stddev_coefficient_of_variation_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "rsd", // alias of 'cv' .hash = 0, .value = RRDR_GROUPING_CV, + .add_flush = RRDR_GROUPING_CV, .init = NULL, - .create= grouping_create_stddev, // not an error, stddev calculates this too - .reset = grouping_reset_stddev, // not an error, stddev calculates this too - .free = grouping_free_stddev, // not an error, stddev calculates this too - .add = grouping_add_stddev, // not an error, stddev calculates this too - .flush = grouping_flush_coefficient_of_variation, - .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE - }, - - /* - {.name = "mean", // same as average, no need to define it again - .hash = 0, - .value = RRDR_GROUPING_MEAN, - .setup = NULL, - .create= grouping_create_stddev, - .reset = grouping_reset_stddev, - .free = grouping_free_stddev, - .add = grouping_add_stddev, - .flush = grouping_flush_mean, - .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE - }, - */ - - /* - {.name = "variance", // meaningless to offer - .hash = 0, - .value = RRDR_GROUPING_VARIANCE, - .setup = NULL, - .create= grouping_create_stddev, - .reset = grouping_reset_stddev, - .free = grouping_free_stddev, - .add = grouping_add_stddev, - .flush = grouping_flush_variance, + .create= tg_stddev_create, // not an error, stddev calculates this too + .reset = tg_stddev_reset, // not an error, stddev calculates this too + .free = tg_stddev_free, // not an error, stddev calculates this too + .add = tg_stddev_add, // not an error, stddev calculates this too + .flush = tg_stddev_coefficient_of_variation_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, - */ // single exponential smoothing {.name = "ses", .hash = 0, .value = RRDR_GROUPING_SES, - .init = grouping_init_ses, - .create= grouping_create_ses, - .reset = grouping_reset_ses, - .free = grouping_free_ses, - .add = grouping_add_ses, - .flush = grouping_flush_ses, + .add_flush = RRDR_GROUPING_SES, + .init = tg_ses_init, + .create= tg_ses_create, + .reset = tg_ses_reset, + .free = tg_ses_free, + .add = tg_ses_add, + .flush = tg_ses_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "ema", // alias for 'ses' .hash = 0, .value = RRDR_GROUPING_SES, + .add_flush = RRDR_GROUPING_SES, .init = NULL, - .create= grouping_create_ses, - .reset = grouping_reset_ses, - .free = grouping_free_ses, - .add = grouping_add_ses, - .flush = grouping_flush_ses, + .create= tg_ses_create, + .reset = tg_ses_reset, + .free = tg_ses_free, + .add = tg_ses_add, + .flush = tg_ses_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "ewma", // alias for ses .hash = 0, .value = RRDR_GROUPING_SES, + .add_flush = RRDR_GROUPING_SES, .init = NULL, - .create= grouping_create_ses, - .reset = grouping_reset_ses, - .free = grouping_free_ses, - .add = grouping_add_ses, - .flush = grouping_flush_ses, + .create= tg_ses_create, + .reset = tg_ses_reset, + .free = tg_ses_free, + .add = tg_ses_add, + .flush = tg_ses_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, @@ -555,24 +582,26 @@ static struct { {.name = "des", .hash = 0, .value = RRDR_GROUPING_DES, - .init = grouping_init_des, - .create= grouping_create_des, - .reset = grouping_reset_des, - .free = grouping_free_des, - .add = grouping_add_des, - .flush = grouping_flush_des, + .add_flush = RRDR_GROUPING_DES, + .init = tg_des_init, + .create= tg_des_create, + .reset = tg_des_reset, + .free = tg_des_free, + .add = tg_des_add, + .flush = tg_des_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, {.name = "countif", .hash = 0, .value = RRDR_GROUPING_COUNTIF, + .add_flush = RRDR_GROUPING_COUNTIF, .init = NULL, - .create= grouping_create_countif, - .reset = grouping_reset_countif, - .free = grouping_free_countif, - .add = grouping_add_countif, - .flush = grouping_flush_countif, + .create= tg_countif_create, + .reset = tg_countif_reset, + .free = tg_countif_free, + .add = tg_countif_add, + .flush = tg_countif_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE }, @@ -580,17 +609,18 @@ static struct { {.name = NULL, .hash = 0, .value = RRDR_GROUPING_UNDEFINED, + .add_flush = RRDR_GROUPING_AVERAGE, .init = NULL, - .create= grouping_create_average, - .reset = grouping_reset_average, - .free = grouping_free_average, - .add = grouping_add_average, - .flush = grouping_flush_average, + .create= tg_average_create, + .reset = tg_average_reset, + .free = tg_average_free, + .add = tg_average_add, + .flush = tg_average_flush, .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE } }; -void web_client_api_v1_init_grouping(void) { +void time_grouping_init(void) { int i; for(i = 0; api_v1_data_groups[i].name ; i++) { @@ -601,7 +631,7 @@ void web_client_api_v1_init_grouping(void) { } } -const char *group_method2string(RRDR_GROUPING group) { +const char *time_grouping_method2string(RRDR_TIME_GROUPING group) { int i; for(i = 0; api_v1_data_groups[i].name ; i++) { @@ -613,7 +643,7 @@ const char *group_method2string(RRDR_GROUPING group) { return "unknown-group-method"; } -RRDR_GROUPING web_client_api_request_v1_data_group(const char *name, RRDR_GROUPING def) { +RRDR_TIME_GROUPING time_grouping_parse(const char *name, RRDR_TIME_GROUPING def) { int i; uint32_t hash = simple_hash(name); @@ -624,7 +654,7 @@ RRDR_GROUPING web_client_api_request_v1_data_group(const char *name, RRDR_GROUPI return def; } -const char *web_client_api_request_v1_data_group_to_string(RRDR_GROUPING group) { +const char *time_grouping_tostring(RRDR_TIME_GROUPING group) { int i; for(i = 0; api_v1_data_groups[i].name ; i++) @@ -634,28 +664,242 @@ const char *web_client_api_request_v1_data_group_to_string(RRDR_GROUPING group) return "unknown"; } -static void rrdr_set_grouping_function(RRDR *r, RRDR_GROUPING group_method) { +static void rrdr_set_grouping_function(RRDR *r, RRDR_TIME_GROUPING group_method) { int i, found = 0; for(i = 0; !found && api_v1_data_groups[i].name ;i++) { if(api_v1_data_groups[i].value == group_method) { - r->internal.grouping_create = api_v1_data_groups[i].create; - r->internal.grouping_reset = api_v1_data_groups[i].reset; - r->internal.grouping_free = api_v1_data_groups[i].free; - r->internal.grouping_add = api_v1_data_groups[i].add; - r->internal.grouping_flush = api_v1_data_groups[i].flush; - r->internal.tier_query_fetch = api_v1_data_groups[i].tier_query_fetch; + r->time_grouping.create = api_v1_data_groups[i].create; + r->time_grouping.reset = api_v1_data_groups[i].reset; + r->time_grouping.free = api_v1_data_groups[i].free; + r->time_grouping.add = api_v1_data_groups[i].add; + r->time_grouping.flush = api_v1_data_groups[i].flush; + r->time_grouping.tier_query_fetch = api_v1_data_groups[i].tier_query_fetch; + r->time_grouping.add_flush = api_v1_data_groups[i].add_flush; found = 1; } } if(!found) { errno = 0; internal_error(true, "QUERY: grouping method %u not found. Using 'average'", (unsigned int)group_method); - r->internal.grouping_create = grouping_create_average; - r->internal.grouping_reset = grouping_reset_average; - r->internal.grouping_free = grouping_free_average; - r->internal.grouping_add = grouping_add_average; - r->internal.grouping_flush = grouping_flush_average; - r->internal.tier_query_fetch = TIER_QUERY_FETCH_AVERAGE; + r->time_grouping.create = tg_average_create; + r->time_grouping.reset = tg_average_reset; + r->time_grouping.free = tg_average_free; + r->time_grouping.add = tg_average_add; + r->time_grouping.flush = tg_average_flush; + r->time_grouping.tier_query_fetch = TIER_QUERY_FETCH_AVERAGE; + r->time_grouping.add_flush = RRDR_GROUPING_AVERAGE; + } +} + +static inline void time_grouping_add(RRDR *r, NETDATA_DOUBLE value, const RRDR_TIME_GROUPING add_flush) { + switch(add_flush) { + case RRDR_GROUPING_AVERAGE: + tg_average_add(r, value); + break; + + case RRDR_GROUPING_MAX: + tg_max_add(r, value); + break; + + case RRDR_GROUPING_MIN: + tg_min_add(r, value); + break; + + case RRDR_GROUPING_MEDIAN: + tg_median_add(r, value); + break; + + case RRDR_GROUPING_STDDEV: + case RRDR_GROUPING_CV: + tg_stddev_add(r, value); + break; + + case RRDR_GROUPING_SUM: + tg_sum_add(r, value); + break; + + case RRDR_GROUPING_COUNTIF: + tg_countif_add(r, value); + break; + + case RRDR_GROUPING_TRIMMED_MEAN: + tg_trimmed_mean_add(r, value); + break; + + case RRDR_GROUPING_PERCENTILE: + tg_percentile_add(r, value); + break; + + case RRDR_GROUPING_SES: + tg_ses_add(r, value); + break; + + case RRDR_GROUPING_DES: + tg_des_add(r, value); + break; + + case RRDR_GROUPING_INCREMENTAL_SUM: + tg_incremental_sum_add(r, value); + break; + + default: + r->time_grouping.add(r, value); + break; + } +} + +static inline NETDATA_DOUBLE time_grouping_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr, const RRDR_TIME_GROUPING add_flush) { + switch(add_flush) { + case RRDR_GROUPING_AVERAGE: + return tg_average_flush(r, rrdr_value_options_ptr); + + case RRDR_GROUPING_MAX: + return tg_max_flush(r, rrdr_value_options_ptr); + + case RRDR_GROUPING_MIN: + return tg_min_flush(r, rrdr_value_options_ptr); + + case RRDR_GROUPING_MEDIAN: + return tg_median_flush(r, rrdr_value_options_ptr); + + case RRDR_GROUPING_STDDEV: + return tg_stddev_flush(r, rrdr_value_options_ptr); + + case RRDR_GROUPING_CV: + return tg_stddev_coefficient_of_variation_flush(r, rrdr_value_options_ptr); + + case RRDR_GROUPING_SUM: + return tg_sum_flush(r, rrdr_value_options_ptr); + + case RRDR_GROUPING_COUNTIF: + return tg_countif_flush(r, rrdr_value_options_ptr); + + case RRDR_GROUPING_TRIMMED_MEAN: + return tg_trimmed_mean_flush(r, rrdr_value_options_ptr); + + case RRDR_GROUPING_PERCENTILE: + return tg_percentile_flush(r, rrdr_value_options_ptr); + + case RRDR_GROUPING_SES: + return tg_ses_flush(r, rrdr_value_options_ptr); + + case RRDR_GROUPING_DES: + return tg_des_flush(r, rrdr_value_options_ptr); + + case RRDR_GROUPING_INCREMENTAL_SUM: + return tg_incremental_sum_flush(r, rrdr_value_options_ptr); + + default: + return r->time_grouping.flush(r, rrdr_value_options_ptr); + } +} + +RRDR_GROUP_BY group_by_parse(char *s) { + RRDR_GROUP_BY group_by = RRDR_GROUP_BY_NONE; + + while(s) { + char *key = strsep_skip_consecutive_separators(&s, ",| "); + if (!key || !*key) continue; + + if (strcmp(key, "selected") == 0) + group_by |= RRDR_GROUP_BY_SELECTED; + + if (strcmp(key, "dimension") == 0) + group_by |= RRDR_GROUP_BY_DIMENSION; + + if (strcmp(key, "instance") == 0) + group_by |= RRDR_GROUP_BY_INSTANCE; + + if (strcmp(key, "percentage-of-instance") == 0) + group_by |= RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE; + + if (strcmp(key, "label") == 0) + group_by |= RRDR_GROUP_BY_LABEL; + + if (strcmp(key, "node") == 0) + group_by |= RRDR_GROUP_BY_NODE; + + if (strcmp(key, "context") == 0) + group_by |= RRDR_GROUP_BY_CONTEXT; + + if (strcmp(key, "units") == 0) + group_by |= RRDR_GROUP_BY_UNITS; + } + + if((group_by & RRDR_GROUP_BY_SELECTED) && (group_by & ~RRDR_GROUP_BY_SELECTED)) { + internal_error(true, "group-by given by query has 'selected' together with more groupings"); + group_by = RRDR_GROUP_BY_SELECTED; // remove all other groupings + } + + if(group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE) + group_by = RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE; // remove all other groupings + + return group_by; +} + +void buffer_json_group_by_to_array(BUFFER *wb, RRDR_GROUP_BY group_by) { + if(group_by == RRDR_GROUP_BY_NONE) + buffer_json_add_array_item_string(wb, "none"); + else { + if (group_by & RRDR_GROUP_BY_DIMENSION) + buffer_json_add_array_item_string(wb, "dimension"); + + if (group_by & RRDR_GROUP_BY_INSTANCE) + buffer_json_add_array_item_string(wb, "instance"); + + if (group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE) + buffer_json_add_array_item_string(wb, "percentage-of-instance"); + + if (group_by & RRDR_GROUP_BY_LABEL) + buffer_json_add_array_item_string(wb, "label"); + + if (group_by & RRDR_GROUP_BY_NODE) + buffer_json_add_array_item_string(wb, "node"); + + if (group_by & RRDR_GROUP_BY_CONTEXT) + buffer_json_add_array_item_string(wb, "context"); + + if (group_by & RRDR_GROUP_BY_UNITS) + buffer_json_add_array_item_string(wb, "units"); + + if (group_by & RRDR_GROUP_BY_SELECTED) + buffer_json_add_array_item_string(wb, "selected"); + } +} + +RRDR_GROUP_BY_FUNCTION group_by_aggregate_function_parse(const char *s) { + if(strcmp(s, "average") == 0) + return RRDR_GROUP_BY_FUNCTION_AVERAGE; + + if(strcmp(s, "avg") == 0) + return RRDR_GROUP_BY_FUNCTION_AVERAGE; + + if(strcmp(s, "min") == 0) + return RRDR_GROUP_BY_FUNCTION_MIN; + + if(strcmp(s, "max") == 0) + return RRDR_GROUP_BY_FUNCTION_MAX; + + if(strcmp(s, "sum") == 0) + return RRDR_GROUP_BY_FUNCTION_SUM; + + return RRDR_GROUP_BY_FUNCTION_AVERAGE; +} + +const char *group_by_aggregate_function_to_string(RRDR_GROUP_BY_FUNCTION group_by_function) { + switch(group_by_function) { + default: + case RRDR_GROUP_BY_FUNCTION_AVERAGE: + return "average"; + + case RRDR_GROUP_BY_FUNCTION_MIN: + return "min"; + + case RRDR_GROUP_BY_FUNCTION_MAX: + return "max"; + + case RRDR_GROUP_BY_FUNCTION_SUM: + return "sum"; } } @@ -670,28 +914,20 @@ static inline NETDATA_DOUBLE *UNUSED_FUNCTION(rrdr_line_values)(RRDR *r, long rr return &r->v[ rrdr_line * r->d ]; } -static inline long rrdr_line_init(RRDR *r, time_t t, long rrdr_line) { +static inline long rrdr_line_init(RRDR *r __maybe_unused, time_t t __maybe_unused, long rrdr_line) { rrdr_line++; - internal_error(rrdr_line >= (long)r->n, + internal_fatal(rrdr_line >= (long)r->n, "QUERY: requested to step above RRDR size for query '%s'", r->internal.qt->id); - internal_error(r->t[rrdr_line] != 0 && r->t[rrdr_line] != t, - "QUERY: overwriting the timestamp of RRDR line %zu from %zu to %zu, of query '%s'", - (size_t)rrdr_line, (size_t)r->t[rrdr_line], (size_t)t, r->internal.qt->id); - - // save the time - r->t[rrdr_line] = t; + internal_fatal(r->t[rrdr_line] != t, + "QUERY: wrong timestamp at RRDR line %ld, expected %ld, got %ld, of query '%s'", + rrdr_line, r->t[rrdr_line], t, r->internal.qt->id); return rrdr_line; } -static inline void rrdr_done(RRDR *r, long rrdr_line) { - r->rows = rrdr_line + 1; -} - - // ---------------------------------------------------------------------------- // tier management @@ -822,7 +1058,7 @@ static size_t rrddim_find_best_tier_for_timeframe(QUERY_TARGET *qt, time_t after // 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]; + QUERY_METRIC *qm = query_metric(qt, i); time_t first_time_s = qm->tiers[tier].db_first_time_s; time_t last_time_s = qm->tiers[tier].db_last_time_s; @@ -872,7 +1108,7 @@ static time_t rrdset_find_natural_update_every_for_timeframe(QUERY_TARGET *qt, t // find the db minimum update every for this tier for all metrics 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]; + QUERY_METRIC *qm = query_metric(qt, i); time_t update_every_s = qm->tiers[best_tier].db_update_every_s; @@ -889,24 +1125,20 @@ static time_t rrdset_find_natural_update_every_for_timeframe(QUERY_TARGET *qt, t // query ops typedef struct query_point { - time_t end_time; - time_t start_time; + STORAGE_POINT sp; NETDATA_DOUBLE value; - NETDATA_DOUBLE anomaly; - SN_FLAGS flags; + bool added; #ifdef NETDATA_INTERNAL_CHECKS size_t id; #endif } QUERY_POINT; QUERY_POINT QUERY_POINT_EMPTY = { - .end_time = 0, - .start_time = 0, - .value = NAN, - .anomaly = 0, - .flags = SN_FLAG_NONE, + .sp = STORAGE_POINT_UNSET, + .value = NAN, + .added = false, #ifdef NETDATA_INTERNAL_CHECKS - .id = 0, + .id = 0, #endif }; @@ -934,21 +1166,27 @@ typedef struct query_engine_ops { size_t tier; struct query_metric_tier *tier_ptr; 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); // aggregating points over time - void (*grouping_add)(struct rrdresult *r, NETDATA_DOUBLE value); - NETDATA_DOUBLE (*grouping_flush)(struct rrdresult *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); size_t group_points_non_zero; size_t group_points_added; - NETDATA_DOUBLE group_anomaly_rate; + STORAGE_POINT group_point; // aggregates min, max, sum, count, anomaly count for each group point + STORAGE_POINT query_point; // aggregates min, max, sum, count, anomaly count across the whole query RRDR_VALUE_FLAGS group_value_flags; // statistics size_t db_total_points_read; size_t db_points_read_per_tier[RRD_STORAGE_TIERS]; + + struct { + time_t expanded_after; + time_t expanded_before; + struct storage_engine_query_handle handle; + bool initialized; + bool finalized; + } plans[QUERY_PLANS_MAX]; + + struct query_engine_ops *next; } QUERY_ENGINE_OPS; @@ -1005,40 +1243,28 @@ static void query_planer_initialize_plans(QUERY_ENGINE_OPS *ops) { 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; + ops->plans[p].expanded_after = after; + ops->plans[p].expanded_before = before; + + ops->r->internal.qt->db.tiers[tier].queries++; 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; + STORAGE_ENGINE *eng = query_metric_storage_engine(ops->r->internal.qt, qm, tier); + storage_engine_query_init(eng->backend, tier_ptr->db_metric_handle, &ops->plans[p].handle, + after, before, ops->r->internal.qt->request.priority); + + ops->plans[p].initialized = true; + ops->plans[p].finalized = false; } } static void query_planer_finalize_plan(QUERY_ENGINE_OPS *ops, size_t plan_id) { - QUERY_METRIC *qm = ops->qm; + // 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; - } + if(ops->plans[plan_id].initialized && !ops->plans[plan_id].finalized) { + storage_engine_query_finalize(&ops->plans[plan_id].handle); + ops->plans[plan_id].initialized = false; + ops->plans[plan_id].finalized = true; } } @@ -1053,17 +1279,14 @@ static void query_planer_activate_plan(QUERY_ENGINE_OPS *ops, size_t plan_id, ti 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(!ops->plans[plan_id].initialized, "QUERY: plan has not been initialized"); + internal_fatal(ops->plans[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->handle = &ops->plans[plan_id].handle; ops->current_plan = plan_id; if(plan_id + 1 < qm->plan.used && qm->plan.array[plan_id + 1].after < qm->plan.array[plan_id].before) @@ -1071,8 +1294,8 @@ static void query_planer_activate_plan(QUERY_ENGINE_OPS *ops, size_t plan_id, ti 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; + ops->plan_expanded_after = ops->plans[plan_id].expanded_after; + ops->plan_expanded_before = ops->plans[plan_id].expanded_before; } static bool query_planer_next_plan(QUERY_ENGINE_OPS *ops, time_t now, time_t last_point_end_time) { @@ -1117,18 +1340,17 @@ static bool query_plan(QUERY_ENGINE_OPS *ops, time_t after_wanted, time_t before // put our selected tier as the first plan size_t selected_tier; + bool switch_tiers = true; - if(ops->r->internal.query_options & RRDR_OPTION_SELECTED_TIER + if((ops->r->internal.qt->window.options & RRDR_OPTION_SELECTED_TIER) && ops->r->internal.qt->window.tier < storage_tiers && query_metric_is_valid_tier(qm, ops->r->internal.qt->window.tier)) { selected_tier = ops->r->internal.qt->window.tier; + switch_tiers = false; } else { 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; @@ -1142,7 +1364,7 @@ static bool query_plan(QUERY_ENGINE_OPS *ops, time_t after_wanted, time_t before 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)) { + if(switch_tiers) { // the selected tier time_t selected_tier_first_time_s = qm->plan.array[0].after; time_t selected_tier_last_time_s = qm->plan.array[0].before; @@ -1150,7 +1372,7 @@ static bool query_plan(QUERY_ENGINE_OPS *ops, time_t after_wanted, time_t before // check if our selected tier can start the query 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++) { + for (size_t tr = (int)selected_tier + 1; tr < storage_tiers && qm->plan.used < QUERY_PLANS_MAX ; tr++) { if(!query_metric_is_valid_tier(qm, tr)) continue; @@ -1164,9 +1386,9 @@ static bool query_plan(QUERY_ENGINE_OPS *ops, time_t after_wanted, time_t before .tier = tr, .after = (tier_first_time_s < after_wanted) ? after_wanted : tier_first_time_s, .before = selected_tier_first_time_s, - .initialized = false, - .finalized = false, }; + ops->plans[qm->plan.used].initialized = false; + ops->plans[qm->plan.used].finalized = false; qm->plan.array[qm->plan.used++] = t; internal_fatal(!t.after || !t.before, "QUERY: invalid plan selected"); @@ -1183,7 +1405,7 @@ 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_s < before_wanted) { // we need some help from other tiers - for (int tr = (int)selected_tier - 1; tr >= 0; tr--) { + for (int tr = (int)selected_tier - 1; tr >= 0 && qm->plan.used < QUERY_PLANS_MAX ; tr--) { if(!query_metric_is_valid_tier(qm, tr)) continue; @@ -1199,9 +1421,9 @@ static bool query_plan(QUERY_ENGINE_OPS *ops, time_t after_wanted, time_t before .tier = tr, .after = selected_tier_last_time_s, .before = (tier_last_time_s > before_wanted) ? before_wanted : tier_last_time_s, - .initialized = false, - .finalized = false, }; + ops->plans[qm->plan.used].initialized = false; + ops->plans[qm->plan.used].finalized = false; qm->plan.array[qm->plan.used++] = t; // prepare for the tier @@ -1244,60 +1466,102 @@ static bool query_plan(QUERY_ENGINE_OPS *ops, time_t after_wanted, time_t before #define query_interpolate_point(this_point, last_point, now) do { \ if(likely( \ /* the point to interpolate is more than 1s wide */ \ - (this_point).end_time - (this_point).start_time > 1 \ + (this_point).sp.end_time_s - (this_point).sp.start_time_s > 1 \ \ /* the two points are exactly next to each other */ \ - && (last_point).end_time == (this_point).start_time \ + && (last_point).sp.end_time_s == (this_point).sp.start_time_s \ \ /* both points are valid numbers */ \ && netdata_double_isnumber((this_point).value) \ && netdata_double_isnumber((last_point).value) \ \ )) { \ - (this_point).value = (last_point).value + ((this_point).value - (last_point).value) * (1.0 - (NETDATA_DOUBLE)((this_point).end_time - (now)) / (NETDATA_DOUBLE)((this_point).end_time - (this_point).start_time)); \ - (this_point).end_time = now; \ + (this_point).value = (last_point).value + ((this_point).value - (last_point).value) * (1.0 - (NETDATA_DOUBLE)((this_point).sp.end_time_s - (now)) / (NETDATA_DOUBLE)((this_point).sp.end_time_s - (this_point).sp.start_time_s)); \ + (this_point).sp.end_time_s = now; \ } \ } while(0) -#define query_add_point_to_group(r, point, ops) do { \ +#define query_add_point_to_group(r, point, ops, add_flush) do { \ if(likely(netdata_double_isnumber((point).value))) { \ if(likely(fpclassify((point).value) != FP_ZERO)) \ (ops)->group_points_non_zero++; \ \ - if(unlikely((point).flags & SN_FLAG_RESET)) \ + if(unlikely((point).sp.flags & SN_FLAG_RESET)) \ (ops)->group_value_flags |= RRDR_VALUE_RESET; \ \ - (ops)->grouping_add(r, (point).value); \ + time_grouping_add(r, (point).value, add_flush); \ + \ + storage_point_merge_to((ops)->group_point, (point).sp); \ + if(!(point).added) \ + storage_point_merge_to((ops)->query_point, (point).sp); \ } \ \ (ops)->group_points_added++; \ - (ops)->group_anomaly_rate += (point).anomaly; \ } while(0) -static QUERY_ENGINE_OPS *rrd2rrdr_query_prep(RRDR *r, size_t dim_id_in_rrdr) { +static __thread QUERY_ENGINE_OPS *released_ops = NULL; + +static void rrd2rrdr_query_ops_freeall(RRDR *r __maybe_unused) { + while(released_ops) { + QUERY_ENGINE_OPS *ops = released_ops; + released_ops = ops->next; + + onewayalloc_freez(r->internal.owa, ops); + } +} + +static void rrd2rrdr_query_ops_release(QUERY_ENGINE_OPS *ops) { + if(!ops) return; + + ops->next = released_ops; + released_ops = ops; +} + +static QUERY_ENGINE_OPS *rrd2rrdr_query_ops_get(RRDR *r) { + QUERY_ENGINE_OPS *ops; + if(released_ops) { + ops = released_ops; + released_ops = ops->next; + } + else { + ops = onewayalloc_mallocz(r->internal.owa, sizeof(QUERY_ENGINE_OPS)); + } + + memset(ops, 0, sizeof(*ops)); + return ops; +} + +static QUERY_ENGINE_OPS *rrd2rrdr_query_ops_prep(RRDR *r, size_t query_metric_id) { QUERY_TARGET *qt = r->internal.qt; - QUERY_ENGINE_OPS *ops = onewayalloc_mallocz(r->internal.owa, sizeof(QUERY_ENGINE_OPS)); + QUERY_ENGINE_OPS *ops = rrd2rrdr_query_ops_get(r); *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), + .qm = query_metric(qt, query_metric_id), + .tier_query_fetch = r->time_grouping.tier_query_fetch, + .view_update_every = r->view.update_every, + .query_granularity = (time_t)(r->view.update_every / r->view.group), .group_value_flags = RRDR_VALUE_NOTHING, }; - if(!query_plan(ops, qt->window.after, qt->window.before, qt->window.points)) + if(!query_plan(ops, qt->window.after, qt->window.before, qt->window.points)) { + rrd2rrdr_query_ops_release(ops); 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; + QUERY_METRIC *qm = ops->qm; + + const RRDR_TIME_GROUPING add_flush = r->time_grouping.add_flush; + + ops->group_point = STORAGE_POINT_UNSET; + ops->query_point = STORAGE_POINT_UNSET; + + RRDR_OPTIONS options = qt->window.options; size_t points_wanted = qt->window.points; time_t after_wanted = qt->window.after; time_t before_wanted = qt->window.before; (void)before_wanted; @@ -1306,15 +1570,12 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ // if(strcmp("user", string2str(rd->id)) == 0 && strcmp("system.cpu", string2str(rd->rrdset->id)) == 0) // debug_this = true; - time_t max_date = 0, - min_date = 0; - size_t points_added = 0; long rrdr_line = -1; - bool use_anomaly_bit_as_value = (r->internal.query_options & RRDR_OPTION_ANOMALY_BIT) ? true : false; + bool use_anomaly_bit_as_value = (r->internal.qt->window.options & RRDR_OPTION_ANOMALY_BIT) ? true : false; - NETDATA_DOUBLE min = r->min, max = r->max; + NETDATA_DOUBLE min = r->view.min, max = r->view.max; QUERY_POINT last2_point = QUERY_POINT_EMPTY; QUERY_POINT last1_point = QUERY_POINT_EMPTY; @@ -1329,12 +1590,14 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ 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; + size_t query_is_finished_counter = 0; // 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 && query_is_finished_counter <= 10 ; + 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.sp.end_time_s); db_points_read_since_plan_switch = 0; } @@ -1347,26 +1610,35 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ last1_point = new_point; } - if(unlikely(ops->is_finished(ops->handle))) { + if(unlikely(storage_engine_query_is_finished(ops->handle))) { + query_is_finished_counter++; + if(count_same_end_time != 0) { last2_point = last1_point; last1_point = new_point; } new_point = QUERY_POINT_EMPTY; - new_point.start_time = last1_point.end_time; - new_point.end_time = now_end_time; + new_point.sp.start_time_s = last1_point.sp.end_time_s; + new_point.sp.end_time_s = now_end_time; // // if(debug_this) info("QUERY: is finished() returned true"); // break; } + else + query_is_finished_counter = 0; // fetch the new point { STORAGE_POINT sp; if(likely(storage_point_is_unset(next1_point))) { db_points_read_since_plan_switch++; - sp = ops->next_metric(ops->handle); + sp = storage_engine_query_next_metric(ops->handle); + ops->db_points_read_per_tier[ops->tier]++; + ops->db_total_points_read++; + + if(unlikely(options & RRDR_OPTION_ABSOLUTE)) + storage_point_make_positive(sp); } else { // ONE POINT READ-AHEAD @@ -1377,7 +1649,7 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ // 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))) { + query_planer_next_plan(ops, now_end_time, new_point.sp.end_time_s))) { // The end time of the current point, crosses our plans (tiers) // so, we switched plan (tier) @@ -1387,7 +1659,12 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ // 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); + STORAGE_POINT sp2 = storage_engine_query_next_metric(ops->handle); + ops->db_points_read_per_tier[ops->tier]++; + ops->db_total_points_read++; + + if(unlikely(options & RRDR_OPTION_ABSOLUTE)) + storage_point_make_positive(sp); if(sp.start_time_s > sp2.start_time_s) // the point from the previous plan is useless @@ -1399,12 +1676,8 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ next1_point = sp2; } - ops->db_points_read_per_tier[ops->tier]++; - ops->db_total_points_read++; - - 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; + new_point.sp = sp; + new_point.added = false; query_point_set_id(new_point, ops->db_total_points_read); // if(debug_this) @@ -1415,13 +1688,13 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ 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; + new_point.value = storage_point_anomaly_rate(new_point.sp); else { switch (ops->tier_query_fetch) { default: case TIER_QUERY_FETCH_AVERAGE: - new_point.value = sp.sum / sp.count; + new_point.value = sp.sum / (NETDATA_DOUBLE)sp.count; break; case TIER_QUERY_FETCH_MIN: @@ -1438,36 +1711,34 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ }; } } - else { + else new_point.value = NAN; - new_point.flags = SN_FLAG_NONE; - } } // check if the db is giving us zero duration points if(unlikely(db_points_read_since_plan_switch > 1 && - new_point.start_time == new_point.end_time)) { + new_point.sp.start_time_s == new_point.sp.end_time_s)) { 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); + qt->id, query_metric_id(qt, qm), + new_point.id, new_point.sp.start_time_s, new_point.sp.end_time_s); - new_point.start_time = new_point.end_time - ops->tier_ptr->db_update_every_s; + new_point.sp.start_time_s = new_point.sp.end_time_s - ops->tier_ptr->db_update_every_s; } // check if the db is advancing the query if(unlikely(db_points_read_since_plan_switch > 1 && - new_point.end_time <= last1_point.end_time)) { + new_point.sp.end_time_s <= last1_point.sp.end_time_s)) { 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, + qt->id, query_metric_id(qt, qm), + new_point.id, new_point.sp.start_time_s, new_point.sp.end_time_s, + last1_point.id, last1_point.sp.start_time_s, last1_point.sp.end_time_s, now_start_time, now_end_time); count_same_end_time++; @@ -1476,13 +1747,14 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ count_same_end_time = 0; // decide how to use this point - if(likely(new_point.end_time < now_end_time)) { // likely to favor tier0 + if(likely(new_point.sp.end_time_s < now_end_time)) { // likely to favor tier0 // this db point ends before our now_end_time - if(likely(new_point.end_time >= now_start_time)) { // likely to favor tier0 + if(likely(new_point.sp.end_time_s >= now_start_time)) { // likely to favor tier0 // this db point ends after our now_start time - query_add_point_to_group(r, new_point, ops); + query_add_point_to_group(r, new_point, ops, add_flush); + new_point.added = true; } else { // we don't need this db point @@ -1493,14 +1765,14 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ // at exactly the time we will want // we only log if this is not point 1 - internal_error(new_point.end_time < ops->plan_expanded_after && + internal_error(new_point.sp.end_time_s < 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, + qt->id, query_metric_id(qt, qm), + new_point.id, new_point.sp.start_time_s, new_point.sp.end_time_s, now_start_time, now_end_time, ops->plan_expanded_after, ops->plan_expanded_before); } @@ -1518,15 +1790,15 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ "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); + qt->id, query_metric_id(qt, qm), + last1_point.sp.end_time_s, count_same_end_time); - if(unlikely(new_point.end_time <= last1_point.end_time)) - new_point.end_time = now_end_time; + if(unlikely(new_point.sp.end_time_s <= last1_point.sp.end_time_s)) + new_point.sp.end_time_s = now_end_time; } - time_t stop_time = new_point.end_time; - if(unlikely(!storage_point_is_unset(next1_point))) { + time_t stop_time = new_point.sp.end_time_s; + if(unlikely(!storage_point_is_unset(next1_point) && next1_point.start_time_s >= now_end_time)) { // ONE POINT READ-AHEAD // the point crosses the start time of the // read ahead storage point we have read @@ -1537,18 +1809,20 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ // 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 <= stop_time && points_added < points_wanted ; - now_end_time += ops->view_update_every, iterations++) { + internal_fatal(now_end_time > stop_time || points_added >= points_wanted, + "QUERY: first part of query provides invalid point to interpolate (now_end_time %ld, stop_time %ld", + now_end_time, stop_time); + do { // now_start_time is wrong in this loop // but, we don't need it QUERY_POINT current_point; - if(likely(now_end_time > new_point.start_time)) { + if(likely(now_end_time > new_point.sp.start_time_s)) { // it is time for our NEW point to be used current_point = new_point; + new_point.added = true; // first copy, then set it, so that new_point will not be added again query_interpolate_point(current_point, last1_point, now_end_time); // internal_error(current_point.id > 0 @@ -1564,9 +1838,10 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ // current_point.id, current_point.start_time, current_point.end_time, // now_end_time); } - else if(likely(now_end_time <= last1_point.end_time)) { + else if(likely(now_end_time <= last1_point.sp.end_time_s)) { // our LAST point is still valid current_point = last1_point; + last1_point.added = true; // first copy, then set it, so that last1_point will not be added again query_interpolate_point(current_point, last2_point, now_end_time); // internal_error(current_point.id > 0 @@ -1586,14 +1861,11 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ current_point = QUERY_POINT_EMPTY; } - query_add_point_to_group(r, current_point, ops); + query_add_point_to_group(r, current_point, ops, add_flush); rrdr_line = rrdr_line_init(r, now_end_time, rrdr_line); size_t rrdr_o_v_index = rrdr_line * r->d + dim_id_in_rrdr; - if(unlikely(!min_date)) min_date = now_end_time; - max_date = now_end_time; - // find the place to store our values RRDR_VALUE_FLAGS *rrdr_value_options_ptr = &r->o[rrdr_o_v_index]; @@ -1605,15 +1877,12 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ *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 = time_grouping_flush(r, rrdr_value_options_ptr, add_flush); 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] = storage_point_anomaly_rate(ops->group_point); - if(likely(points_added || dim_id_in_rrdr)) { + if(likely(points_added || r->internal.queries_count)) { // find the min/max across all dimensions if(unlikely(group_value < min)) min = group_value; @@ -1621,7 +1890,7 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ } else { - // runs only when dim_id_in_rrdr == 0 && points_added == 0 + // runs only when r->internal.queries_count == 0 && points_added == 0 // so, on the first point added for the query. min = max = group_value; } @@ -1630,31 +1899,38 @@ static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_ 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, + ops->group_point = STORAGE_POINT_UNSET; + + now_end_time += ops->view_update_every; + } while(now_end_time <= stop_time && points_added < points_wanted); + + // the loop above increased "now" by ops->view_update_every, // 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; } query_planer_finalize_remaining_plans(ops); - r->internal.result_points_generated += points_added; - r->internal.db_points_read += ops->db_total_points_read; + qm->query_points = ops->query_point; + + // fill the rest of the points with empty values + while (points_added < points_wanted) { + rrdr_line++; + size_t rrdr_o_v_index = rrdr_line * r->d + dim_id_in_rrdr; + r->o[rrdr_o_v_index] = RRDR_VALUE_EMPTY; + r->v[rrdr_o_v_index] = 0.0; + r->ar[rrdr_o_v_index] = 0.0; + points_added++; + } + + r->internal.queries_count++; + r->view.min = min; + r->view.max = max; + + r->stats.result_points_generated += points_added; + r->stats.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->min = min; - r->max = max; - r->before = max_date; - 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); + qt->db.tiers[tr].points += ops->db_points_read_per_tier[tr]; } // ---------------------------------------------------------------------------- @@ -1669,7 +1945,7 @@ void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now_s struct rrddim_tier *t = &rd->tiers[tier]; if(unlikely(!t)) return; - time_t latest_time_s = t->query_ops->latest_time_s(t->db_metric_handle); + time_t latest_time_s = storage_engine_latest_time_s(t->backend, t->db_metric_handle); time_t granularity = (time_t)t->tier_grouping * (time_t)rd->update_every; time_t time_diff = now_s - latest_time_s; @@ -1683,21 +1959,21 @@ void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now_s // 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_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); + time_t smaller_tier_first_time = storage_engine_oldest_time_s(rd->tiers[read_tier].backend, rd->tiers[read_tier].db_metric_handle); + time_t smaller_tier_last_time = storage_engine_latest_time_s(rd->tiers[read_tier].backend, 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_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, STORAGE_PRIORITY_HIGH); + storage_engine_query_init(tmp->backend, tmp->db_metric_handle, &handle, after_wanted, before_wanted, STORAGE_PRIORITY_HIGH); size_t points_read = 0; - while(!tmp->query_ops->is_finished(&handle)) { + while(!storage_engine_query_is_finished(&handle)) { - STORAGE_POINT sp = tmp->query_ops->next_metric(&handle); + STORAGE_POINT sp = storage_engine_query_next_metric(&handle); points_read++; if(sp.end_time_s > latest_time_s) { @@ -1706,7 +1982,7 @@ void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now_s } } - tmp->query_ops->finalize(&handle); + storage_engine_query_finalize(&handle); store_metric_collection_completed(); global_statistics_backfill_query_completed(points_read); @@ -1721,7 +1997,7 @@ void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now_s #ifdef NETDATA_INTERNAL_CHECKS static void rrd2rrdr_log_request_response_metadata(RRDR *r , RRDR_OPTIONS options __maybe_unused - , RRDR_GROUPING group_method + , RRDR_TIME_GROUPING group_method , bool aligned , size_t group , time_t resampling_time @@ -1737,8 +2013,9 @@ static void rrd2rrdr_log_request_response_metadata(RRDR *r , const char *msg ) { - time_t first_entry_s = r->internal.qt->db.first_time_s; - time_t last_entry_s = r->internal.qt->db.last_time_s; + QUERY_TARGET *qt = r->internal.qt; + time_t first_entry_s = qt->db.first_time_s; + time_t last_entry_s = qt->db.last_time_s; internal_error( true, @@ -1748,33 +2025,33 @@ static void rrd2rrdr_log_request_response_metadata(RRDR *r "duration (got: %ld, want: %ld, req: %ld, db: %ld), " "points (got: %zu, want: %zu, req: %zu), " "%s" - , r->internal.qt->id - , r->internal.qt->window.query_granularity + , qt->id + , qt->window.query_granularity // grouping , (aligned) ? "aligned" : "unaligned" - , group_method2string(group_method) + , time_grouping_method2string(group_method) , group , resampling_time , resampling_group // after - , r->after + , r->view.after , after_wanted , after_requested , first_entry_s // before - , r->before + , r->view.before , before_wanted , before_requested , 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)(r->view.before - r->view.after + qt->window.query_granularity) + , (long)(before_wanted - after_wanted + qt->window.query_granularity) , (long)before_requested - after_requested - , (long)((last_entry_s - first_entry_s) + r->internal.qt->window.query_granularity) + , (long)((last_entry_s - first_entry_s) + qt->window.query_granularity) // points , r->rows @@ -1788,9 +2065,12 @@ static void rrd2rrdr_log_request_response_metadata(RRDR *r #endif // NETDATA_INTERNAL_CHECKS // Returns 1 if an absolute period was requested or 0 if it was a relative period -bool rrdr_relative_window_to_absolute(time_t *after, time_t *before) { +bool rrdr_relative_window_to_absolute(time_t *after, time_t *before, time_t *now_ptr) { time_t now = now_realtime_sec() - 1; + if(now_ptr) + *now_ptr = now; + int absolute_period_requested = -1; long long after_requested, before_requested; @@ -1890,11 +2170,11 @@ bool query_target_calculate_window(QUERY_TARGET *qt) { size_t points_requested = (long)qt->request.points; time_t after_requested = qt->request.after; time_t before_requested = qt->request.before; - RRDR_GROUPING group_method = qt->request.group_method; + RRDR_TIME_GROUPING group_method = qt->request.time_group_method; time_t resampling_time_requested = qt->request.resampling_time; - RRDR_OPTIONS options = qt->request.options; + RRDR_OPTIONS options = qt->window.options; size_t tier = qt->request.tier; - time_t update_every = qt->db.minimum_latest_update_every_s; + time_t update_every = qt->db.minimum_latest_update_every_s ? qt->db.minimum_latest_update_every_s : 1; // RULES // points_requested = 0 @@ -1953,27 +2233,36 @@ bool query_target_calculate_window(QUERY_TARGET *qt) { time_t last_entry_s = qt->db.last_time_s; 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; - } + 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); + after_wanted = qt->window.after; + before_wanted = qt->window.before; - query_debug_log(":first_entry_t %ld, last_entry_t %ld", first_entry_s, last_entry_s); + if(after_wanted == before_wanted) + after_wanted = before_wanted - update_every; - if (after_wanted == 0) { - after_wanted = first_entry_s; - query_debug_log(":zero after_wanted %ld", after_wanted); + if (points_wanted == 0) { + points_wanted = (before_wanted - after_wanted) / update_every; + query_debug_log(":zero points_wanted %zu", points_wanted); + } } + else { + query_debug_log(":first_entry_t %ld, last_entry_t %ld", first_entry_s, last_entry_s); - if (before_wanted == 0) { - before_wanted = last_entry_s; - before_is_aligned_to_db_end = true; - query_debug_log(":zero before_wanted %ld", before_wanted); - } + if (after_wanted == 0) { + after_wanted = first_entry_s; + query_debug_log(":zero after_wanted %ld", after_wanted); + } + + if (before_wanted == 0) { + 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_s - first_entry_s) / update_every; - query_debug_log(":zero points_wanted %zu", points_wanted); + if (points_wanted == 0) { + points_wanted = (last_entry_s - first_entry_s) / update_every; + query_debug_log(":zero points_wanted %zu", points_wanted); + } } } @@ -1983,7 +2272,7 @@ bool query_target_calculate_window(QUERY_TARGET *qt) { } // convert our before_wanted and after_wanted to absolute - rrdr_relative_window_to_absolute(&after_wanted, &before_wanted); + rrdr_relative_window_to_absolute(&after_wanted, &before_wanted, NULL); query_debug_log(":relative2absolute after %ld, before %ld", after_wanted, before_wanted); if (natural_points && (options & RRDR_OPTION_SELECTED_TIER) && tier > 0 && storage_tiers > 1) { @@ -2145,8 +2434,8 @@ bool query_target_calculate_window(QUERY_TARGET *qt) { qt->window.relative = relative_period_requested; qt->window.points = points_wanted; qt->window.group = group; - qt->window.group_method = group_method; - qt->window.group_options = qt->request.group_options; + qt->window.time_group_method = group_method; + qt->window.time_group_options = qt->request.time_group_options; qt->window.query_granularity = query_granularity; qt->window.resampling_group = resampling_group; qt->window.resampling_divisor = resampling_divisor; @@ -2157,204 +2446,1290 @@ bool query_target_calculate_window(QUERY_TARGET *qt) { return true; } -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, - STORAGE_PRIORITY priority) { +// ---------------------------------------------------------------------------- +// group by - QUERY_TARGET_REQUEST qtr = { - .st = st, - .points = points, - .after = after, - .before = before, - .group_method = group_method, - .resampling_time = resampling_time, - .options = options, - .dimensions = dimensions, - .group_options = group_options, - .timeout = timeout, - .tier = tier, - .query_source = query_source, - .priority = priority, - }; +struct group_by_label_key { + DICTIONARY *values; +}; + +static void group_by_label_key_insert_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) { + // add the key to our r->label_keys global keys dictionary + DICTIONARY *label_keys = data; + dictionary_set(label_keys, dictionary_acquired_item_name(item), NULL, 0); - return rrd2rrdr(owa, query_target_create(&qtr)); + // create a dictionary for the values of this key + struct group_by_label_key *k = value; + k->values = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE, NULL, 0); } -RRDR *rrd2rrdr(ONEWAYALLOC *owa, QUERY_TARGET *qt) { - if(!qt) - return NULL; +static void group_by_label_key_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) { + struct group_by_label_key *k = value; + dictionary_destroy(k->values); +} - if(!owa) { - query_target_release(qt); - return NULL; - } +static int rrdlabels_traversal_cb_to_group_by_label_key(const char *name, const char *value, RRDLABEL_SRC ls __maybe_unused, void *data) { + DICTIONARY *dl = data; + struct group_by_label_key *k = dictionary_set(dl, name, NULL, sizeof(struct group_by_label_key)); + dictionary_set(k->values, value, NULL, 0); + return 1; +} - // qt.window members are the WANTED ones. - // qt.request members are the REQUESTED ones. +void rrdr_json_group_by_labels(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options) { + if(!r->label_keys || !r->dl) + return; + + buffer_json_member_add_object(wb, key); + + void *t; + dfe_start_read(r->label_keys, t) { + buffer_json_member_add_array(wb, t_dfe.name); + + for(size_t d = 0; d < r->d ;d++) { + if(!rrdr_dimension_should_be_exposed(r->od[d], options)) + continue; + + struct group_by_label_key *k = dictionary_get(r->dl[d], t_dfe.name); + if(k) { + buffer_json_add_array_item_array(wb); + void *tt; + dfe_start_read(k->values, tt) { + buffer_json_add_array_item_string(wb, tt_dfe.name); + } + dfe_done(tt); + buffer_json_array_close(wb); + } + else + buffer_json_add_array_item_string(wb, NULL); + } - RRDR *r = rrdr_create(owa, qt); - if(unlikely(!r)) { - internal_error(true, "QUERY: cannot create RRDR for %s, after=%ld, before=%ld, points=%zu", - qt->id, qt->window.after, qt->window.before, qt->window.points); - return NULL; - } + buffer_json_array_close(wb); + } + dfe_done(t); - if(unlikely(!r->d || !qt->window.points)) { - internal_error(true, "QUERY: returning empty RRDR (no dimensions in RRDSET) for %s, after=%ld, before=%ld, points=%zu", - qt->id, qt->window.after, qt->window.before, qt->window.points); - return r; - } + buffer_json_object_close(wb); // key +} - if(qt->window.relative) - r->result_options |= RRDR_RESULT_OPTION_RELATIVE; - else - r->result_options |= RRDR_RESULT_OPTION_ABSOLUTE; +static int group_by_label_is_space(char c) { + if(c == ',' || c == '|') + return 1; - // ------------------------------------------------------------------------- - // initialize RRDR + return 0; +} - r->group = qt->window.group; - r->update_every = (int) (qt->window.group * qt->window.query_granularity); - r->before = qt->window.before; - r->after = qt->window.after; - r->internal.points_wanted = qt->window.points; - r->internal.resampling_group = qt->window.resampling_group; - r->internal.resampling_divisor = qt->window.resampling_divisor; - r->internal.query_options = qt->window.options; +static void rrd2rrdr_set_timestamps(RRDR *r) { + QUERY_TARGET *qt = r->internal.qt; - // ------------------------------------------------------------------------- - // assign the processor functions - rrdr_set_grouping_function(r, qt->window.group_method); + internal_fatal(qt->window.points != r->n, "QUERY: mismatch to the number of points in qt and r"); - // allocate any memory required by the grouping method - r->internal.grouping_create(r, qt->window.group_options); + r->view.group = qt->window.group; + r->view.update_every = (int) query_view_update_every(qt); + r->view.before = qt->window.before; + r->view.after = qt->window.after; - // ------------------------------------------------------------------------- - // do the work for each dimension + r->time_grouping.points_wanted = qt->window.points; + r->time_grouping.resampling_group = qt->window.resampling_group; + r->time_grouping.resampling_divisor = qt->window.resampling_divisor; - time_t max_after = 0, min_before = 0; - size_t max_rows = 0; + r->rows = qt->window.points; - long dimensions_used = 0, dimensions_nonzero = 0; - struct timeval query_start_time; - struct timeval query_current_time; - if (qt->request.timeout) - now_realtime_timeval(&query_start_time); + size_t points_wanted = qt->window.points; + time_t after_wanted = qt->window.after; + time_t before_wanted = qt->window.before; (void)before_wanted; - size_t last_db_points_read = 0; - size_t last_result_points_generated = 0; + time_t view_update_every = r->view.update_every; + time_t query_granularity = (time_t)(r->view.update_every / r->view.group); - QUERY_ENGINE_OPS **ops = onewayalloc_callocz(r->internal.owa, qt->query.used, sizeof(QUERY_ENGINE_OPS *)); + size_t rrdr_line = 0; + time_t first_point_end_time = after_wanted + view_update_every - query_granularity; + time_t now_end_time = first_point_end_time; - 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++; + while (rrdr_line < points_wanted) { + r->t[rrdr_line++] = now_end_time; + now_end_time += view_update_every; } - for(size_t c = 0, max = qt->query.used; c < max ; c++) { + internal_fatal(r->t[0] != first_point_end_time, "QUERY: wrong first timestamp in the query"); + internal_error(r->t[points_wanted - 1] != before_wanted, + "QUERY: wrong last timestamp in the query, expected %ld, found %ld", + before_wanted, r->t[points_wanted - 1]); +} - if(queries_prepared < max) { - // preload another query - ops[queries_prepared] = rrd2rrdr_query_prep(r, queries_prepared); - queries_prepared++; +static void query_group_by_make_dimension_key(BUFFER *key, RRDR_GROUP_BY group_by, size_t group_by_id, QUERY_TARGET *qt, QUERY_NODE *qn, QUERY_CONTEXT *qc, QUERY_INSTANCE *qi, QUERY_DIMENSION *qd __maybe_unused, QUERY_METRIC *qm, bool query_has_percentage_of_instance) { + buffer_flush(key); + if(unlikely(!query_has_percentage_of_instance && qm->status & RRDR_DIMENSION_HIDDEN)) { + buffer_strcat(key, "__hidden_dimensions__"); + } + else if(unlikely(group_by & RRDR_GROUP_BY_SELECTED)) { + buffer_strcat(key, "selected"); + } + else { + if (group_by & RRDR_GROUP_BY_DIMENSION) { + buffer_fast_strcat(key, "|", 1); + buffer_strcat(key, query_metric_name(qt, qm)); } - // set the query target dimension options to rrdr - r->od[c] = qt->query.array[c].dimension.options; + if (group_by & (RRDR_GROUP_BY_INSTANCE|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)) { + buffer_fast_strcat(key, "|", 1); + buffer_strcat(key, string2str(query_instance_id_fqdn(qi, qt->request.version))); + } - // reset the grouping for the new dimension - r->internal.grouping_reset(r); + if (group_by & RRDR_GROUP_BY_LABEL) { + DICTIONARY *labels = rrdinstance_acquired_labels(qi->ria); + for (size_t l = 0; l < qt->group_by[group_by_id].used; l++) { + buffer_fast_strcat(key, "|", 1); + rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by[group_by_id].label_keys[l], "[unset]"); + } + } - if(ops[c]) { - r->od[c] |= RRDR_DIMENSION_QUERIED; - rrd2rrdr_query_execute(r, c, ops[c]); + if (group_by & RRDR_GROUP_BY_NODE) { + buffer_fast_strcat(key, "|", 1); + buffer_strcat(key, qn->rrdhost->machine_guid); } - else - continue; - 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); + if (group_by & RRDR_GROUP_BY_CONTEXT) { + buffer_fast_strcat(key, "|", 1); + buffer_strcat(key, rrdcontext_acquired_id(qc->rca)); + } - last_db_points_read = r->internal.db_points_read; - last_result_points_generated = r->internal.result_points_generated; + if (group_by & RRDR_GROUP_BY_UNITS) { + buffer_fast_strcat(key, "|", 1); + buffer_strcat(key, query_target_has_percentage_units(qt) ? "%" : rrdinstance_acquired_units(qi->ria)); + } + } +} - if (qt->request.timeout) - now_realtime_timeval(&query_current_time); +static void query_group_by_make_dimension_id(BUFFER *key, RRDR_GROUP_BY group_by, size_t group_by_id, QUERY_TARGET *qt, QUERY_NODE *qn, QUERY_CONTEXT *qc, QUERY_INSTANCE *qi, QUERY_DIMENSION *qd __maybe_unused, QUERY_METRIC *qm, bool query_has_percentage_of_instance) { + buffer_flush(key); + if(unlikely(!query_has_percentage_of_instance && qm->status & RRDR_DIMENSION_HIDDEN)) { + buffer_strcat(key, "__hidden_dimensions__"); + } + else if(unlikely(group_by & RRDR_GROUP_BY_SELECTED)) { + buffer_strcat(key, "selected"); + } + else { + if (group_by & RRDR_GROUP_BY_DIMENSION) { + buffer_strcat(key, query_metric_name(qt, qm)); + } - if(r->od[c] & RRDR_DIMENSION_NONZERO) - dimensions_nonzero++; + if (group_by & (RRDR_GROUP_BY_INSTANCE|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)) { + if (buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); - // verify all dimensions are aligned - if(unlikely(!dimensions_used)) { - min_before = r->before; - max_after = r->after; - max_rows = r->rows; + if (group_by & RRDR_GROUP_BY_NODE) + buffer_strcat(key, rrdinstance_acquired_id(qi->ria)); + else + buffer_strcat(key, string2str(query_instance_id_fqdn(qi, qt->request.version))); } - else { - if(r->after != max_after) { - internal_error(true, "QUERY: 'after' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu", - string2str(qt->query.array[c].dimension.id), (size_t)max_after, string2str(qt->query.array[c].dimension.name), (size_t)r->after); - r->after = (r->after > max_after) ? r->after : max_after; + if (group_by & RRDR_GROUP_BY_LABEL) { + DICTIONARY *labels = rrdinstance_acquired_labels(qi->ria); + for (size_t l = 0; l < qt->group_by[group_by_id].used; l++) { + if (buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by[group_by_id].label_keys[l], "[unset]"); } + } - if(r->before != min_before) { - internal_error(true, "QUERY: 'before' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu", - string2str(qt->query.array[c].dimension.id), (size_t)min_before, string2str(qt->query.array[c].dimension.name), (size_t)r->before); + if (group_by & RRDR_GROUP_BY_NODE) { + if (buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); - r->before = (r->before < min_before) ? r->before : min_before; - } + buffer_strcat(key, qn->rrdhost->machine_guid); + } - if(r->rows != max_rows) { - internal_error(true, "QUERY: 'rows' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu", - string2str(qt->query.array[c].dimension.id), (size_t)max_rows, string2str(qt->query.array[c].dimension.name), (size_t)r->rows); + if (group_by & RRDR_GROUP_BY_CONTEXT) { + if (buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); - r->rows = (r->rows > max_rows) ? r->rows : max_rows; - } + buffer_strcat(key, rrdcontext_acquired_id(qc->rca)); + } + + if (group_by & RRDR_GROUP_BY_UNITS) { + if (buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + + buffer_strcat(key, query_target_has_percentage_units(qt) ? "%" : rrdinstance_acquired_units(qi->ria)); + } + } +} + +static void query_group_by_make_dimension_name(BUFFER *key, RRDR_GROUP_BY group_by, size_t group_by_id, QUERY_TARGET *qt, QUERY_NODE *qn, QUERY_CONTEXT *qc, QUERY_INSTANCE *qi, QUERY_DIMENSION *qd __maybe_unused, QUERY_METRIC *qm, bool query_has_percentage_of_instance) { + buffer_flush(key); + if(unlikely(!query_has_percentage_of_instance && qm->status & RRDR_DIMENSION_HIDDEN)) { + buffer_strcat(key, "__hidden_dimensions__"); + } + else if(unlikely(group_by & RRDR_GROUP_BY_SELECTED)) { + buffer_strcat(key, "selected"); + } + else { + if (group_by & RRDR_GROUP_BY_DIMENSION) { + buffer_strcat(key, query_metric_name(qt, qm)); + } + + if (group_by & (RRDR_GROUP_BY_INSTANCE|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)) { + if (buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + + if (group_by & RRDR_GROUP_BY_NODE) + buffer_strcat(key, rrdinstance_acquired_name(qi->ria)); + else + buffer_strcat(key, string2str(query_instance_name_fqdn(qi, qt->request.version))); + } + + if (group_by & RRDR_GROUP_BY_LABEL) { + DICTIONARY *labels = rrdinstance_acquired_labels(qi->ria); + for (size_t l = 0; l < qt->group_by[group_by_id].used; l++) { + if (buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by[group_by_id].label_keys[l], "[unset]"); + } + } + + if (group_by & RRDR_GROUP_BY_NODE) { + if (buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + + buffer_strcat(key, rrdhost_hostname(qn->rrdhost)); + } + + if (group_by & RRDR_GROUP_BY_CONTEXT) { + if (buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + + buffer_strcat(key, rrdcontext_acquired_id(qc->rca)); + } + + if (group_by & RRDR_GROUP_BY_UNITS) { + if (buffer_strlen(key) != 0) + buffer_fast_strcat(key, ",", 1); + + buffer_strcat(key, query_target_has_percentage_units(qt) ? "%" : rrdinstance_acquired_units(qi->ria)); + } + } +} + +struct rrdr_group_by_entry { + size_t priority; + size_t count; + STRING *id; + STRING *name; + STRING *units; + RRDR_DIMENSION_FLAGS od; + DICTIONARY *dl; +}; + +static RRDR *rrd2rrdr_group_by_initialize(ONEWAYALLOC *owa, QUERY_TARGET *qt) { + RRDR *r_tmp = NULL; + RRDR_OPTIONS options = qt->window.options; + + if(qt->request.version < 2) { + // v1 query + RRDR *r = rrdr_create(owa, qt, qt->query.used, qt->window.points); + if(unlikely(!r)) { + internal_error(true, "QUERY: cannot create RRDR for %s, after=%ld, before=%ld, dimensions=%u, points=%zu", + qt->id, qt->window.after, qt->window.before, qt->query.used, qt->window.points); + return NULL; + } + r->group_by.r = NULL; + + for(size_t d = 0; d < qt->query.used ; d++) { + QUERY_METRIC *qm = query_metric(qt, d); + QUERY_DIMENSION *qd = query_dimension(qt, qm->link.query_dimension_id); + r->di[d] = rrdmetric_acquired_id_dup(qd->rma); + r->dn[d] = rrdmetric_acquired_name_dup(qd->rma); + } + + rrd2rrdr_set_timestamps(r); + return r; + } + // v2 query + + // parse all the group-by label keys + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) { + if (qt->request.group_by[g].group_by & RRDR_GROUP_BY_LABEL && + qt->request.group_by[g].group_by_label && *qt->request.group_by[g].group_by_label) + qt->group_by[g].used = quoted_strings_splitter( + qt->request.group_by[g].group_by_label, qt->group_by[g].label_keys, + GROUP_BY_MAX_LABEL_KEYS, group_by_label_is_space); + + if (!qt->group_by[g].used) + qt->request.group_by[g].group_by &= ~RRDR_GROUP_BY_LABEL; + } + + // make sure there are valid group-by methods + bool query_has_percentage_of_instance = false; + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES - 1 ;g++) { + if(!(qt->request.group_by[g].group_by & SUPPORTED_GROUP_BY_METHODS)) + qt->request.group_by[g].group_by = (g == 0) ? RRDR_GROUP_BY_DIMENSION : RRDR_GROUP_BY_NONE; + + if(qt->request.group_by[g].group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE) + query_has_percentage_of_instance = true; + } + + // merge all group-by options to upper levels + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES - 1 ;g++) { + if(qt->request.group_by[g].group_by == RRDR_GROUP_BY_NONE) + continue; + + if(qt->request.group_by[g].group_by == RRDR_GROUP_BY_SELECTED) { + for (size_t r = g + 1; r < MAX_QUERY_GROUP_BY_PASSES; r++) + qt->request.group_by[r].group_by = RRDR_GROUP_BY_NONE; + } + else { + for (size_t r = g + 1; r < MAX_QUERY_GROUP_BY_PASSES; r++) { + if (qt->request.group_by[r].group_by == RRDR_GROUP_BY_NONE) + continue; + + if (qt->request.group_by[r].group_by != RRDR_GROUP_BY_SELECTED) { + if(qt->request.group_by[r].group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE) + qt->request.group_by[g].group_by |= RRDR_GROUP_BY_INSTANCE; + else + qt->request.group_by[g].group_by |= qt->request.group_by[r].group_by; + + if(qt->request.group_by[r].group_by & RRDR_GROUP_BY_LABEL) { + for (size_t lr = 0; lr < qt->group_by[r].used; lr++) { + bool found = false; + for (size_t lg = 0; lg < qt->group_by[g].used; lg++) { + if (strcmp(qt->group_by[g].label_keys[lg], qt->group_by[r].label_keys[lr]) == 0) { + found = true; + break; + } + } + + if (!found && qt->group_by[g].used < GROUP_BY_MAX_LABEL_KEYS * MAX_QUERY_GROUP_BY_PASSES) + qt->group_by[g].label_keys[qt->group_by[g].used++] = qt->group_by[r].label_keys[lr]; + } + } + } + } + } + } + + int added = 0; + RRDR *first_r = NULL, *last_r = NULL; + BUFFER *key = buffer_create(0, NULL); + struct rrdr_group_by_entry *entries = onewayalloc_mallocz(owa, qt->query.used * sizeof(struct rrdr_group_by_entry)); + DICTIONARY *groups = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE); + DICTIONARY *label_keys = NULL; + + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) { + RRDR_GROUP_BY group_by = qt->request.group_by[g].group_by; + + if(group_by == RRDR_GROUP_BY_NONE) + break; + + memset(entries, 0, qt->query.used * sizeof(struct rrdr_group_by_entry)); + dictionary_flush(groups); + added = 0; + + size_t hidden_dimensions = 0; + bool final_grouping = (g == MAX_QUERY_GROUP_BY_PASSES - 1 || qt->request.group_by[g + 1].group_by == RRDR_GROUP_BY_NONE) ? true : false; + + if (final_grouping && (options & RRDR_OPTION_GROUP_BY_LABELS)) + label_keys = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE, NULL, 0); + + QUERY_INSTANCE *last_qi = NULL; + size_t priority = 0; + time_t update_every_max = 0; + for (size_t d = 0; d < qt->query.used; d++) { + QUERY_METRIC *qm = query_metric(qt, d); + QUERY_DIMENSION *qd = query_dimension(qt, qm->link.query_dimension_id); + QUERY_INSTANCE *qi = query_instance(qt, qm->link.query_instance_id); + QUERY_CONTEXT *qc = query_context(qt, qm->link.query_context_id); + QUERY_NODE *qn = query_node(qt, qm->link.query_node_id); + + if (qi != last_qi) { + last_qi = qi; + + time_t update_every = rrdinstance_acquired_update_every(qi->ria); + if (update_every > update_every_max) + update_every_max = update_every; + } + + priority = qd->priority; + + if(qm->status & RRDR_DIMENSION_HIDDEN) + hidden_dimensions++; + + // -------------------------------------------------------------------- + // generate the group by key + + query_group_by_make_dimension_key(key, group_by, g, qt, qn, qc, qi, qd, qm, query_has_percentage_of_instance); + + // lookup the key in the dictionary + + int pos = -1; + int *set = dictionary_set(groups, buffer_tostring(key), &pos, sizeof(pos)); + if (*set == -1) { + // the key just added to the dictionary + + *set = pos = added++; + + // ---------------------------------------------------------------- + // generate the dimension id + + query_group_by_make_dimension_id(key, group_by, g, qt, qn, qc, qi, qd, qm, query_has_percentage_of_instance); + entries[pos].id = string_strdupz(buffer_tostring(key)); + + // ---------------------------------------------------------------- + // generate the dimension name + + query_group_by_make_dimension_name(key, group_by, g, qt, qn, qc, qi, qd, qm, query_has_percentage_of_instance); + entries[pos].name = string_strdupz(buffer_tostring(key)); + + // add the rest of the info + entries[pos].units = rrdinstance_acquired_units_dup(qi->ria); + entries[pos].priority = priority; + + if (label_keys) { + entries[pos].dl = dictionary_create_advanced( + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_FIXED_SIZE | DICT_OPTION_DONT_OVERWRITE_VALUE, + NULL, sizeof(struct group_by_label_key)); + dictionary_register_insert_callback(entries[pos].dl, group_by_label_key_insert_cb, label_keys); + dictionary_register_delete_callback(entries[pos].dl, group_by_label_key_delete_cb, label_keys); + } + } else { + // the key found in the dictionary + pos = *set; + } + + entries[pos].count++; + + if (unlikely(priority < entries[pos].priority)) + entries[pos].priority = priority; + + if(g > 0) + last_r->dgbs[qm->grouped_as.slot] = pos; + else + qm->grouped_as.first_slot = pos; + + qm->grouped_as.slot = pos; + qm->grouped_as.id = entries[pos].id; + qm->grouped_as.name = entries[pos].name; + qm->grouped_as.units = entries[pos].units; + + // copy the dimension flags decided by the query target + // we need this, because if a dimension is explicitly selected + // the query target adds to it the non-zero flag + qm->status |= RRDR_DIMENSION_GROUPED; + + if(query_has_percentage_of_instance) + // when the query has percentage of instance + // there will be no hidden dimensions in the final query + // so we have to remove the hidden flag from all dimensions + entries[pos].od |= qm->status & ~RRDR_DIMENSION_HIDDEN; + else + entries[pos].od |= qm->status; + + if (entries[pos].dl) + rrdlabels_walkthrough_read(rrdinstance_acquired_labels(qi->ria), + rrdlabels_traversal_cb_to_group_by_label_key, entries[pos].dl); + } + + RRDR *r = rrdr_create(owa, qt, added, qt->window.points); + if (!r) { + internal_error(true, + "QUERY: cannot create group by RRDR for %s, after=%ld, before=%ld, dimensions=%d, points=%zu", + qt->id, qt->window.after, qt->window.before, added, qt->window.points); + goto cleanup; + } + + bool hidden_dimension_on_percentage_of_instance = hidden_dimensions && (group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE); + + // prevent double cleanup in case of error + added = 0; + + if(!last_r) + first_r = last_r = r; + else + last_r->group_by.r = r; + + last_r = r; + + rrd2rrdr_set_timestamps(r); + r->dp = onewayalloc_callocz(owa, r->d, sizeof(*r->dp)); + r->dview = onewayalloc_callocz(owa, r->d, sizeof(*r->dview)); + r->dgbc = onewayalloc_callocz(owa, r->d, sizeof(*r->dgbc)); + r->gbc = onewayalloc_callocz(owa, r->n * r->d, sizeof(*r->gbc)); + r->dqp = onewayalloc_callocz(owa, r->d, sizeof(STORAGE_POINT)); + + if(hidden_dimension_on_percentage_of_instance) + // this is where we are going to group the hidden dimensions + r->vh = onewayalloc_mallocz(owa, r->n * r->d * sizeof(*r->vh)); + + if(!final_grouping) + // this is where we are going to store the slot in the next RRDR + // that we are going to group by the dimension of this RRDR + r->dgbs = onewayalloc_callocz(owa, r->d, sizeof(*r->dgbs)); + + if (label_keys) { + r->dl = onewayalloc_callocz(owa, r->d, sizeof(DICTIONARY *)); + r->label_keys = label_keys; + label_keys = NULL; + } + + // zero r (dimension options, names, and ids) + // this is required, because group-by may lead to empty dimensions + for (size_t d = 0; d < r->d; d++) { + r->di[d] = entries[d].id; + r->dn[d] = entries[d].name; + + r->od[d] = entries[d].od; + r->du[d] = entries[d].units; + r->dp[d] = entries[d].priority; + r->dgbc[d] = entries[d].count; + + if (r->dl) + r->dl[d] = entries[d].dl; + } + + // initialize partial trimming + r->partial_data_trimming.max_update_every = update_every_max; + r->partial_data_trimming.expected_after = + (!(qt->window.options & RRDR_OPTION_RETURN_RAW) && + qt->window.before >= qt->window.now - update_every_max) ? + qt->window.before - update_every_max : + qt->window.before; + r->partial_data_trimming.trimmed_after = qt->window.before; + + // make all values empty + for (size_t i = 0; i != r->n; i++) { + NETDATA_DOUBLE *cn = &r->v[i * r->d]; + RRDR_VALUE_FLAGS *co = &r->o[i * r->d]; + NETDATA_DOUBLE *ar = &r->ar[i * r->d]; + NETDATA_DOUBLE *vh = r->vh ? &r->vh[i * r->d] : NULL; + + for (size_t d = 0; d < r->d; d++) { + cn[d] = NAN; + ar[d] = 0.0; + co[d] = RRDR_VALUE_EMPTY; + + if(vh) + *vh = NAN; + } + } + } + + if(!first_r || !last_r) + goto cleanup; + + r_tmp = rrdr_create(owa, qt, 1, qt->window.points); + if (!r_tmp) { + internal_error(true, + "QUERY: cannot create group by temporary RRDR for %s, after=%ld, before=%ld, dimensions=%d, points=%zu", + qt->id, qt->window.after, qt->window.before, 1, qt->window.points); + goto cleanup; + } + rrd2rrdr_set_timestamps(r_tmp); + r_tmp->group_by.r = first_r; + +cleanup: + if(!first_r || !last_r || !r_tmp) { + if(r_tmp) { + r_tmp->group_by.r = NULL; + rrdr_free(owa, r_tmp); + } + + if(first_r) { + RRDR *r = first_r; + while (r) { + r_tmp = r->group_by.r; + r->group_by.r = NULL; + rrdr_free(owa, r); + r = r_tmp; + } + } + + if(entries && added) { + for (int d = 0; d < added; d++) { + string_freez(entries[d].id); + string_freez(entries[d].name); + string_freez(entries[d].units); + dictionary_destroy(entries[d].dl); + } + } + dictionary_destroy(label_keys); + + first_r = last_r = r_tmp = NULL; + } + + buffer_free(key); + onewayalloc_freez(owa, entries); + dictionary_destroy(groups); + + return r_tmp; +} + +static void rrd2rrdr_group_by_add_metric(RRDR *r_dst, size_t d_dst, RRDR *r_tmp, size_t d_tmp, + RRDR_GROUP_BY_FUNCTION group_by_aggregate_function, + STORAGE_POINT *query_points, size_t pass __maybe_unused) { + if(!r_tmp || r_dst == r_tmp || !(r_tmp->od[d_tmp] & RRDR_DIMENSION_QUERIED)) + return; + + internal_fatal(r_dst->n != r_tmp->n, "QUERY: group-by source and destination do not have the same number of rows"); + internal_fatal(d_dst >= r_dst->d, "QUERY: group-by destination dimension number exceeds destination RRDR size"); + internal_fatal(d_tmp >= r_tmp->d, "QUERY: group-by source dimension number exceeds source RRDR size"); + internal_fatal(!r_dst->dqp, "QUERY: group-by destination is not properly prepared (missing dqp array)"); + internal_fatal(!r_dst->gbc, "QUERY: group-by destination is not properly prepared (missing gbc array)"); + + bool hidden_dimension_on_percentage_of_instance = (r_tmp->od[d_tmp] & RRDR_DIMENSION_HIDDEN) && r_dst->vh; + + if(!hidden_dimension_on_percentage_of_instance) { + r_dst->od[d_dst] |= r_tmp->od[d_tmp]; + storage_point_merge_to(r_dst->dqp[d_dst], *query_points); + } + + // do the group_by + for(size_t i = 0; i != rrdr_rows(r_tmp) ; i++) { + + size_t idx_tmp = i * r_tmp->d + d_tmp; + NETDATA_DOUBLE n_tmp = r_tmp->v[ idx_tmp ]; + RRDR_VALUE_FLAGS o_tmp = r_tmp->o[ idx_tmp ]; + NETDATA_DOUBLE ar_tmp = r_tmp->ar[ idx_tmp ]; + + if(o_tmp & RRDR_VALUE_EMPTY) + continue; + + size_t idx_dst = i * r_dst->d + d_dst; + NETDATA_DOUBLE *cn = (hidden_dimension_on_percentage_of_instance) ? &r_dst->vh[ idx_dst ] : &r_dst->v[ idx_dst ]; + RRDR_VALUE_FLAGS *co = &r_dst->o[ idx_dst ]; + NETDATA_DOUBLE *ar = &r_dst->ar[ idx_dst ]; + uint32_t *gbc = &r_dst->gbc[ idx_dst ]; + + switch(group_by_aggregate_function) { + default: + case RRDR_GROUP_BY_FUNCTION_AVERAGE: + case RRDR_GROUP_BY_FUNCTION_SUM: + if(isnan(*cn)) + *cn = n_tmp; + else + *cn += n_tmp; + break; + + case RRDR_GROUP_BY_FUNCTION_MIN: + if(isnan(*cn) || n_tmp < *cn) + *cn = n_tmp; + break; + + case RRDR_GROUP_BY_FUNCTION_MAX: + if(isnan(*cn) || n_tmp > *cn) + *cn = n_tmp; + break; + } + + if(!hidden_dimension_on_percentage_of_instance) { + *co &= ~RRDR_VALUE_EMPTY; + *co |= (o_tmp & (RRDR_VALUE_RESET | RRDR_VALUE_PARTIAL)); + *ar += ar_tmp; + (*gbc)++; + } + } +} + +static void rrdr2rrdr_group_by_partial_trimming(RRDR *r) { + time_t trimmable_after = r->partial_data_trimming.expected_after; + + // find the point just before the trimmable ones + ssize_t i = (ssize_t)r->n - 1; + for( ; i >= 0 ;i--) { + if (r->t[i] < trimmable_after) + break; + } + + if(unlikely(i < 0)) + return; + + size_t last_row_gbc = 0; + for (; i < (ssize_t)r->n; i++) { + size_t row_gbc = 0; + for (size_t d = 0; d < r->d; d++) { + if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED))) + continue; + + row_gbc += r->gbc[ i * r->d + d ]; + } + + if (unlikely(r->t[i] >= trimmable_after && row_gbc < last_row_gbc)) { + // discard the rest of the points + r->partial_data_trimming.trimmed_after = r->t[i]; + r->rows = i; + break; + } + else + last_row_gbc = row_gbc; + } +} + +static void rrdr2rrdr_group_by_calculate_percentage_of_instance(RRDR *r) { + if(!r->vh) + return; + + for(size_t i = 0; i < r->n ;i++) { + NETDATA_DOUBLE *cn = &r->v[ i * r->d ]; + NETDATA_DOUBLE *ch = &r->vh[ i * r->d ]; + + for(size_t d = 0; d < r->d ;d++) { + NETDATA_DOUBLE n = cn[d]; + NETDATA_DOUBLE h = ch[d]; + + if(isnan(n)) + cn[d] = 0.0; + + else if(isnan(h)) + cn[d] = 100.0; + + else + cn[d] = n * 100.0 / (n + h); + } + } +} + +static void rrd2rrdr_convert_to_percentage(RRDR *r) { + size_t global_min_max_values = 0; + NETDATA_DOUBLE global_min = NAN, global_max = NAN; + + for(size_t i = 0; i != r->n ;i++) { + NETDATA_DOUBLE *cn = &r->v[ i * r->d ]; + RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ]; + + NETDATA_DOUBLE total = 0; + for (size_t d = 0; d < r->d; d++) { + if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED))) + continue; + + if(co[d] & RRDR_VALUE_EMPTY) + continue; + + total += cn[d]; + } + + if(total == 0.0) + total = 1.0; + + for (size_t d = 0; d < r->d; d++) { + if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED))) + continue; + + if(co[d] & RRDR_VALUE_EMPTY) + continue; + + NETDATA_DOUBLE n = cn[d]; + n = cn[d] = n * 100.0 / total; + + if(unlikely(!global_min_max_values++)) + global_min = global_max = n; + else { + if(n < global_min) + global_min = n; + if(n > global_max) + global_max = n; + } + } + } + + r->view.min = global_min; + r->view.max = global_max; + + if(!r->dview) + // v1 query + return; + + // v2 query + + for (size_t d = 0; d < r->d; d++) { + if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED))) + continue; + + size_t count = 0; + NETDATA_DOUBLE min = 0.0, max = 0.0, sum = 0.0, ars = 0.0; + for(size_t i = 0; i != r->rows ;i++) { // we use r->rows to respect trimming + size_t idx = i * r->d + d; + + RRDR_VALUE_FLAGS o = r->o[ idx ]; + + if (o & RRDR_VALUE_EMPTY) + continue; + + NETDATA_DOUBLE ar = r->ar[ idx ]; + ars += ar; + + NETDATA_DOUBLE n = r->v[ idx ]; + sum += n; + + if(!count++) + min = max = n; + else { + if(n < min) + min = n; + if(n > max) + max = n; + } + } + + r->dview[d] = (STORAGE_POINT) { + .sum = sum, + .count = count, + .min = min, + .max = max, + .anomaly_count = (size_t)(ars * (NETDATA_DOUBLE)count), + }; + } +} + +static RRDR *rrd2rrdr_group_by_finalize(RRDR *r_tmp) { + QUERY_TARGET *qt = r_tmp->internal.qt; + RRDR_OPTIONS options = qt->window.options; + + if(!r_tmp->group_by.r) { + // v1 query + if(options & RRDR_OPTION_PERCENTAGE) + rrd2rrdr_convert_to_percentage(r_tmp); + return r_tmp; + } + // v2 query + + // do the additional passes on RRDRs + RRDR *last_r = r_tmp->group_by.r; + rrdr2rrdr_group_by_calculate_percentage_of_instance(last_r); + + RRDR *r = last_r->group_by.r; + size_t pass = 0; + while(r) { + pass++; + for(size_t d = 0; d < last_r->d ;d++) { + rrd2rrdr_group_by_add_metric(r, last_r->dgbs[d], last_r, d, + qt->request.group_by[pass].aggregation, + &last_r->dqp[d], pass); + } + rrdr2rrdr_group_by_calculate_percentage_of_instance(r); + + last_r = r; + r = last_r->group_by.r; + } + + // free all RRDRs except the last one + r = r_tmp; + while(r != last_r) { + r_tmp = r->group_by.r; + r->group_by.r = NULL; + rrdr_free(r->internal.owa, r); + r = r_tmp; + } + r = last_r; + + // find the final aggregation + RRDR_GROUP_BY_FUNCTION aggregation = qt->request.group_by[0].aggregation; + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) + if(qt->request.group_by[g].group_by != RRDR_GROUP_BY_NONE) + aggregation = qt->request.group_by[g].aggregation; + + if(!(options & RRDR_OPTION_RETURN_RAW) && r->partial_data_trimming.expected_after < qt->window.before) + rrdr2rrdr_group_by_partial_trimming(r); + + // apply averaging, remove RRDR_VALUE_EMPTY, find the non-zero dimensions, min and max + size_t global_min_max_values = 0; + size_t dimensions_nonzero = 0; + NETDATA_DOUBLE global_min = NAN, global_max = NAN; + for (size_t d = 0; d < r->d; d++) { + if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED))) + continue; + + size_t points_nonzero = 0; + NETDATA_DOUBLE min = 0, max = 0, sum = 0, ars = 0; + size_t count = 0; + + for(size_t i = 0; i != r->n ;i++) { + size_t idx = i * r->d + d; + + NETDATA_DOUBLE *cn = &r->v[ idx ]; + RRDR_VALUE_FLAGS *co = &r->o[ idx ]; + NETDATA_DOUBLE *ar = &r->ar[ idx ]; + uint32_t gbc = r->gbc[ idx ]; + + if(likely(gbc)) { + *co &= ~RRDR_VALUE_EMPTY; + + if(gbc != r->dgbc[d]) + *co |= RRDR_VALUE_PARTIAL; + + NETDATA_DOUBLE n; + + sum += *cn; + ars += *ar; + + if(aggregation == RRDR_GROUP_BY_FUNCTION_AVERAGE && !query_target_aggregatable(qt)) + n = (*cn /= gbc); + else + n = *cn; + + if(!query_target_aggregatable(qt)) + *ar /= gbc; + + if(islessgreater(n, 0.0)) + points_nonzero++; + + if(unlikely(!count)) + min = max = n; + else { + if(n < min) + min = n; + + if(n > max) + max = n; + } + + if(unlikely(!global_min_max_values++)) + global_min = global_max = n; + else { + if(n < global_min) + global_min = n; + + if(n > global_max) + global_max = n; + } + + count += gbc; + } + } + + if(points_nonzero) { + r->od[d] |= RRDR_DIMENSION_NONZERO; + dimensions_nonzero++; + } + + r->dview[d] = (STORAGE_POINT) { + .sum = sum, + .count = count, + .min = min, + .max = max, + .anomaly_count = (size_t)(ars * RRDR_DVIEW_ANOMALY_COUNT_MULTIPLIER / 100.0), + }; + } + + r->view.min = global_min; + r->view.max = global_max; + + if(!dimensions_nonzero && (qt->window.options & RRDR_OPTION_NONZERO)) { + // all dimensions are zero + // remove the nonzero option + qt->window.options &= ~RRDR_OPTION_NONZERO; + } + + if(options & RRDR_OPTION_PERCENTAGE && !(options & RRDR_OPTION_RETURN_RAW)) + rrd2rrdr_convert_to_percentage(r); + + // update query instance counts in query host and query context + { + size_t h = 0, c = 0, i = 0; + for(; h < qt->nodes.used ; h++) { + QUERY_NODE *qn = &qt->nodes.array[h]; + + for(; c < qt->contexts.used ;c++) { + QUERY_CONTEXT *qc = &qt->contexts.array[c]; + + if(!rrdcontext_acquired_belongs_to_host(qc->rca, qn->rrdhost)) + break; + + for(; i < qt->instances.used ;i++) { + QUERY_INSTANCE *qi = &qt->instances.array[i]; + + if(!rrdinstance_acquired_belongs_to_context(qi->ria, qc->rca)) + break; + + if(qi->metrics.queried) { + qc->instances.queried++; + qn->instances.queried++; + } + else if(qi->metrics.failed) { + qc->instances.failed++; + qn->instances.failed++; + } + } + } + } + } + + return r; +} + +// ---------------------------------------------------------------------------- +// query entry point + +RRDR *rrd2rrdr_legacy( + ONEWAYALLOC *owa, + RRDSET *st, size_t points, time_t after, time_t before, + RRDR_TIME_GROUPING group_method, time_t resampling_time, RRDR_OPTIONS options, const char *dimensions, + const char *group_options, time_t timeout_ms, size_t tier, QUERY_SOURCE query_source, + STORAGE_PRIORITY priority) { + + QUERY_TARGET_REQUEST qtr = { + .version = 1, + .st = st, + .points = points, + .after = after, + .before = before, + .time_group_method = group_method, + .resampling_time = resampling_time, + .options = options, + .dimensions = dimensions, + .time_group_options = group_options, + .timeout_ms = timeout_ms, + .tier = tier, + .query_source = query_source, + .priority = priority, + }; + + QUERY_TARGET *qt = query_target_create(&qtr); + RRDR *r = rrd2rrdr(owa, qt); + if(!r) { + query_target_release(qt); + return NULL; + } + + r->internal.release_with_rrdr_qt = qt; + return r; +} + +RRDR *rrd2rrdr(ONEWAYALLOC *owa, QUERY_TARGET *qt) { + if(!qt || !owa) + return NULL; + + // qt.window members are the WANTED ones. + // qt.request members are the REQUESTED ones. + + RRDR *r_tmp = rrd2rrdr_group_by_initialize(owa, qt); + if(!r_tmp) + return NULL; + + // the RRDR we group-by at + RRDR *r = (r_tmp->group_by.r) ? r_tmp->group_by.r : r_tmp; + + // the final RRDR to return to callers + RRDR *last_r = r_tmp; + while(last_r->group_by.r) + last_r = last_r->group_by.r; + + if(qt->window.relative) + last_r->view.flags |= RRDR_RESULT_FLAG_RELATIVE; + else + last_r->view.flags |= RRDR_RESULT_FLAG_ABSOLUTE; + + // ------------------------------------------------------------------------- + // assign the processor functions + rrdr_set_grouping_function(r_tmp, qt->window.time_group_method); + + // allocate any memory required by the grouping method + r_tmp->time_grouping.create(r_tmp, qt->window.time_group_options); + + // ------------------------------------------------------------------------- + // do the work for each dimension + + time_t max_after = 0, min_before = 0; + size_t max_rows = 0; + + long dimensions_used = 0, dimensions_nonzero = 0; + size_t last_db_points_read = 0; + size_t last_result_points_generated = 0; + + internal_fatal(released_ops, "QUERY: released_ops should be NULL when the query starts"); + + QUERY_ENGINE_OPS **ops = NULL; + if(qt->query.used) + ops = onewayalloc_callocz(owa, qt->query.used, sizeof(QUERY_ENGINE_OPS *)); + + size_t capacity = libuv_worker_threads * 10; + 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_ops_prep(r_tmp, queries_prepared); + queries_prepared++; + } + + QUERY_NODE *last_qn = NULL; + usec_t last_ut = now_monotonic_usec(); + usec_t last_qn_ut = last_ut; + + for(size_t d = 0; d < qt->query.used ; d++) { + QUERY_METRIC *qm = query_metric(qt, d); + QUERY_DIMENSION *qd = query_dimension(qt, qm->link.query_dimension_id); + QUERY_INSTANCE *qi = query_instance(qt, qm->link.query_instance_id); + QUERY_CONTEXT *qc = query_context(qt, qm->link.query_context_id); + QUERY_NODE *qn = query_node(qt, qm->link.query_node_id); + + usec_t now_ut = last_ut; + if(qn != last_qn) { + if(last_qn) + last_qn->duration_ut = now_ut - last_qn_ut; + + last_qn = qn; + last_qn_ut = now_ut; + } + + if(queries_prepared < qt->query.used) { + // preload another query + ops[queries_prepared] = rrd2rrdr_query_ops_prep(r_tmp, queries_prepared); + queries_prepared++; + } + + size_t dim_in_rrdr_tmp = (r_tmp != r) ? 0 : d; + + // set the query target dimension options to rrdr + r_tmp->od[dim_in_rrdr_tmp] = qm->status; + + // reset the grouping for the new dimension + r_tmp->time_grouping.reset(r_tmp); + + if(ops[d]) { + rrd2rrdr_query_execute(r_tmp, dim_in_rrdr_tmp, ops[d]); + r_tmp->od[dim_in_rrdr_tmp] |= RRDR_DIMENSION_QUERIED; + + now_ut = now_monotonic_usec(); + qm->duration_ut = now_ut - last_ut; + last_ut = now_ut; + + if(r_tmp != r) { + // copy back whatever got updated from the temporary r + + // the query updates RRDR_DIMENSION_NONZERO + qm->status = r_tmp->od[dim_in_rrdr_tmp]; + + // the query updates these + r->view.min = r_tmp->view.min; + r->view.max = r_tmp->view.max; + r->view.after = r_tmp->view.after; + r->view.before = r_tmp->view.before; + r->rows = r_tmp->rows; + + rrd2rrdr_group_by_add_metric(r, qm->grouped_as.first_slot, r_tmp, dim_in_rrdr_tmp, + qt->request.group_by[0].aggregation, &qm->query_points, 0); + } + + rrd2rrdr_query_ops_release(ops[d]); // reuse this ops allocation + ops[d] = NULL; + + qi->metrics.queried++; + qc->metrics.queried++; + qn->metrics.queried++; + + qd->status |= QUERY_STATUS_QUERIED; + qm->status |= RRDR_DIMENSION_QUERIED; + + if(qt->request.version >= 2) { + // we need to make the query points positive now + // since we will aggregate it across multiple dimensions + storage_point_make_positive(qm->query_points); + storage_point_merge_to(qi->query_points, qm->query_points); + storage_point_merge_to(qc->query_points, qm->query_points); + storage_point_merge_to(qn->query_points, qm->query_points); + storage_point_merge_to(qt->query_points, qm->query_points); + } + } + else { + qi->metrics.failed++; + qc->metrics.failed++; + qn->metrics.failed++; + + qd->status |= QUERY_STATUS_FAILED; + qm->status |= RRDR_DIMENSION_FAILED; + + continue; + } + + global_statistics_rrdr_query_completed( + 1, + r_tmp->stats.db_points_read - last_db_points_read, + r_tmp->stats.result_points_generated - last_result_points_generated, + qt->request.query_source); + + last_db_points_read = r_tmp->stats.db_points_read; + last_result_points_generated = r_tmp->stats.result_points_generated; + + if(qm->status & RRDR_DIMENSION_NONZERO) + dimensions_nonzero++; + + // verify all dimensions are aligned + if(unlikely(!dimensions_used)) { + min_before = r->view.before; + max_after = r->view.after; + max_rows = r->rows; + } + else { + if(r->view.after != max_after) { + internal_error(true, "QUERY: 'after' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu", + rrdinstance_acquired_id(qi->ria), (size_t)max_after, rrdmetric_acquired_id(qd->rma), (size_t)r->view.after); + + r->view.after = (r->view.after > max_after) ? r->view.after : max_after; + } + + if(r->view.before != min_before) { + internal_error(true, "QUERY: 'before' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu", + rrdinstance_acquired_id(qi->ria), (size_t)min_before, rrdmetric_acquired_id(qd->rma), (size_t)r->view.before); + + r->view.before = (r->view.before < min_before) ? r->view.before : min_before; + } + + if(r->rows != max_rows) { + internal_error(true, "QUERY: 'rows' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu", + rrdinstance_acquired_id(qi->ria), (size_t)max_rows, rrdmetric_acquired_id(qd->rma), (size_t)r->rows); + + r->rows = (r->rows > max_rows) ? r->rows : max_rows; + } } dimensions_used++; - if (qt->request.timeout && ((NETDATA_DOUBLE)dt_usec(&query_start_time, &query_current_time) / 1000.0) > (NETDATA_DOUBLE)qt->request.timeout) { + + bool cancel = false; + if (qt->request.interrupt_callback && qt->request.interrupt_callback(qt->request.interrupt_callback_data)) { + cancel = true; + log_access("QUERY INTERRUPTED"); + } + + if (qt->request.timeout_ms && ((NETDATA_DOUBLE)(now_ut - qt->timings.received_ut) / 1000.0) > (NETDATA_DOUBLE)qt->request.timeout_ms) { + cancel = true; 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; + (NETDATA_DOUBLE)(now_ut - qt->timings.received_ut) / 1000.0, (long long)qt->request.timeout_ms); + } - for(size_t i = c + 1; i < queries_prepared ; i++) { - if(ops[i]) + if(cancel) { + r->view.flags |= RRDR_RESULT_FLAG_CANCEL; + + for(size_t i = d + 1; i < queries_prepared ; i++) { + if(ops[i]) { query_planer_finalize_remaining_plans(ops[i]); + rrd2rrdr_query_ops_release(ops[i]); + ops[i] = NULL; + } } break; } } + // free all resources used by the grouping method + r_tmp->time_grouping.free(r_tmp); + + // get the final RRDR to send to the caller + r = rrd2rrdr_group_by_finalize(r_tmp); + #ifdef NETDATA_INTERNAL_CHECKS - if (dimensions_used) { + if (dimensions_used && !(r->view.flags & RRDR_RESULT_FLAG_CANCEL)) { if(r->internal.log) - rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, + rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.time_group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, qt->window.after, qt->request.after, qt->window.before, qt->request.before, qt->request.points, qt->window.points, /*after_slot, before_slot,*/ r->internal.log); if(r->rows != qt->window.points) - rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, + rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.time_group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, qt->window.after, qt->request.after, qt->window.before, qt->request.before, qt->request.points, qt->window.points, /*after_slot, before_slot,*/ "got 'points' is not wanted 'points'"); - if(qt->window.aligned && (r->before % (qt->window.group * qt->window.query_granularity)) != 0) - rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, - qt->window.after, qt->request.after, qt->window.before,qt->request.before, + if(qt->window.aligned && (r->view.before % query_view_update_every(qt)) != 0) + rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.time_group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, + qt->window.after, qt->request.after, qt->window.before, qt->request.before, qt->request.points, qt->window.points, /*after_slot, before_slot,*/ "'before' is not aligned but alignment is required"); @@ -2362,21 +3737,21 @@ RRDR *rrd2rrdr(ONEWAYALLOC *owa, QUERY_TARGET *qt) { //if(qt->window.aligned && (r->after % group) != 0) // rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, qt->window.after, after_requested, before_wanted, before_requested, points_requested, points_wanted, after_slot, before_slot, "'after' is not aligned but alignment is required"); - if(r->before != qt->window.before) - rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, + if(r->view.before != qt->window.before) + rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.time_group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, qt->window.after, qt->request.after, qt->window.before, qt->request.before, qt->request.points, qt->window.points, /*after_slot, before_slot,*/ "chart is not aligned to requested 'before'"); - if(r->before != qt->window.before) - rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, + if(r->view.before != qt->window.before) + rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.time_group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, qt->window.after, qt->request.after, qt->window.before, qt->request.before, qt->request.points, qt->window.points, /*after_slot, before_slot,*/ "got 'before' is not wanted 'before'"); // reported 'after' varies, depending on group - if(r->after != qt->window.after) - rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, + if(r->view.after != qt->window.after) + rrd2rrdr_log_request_response_metadata(r, qt->window.options, qt->window.time_group_method, qt->window.aligned, qt->window.group, qt->request.resampling_time, qt->window.resampling_group, qt->window.after, qt->request.after, qt->window.before, qt->request.before, qt->request.points, qt->window.points, /*after_slot, before_slot,*/ "got 'after' is not wanted 'after'"); @@ -2384,26 +3759,21 @@ RRDR *rrd2rrdr(ONEWAYALLOC *owa, QUERY_TARGET *qt) { } #endif - // free all resources used by the grouping method - r->internal.grouping_free(r); + // free the query pipelining ops + for(size_t d = 0; d < qt->query.used ; d++) { + rrd2rrdr_query_ops_release(ops[d]); + ops[d] = NULL; + } + rrd2rrdr_query_ops_freeall(r); + internal_fatal(released_ops, "QUERY: released_ops should be NULL when the query ends"); + + onewayalloc_freez(owa, ops); - if(likely(dimensions_used)) { + if(likely(dimensions_used && (qt->window.options & RRDR_OPTION_NONZERO) && !dimensions_nonzero)) // when all the dimensions are zero, we should return all of them - if (unlikely((qt->window.options & RRDR_OPTION_NONZERO) && !dimensions_nonzero && - !(r->result_options & RRDR_RESULT_OPTION_CANCEL))) { - // all the dimensions are zero - // mark them as NONZERO to send them all - for (size_t c = 0, max = qt->query.used; c < max; c++) { - if (unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue; - if (unlikely(!(r->od[c] & RRDR_DIMENSION_QUERIED))) continue; - r->od[c] |= RRDR_DIMENSION_NONZERO; - } - } + qt->window.options &= ~RRDR_OPTION_NONZERO; - return r; - } + qt->timings.executed_ut = now_monotonic_usec(); - // we couldn't query any dimension - rrdr_free(owa, r); - return NULL; + return r; } diff --git a/web/api/queries/query.h b/web/api/queries/query.h index ebad5a1f..e6fdcfbe 100644 --- a/web/api/queries/query.h +++ b/web/api/queries/query.h @@ -7,7 +7,7 @@ extern "C" { #endif -typedef enum rrdr_grouping { +typedef enum rrdr_time_grouping { RRDR_GROUPING_UNDEFINED = 0, RRDR_GROUPING_AVERAGE, RRDR_GROUPING_MIN, @@ -17,7 +17,7 @@ typedef enum rrdr_grouping { RRDR_GROUPING_TRIMMED_MEAN1, RRDR_GROUPING_TRIMMED_MEAN2, RRDR_GROUPING_TRIMMED_MEAN3, - RRDR_GROUPING_TRIMMED_MEAN5, + RRDR_GROUPING_TRIMMED_MEAN, RRDR_GROUPING_TRIMMED_MEAN10, RRDR_GROUPING_TRIMMED_MEAN15, RRDR_GROUPING_TRIMMED_MEAN20, @@ -36,7 +36,7 @@ typedef enum rrdr_grouping { RRDR_GROUPING_PERCENTILE75, RRDR_GROUPING_PERCENTILE80, RRDR_GROUPING_PERCENTILE90, - RRDR_GROUPING_PERCENTILE95, + RRDR_GROUPING_PERCENTILE, RRDR_GROUPING_PERCENTILE97, RRDR_GROUPING_PERCENTILE98, RRDR_GROUPING_PERCENTILE99, @@ -45,12 +45,50 @@ typedef enum rrdr_grouping { RRDR_GROUPING_SES, RRDR_GROUPING_DES, RRDR_GROUPING_COUNTIF, -} RRDR_GROUPING; +} RRDR_TIME_GROUPING; -const char *group_method2string(RRDR_GROUPING group); -void web_client_api_v1_init_grouping(void); -RRDR_GROUPING web_client_api_request_v1_data_group(const char *name, RRDR_GROUPING def); -const char *web_client_api_request_v1_data_group_to_string(RRDR_GROUPING group); +const char *time_grouping_method2string(RRDR_TIME_GROUPING group); +void time_grouping_init(void); +RRDR_TIME_GROUPING time_grouping_parse(const char *name, RRDR_TIME_GROUPING def); +const char *time_grouping_tostring(RRDR_TIME_GROUPING group); + +typedef enum rrdr_group_by { + RRDR_GROUP_BY_NONE = 0, + RRDR_GROUP_BY_SELECTED = (1 << 0), + RRDR_GROUP_BY_DIMENSION = (1 << 1), + RRDR_GROUP_BY_INSTANCE = (1 << 2), + RRDR_GROUP_BY_LABEL = (1 << 3), + RRDR_GROUP_BY_NODE = (1 << 4), + RRDR_GROUP_BY_CONTEXT = (1 << 5), + RRDR_GROUP_BY_UNITS = (1 << 6), + RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE = (1 << 7), +} RRDR_GROUP_BY; + +#define SUPPORTED_GROUP_BY_METHODS (\ + RRDR_GROUP_BY_SELECTED |\ + RRDR_GROUP_BY_DIMENSION |\ + RRDR_GROUP_BY_INSTANCE |\ + RRDR_GROUP_BY_LABEL |\ + RRDR_GROUP_BY_NODE |\ + RRDR_GROUP_BY_CONTEXT |\ + RRDR_GROUP_BY_UNITS |\ + RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE \ +) + +struct web_buffer; + +RRDR_GROUP_BY group_by_parse(char *s); +void buffer_json_group_by_to_array(struct web_buffer *wb, RRDR_GROUP_BY group_by); + +typedef enum rrdr_group_by_function { + RRDR_GROUP_BY_FUNCTION_AVERAGE = 0, + RRDR_GROUP_BY_FUNCTION_MIN, + RRDR_GROUP_BY_FUNCTION_MAX, + RRDR_GROUP_BY_FUNCTION_SUM, +} RRDR_GROUP_BY_FUNCTION; + +RRDR_GROUP_BY_FUNCTION group_by_aggregate_function_parse(const char *s); +const char *group_by_aggregate_function_to_string(RRDR_GROUP_BY_FUNCTION group_by_function); #ifdef __cplusplus } diff --git a/web/api/queries/rrdr.c b/web/api/queries/rrdr.c index 676224c9..2a001689 100644 --- a/web/api/queries/rrdr.c +++ b/web/api/queries/rrdr.c @@ -61,41 +61,86 @@ static void rrdr_dump(RRDR *r) inline void rrdr_free(ONEWAYALLOC *owa, RRDR *r) { if(unlikely(!r)) return; - query_target_release(r->internal.qt); + for(size_t d = 0; d < r->d ;d++) { + string_freez(r->di[d]); + string_freez(r->dn[d]); + string_freez(r->du[d]); + } + + query_target_release(r->internal.release_with_rrdr_qt); + onewayalloc_freez(owa, r->t); onewayalloc_freez(owa, r->v); + onewayalloc_freez(owa, r->vh); onewayalloc_freez(owa, r->o); onewayalloc_freez(owa, r->od); + onewayalloc_freez(owa, r->di); + onewayalloc_freez(owa, r->dn); + onewayalloc_freez(owa, r->du); + onewayalloc_freez(owa, r->dp); + onewayalloc_freez(owa, r->dview); + onewayalloc_freez(owa, r->dqp); onewayalloc_freez(owa, r->ar); + onewayalloc_freez(owa, r->gbc); + onewayalloc_freez(owa, r->dgbc); + onewayalloc_freez(owa, r->dgbs); + + if(r->dl) { + for(size_t d = 0; d < r->d ;d++) + dictionary_destroy(r->dl[d]); + + onewayalloc_freez(owa, r->dl); + } + + dictionary_destroy(r->label_keys); + + if(r->group_by.r) { + // prevent accidental infinite recursion + r->group_by.r->group_by.r = NULL; + + // do not release qt twice + r->group_by.r->internal.qt = NULL; + + rrdr_free(owa, r->group_by.r); + } + onewayalloc_freez(owa, r); } -RRDR *rrdr_create(ONEWAYALLOC *owa, QUERY_TARGET *qt) { - if(unlikely(!qt || !qt->query.used || !qt->window.points)) +RRDR *rrdr_create(ONEWAYALLOC *owa, QUERY_TARGET *qt, size_t dimensions, size_t points) { + if(unlikely(!qt)) return NULL; - size_t dimensions = qt->query.used; - size_t points = qt->window.points; - // create the rrdr RRDR *r = onewayalloc_callocz(owa, 1, sizeof(RRDR)); r->internal.owa = owa; r->internal.qt = qt; - r->before = qt->window.before; - r->after = qt->window.after; - r->internal.points_wanted = qt->window.points; + r->view.before = qt->window.before; + r->view.after = qt->window.after; + r->time_grouping.points_wanted = points; r->d = (int)dimensions; r->n = (int)points; - r->t = onewayalloc_callocz(owa, points, sizeof(time_t)); - r->v = onewayalloc_mallocz(owa, points * dimensions * sizeof(NETDATA_DOUBLE)); - r->o = onewayalloc_mallocz(owa, points * dimensions * sizeof(RRDR_VALUE_FLAGS)); - r->ar = onewayalloc_mallocz(owa, points * dimensions * sizeof(NETDATA_DOUBLE)); - r->od = onewayalloc_mallocz(owa, dimensions * sizeof(RRDR_DIMENSION_FLAGS)); + if(points && dimensions) { + r->v = onewayalloc_mallocz(owa, points * dimensions * sizeof(NETDATA_DOUBLE)); + r->o = onewayalloc_mallocz(owa, points * dimensions * sizeof(RRDR_VALUE_FLAGS)); + r->ar = onewayalloc_mallocz(owa, points * dimensions * sizeof(NETDATA_DOUBLE)); + } + + if(points) { + r->t = onewayalloc_callocz(owa, points, sizeof(time_t)); + } + + if(dimensions) { + r->od = onewayalloc_mallocz(owa, dimensions * sizeof(RRDR_DIMENSION_FLAGS)); + r->di = onewayalloc_callocz(owa, dimensions, sizeof(STRING *)); + r->dn = onewayalloc_callocz(owa, dimensions, sizeof(STRING *)); + r->du = onewayalloc_callocz(owa, dimensions, sizeof(STRING *)); + } - r->group = 1; - r->update_every = 1; + r->view.group = 1; + r->view.update_every = 1; return r; } diff --git a/web/api/queries/rrdr.h b/web/api/queries/rrdr.h index 2d982b13..c57be67f 100644 --- a/web/api/queries/rrdr.h +++ b/web/api/queries/rrdr.h @@ -18,111 +18,152 @@ typedef enum tier_query_fetch { } TIER_QUERY_FETCH; typedef enum rrdr_options { - RRDR_OPTION_NONZERO = 0x00000001, // don't output dimensions with just zero values - RRDR_OPTION_REVERSED = 0x00000002, // output the rows in reverse order (oldest to newest) - RRDR_OPTION_ABSOLUTE = 0x00000004, // values positive, for DATASOURCE_SSV before summing - RRDR_OPTION_MIN2MAX = 0x00000008, // when adding dimensions, use max - min, instead of sum - RRDR_OPTION_SECONDS = 0x00000010, // output seconds, instead of dates - RRDR_OPTION_MILLISECONDS = 0x00000020, // output milliseconds, instead of dates - RRDR_OPTION_NULL2ZERO = 0x00000040, // do not show nulls, convert them to zeros - RRDR_OPTION_OBJECTSROWS = 0x00000080, // each row of values should be an object, not an array - RRDR_OPTION_GOOGLE_JSON = 0x00000100, // comply with google JSON/JSONP specs - RRDR_OPTION_JSON_WRAP = 0x00000200, // wrap the response in a JSON header with info about the result - RRDR_OPTION_LABEL_QUOTES = 0x00000400, // in CSV output, wrap header labels in double quotes - RRDR_OPTION_PERCENTAGE = 0x00000800, // give values as percentage of total - RRDR_OPTION_NOT_ALIGNED = 0x00001000, // do not align charts for persistent timeframes - RRDR_OPTION_DISPLAY_ABS = 0x00002000, // for badges, display the absolute value, but calculate colors with sign - RRDR_OPTION_MATCH_IDS = 0x00004000, // when filtering dimensions, match only IDs - RRDR_OPTION_MATCH_NAMES = 0x00008000, // when filtering dimensions, match only names - RRDR_OPTION_NATURAL_POINTS = 0x00020000, // return the natural points of the database - RRDR_OPTION_VIRTUAL_POINTS = 0x00040000, // return virtual points - RRDR_OPTION_ANOMALY_BIT = 0x00080000, // Return the anomaly bit stored in each collected_number - 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_SHOW_PLAN = 0x01000000, // Return the query plan in jsonwrap + RRDR_OPTION_NONZERO = (1 << 0), // don't output dimensions with just zero values + RRDR_OPTION_REVERSED = (1 << 1), // output the rows in reverse order (oldest to newest) + RRDR_OPTION_ABSOLUTE = (1 << 2), // values positive, for DATASOURCE_SSV before summing + RRDR_OPTION_MIN2MAX = (1 << 3), // when adding dimensions, use max - min, instead of sum + RRDR_OPTION_SECONDS = (1 << 4), // output seconds, instead of dates + RRDR_OPTION_MILLISECONDS = (1 << 5), // output milliseconds, instead of dates + RRDR_OPTION_NULL2ZERO = (1 << 6), // do not show nulls, convert them to zeros + RRDR_OPTION_OBJECTSROWS = (1 << 7), // each row of values should be an object, not an array + RRDR_OPTION_GOOGLE_JSON = (1 << 8), // comply with google JSON/JSONP specs + RRDR_OPTION_JSON_WRAP = (1 << 9), // wrap the response in a JSON header with info about the result + RRDR_OPTION_LABEL_QUOTES = (1 << 10), // in CSV output, wrap header labels in double quotes + RRDR_OPTION_PERCENTAGE = (1 << 11), // give values as percentage of total + RRDR_OPTION_NOT_ALIGNED = (1 << 12), // do not align charts for persistent timeframes + RRDR_OPTION_DISPLAY_ABS = (1 << 13), // for badges, display the absolute value, but calculate colors with sign + RRDR_OPTION_MATCH_IDS = (1 << 14), // when filtering dimensions, match only IDs + RRDR_OPTION_MATCH_NAMES = (1 << 15), // when filtering dimensions, match only names + RRDR_OPTION_NATURAL_POINTS = (1 << 16), // return the natural points of the database + RRDR_OPTION_VIRTUAL_POINTS = (1 << 17), // return virtual points + RRDR_OPTION_ANOMALY_BIT = (1 << 18), // Return the anomaly bit stored in each collected_number + RRDR_OPTION_RETURN_RAW = (1 << 19), // Return raw data for aggregating across multiple nodes + RRDR_OPTION_RETURN_JWAR = (1 << 20), // Return anomaly rates in jsonwrap + RRDR_OPTION_SELECTED_TIER = (1 << 21), // Use the selected tier for the query + RRDR_OPTION_ALL_DIMENSIONS = (1 << 22), // Return the full dimensions list + RRDR_OPTION_SHOW_DETAILS = (1 << 23), // v2 returns detailed object tree + RRDR_OPTION_DEBUG = (1 << 24), // v2 returns request description + RRDR_OPTION_MINIFY = (1 << 25), // remove JSON spaces and newlines from JSON output + RRDR_OPTION_GROUP_BY_LABELS = (1 << 26), // v2 returns flattened labels per dimension of the chart // 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 - RRDR_OPTION_HEALTH_RSRVD1 = 0x80000000, // reserved for RRDCALC_OPTION_NO_CLEAR_NOTIFICATION + RRDR_OPTION_HEALTH_RSRVD1 = (1 << 30), // reserved for RRDCALC_OPTION_NO_CLEAR_NOTIFICATION + RRDR_OPTION_INTERNAL_AR = (1 << 31), // internal use only, to let the formatters know we want to render the anomaly rate } RRDR_OPTIONS; -typedef enum rrdr_value_flag { - RRDR_VALUE_NOTHING = 0x00, // no flag set (a good default) - RRDR_VALUE_EMPTY = 0x01, // the database value is empty - RRDR_VALUE_RESET = 0x02, // the database value is marked as reset (overflown) +typedef enum __attribute__ ((__packed__)) rrdr_value_flag { + + // IMPORTANT: + // THIS IS AN AGREED BIT MAP BETWEEN AGENT, CLOUD FRONT-END AND CLOUD BACK-END + // DO NOT CHANGE THE MAPPINGS ! + + RRDR_VALUE_NOTHING = 0, // no flag set (a good default) + RRDR_VALUE_EMPTY = (1 << 0), // the database value is empty + RRDR_VALUE_RESET = (1 << 1), // the database value is marked as reset (overflown) + RRDR_VALUE_PARTIAL = (1 << 2), // the database provides partial data about this point (used in group-by) } RRDR_VALUE_FLAGS; -typedef enum rrdr_dimension_flag { - RRDR_DIMENSION_DEFAULT = 0x00, - RRDR_DIMENSION_HIDDEN = 0x04, // the dimension is hidden (not to be presented to callers) - RRDR_DIMENSION_NONZERO = 0x08, // the dimension is non zero (contains non-zero values) - RRDR_DIMENSION_QUERIED = 0x10, // the dimension is selected for evaluation in this RRDR +typedef enum __attribute__ ((__packed__)) rrdr_dimension_flag { + RRDR_DIMENSION_DEFAULT = 0, + RRDR_DIMENSION_HIDDEN = (1 << 0), // the dimension is hidden (not to be presented to callers) + RRDR_DIMENSION_NONZERO = (1 << 1), // the dimension is non zero (contains non-zero values) + RRDR_DIMENSION_SELECTED = (1 << 2), // the dimension has been selected for query + RRDR_DIMENSION_QUERIED = (1 << 3), // the dimension has been queried + RRDR_DIMENSION_FAILED = (1 << 4), // the dimension failed to be queried + RRDR_DIMENSION_GROUPED = (1 << 5), // the dimension has been grouped in this RRDR } RRDR_DIMENSION_FLAGS; // RRDR result options -typedef enum rrdr_result_flags { - RRDR_RESULT_OPTION_ABSOLUTE = 0x00000001, // the query uses absolute time-frames - // (can be cached by browsers and proxies) - RRDR_RESULT_OPTION_RELATIVE = 0x00000002, // the query uses relative time-frames - // (should not to be cached by browsers and proxies) - RRDR_RESULT_OPTION_VARIABLE_STEP = 0x00000004, // the query uses variable-step time-frames - RRDR_RESULT_OPTION_CANCEL = 0x00000008, // the query needs to be cancelled -} RRDR_RESULT_OPTIONS; +typedef enum __attribute__ ((__packed__)) rrdr_result_flags { + RRDR_RESULT_FLAG_ABSOLUTE = (1 << 0), // the query uses absolute time-frames + // (can be cached by browsers and proxies) + RRDR_RESULT_FLAG_RELATIVE = (1 << 1), // the query uses relative time-frames + // (should not to be cached by browsers and proxies) + RRDR_RESULT_FLAG_CANCEL = (1 << 2), // the query needs to be cancelled +} RRDR_RESULT_FLAGS; -typedef struct rrdresult { - RRDR_RESULT_OPTIONS result_options; // RRDR_RESULT_OPTION_* +#define RRDR_DVIEW_ANOMALY_COUNT_MULTIPLIER 1000.0 +typedef struct rrdresult { size_t d; // the number of dimensions - size_t n; // the number of values in the arrays - size_t rows; // the number of rows used + size_t n; // the number of values in the arrays (number of points per dimension) + size_t rows; // the number of actual rows used RRDR_DIMENSION_FLAGS *od; // the options for the dimensions + STRING **di; // array of d dimension ids + STRING **dn; // array of d dimension names + STRING **du; // array of d dimension units + uint32_t *dgbs; // array of d dimension group by slots - NOT ALLOCATED when RRDR is created + uint32_t *dgbc; // array of d dimension group by counts - NOT ALLOCATED when RRDR is created + uint32_t *dp; // array of d dimension priority - NOT ALLOCATED when RRDR is created + DICTIONARY **dl; // array of d dimension labels - NOT ALLOCATED when RRDR is created + STORAGE_POINT *dqp; // array of d dimensions query points - NOT ALLOCATED when RRDR is created + STORAGE_POINT *dview; // array of d dimensions group by view - NOT ALLOCATED when RRDR is created + NETDATA_DOUBLE *vh; // array of n x d hidden values, while grouping - NOT ALLOCATED when RRDR is created + + DICTIONARY *label_keys; + time_t *t; // array of n timestamps NETDATA_DOUBLE *v; // array n x d values RRDR_VALUE_FLAGS *o; // array n x d options for each value returned NETDATA_DOUBLE *ar; // array n x d of anomaly rates (0 - 100) + uint32_t *gbc; // array n x d of group by count - NOT ALLOCATED when RRDR is created - size_t group; // how many collected values were grouped for each row - time_t update_every; // what is the suggested update frequency in seconds - - NETDATA_DOUBLE min; - NETDATA_DOUBLE max; + struct { + size_t group; // how many collected values were grouped for each row - NEEDED BY GROUPING FUNCTIONS + time_t after; + time_t before; + time_t update_every; // what is the suggested update frequency in seconds + NETDATA_DOUBLE min; + NETDATA_DOUBLE max; + RRDR_RESULT_FLAGS flags; // RRDR_RESULT_FLAG_* + } view; - time_t before; - time_t after; + struct { + size_t db_points_read; + size_t result_points_generated; + } stats; - // internal rrd2rrdr() members below this point struct { - ONEWAYALLOC *owa; // the allocator used - struct query_target *qt; // the QUERY_TARGET + void *data; // the internal data of the grouping function - RRDR_OPTIONS query_options; // RRDR_OPTION_* (as run by the query) + // grouping function pointers + RRDR_TIME_GROUPING add_flush; + void (*create)(struct rrdresult *r, const char *options); + void (*reset)(struct rrdresult *r); + void (*free)(struct rrdresult *r); + void (*add)(struct rrdresult *r, NETDATA_DOUBLE value); + NETDATA_DOUBLE (*flush)(struct rrdresult *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + + TIER_QUERY_FETCH tier_query_fetch; // which value to use from STORAGE_POINT size_t points_wanted; // used by SES and DES size_t resampling_group; // used by AVERAGE NETDATA_DOUBLE resampling_divisor; // used by AVERAGE + } time_grouping; - // grouping function pointers - void (*grouping_create)(struct rrdresult *r, const char *options); - void (*grouping_reset)(struct rrdresult *r); - void (*grouping_free)(struct rrdresult *r); - void (*grouping_add)(struct rrdresult *r, NETDATA_DOUBLE value); - NETDATA_DOUBLE (*grouping_flush)(struct rrdresult *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); + struct { + struct rrdresult *r; + } group_by; - TIER_QUERY_FETCH tier_query_fetch; // which value to use from STORAGE_POINT - void *grouping_data; // the internal data of the grouping function + struct { + time_t max_update_every; + time_t expected_after; + time_t trimmed_after; + } partial_data_trimming; + + struct { + ONEWAYALLOC *owa; // the allocator used + struct query_target *qt; // the QUERY_TARGET + size_t contexts; // temp needed between json_wrapper_begin2() and json_wrapper_end2() + size_t queries_count; // temp needed to know if a query is the first executed #ifdef NETDATA_INTERNAL_CHECKS const char *log; #endif - // statistics - size_t db_points_read; - size_t result_points_generated; - size_t tier_points_read[RRD_STORAGE_TIERS]; + struct query_target *release_with_rrdr_qt; } internal; } RRDR; @@ -130,7 +171,7 @@ typedef struct rrdresult { #include "database/rrd.h" void rrdr_free(ONEWAYALLOC *owa, RRDR *r); -RRDR *rrdr_create(ONEWAYALLOC *owa, struct query_target *qt); +RRDR *rrdr_create(ONEWAYALLOC *owa, struct query_target *qt, size_t dimensions, size_t points); #include "../web_api_v1.h" #include "web/api/queries/query.h" @@ -138,14 +179,14 @@ RRDR *rrdr_create(ONEWAYALLOC *owa, struct query_target *qt); 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, + RRDR_TIME_GROUPING group_method, time_t resampling_time, RRDR_OPTIONS options, const char *dimensions, + const char *group_options, time_t timeout_ms, 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); -bool rrdr_relative_window_to_absolute(time_t *after, time_t *before); +bool rrdr_relative_window_to_absolute(time_t *after, time_t *before, time_t *now_ptr); #ifdef __cplusplus } diff --git a/web/api/queries/ses/README.md b/web/api/queries/ses/README.md index b835b812..56634d36 100644 --- a/web/api/queries/ses/README.md +++ b/web/api/queries/ses/README.md @@ -1,6 +1,10 @@ # Single (or Simple) Exponential Smoothing (`ses`) diff --git a/web/api/queries/ses/ses.c b/web/api/queries/ses/ses.c index 5e94002c..39eb445a 100644 --- a/web/api/queries/ses/ses.c +++ b/web/api/queries/ses/ses.c @@ -6,85 +6,3 @@ // ---------------------------------------------------------------------------- // single exponential smoothing -struct grouping_ses { - NETDATA_DOUBLE alpha; - NETDATA_DOUBLE alpha_other; - NETDATA_DOUBLE level; - size_t count; -}; - -static size_t max_window_size = 15; - -void grouping_init_ses(void) { - long long ret = config_get_number(CONFIG_SECTION_WEB, "ses max window", (long long)max_window_size); - if(ret <= 1) { - config_set_number(CONFIG_SECTION_WEB, "ses max window", (long long)max_window_size); - } - else { - max_window_size = (size_t) ret; - } -} - -static inline NETDATA_DOUBLE window(RRDR *r, struct grouping_ses *g) { - (void)g; - - NETDATA_DOUBLE points; - if(r->group == 1) { - // provide a running DES - points = (NETDATA_DOUBLE)r->internal.points_wanted; - } - else { - // provide a SES with flush points - points = (NETDATA_DOUBLE)r->group; - } - - return (points > (NETDATA_DOUBLE)max_window_size) ? (NETDATA_DOUBLE)max_window_size : points; -} - -static inline void set_alpha(RRDR *r, struct grouping_ses *g) { - // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average - // A commonly used value for alpha is 2 / (N + 1) - g->alpha = 2.0 / (window(r, g) + 1.0); - g->alpha_other = 1.0 - g->alpha; -} - -void grouping_create_ses(RRDR *r, const char *options __maybe_unused) { - struct grouping_ses *g = (struct grouping_ses *)onewayalloc_callocz(r->internal.owa, 1, sizeof(struct grouping_ses)); - set_alpha(r, g); - g->level = 0.0; - r->internal.grouping_data = g; -} - -// resets when switches dimensions -// so, clear everything to restart -void grouping_reset_ses(RRDR *r) { - struct grouping_ses *g = (struct grouping_ses *)r->internal.grouping_data; - g->level = 0.0; - g->count = 0; -} - -void grouping_free_ses(RRDR *r) { - onewayalloc_freez(r->internal.owa, r->internal.grouping_data); - r->internal.grouping_data = NULL; -} - -void grouping_add_ses(RRDR *r, NETDATA_DOUBLE value) { - struct grouping_ses *g = (struct grouping_ses *)r->internal.grouping_data; - - if(unlikely(!g->count)) - g->level = value; - - g->level = g->alpha * value + g->alpha_other * g->level; - g->count++; -} - -NETDATA_DOUBLE grouping_flush_ses(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_ses *g = (struct grouping_ses *)r->internal.grouping_data; - - if(unlikely(!g->count || !netdata_double_isnumber(g->level))) { - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - return 0.0; - } - - return g->level; -} diff --git a/web/api/queries/ses/ses.h b/web/api/queries/ses/ses.h index 79b09fbd..de8645ff 100644 --- a/web/api/queries/ses/ses.h +++ b/web/api/queries/ses/ses.h @@ -6,12 +6,87 @@ #include "../query.h" #include "../rrdr.h" -void grouping_init_ses(void); +struct tg_ses { + NETDATA_DOUBLE alpha; + NETDATA_DOUBLE alpha_other; + NETDATA_DOUBLE level; + size_t count; +}; -void grouping_create_ses(RRDR *r, const char *options __maybe_unused); -void grouping_reset_ses(RRDR *r); -void grouping_free_ses(RRDR *r); -void grouping_add_ses(RRDR *r, NETDATA_DOUBLE value); -NETDATA_DOUBLE grouping_flush_ses(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +static size_t tg_ses_max_window_size = 15; + +static inline void tg_ses_init(void) { + long long ret = config_get_number(CONFIG_SECTION_WEB, "ses max tg_des_window", (long long)tg_ses_max_window_size); + if(ret <= 1) { + config_set_number(CONFIG_SECTION_WEB, "ses max tg_des_window", (long long)tg_ses_max_window_size); + } + else { + tg_ses_max_window_size = (size_t) ret; + } +} + +static inline NETDATA_DOUBLE tg_ses_window(RRDR *r, struct tg_ses *g) { + (void)g; + + NETDATA_DOUBLE points; + if(r->view.group == 1) { + // provide a running DES + points = (NETDATA_DOUBLE)r->time_grouping.points_wanted; + } + else { + // provide a SES with flush points + points = (NETDATA_DOUBLE)r->view.group; + } + + return (points > (NETDATA_DOUBLE)tg_ses_max_window_size) ? (NETDATA_DOUBLE)tg_ses_max_window_size : points; +} + +static inline void tg_ses_set_alpha(RRDR *r, struct tg_ses *g) { + // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + // A commonly used value for alpha is 2 / (N + 1) + g->alpha = 2.0 / (tg_ses_window(r, g) + 1.0); + g->alpha_other = 1.0 - g->alpha; +} + +static inline void tg_ses_create(RRDR *r, const char *options __maybe_unused) { + struct tg_ses *g = (struct tg_ses *)onewayalloc_callocz(r->internal.owa, 1, sizeof(struct tg_ses)); + tg_ses_set_alpha(r, g); + g->level = 0.0; + r->time_grouping.data = g; +} + +// resets when switches dimensions +// so, clear everything to restart +static inline void tg_ses_reset(RRDR *r) { + struct tg_ses *g = (struct tg_ses *)r->time_grouping.data; + g->level = 0.0; + g->count = 0; +} + +static inline void tg_ses_free(RRDR *r) { + onewayalloc_freez(r->internal.owa, r->time_grouping.data); + r->time_grouping.data = NULL; +} + +static inline void tg_ses_add(RRDR *r, NETDATA_DOUBLE value) { + struct tg_ses *g = (struct tg_ses *)r->time_grouping.data; + + if(unlikely(!g->count)) + g->level = value; + + g->level = g->alpha * value + g->alpha_other * g->level; + g->count++; +} + +static inline NETDATA_DOUBLE tg_ses_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_ses *g = (struct tg_ses *)r->time_grouping.data; + + if(unlikely(!g->count || !netdata_double_isnumber(g->level))) { + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + return 0.0; + } + + return g->level; +} #endif //NETDATA_API_QUERIES_SES_H diff --git a/web/api/queries/stddev/README.md b/web/api/queries/stddev/README.md index 2fca47d5..f0586a06 100644 --- a/web/api/queries/stddev/README.md +++ b/web/api/queries/stddev/README.md @@ -1,6 +1,10 @@ # standard deviation (`stddev`) diff --git a/web/api/queries/stddev/stddev.c b/web/api/queries/stddev/stddev.c index 92a67b42..8f543119 100644 --- a/web/api/queries/stddev/stddev.c +++ b/web/api/queries/stddev/stddev.c @@ -6,123 +6,11 @@ // ---------------------------------------------------------------------------- // stddev -// this implementation comes from: -// https://www.johndcook.com/blog/standard_deviation/ - -struct grouping_stddev { - long count; - NETDATA_DOUBLE m_oldM, m_newM, m_oldS, m_newS; -}; - -void grouping_create_stddev(RRDR *r, const char *options __maybe_unused) { - r->internal.grouping_data = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct grouping_stddev)); -} - -// resets when switches dimensions -// so, clear everything to restart -void grouping_reset_stddev(RRDR *r) { - struct grouping_stddev *g = (struct grouping_stddev *)r->internal.grouping_data; - g->count = 0; -} - -void grouping_free_stddev(RRDR *r) { - onewayalloc_freez(r->internal.owa, r->internal.grouping_data); - r->internal.grouping_data = NULL; -} - -void grouping_add_stddev(RRDR *r, NETDATA_DOUBLE value) { - struct grouping_stddev *g = (struct grouping_stddev *)r->internal.grouping_data; - - g->count++; - - // See Knuth TAOCP vol 2, 3rd edition, page 232 - if (g->count == 1) { - g->m_oldM = g->m_newM = value; - g->m_oldS = 0.0; - } - else { - g->m_newM = g->m_oldM + (value - g->m_oldM) / g->count; - g->m_newS = g->m_oldS + (value - g->m_oldM) * (value - g->m_newM); - - // set up for next iteration - g->m_oldM = g->m_newM; - g->m_oldS = g->m_newS; - } -} - -static inline NETDATA_DOUBLE mean(struct grouping_stddev *g) { - return (g->count > 0) ? g->m_newM : 0.0; -} - -static inline NETDATA_DOUBLE variance(struct grouping_stddev *g) { - return ( (g->count > 1) ? g->m_newS/(NETDATA_DOUBLE)(g->count - 1) : 0.0 ); -} -static inline NETDATA_DOUBLE stddev(struct grouping_stddev *g) { - return sqrtndd(variance(g)); -} - -NETDATA_DOUBLE grouping_flush_stddev(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_stddev *g = (struct grouping_stddev *)r->internal.grouping_data; - - NETDATA_DOUBLE value; - - if(likely(g->count > 1)) { - value = stddev(g); - - if(!netdata_double_isnumber(value)) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - } - else if(g->count == 1) { - value = 0.0; - } - else { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - - grouping_reset_stddev(r); - - return value; -} - -// https://en.wikipedia.org/wiki/Coefficient_of_variation -NETDATA_DOUBLE grouping_flush_coefficient_of_variation(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_stddev *g = (struct grouping_stddev *)r->internal.grouping_data; - - NETDATA_DOUBLE value; - - if(likely(g->count > 1)) { - NETDATA_DOUBLE m = mean(g); - value = 100.0 * stddev(g) / ((m < 0)? -m : m); - - if(unlikely(!netdata_double_isnumber(value))) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - } - else if(g->count == 1) { - // one value collected - value = 0.0; - } - else { - // no values collected - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - - grouping_reset_stddev(r); - - return value; -} - - /* * Mean = average * NETDATA_DOUBLE grouping_flush_mean(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_stddev *g = (struct grouping_stddev *)r->internal.grouping_data; + struct grouping_stddev *g = (struct grouping_stddev *)r->grouping.grouping_data; NETDATA_DOUBLE value; @@ -149,7 +37,7 @@ NETDATA_DOUBLE grouping_flush_mean(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options * It is not advised to use this version of variance directly * NETDATA_DOUBLE grouping_flush_variance(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_stddev *g = (struct grouping_stddev *)r->internal.grouping_data; + struct grouping_stddev *g = (struct grouping_stddev *)r->grouping.grouping_data; NETDATA_DOUBLE value; diff --git a/web/api/queries/stddev/stddev.h b/web/api/queries/stddev/stddev.h index 4b8ffcd5..f7a1a06c 100644 --- a/web/api/queries/stddev/stddev.h +++ b/web/api/queries/stddev/stddev.h @@ -6,13 +6,115 @@ #include "../query.h" #include "../rrdr.h" -void grouping_create_stddev(RRDR *r, const char *options __maybe_unused); -void grouping_reset_stddev(RRDR *r); -void grouping_free_stddev(RRDR *r); -void grouping_add_stddev(RRDR *r, NETDATA_DOUBLE value); -NETDATA_DOUBLE grouping_flush_stddev(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); -NETDATA_DOUBLE grouping_flush_coefficient_of_variation(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); -// NETDATA_DOUBLE grouping_flush_mean(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); -// NETDATA_DOUBLE grouping_flush_variance(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +// this implementation comes from: +// https://www.johndcook.com/blog/standard_deviation/ + +struct tg_stddev { + long count; + NETDATA_DOUBLE m_oldM, m_newM, m_oldS, m_newS; +}; + +static inline void tg_stddev_create(RRDR *r, const char *options __maybe_unused) { + r->time_grouping.data = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct tg_stddev)); +} + +// resets when switches dimensions +// so, clear everything to restart +static inline void tg_stddev_reset(RRDR *r) { + struct tg_stddev *g = (struct tg_stddev *)r->time_grouping.data; + g->count = 0; +} + +static inline void tg_stddev_free(RRDR *r) { + onewayalloc_freez(r->internal.owa, r->time_grouping.data); + r->time_grouping.data = NULL; +} + +static inline void tg_stddev_add(RRDR *r, NETDATA_DOUBLE value) { + struct tg_stddev *g = (struct tg_stddev *)r->time_grouping.data; + + g->count++; + + // See Knuth TAOCP vol 2, 3rd edition, page 232 + if (g->count == 1) { + g->m_oldM = g->m_newM = value; + g->m_oldS = 0.0; + } + else { + g->m_newM = g->m_oldM + (value - g->m_oldM) / g->count; + g->m_newS = g->m_oldS + (value - g->m_oldM) * (value - g->m_newM); + + // set up for next iteration + g->m_oldM = g->m_newM; + g->m_oldS = g->m_newS; + } +} + +static inline NETDATA_DOUBLE tg_stddev_mean(struct tg_stddev *g) { + return (g->count > 0) ? g->m_newM : 0.0; +} + +static inline NETDATA_DOUBLE tg_stddev_variance(struct tg_stddev *g) { + return ( (g->count > 1) ? g->m_newS/(NETDATA_DOUBLE)(g->count - 1) : 0.0 ); +} +static inline NETDATA_DOUBLE tg_stddev_stddev(struct tg_stddev *g) { + return sqrtndd(tg_stddev_variance(g)); +} + +static inline NETDATA_DOUBLE tg_stddev_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_stddev *g = (struct tg_stddev *)r->time_grouping.data; + + NETDATA_DOUBLE value; + + if(likely(g->count > 1)) { + value = tg_stddev_stddev(g); + + if(!netdata_double_isnumber(value)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + } + else if(g->count == 1) { + value = 0.0; + } + else { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + + tg_stddev_reset(r); + + return value; +} + +// https://en.wikipedia.org/wiki/Coefficient_of_variation +static inline NETDATA_DOUBLE tg_stddev_coefficient_of_variation_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_stddev *g = (struct tg_stddev *)r->time_grouping.data; + + NETDATA_DOUBLE value; + + if(likely(g->count > 1)) { + NETDATA_DOUBLE m = tg_stddev_mean(g); + value = 100.0 * tg_stddev_stddev(g) / ((m < 0)? -m : m); + + if(unlikely(!netdata_double_isnumber(value))) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + } + else if(g->count == 1) { + // one value collected + value = 0.0; + } + else { + // no values collected + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + + tg_stddev_reset(r); + + return value; +} #endif //NETDATA_API_QUERIES_STDDEV_H diff --git a/web/api/queries/sum/README.md b/web/api/queries/sum/README.md index d4465bd8..62e18aca 100644 --- a/web/api/queries/sum/README.md +++ b/web/api/queries/sum/README.md @@ -1,6 +1,10 @@ # Sum diff --git a/web/api/queries/sum/sum.c b/web/api/queries/sum/sum.c index eec6e2ad..cf448421 100644 --- a/web/api/queries/sum/sum.c +++ b/web/api/queries/sum/sum.c @@ -5,51 +5,5 @@ // ---------------------------------------------------------------------------- // sum -struct grouping_sum { - NETDATA_DOUBLE sum; - size_t count; -}; - -void grouping_create_sum(RRDR *r, const char *options __maybe_unused) { - r->internal.grouping_data = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct grouping_sum)); -} - -// resets when switches dimensions -// so, clear everything to restart -void grouping_reset_sum(RRDR *r) { - struct grouping_sum *g = (struct grouping_sum *)r->internal.grouping_data; - g->sum = 0; - g->count = 0; -} - -void grouping_free_sum(RRDR *r) { - onewayalloc_freez(r->internal.owa, r->internal.grouping_data); - r->internal.grouping_data = NULL; -} - -void grouping_add_sum(RRDR *r, NETDATA_DOUBLE value) { - struct grouping_sum *g = (struct grouping_sum *)r->internal.grouping_data; - g->sum += value; - g->count++; -} - -NETDATA_DOUBLE grouping_flush_sum(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_sum *g = (struct grouping_sum *)r->internal.grouping_data; - - NETDATA_DOUBLE value; - - if(unlikely(!g->count)) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - else { - value = g->sum; - } - - g->sum = 0.0; - g->count = 0; - - return value; -} diff --git a/web/api/queries/sum/sum.h b/web/api/queries/sum/sum.h index 89878277..5e07f45d 100644 --- a/web/api/queries/sum/sum.h +++ b/web/api/queries/sum/sum.h @@ -6,10 +6,51 @@ #include "../query.h" #include "../rrdr.h" -void grouping_create_sum(RRDR *r, const char *options __maybe_unused); -void grouping_reset_sum(RRDR *r); -void grouping_free_sum(RRDR *r); -void grouping_add_sum(RRDR *r, NETDATA_DOUBLE value); -NETDATA_DOUBLE grouping_flush_sum(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +struct tg_sum { + NETDATA_DOUBLE sum; + size_t count; +}; + +static inline void tg_sum_create(RRDR *r, const char *options __maybe_unused) { + r->time_grouping.data = onewayalloc_callocz(r->internal.owa, 1, sizeof(struct tg_sum)); +} + +// resets when switches dimensions +// so, clear everything to restart +static inline void tg_sum_reset(RRDR *r) { + struct tg_sum *g = (struct tg_sum *)r->time_grouping.data; + g->sum = 0; + g->count = 0; +} + +static inline void tg_sum_free(RRDR *r) { + onewayalloc_freez(r->internal.owa, r->time_grouping.data); + r->time_grouping.data = NULL; +} + +static inline void tg_sum_add(RRDR *r, NETDATA_DOUBLE value) { + struct tg_sum *g = (struct tg_sum *)r->time_grouping.data; + g->sum += value; + g->count++; +} + +static inline NETDATA_DOUBLE tg_sum_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_sum *g = (struct tg_sum *)r->time_grouping.data; + + NETDATA_DOUBLE value; + + if(unlikely(!g->count)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else { + value = g->sum; + } + + g->sum = 0.0; + g->count = 0; + + return value; +} #endif //NETDATA_API_QUERY_SUM_H diff --git a/web/api/queries/trimmed_mean/README.md b/web/api/queries/trimmed_mean/README.md index 71cdb85d..08a32b83 100644 --- a/web/api/queries/trimmed_mean/README.md +++ b/web/api/queries/trimmed_mean/README.md @@ -1,7 +1,11 @@ # Trimmed Mean diff --git a/web/api/queries/trimmed_mean/trimmed_mean.c b/web/api/queries/trimmed_mean/trimmed_mean.c index 2277208a..c50db7ed 100644 --- a/web/api/queries/trimmed_mean/trimmed_mean.c +++ b/web/api/queries/trimmed_mean/trimmed_mean.c @@ -5,162 +5,3 @@ // ---------------------------------------------------------------------------- // median -struct grouping_trimmed_mean { - size_t series_size; - size_t next_pos; - NETDATA_DOUBLE percent; - - NETDATA_DOUBLE *series; -}; - -static void grouping_create_trimmed_mean_internal(RRDR *r, const char *options, NETDATA_DOUBLE def) { - long entries = r->group; - if(entries < 10) entries = 10; - - struct grouping_trimmed_mean *g = (struct grouping_trimmed_mean *)onewayalloc_callocz(r->internal.owa, 1, sizeof(struct grouping_trimmed_mean)); - g->series = onewayalloc_mallocz(r->internal.owa, entries * sizeof(NETDATA_DOUBLE)); - g->series_size = (size_t)entries; - - g->percent = def; - if(options && *options) { - g->percent = str2ndd(options, NULL); - if(!netdata_double_isnumber(g->percent)) g->percent = 0.0; - if(g->percent < 0.0) g->percent = 0.0; - if(g->percent > 50.0) g->percent = 50.0; - } - - g->percent = 1.0 - ((g->percent / 100.0) * 2.0); - r->internal.grouping_data = g; -} - -void grouping_create_trimmed_mean1(RRDR *r, const char *options) { - grouping_create_trimmed_mean_internal(r, options, 1.0); -} -void grouping_create_trimmed_mean2(RRDR *r, const char *options) { - grouping_create_trimmed_mean_internal(r, options, 2.0); -} -void grouping_create_trimmed_mean3(RRDR *r, const char *options) { - grouping_create_trimmed_mean_internal(r, options, 3.0); -} -void grouping_create_trimmed_mean5(RRDR *r, const char *options) { - grouping_create_trimmed_mean_internal(r, options, 5.0); -} -void grouping_create_trimmed_mean10(RRDR *r, const char *options) { - grouping_create_trimmed_mean_internal(r, options, 10.0); -} -void grouping_create_trimmed_mean15(RRDR *r, const char *options) { - grouping_create_trimmed_mean_internal(r, options, 15.0); -} -void grouping_create_trimmed_mean20(RRDR *r, const char *options) { - grouping_create_trimmed_mean_internal(r, options, 20.0); -} -void grouping_create_trimmed_mean25(RRDR *r, const char *options) { - grouping_create_trimmed_mean_internal(r, options, 25.0); -} - -// resets when switches dimensions -// so, clear everything to restart -void grouping_reset_trimmed_mean(RRDR *r) { - struct grouping_trimmed_mean *g = (struct grouping_trimmed_mean *)r->internal.grouping_data; - g->next_pos = 0; -} - -void grouping_free_trimmed_mean(RRDR *r) { - struct grouping_trimmed_mean *g = (struct grouping_trimmed_mean *)r->internal.grouping_data; - if(g) onewayalloc_freez(r->internal.owa, g->series); - - onewayalloc_freez(r->internal.owa, r->internal.grouping_data); - r->internal.grouping_data = NULL; -} - -void grouping_add_trimmed_mean(RRDR *r, NETDATA_DOUBLE value) { - struct grouping_trimmed_mean *g = (struct grouping_trimmed_mean *)r->internal.grouping_data; - - if(unlikely(g->next_pos >= g->series_size)) { - g->series = onewayalloc_doublesize( r->internal.owa, g->series, g->series_size * sizeof(NETDATA_DOUBLE)); - g->series_size *= 2; - } - - g->series[g->next_pos++] = value; -} - -NETDATA_DOUBLE grouping_flush_trimmed_mean(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { - struct grouping_trimmed_mean *g = (struct grouping_trimmed_mean *)r->internal.grouping_data; - - NETDATA_DOUBLE value; - size_t available_slots = g->next_pos; - - if(unlikely(!available_slots)) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - else if(available_slots == 1) { - value = g->series[0]; - } - else { - sort_series(g->series, available_slots); - - NETDATA_DOUBLE min = g->series[0]; - NETDATA_DOUBLE max = g->series[available_slots - 1]; - - if (min != max) { - size_t slots_to_use = (size_t)((NETDATA_DOUBLE)available_slots * g->percent); - if(!slots_to_use) slots_to_use = 1; - - NETDATA_DOUBLE percent_to_use = (NETDATA_DOUBLE)slots_to_use / (NETDATA_DOUBLE)available_slots; - NETDATA_DOUBLE percent_delta = g->percent - percent_to_use; - - NETDATA_DOUBLE percent_interpolation_slot = 0.0; - NETDATA_DOUBLE percent_last_slot = 0.0; - if(percent_delta > 0.0) { - NETDATA_DOUBLE percent_to_use_plus_1_slot = (NETDATA_DOUBLE)(slots_to_use + 1) / (NETDATA_DOUBLE)available_slots; - NETDATA_DOUBLE percent_1slot = percent_to_use_plus_1_slot - percent_to_use; - - percent_interpolation_slot = percent_delta / percent_1slot; - percent_last_slot = 1 - percent_interpolation_slot; - } - - int start_slot, stop_slot, step, last_slot, interpolation_slot; - if(min >= 0.0 && max >= 0.0) { - start_slot = (int)((available_slots - slots_to_use) / 2); - stop_slot = start_slot + (int)slots_to_use; - last_slot = stop_slot - 1; - interpolation_slot = stop_slot; - step = 1; - } - else { - start_slot = (int)available_slots - 1 - (int)((available_slots - slots_to_use) / 2); - stop_slot = start_slot - (int)slots_to_use; - last_slot = stop_slot + 1; - interpolation_slot = stop_slot; - step = -1; - } - - value = 0.0; - for(int slot = start_slot; slot != stop_slot ; slot += step) - value += g->series[slot]; - - size_t counted = slots_to_use; - if(percent_interpolation_slot > 0.0 && interpolation_slot >= 0 && interpolation_slot < (int)available_slots) { - value += g->series[interpolation_slot] * percent_interpolation_slot; - value += g->series[last_slot] * percent_last_slot; - counted++; - } - - value = value / (NETDATA_DOUBLE)counted; - } - else - value = min; - } - - if(unlikely(!netdata_double_isnumber(value))) { - value = 0.0; - *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; - } - - //log_series_to_stderr(g->series, g->next_pos, value, "trimmed_mean"); - - g->next_pos = 0; - - return value; -} diff --git a/web/api/queries/trimmed_mean/trimmed_mean.h b/web/api/queries/trimmed_mean/trimmed_mean.h index e66d9254..3c09015b 100644 --- a/web/api/queries/trimmed_mean/trimmed_mean.h +++ b/web/api/queries/trimmed_mean/trimmed_mean.h @@ -6,17 +6,164 @@ #include "../query.h" #include "../rrdr.h" -void grouping_create_trimmed_mean1(RRDR *r, const char *options); -void grouping_create_trimmed_mean2(RRDR *r, const char *options); -void grouping_create_trimmed_mean3(RRDR *r, const char *options); -void grouping_create_trimmed_mean5(RRDR *r, const char *options); -void grouping_create_trimmed_mean10(RRDR *r, const char *options); -void grouping_create_trimmed_mean15(RRDR *r, const char *options); -void grouping_create_trimmed_mean20(RRDR *r, const char *options); -void grouping_create_trimmed_mean25(RRDR *r, const char *options); -void grouping_reset_trimmed_mean(RRDR *r); -void grouping_free_trimmed_mean(RRDR *r); -void grouping_add_trimmed_mean(RRDR *r, NETDATA_DOUBLE value); -NETDATA_DOUBLE grouping_flush_trimmed_mean(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr); +struct tg_trimmed_mean { + size_t series_size; + size_t next_pos; + NETDATA_DOUBLE percent; + + NETDATA_DOUBLE *series; +}; + +static inline void tg_trimmed_mean_create_internal(RRDR *r, const char *options, NETDATA_DOUBLE def) { + long entries = r->view.group; + if(entries < 10) entries = 10; + + struct tg_trimmed_mean *g = (struct tg_trimmed_mean *)onewayalloc_callocz(r->internal.owa, 1, sizeof(struct tg_trimmed_mean)); + g->series = onewayalloc_mallocz(r->internal.owa, entries * sizeof(NETDATA_DOUBLE)); + g->series_size = (size_t)entries; + + g->percent = def; + if(options && *options) { + g->percent = str2ndd(options, NULL); + if(!netdata_double_isnumber(g->percent)) g->percent = 0.0; + if(g->percent < 0.0) g->percent = 0.0; + if(g->percent > 50.0) g->percent = 50.0; + } + + g->percent = 1.0 - ((g->percent / 100.0) * 2.0); + r->time_grouping.data = g; +} + +static inline void tg_trimmed_mean_create_1(RRDR *r, const char *options) { + tg_trimmed_mean_create_internal(r, options, 1.0); +} +static inline void tg_trimmed_mean_create_2(RRDR *r, const char *options) { + tg_trimmed_mean_create_internal(r, options, 2.0); +} +static inline void tg_trimmed_mean_create_3(RRDR *r, const char *options) { + tg_trimmed_mean_create_internal(r, options, 3.0); +} +static inline void tg_trimmed_mean_create_5(RRDR *r, const char *options) { + tg_trimmed_mean_create_internal(r, options, 5.0); +} +static inline void tg_trimmed_mean_create_10(RRDR *r, const char *options) { + tg_trimmed_mean_create_internal(r, options, 10.0); +} +static inline void tg_trimmed_mean_create_15(RRDR *r, const char *options) { + tg_trimmed_mean_create_internal(r, options, 15.0); +} +static inline void tg_trimmed_mean_create_20(RRDR *r, const char *options) { + tg_trimmed_mean_create_internal(r, options, 20.0); +} +static inline void tg_trimmed_mean_create_25(RRDR *r, const char *options) { + tg_trimmed_mean_create_internal(r, options, 25.0); +} + +// resets when switches dimensions +// so, clear everything to restart +static inline void tg_trimmed_mean_reset(RRDR *r) { + struct tg_trimmed_mean *g = (struct tg_trimmed_mean *)r->time_grouping.data; + g->next_pos = 0; +} + +static inline void tg_trimmed_mean_free(RRDR *r) { + struct tg_trimmed_mean *g = (struct tg_trimmed_mean *)r->time_grouping.data; + if(g) onewayalloc_freez(r->internal.owa, g->series); + + onewayalloc_freez(r->internal.owa, r->time_grouping.data); + r->time_grouping.data = NULL; +} + +static inline void tg_trimmed_mean_add(RRDR *r, NETDATA_DOUBLE value) { + struct tg_trimmed_mean *g = (struct tg_trimmed_mean *)r->time_grouping.data; + + if(unlikely(g->next_pos >= g->series_size)) { + g->series = onewayalloc_doublesize( r->internal.owa, g->series, g->series_size * sizeof(NETDATA_DOUBLE)); + g->series_size *= 2; + } + + g->series[g->next_pos++] = value; +} + +static inline NETDATA_DOUBLE tg_trimmed_mean_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr) { + struct tg_trimmed_mean *g = (struct tg_trimmed_mean *)r->time_grouping.data; + + NETDATA_DOUBLE value; + size_t available_slots = g->next_pos; + + if(unlikely(!available_slots)) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + else if(available_slots == 1) { + value = g->series[0]; + } + else { + sort_series(g->series, available_slots); + + NETDATA_DOUBLE min = g->series[0]; + NETDATA_DOUBLE max = g->series[available_slots - 1]; + + if (min != max) { + size_t slots_to_use = (size_t)((NETDATA_DOUBLE)available_slots * g->percent); + if(!slots_to_use) slots_to_use = 1; + + NETDATA_DOUBLE percent_to_use = (NETDATA_DOUBLE)slots_to_use / (NETDATA_DOUBLE)available_slots; + NETDATA_DOUBLE percent_delta = g->percent - percent_to_use; + + NETDATA_DOUBLE percent_interpolation_slot = 0.0; + NETDATA_DOUBLE percent_last_slot = 0.0; + if(percent_delta > 0.0) { + NETDATA_DOUBLE percent_to_use_plus_1_slot = (NETDATA_DOUBLE)(slots_to_use + 1) / (NETDATA_DOUBLE)available_slots; + NETDATA_DOUBLE percent_1slot = percent_to_use_plus_1_slot - percent_to_use; + + percent_interpolation_slot = percent_delta / percent_1slot; + percent_last_slot = 1 - percent_interpolation_slot; + } + + int start_slot, stop_slot, step, last_slot, interpolation_slot; + if(min >= 0.0 && max >= 0.0) { + start_slot = (int)((available_slots - slots_to_use) / 2); + stop_slot = start_slot + (int)slots_to_use; + last_slot = stop_slot - 1; + interpolation_slot = stop_slot; + step = 1; + } + else { + start_slot = (int)available_slots - 1 - (int)((available_slots - slots_to_use) / 2); + stop_slot = start_slot - (int)slots_to_use; + last_slot = stop_slot + 1; + interpolation_slot = stop_slot; + step = -1; + } + + value = 0.0; + for(int slot = start_slot; slot != stop_slot ; slot += step) + value += g->series[slot]; + + size_t counted = slots_to_use; + if(percent_interpolation_slot > 0.0 && interpolation_slot >= 0 && interpolation_slot < (int)available_slots) { + value += g->series[interpolation_slot] * percent_interpolation_slot; + value += g->series[last_slot] * percent_last_slot; + counted++; + } + + value = value / (NETDATA_DOUBLE)counted; + } + else + value = min; + } + + if(unlikely(!netdata_double_isnumber(value))) { + value = 0.0; + *rrdr_value_options_ptr |= RRDR_VALUE_EMPTY; + } + + //log_series_to_stderr(g->series, g->next_pos, value, "trimmed_mean"); + + g->next_pos = 0; + + return value; +} #endif //NETDATA_API_QUERIES_TRIMMED_MEAN_H diff --git a/web/api/queries/weights.c b/web/api/queries/weights.c index 485aaca2..0830a969 100644 --- a/web/api/queries/weights.c +++ b/web/api/queries/weights.c @@ -24,10 +24,11 @@ static struct { const char *name; WEIGHTS_METHOD value; } weights_methods[] = { - { "ks2" , WEIGHTS_METHOD_MC_KS2} - , { "volume" , WEIGHTS_METHOD_MC_VOLUME} - , { "anomaly-rate" , WEIGHTS_METHOD_ANOMALY_RATE} - , { NULL , 0 } + { "ks2" , WEIGHTS_METHOD_MC_KS2} + , { "volume" , WEIGHTS_METHOD_MC_VOLUME} + , { "anomaly-rate" , WEIGHTS_METHOD_ANOMALY_RATE} + , { "value" , WEIGHTS_METHOD_VALUE} + , { NULL , 0 } }; WEIGHTS_METHOD weights_string_to_method(const char *method) { @@ -56,14 +57,18 @@ typedef enum { struct register_result { RESULT_FLAGS flags; + RRDHOST *host; RRDCONTEXT_ACQUIRED *rca; RRDINSTANCE_ACQUIRED *ria; RRDMETRIC_ACQUIRED *rma; NETDATA_DOUBLE value; + STORAGE_POINT highlighted; + STORAGE_POINT baseline; + usec_t duration_ut; }; static DICTIONARY *register_result_init() { - DICTIONARY *results = dictionary_create(DICT_OPTION_SINGLE_THREADED); + DICTIONARY *results = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_FIXED_SIZE, NULL, sizeof(struct register_result)); return results; } @@ -71,14 +76,10 @@ static void register_result_destroy(DICTIONARY *results) { dictionary_destroy(results); } -static void register_result(DICTIONARY *results, - RRDCONTEXT_ACQUIRED *rca, - RRDINSTANCE_ACQUIRED *ria, - RRDMETRIC_ACQUIRED *rma, - NETDATA_DOUBLE value, - RESULT_FLAGS flags, - WEIGHTS_STATS *stats, - bool register_zero) { +static void register_result(DICTIONARY *results, RRDHOST *host, RRDCONTEXT_ACQUIRED *rca, RRDINSTANCE_ACQUIRED *ria, + RRDMETRIC_ACQUIRED *rma, NETDATA_DOUBLE value, RESULT_FLAGS flags, + STORAGE_POINT *highlighted, STORAGE_POINT *baseline, WEIGHTS_STATS *stats, + bool register_zero, usec_t duration_ut) { if(!netdata_double_isnumber(value)) return; @@ -90,17 +91,25 @@ static void register_result(DICTIONARY *results, return; // keep track of the max of the baseline / highlight ratio - if(flags & RESULT_IS_BASE_HIGH_RATIO && v > stats->max_base_high_ratio) + if((flags & RESULT_IS_BASE_HIGH_RATIO) && v > stats->max_base_high_ratio) stats->max_base_high_ratio = v; struct register_result t = { .flags = flags, + .host = host, .rca = rca, .ria = ria, .rma = rma, - .value = v + .value = v, + .duration_ut = duration_ut, }; + if(highlighted) + t.highlighted = *highlighted; + + if(baseline) + t.baseline = *baseline; + // we can use the pointer address or RMA as a unique key for each metric char buf[20 + 1]; ssize_t len = snprintfz(buf, 20, "%p", rma); @@ -114,112 +123,92 @@ static void results_header_to_json(DICTIONARY *results __maybe_unused, BUFFER *w time_t after, time_t before, time_t baseline_after, time_t baseline_before, size_t points, WEIGHTS_METHOD method, - RRDR_GROUPING group, RRDR_OPTIONS options, uint32_t shifts, + RRDR_TIME_GROUPING group, RRDR_OPTIONS options, uint32_t shifts, size_t examined_dimensions __maybe_unused, usec_t duration, WEIGHTS_STATS *stats) { - buffer_sprintf(wb, "{\n" - "\t\"after\": %lld,\n" - "\t\"before\": %lld,\n" - "\t\"duration\": %lld,\n" - "\t\"points\": %zu,\n", - (long long)after, - (long long)before, - (long long)(before - after), - points - ); - - if(method == WEIGHTS_METHOD_MC_KS2 || method == WEIGHTS_METHOD_MC_VOLUME) - buffer_sprintf(wb, "" - "\t\"baseline_after\": %lld,\n" - "\t\"baseline_before\": %lld,\n" - "\t\"baseline_duration\": %lld,\n" - "\t\"baseline_points\": %zu,\n", - (long long)baseline_after, - (long long)baseline_before, - (long long)(baseline_before - baseline_after), - points << shifts - ); - - buffer_sprintf(wb, "" - "\t\"statistics\": {\n" - "\t\t\"query_time_ms\": %f,\n" - "\t\t\"db_queries\": %zu,\n" - "\t\t\"query_result_points\": %zu,\n" - "\t\t\"binary_searches\": %zu,\n" - "\t\t\"db_points_read\": %zu,\n" - "\t\t\"db_points_per_tier\": [ ", - (double)duration / (double)USEC_PER_MS, - stats->db_queries, - stats->result_points, - stats->binary_searches, - stats->db_points - ); - - for(size_t tier = 0; tier < storage_tiers ;tier++) - buffer_sprintf(wb, "%s%zu", tier?", ":"", stats->db_points_per_tier[tier]); - - buffer_sprintf(wb, " ]\n" - "\t},\n" - "\t\"group\": \"%s\",\n" - "\t\"method\": \"%s\",\n" - "\t\"options\": \"", - web_client_api_request_v1_data_group_to_string(group), - weights_method_to_string(method) - ); - - web_client_api_request_v1_data_options_to_buffer(wb, options); + buffer_json_member_add_time_t(wb, "after", after); + buffer_json_member_add_time_t(wb, "before", before); + buffer_json_member_add_time_t(wb, "duration", before - after); + buffer_json_member_add_uint64(wb, "points", points); + + if(method == WEIGHTS_METHOD_MC_KS2 || method == WEIGHTS_METHOD_MC_VOLUME) { + buffer_json_member_add_time_t(wb, "baseline_after", baseline_after); + buffer_json_member_add_time_t(wb, "baseline_before", baseline_before); + buffer_json_member_add_time_t(wb, "baseline_duration", baseline_before - baseline_after); + buffer_json_member_add_uint64(wb, "baseline_points", points << shifts); + } + + buffer_json_member_add_object(wb, "statistics"); + { + buffer_json_member_add_double(wb, "query_time_ms", (double) duration / (double) USEC_PER_MS); + buffer_json_member_add_uint64(wb, "db_queries", stats->db_queries); + buffer_json_member_add_uint64(wb, "query_result_points", stats->result_points); + buffer_json_member_add_uint64(wb, "binary_searches", stats->binary_searches); + buffer_json_member_add_uint64(wb, "db_points_read", stats->db_points); + + buffer_json_member_add_array(wb, "db_points_per_tier"); + { + for (size_t tier = 0; tier < storage_tiers; tier++) + buffer_json_add_array_item_uint64(wb, stats->db_points_per_tier[tier]); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_string(wb, "group", time_grouping_tostring(group)); + buffer_json_member_add_string(wb, "method", weights_method_to_string(method)); + web_client_api_request_v1_data_options_to_buffer_json_array(wb, "options", options); } static size_t registered_results_to_json_charts(DICTIONARY *results, BUFFER *wb, time_t after, time_t before, time_t baseline_after, time_t baseline_before, size_t points, WEIGHTS_METHOD method, - RRDR_GROUPING group, RRDR_OPTIONS options, uint32_t shifts, + RRDR_TIME_GROUPING group, RRDR_OPTIONS options, uint32_t shifts, size_t examined_dimensions, usec_t duration, WEIGHTS_STATS *stats) { + buffer_json_initialize(wb, "\"", "\"", 0, true, options & RRDR_OPTION_MINIFY); + results_header_to_json(results, wb, after, before, baseline_after, baseline_before, points, method, group, options, shifts, examined_dimensions, duration, stats); - buffer_strcat(wb, "\",\n\t\"correlated_charts\": {\n"); + buffer_json_member_add_object(wb, "correlated_charts"); - size_t charts = 0, chart_dims = 0, total_dimensions = 0; + size_t charts = 0, total_dimensions = 0; struct register_result *t; RRDINSTANCE_ACQUIRED *last_ria = NULL; // never access this - we use it only for comparison dfe_start_read(results, t) { if(t->ria != last_ria) { last_ria = t->ria; - if(charts) buffer_strcat(wb, "\n\t\t\t}\n\t\t},\n"); - buffer_strcat(wb, "\t\t\""); - buffer_strcat(wb, rrdinstance_acquired_id(t->ria)); - buffer_strcat(wb, "\": {\n"); - buffer_strcat(wb, "\t\t\t\"context\": \""); - buffer_strcat(wb, rrdcontext_acquired_id(t->rca)); - buffer_strcat(wb, "\",\n\t\t\t\"dimensions\": {\n"); + if(charts) { + buffer_json_object_close(wb); // dimensions + buffer_json_object_close(wb); // chart:id + } + + buffer_json_member_add_object(wb, rrdinstance_acquired_id(t->ria)); + buffer_json_member_add_string(wb, "context", rrdcontext_acquired_id(t->rca)); + buffer_json_member_add_object(wb, "dimensions"); charts++; - chart_dims = 0; } - if (chart_dims) buffer_sprintf(wb, ",\n"); - buffer_sprintf(wb, "\t\t\t\t\"%s\": " NETDATA_DOUBLE_FORMAT, rrdmetric_acquired_name(t->rma), t->value); - chart_dims++; + buffer_json_member_add_double(wb, rrdmetric_acquired_name(t->rma), t->value); total_dimensions++; } dfe_done(t); // close dimensions and chart - if (total_dimensions) - buffer_strcat(wb, "\n\t\t\t}\n\t\t}\n"); - - // close correlated_charts - buffer_sprintf(wb, "\t},\n" - "\t\"correlated_dimensions\": %zu,\n" - "\t\"total_dimensions_count\": %zu\n" - "}\n", - total_dimensions, - examined_dimensions - ); + if (total_dimensions) { + buffer_json_object_close(wb); // dimensions + buffer_json_object_close(wb); // chart:id + } + + buffer_json_object_close(wb); + + buffer_json_member_add_uint64(wb, "correlated_dimensions", total_dimensions); + buffer_json_member_add_uint64(wb, "total_dimensions_count", examined_dimensions); + buffer_json_finalize(wb); return total_dimensions; } @@ -228,14 +217,16 @@ static size_t registered_results_to_json_contexts(DICTIONARY *results, BUFFER *w time_t after, time_t before, time_t baseline_after, time_t baseline_before, size_t points, WEIGHTS_METHOD method, - RRDR_GROUPING group, RRDR_OPTIONS options, uint32_t shifts, + RRDR_TIME_GROUPING group, RRDR_OPTIONS options, uint32_t shifts, size_t examined_dimensions, usec_t duration, WEIGHTS_STATS *stats) { + buffer_json_initialize(wb, "\"", "\"", 0, true, options & RRDR_OPTION_MINIFY); + results_header_to_json(results, wb, after, before, baseline_after, baseline_before, points, method, group, options, shifts, examined_dimensions, duration, stats); - buffer_strcat(wb, "\",\n\t\"contexts\": {\n"); + buffer_json_member_add_object(wb, "contexts"); size_t contexts = 0, charts = 0, total_dimensions = 0, context_dims = 0, chart_dims = 0; NETDATA_DOUBLE contexts_total_weight = 0.0, charts_total_weight = 0.0; @@ -247,18 +238,17 @@ static size_t registered_results_to_json_contexts(DICTIONARY *results, BUFFER *w if(t->rca != last_rca) { last_rca = t->rca; - if(contexts) - buffer_sprintf(wb, "\n" - "\t\t\t\t\t},\n" - "\t\t\t\t\t\"weight\":" NETDATA_DOUBLE_FORMAT "\n" - "\t\t\t\t}\n\t\t\t},\n" - "\t\t\t\"weight\":" NETDATA_DOUBLE_FORMAT "\n\t\t},\n" - , charts_total_weight / (double)chart_dims - , contexts_total_weight / (double)context_dims); + if(contexts) { + buffer_json_object_close(wb); // dimensions + buffer_json_member_add_double(wb, "weight", charts_total_weight / (double) chart_dims); + buffer_json_object_close(wb); // chart:id + buffer_json_object_close(wb); // charts + buffer_json_member_add_double(wb, "weight", contexts_total_weight / (double) context_dims); + buffer_json_object_close(wb); // context + } - buffer_strcat(wb, "\t\t\""); - buffer_strcat(wb, rrdcontext_acquired_id(t->rca)); - buffer_strcat(wb, "\": {\n\t\t\t\"charts\":{\n"); + buffer_json_member_add_object(wb, rrdcontext_acquired_id(t->rca)); + buffer_json_member_add_object(wb, "charts"); contexts++; charts = 0; @@ -271,25 +261,21 @@ static size_t registered_results_to_json_contexts(DICTIONARY *results, BUFFER *w if(t->ria != last_ria) { last_ria = t->ria; - if(charts) - buffer_sprintf(wb, "\n" - "\t\t\t\t\t},\n" - "\t\t\t\t\t\"weight\":" NETDATA_DOUBLE_FORMAT "\n" - "\t\t\t\t},\n" - , charts_total_weight / (double)chart_dims); + if(charts) { + buffer_json_object_close(wb); // dimensions + buffer_json_member_add_double(wb, "weight", charts_total_weight / (double) chart_dims); + buffer_json_object_close(wb); // chart:id + } - buffer_strcat(wb, "\t\t\t\t\""); - buffer_strcat(wb, rrdinstance_acquired_id(t->ria)); - buffer_strcat(wb, "\": {\n"); - buffer_strcat(wb, "\t\t\t\t\t\"dimensions\": {\n"); + buffer_json_member_add_object(wb, rrdinstance_acquired_id(t->ria)); + buffer_json_member_add_object(wb, "dimensions"); charts++; chart_dims = 0; charts_total_weight = 0.0; } - if (chart_dims) buffer_sprintf(wb, ",\n"); - buffer_sprintf(wb, "\t\t\t\t\t\t\"%s\": " NETDATA_DOUBLE_FORMAT, rrdmetric_acquired_name(t->rma), t->value); + buffer_json_member_add_double(wb, rrdmetric_acquired_name(t->rma), t->value); charts_total_weight += t->value; contexts_total_weight += t->value; chart_dims++; @@ -299,25 +285,794 @@ static size_t registered_results_to_json_contexts(DICTIONARY *results, BUFFER *w dfe_done(t); // close dimensions and chart - if (total_dimensions) - buffer_sprintf(wb, "\n" - "\t\t\t\t\t},\n" - "\t\t\t\t\t\"weight\":" NETDATA_DOUBLE_FORMAT "\n" - "\t\t\t\t}\n" - "\t\t\t},\n" - "\t\t\t\"weight\":" NETDATA_DOUBLE_FORMAT "\n" - "\t\t}\n" - , charts_total_weight / (double)chart_dims - , contexts_total_weight / (double)context_dims); - - // close correlated_charts - buffer_sprintf(wb, "\t},\n" - "\t\"weighted_dimensions\": %zu,\n" - "\t\"total_dimensions_count\": %zu\n" - "}\n", - total_dimensions, - examined_dimensions - ); + if (total_dimensions) { + buffer_json_object_close(wb); // dimensions + buffer_json_member_add_double(wb, "weight", charts_total_weight / (double) chart_dims); + buffer_json_object_close(wb); // chart:id + buffer_json_object_close(wb); // charts + buffer_json_member_add_double(wb, "weight", contexts_total_weight / (double) context_dims); + buffer_json_object_close(wb); // context + } + + buffer_json_object_close(wb); + + buffer_json_member_add_uint64(wb, "correlated_dimensions", total_dimensions); + buffer_json_member_add_uint64(wb, "total_dimensions_count", examined_dimensions); + buffer_json_finalize(wb); + + return total_dimensions; +} + +struct query_weights_data { + QUERY_WEIGHTS_REQUEST *qwr; + + SIMPLE_PATTERN *scope_nodes_sp; + SIMPLE_PATTERN *scope_contexts_sp; + SIMPLE_PATTERN *nodes_sp; + SIMPLE_PATTERN *contexts_sp; + SIMPLE_PATTERN *instances_sp; + SIMPLE_PATTERN *dimensions_sp; + SIMPLE_PATTERN *labels_sp; + SIMPLE_PATTERN *alerts_sp; + + usec_t timeout_us; + bool timed_out; + bool interrupted; + + struct query_timings timings; + + size_t examined_dimensions; + bool register_zero; + + DICTIONARY *results; + WEIGHTS_STATS stats; + + uint32_t shifts; + + struct query_versions versions; +}; + +#define AGGREGATED_WEIGHT_EMPTY (struct aggregated_weight) { \ + .min = NAN, \ + .max = NAN, \ + .sum = NAN, \ + .count = 0, \ + .hsp = STORAGE_POINT_UNSET, \ + .bsp = STORAGE_POINT_UNSET, \ +} + +#define merge_into_aw(aw, t) do { \ + if(!(aw).count) { \ + (aw).count = 1; \ + (aw).min = (aw).max = (aw).sum = (t)->value; \ + (aw).hsp = (t)->highlighted; \ + if(baseline) \ + (aw).bsp = (t)->baseline; \ + } \ + else { \ + (aw).count++; \ + (aw).sum += (t)->value; \ + if((t)->value < (aw).min) \ + (aw).min = (t)->value; \ + if((t)->value > (aw).max) \ + (aw).max = (t)->value; \ + storage_point_merge_to((aw).hsp, (t)->highlighted); \ + if(baseline) \ + storage_point_merge_to((aw).bsp, (t)->baseline); \ + } \ +} while(0) + +static void results_header_to_json_v2(DICTIONARY *results __maybe_unused, BUFFER *wb, struct query_weights_data *qwd, + time_t after, time_t before, + time_t baseline_after, time_t baseline_before, + size_t points, WEIGHTS_METHOD method, + RRDR_TIME_GROUPING group, RRDR_OPTIONS options, uint32_t shifts, + size_t examined_dimensions __maybe_unused, usec_t duration __maybe_unused, + WEIGHTS_STATS *stats, bool group_by) { + + buffer_json_member_add_object(wb, "request"); + buffer_json_member_add_string(wb, "method", weights_method_to_string(method)); + web_client_api_request_v1_data_options_to_buffer_json_array(wb, "options", options); + + buffer_json_member_add_object(wb, "scope"); + buffer_json_member_add_string(wb, "scope_nodes", qwd->qwr->scope_nodes ? qwd->qwr->scope_nodes : "*"); + buffer_json_member_add_string(wb, "scope_contexts", qwd->qwr->scope_contexts ? qwd->qwr->scope_contexts : "*"); + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "selectors"); + buffer_json_member_add_string(wb, "nodes", qwd->qwr->nodes ? qwd->qwr->nodes : "*"); + buffer_json_member_add_string(wb, "contexts", qwd->qwr->contexts ? qwd->qwr->contexts : "*"); + buffer_json_member_add_string(wb, "instances", qwd->qwr->instances ? qwd->qwr->instances : "*"); + buffer_json_member_add_string(wb, "dimensions", qwd->qwr->dimensions ? qwd->qwr->dimensions : "*"); + buffer_json_member_add_string(wb, "labels", qwd->qwr->labels ? qwd->qwr->labels : "*"); + buffer_json_member_add_string(wb, "alerts", qwd->qwr->alerts ? qwd->qwr->alerts : "*"); + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "window"); + buffer_json_member_add_time_t(wb, "after", qwd->qwr->after); + buffer_json_member_add_time_t(wb, "before", qwd->qwr->before); + buffer_json_member_add_uint64(wb, "points", qwd->qwr->points); + if(qwd->qwr->options & RRDR_OPTION_SELECTED_TIER) + buffer_json_member_add_uint64(wb, "tier", qwd->qwr->tier); + else + buffer_json_member_add_string(wb, "tier", NULL); + buffer_json_object_close(wb); + + if(method == WEIGHTS_METHOD_MC_KS2 || method == WEIGHTS_METHOD_MC_VOLUME) { + buffer_json_member_add_object(wb, "baseline"); + buffer_json_member_add_time_t(wb, "baseline_after", qwd->qwr->baseline_after); + buffer_json_member_add_time_t(wb, "baseline_before", qwd->qwr->baseline_before); + buffer_json_object_close(wb); + } + + buffer_json_member_add_object(wb, "aggregations"); + buffer_json_member_add_object(wb, "time"); + buffer_json_member_add_string(wb, "time_group", time_grouping_tostring(qwd->qwr->time_group_method)); + buffer_json_member_add_string(wb, "time_group_options", qwd->qwr->time_group_options); + buffer_json_object_close(wb); // time + + buffer_json_member_add_array(wb, "metrics"); + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_array(wb, "group_by"); + buffer_json_group_by_to_array(wb, qwd->qwr->group_by.group_by); + buffer_json_array_close(wb); + +// buffer_json_member_add_array(wb, "group_by_label"); +// buffer_json_array_close(wb); + + buffer_json_member_add_string(wb, "aggregation", group_by_aggregate_function_to_string(qwd->qwr->group_by.aggregation)); + } + buffer_json_object_close(wb); // 1st group by + buffer_json_array_close(wb); // array + buffer_json_object_close(wb); // aggregations + + buffer_json_member_add_uint64(wb, "timeout", qwd->qwr->timeout_ms); + buffer_json_object_close(wb); // request + + buffer_json_member_add_object(wb, "view"); + buffer_json_member_add_string(wb, "format", (group_by)?"grouped":"full"); + buffer_json_member_add_string(wb, "time_group", time_grouping_tostring(group)); + + buffer_json_member_add_object(wb, "window"); + buffer_json_member_add_time_t(wb, "after", after); + buffer_json_member_add_time_t(wb, "before", before); + buffer_json_member_add_time_t(wb, "duration", before - after); + buffer_json_member_add_uint64(wb, "points", points); + buffer_json_object_close(wb); + + if(method == WEIGHTS_METHOD_MC_KS2 || method == WEIGHTS_METHOD_MC_VOLUME) { + buffer_json_member_add_object(wb, "baseline"); + buffer_json_member_add_time_t(wb, "after", baseline_after); + buffer_json_member_add_time_t(wb, "before", baseline_before); + buffer_json_member_add_time_t(wb, "duration", baseline_before - baseline_after); + buffer_json_member_add_uint64(wb, "points", points << shifts); + buffer_json_object_close(wb); + } + + buffer_json_object_close(wb); // view + + buffer_json_member_add_object(wb, "db"); + { + buffer_json_member_add_uint64(wb, "db_queries", stats->db_queries); + buffer_json_member_add_uint64(wb, "query_result_points", stats->result_points); + buffer_json_member_add_uint64(wb, "binary_searches", stats->binary_searches); + buffer_json_member_add_uint64(wb, "db_points_read", stats->db_points); + + buffer_json_member_add_array(wb, "db_points_per_tier"); + { + for (size_t tier = 0; tier < storage_tiers; tier++) + buffer_json_add_array_item_uint64(wb, stats->db_points_per_tier[tier]); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); // db +} + +typedef enum { + WPT_DIMENSION = 0, + WPT_INSTANCE = 1, + WPT_CONTEXT = 2, + WPT_NODE = 3, + WPT_GROUP = 4, +} WEIGHTS_POINT_TYPE; + +struct aggregated_weight { + const char *name; + NETDATA_DOUBLE min; + NETDATA_DOUBLE max; + NETDATA_DOUBLE sum; + size_t count; + STORAGE_POINT hsp; + STORAGE_POINT bsp; +}; + +static inline void storage_point_to_json(BUFFER *wb, WEIGHTS_POINT_TYPE type, ssize_t di, ssize_t ii, ssize_t ci, ssize_t ni, struct aggregated_weight *aw, RRDR_OPTIONS options __maybe_unused, bool baseline) { + if(type != WPT_GROUP) { + buffer_json_add_array_item_array(wb); + buffer_json_add_array_item_uint64(wb, type); // "type" + buffer_json_add_array_item_int64(wb, ni); + if (type != WPT_NODE) { + buffer_json_add_array_item_int64(wb, ci); + if (type != WPT_CONTEXT) { + buffer_json_add_array_item_int64(wb, ii); + if (type != WPT_INSTANCE) + buffer_json_add_array_item_int64(wb, di); + else + buffer_json_add_array_item_string(wb, NULL); + } + else { + buffer_json_add_array_item_string(wb, NULL); + buffer_json_add_array_item_string(wb, NULL); + } + } + else { + buffer_json_add_array_item_string(wb, NULL); + buffer_json_add_array_item_string(wb, NULL); + buffer_json_add_array_item_string(wb, NULL); + } + buffer_json_add_array_item_double(wb, (aw->count) ? aw->sum / (NETDATA_DOUBLE)aw->count : 0.0); // "weight" + } + else { + buffer_json_member_add_array(wb, "v"); + buffer_json_add_array_item_array(wb); + buffer_json_add_array_item_double(wb, aw->min); // "min" + buffer_json_add_array_item_double(wb, (aw->count) ? aw->sum / (NETDATA_DOUBLE)aw->count : 0.0); // "avg" + buffer_json_add_array_item_double(wb, aw->max); // "max" + buffer_json_add_array_item_double(wb, aw->sum); // "sum" + buffer_json_add_array_item_uint64(wb, aw->count); // "count" + buffer_json_array_close(wb); + } + + buffer_json_add_array_item_array(wb); + buffer_json_add_array_item_double(wb, aw->hsp.min); // "min" + buffer_json_add_array_item_double(wb, (aw->hsp.count) ? aw->hsp.sum / (NETDATA_DOUBLE) aw->hsp.count : 0.0); // "avg" + buffer_json_add_array_item_double(wb, aw->hsp.max); // "max" + buffer_json_add_array_item_double(wb, aw->hsp.sum); // "sum" + buffer_json_add_array_item_uint64(wb, aw->hsp.count); // "count" + buffer_json_add_array_item_uint64(wb, aw->hsp.anomaly_count); // "anomaly_count" + buffer_json_array_close(wb); + + if(baseline) { + buffer_json_add_array_item_array(wb); + buffer_json_add_array_item_double(wb, aw->bsp.min); // "min" + buffer_json_add_array_item_double(wb, (aw->bsp.count) ? aw->bsp.sum / (NETDATA_DOUBLE) aw->bsp.count : 0.0); // "avg" + buffer_json_add_array_item_double(wb, aw->bsp.max); // "max" + buffer_json_add_array_item_double(wb, aw->bsp.sum); // "sum" + buffer_json_add_array_item_uint64(wb, aw->bsp.count); // "count" + buffer_json_add_array_item_uint64(wb, aw->bsp.anomaly_count); // "anomaly_count" + buffer_json_array_close(wb); + } + + buffer_json_array_close(wb); +} + +static void multinode_data_schema(BUFFER *wb, RRDR_OPTIONS options __maybe_unused, const char *key, bool baseline, bool group_by) { + buffer_json_member_add_object(wb, key); // schema + + buffer_json_member_add_string(wb, "type", "array"); + buffer_json_member_add_array(wb, "items"); + + if(group_by) { + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_string(wb, "name", "weight"); + buffer_json_member_add_string(wb, "type", "array"); + buffer_json_member_add_array(wb, "labels"); + { + buffer_json_add_array_item_string(wb, "min"); + buffer_json_add_array_item_string(wb, "avg"); + buffer_json_add_array_item_string(wb, "max"); + buffer_json_add_array_item_string(wb, "sum"); + buffer_json_add_array_item_string(wb, "count"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + } + else { + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "name", "row_type"); + buffer_json_member_add_string(wb, "type", "integer"); + buffer_json_member_add_array(wb, "value"); + buffer_json_add_array_item_string(wb, "dimension"); + buffer_json_add_array_item_string(wb, "instance"); + buffer_json_add_array_item_string(wb, "context"); + buffer_json_add_array_item_string(wb, "node"); + buffer_json_array_close(wb); + buffer_json_object_close(wb); + + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_string(wb, "name", "ni"); + buffer_json_member_add_string(wb, "type", "integer"); + buffer_json_member_add_string(wb, "dictionary", "nodes"); + } + buffer_json_object_close(wb); + + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_string(wb, "name", "ci"); + buffer_json_member_add_string(wb, "type", "integer"); + buffer_json_member_add_string(wb, "dictionary", "contexts"); + } + buffer_json_object_close(wb); + + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_string(wb, "name", "ii"); + buffer_json_member_add_string(wb, "type", "integer"); + buffer_json_member_add_string(wb, "dictionary", "instances"); + } + buffer_json_object_close(wb); + + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_string(wb, "name", "di"); + buffer_json_member_add_string(wb, "type", "integer"); + buffer_json_member_add_string(wb, "dictionary", "dimensions"); + } + buffer_json_object_close(wb); + + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_string(wb, "name", "weight"); + buffer_json_member_add_string(wb, "type", "number"); + } + buffer_json_object_close(wb); + } + + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_string(wb, "name", "timeframe"); + buffer_json_member_add_string(wb, "type", "array"); + buffer_json_member_add_array(wb, "labels"); + { + buffer_json_add_array_item_string(wb, "min"); + buffer_json_add_array_item_string(wb, "avg"); + buffer_json_add_array_item_string(wb, "max"); + buffer_json_add_array_item_string(wb, "sum"); + buffer_json_add_array_item_string(wb, "count"); + buffer_json_add_array_item_string(wb, "anomaly_count"); + } + buffer_json_array_close(wb); + buffer_json_member_add_object(wb, "calculations"); + buffer_json_member_add_string(wb, "anomaly rate", "anomaly_count * 100 / count"); + buffer_json_object_close(wb); + } + buffer_json_object_close(wb); + + if(baseline) { + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_string(wb, "name", "baseline timeframe"); + buffer_json_member_add_string(wb, "type", "array"); + buffer_json_member_add_array(wb, "labels"); + { + buffer_json_add_array_item_string(wb, "min"); + buffer_json_add_array_item_string(wb, "avg"); + buffer_json_add_array_item_string(wb, "max"); + buffer_json_add_array_item_string(wb, "sum"); + buffer_json_add_array_item_string(wb, "count"); + buffer_json_add_array_item_string(wb, "anomaly_count"); + } + buffer_json_array_close(wb); + buffer_json_member_add_object(wb, "calculations"); + buffer_json_member_add_string(wb, "anomaly rate", "anomaly_count * 100 / count"); + buffer_json_object_close(wb); + } + buffer_json_object_close(wb); + } + + buffer_json_array_close(wb); // items + buffer_json_object_close(wb); // schema +} + +struct dict_unique_node { + bool existing; + bool exposed; + uint32_t i; + RRDHOST *host; + usec_t duration_ut; +}; + +struct dict_unique_name_units { + bool existing; + bool exposed; + uint32_t i; + const char *units; +}; + +struct dict_unique_id_name { + bool existing; + bool exposed; + uint32_t i; + const char *id; + const char *name; +}; + +static inline struct dict_unique_node *dict_unique_node_add(DICTIONARY *dict, RRDHOST *host, ssize_t *max_id) { + struct dict_unique_node *dun = dictionary_set(dict, host->machine_guid, NULL, sizeof(struct dict_unique_node)); + if(!dun->existing) { + dun->existing = true; + dun->host = host; + dun->i = *max_id; + (*max_id)++; + } + + return dun; +} + +static inline struct dict_unique_name_units *dict_unique_name_units_add(DICTIONARY *dict, const char *name, const char *units, ssize_t *max_id) { + struct dict_unique_name_units *dun = dictionary_set(dict, name, NULL, sizeof(struct dict_unique_name_units)); + if(!dun->existing) { + dun->units = units; + dun->existing = true; + dun->i = *max_id; + (*max_id)++; + } + + return dun; +} + +static inline struct dict_unique_id_name *dict_unique_id_name_add(DICTIONARY *dict, const char *id, const char *name, ssize_t *max_id) { + char key[1024 + 1]; + snprintfz(key, 1024, "%s:%s", id, name); + struct dict_unique_id_name *dun = dictionary_set(dict, key, NULL, sizeof(struct dict_unique_id_name)); + if(!dun->existing) { + dun->existing = true; + dun->i = *max_id; + (*max_id)++; + dun->id = id; + dun->name = name; + } + + return dun; +} + +static size_t registered_results_to_json_multinode_no_group_by( + DICTIONARY *results, BUFFER *wb, + time_t after, time_t before, + time_t baseline_after, time_t baseline_before, + size_t points, WEIGHTS_METHOD method, + RRDR_TIME_GROUPING group, RRDR_OPTIONS options, uint32_t shifts, + size_t examined_dimensions, struct query_weights_data *qwd, + WEIGHTS_STATS *stats, + struct query_versions *versions) { + buffer_json_initialize(wb, "\"", "\"", 0, true, options & RRDR_OPTION_MINIFY); + buffer_json_member_add_uint64(wb, "api", 2); + + results_header_to_json_v2(results, wb, qwd, after, before, baseline_after, baseline_before, + points, method, group, options, shifts, examined_dimensions, + qwd->timings.executed_ut - qwd->timings.received_ut, stats, false); + + version_hashes_api_v2(wb, versions); + + bool baseline = method == WEIGHTS_METHOD_MC_KS2 || method == WEIGHTS_METHOD_MC_VOLUME; + multinode_data_schema(wb, options, "schema", baseline, false); + + DICTIONARY *dict_nodes = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(struct dict_unique_node)); + DICTIONARY *dict_contexts = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(struct dict_unique_name_units)); + DICTIONARY *dict_instances = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(struct dict_unique_id_name)); + DICTIONARY *dict_dimensions = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, NULL, sizeof(struct dict_unique_id_name)); + + buffer_json_member_add_array(wb, "result"); + + struct aggregated_weight node_aw = AGGREGATED_WEIGHT_EMPTY, context_aw = AGGREGATED_WEIGHT_EMPTY, instance_aw = AGGREGATED_WEIGHT_EMPTY; + struct register_result *t; + RRDHOST *last_host = NULL; + RRDCONTEXT_ACQUIRED *last_rca = NULL; + RRDINSTANCE_ACQUIRED *last_ria = NULL; + struct dict_unique_name_units *context_dun = NULL; + struct dict_unique_node *node_dun = NULL; + struct dict_unique_id_name *instance_dun = NULL; + struct dict_unique_id_name *dimension_dun = NULL; + ssize_t di = -1, ii = -1, ci = -1, ni = -1; + ssize_t di_max = 0, ii_max = 0, ci_max = 0, ni_max = 0; + size_t total_dimensions = 0; + dfe_start_read(results, t) { + + // close instance + if(t->ria != last_ria && last_ria) { + storage_point_to_json(wb, WPT_INSTANCE, di, ii, ci, ni, &instance_aw, options, baseline); + instance_dun->exposed = true; + last_ria = NULL; + instance_aw = AGGREGATED_WEIGHT_EMPTY; + } + + // close context + if(t->rca != last_rca && last_rca) { + storage_point_to_json(wb, WPT_CONTEXT, di, ii, ci, ni, &context_aw, options, baseline); + context_dun->exposed = true; + last_rca = NULL; + context_aw = AGGREGATED_WEIGHT_EMPTY; + } + + // close node + if(t->host != last_host && last_host) { + storage_point_to_json(wb, WPT_NODE, di, ii, ci, ni, &node_aw, options, baseline); + node_dun->exposed = true; + last_host = NULL; + node_aw = AGGREGATED_WEIGHT_EMPTY; + } + + // open node + if(t->host != last_host) { + last_host = t->host; + node_dun = dict_unique_node_add(dict_nodes, t->host, &ni_max); + ni = node_dun->i; + } + + // open context + if(t->rca != last_rca) { + last_rca = t->rca; + context_dun = dict_unique_name_units_add(dict_contexts, rrdcontext_acquired_id(t->rca), + rrdcontext_acquired_units(t->rca), &ci_max); + ci = context_dun->i; + } + + // open instance + if(t->ria != last_ria) { + last_ria = t->ria; + instance_dun = dict_unique_id_name_add(dict_instances, rrdinstance_acquired_id(t->ria), rrdinstance_acquired_name(t->ria), &ii_max); + ii = instance_dun->i; + } + + dimension_dun = dict_unique_id_name_add(dict_dimensions, rrdmetric_acquired_id(t->rma), rrdmetric_acquired_name(t->rma), &di_max); + di = dimension_dun->i; + + struct aggregated_weight aw = { + .min = t->value, + .max = t->value, + .sum = t->value, + .count = 1, + .hsp = t->highlighted, + .bsp = t->baseline, + }; + + storage_point_to_json(wb, WPT_DIMENSION, di, ii, ci, ni, &aw, options, baseline); + node_dun->exposed = true; + context_dun->exposed = true; + instance_dun->exposed = true; + dimension_dun->exposed = true; + + merge_into_aw(instance_aw, t); + merge_into_aw(context_aw, t); + merge_into_aw(node_aw, t); + + node_dun->duration_ut += t->duration_ut; + total_dimensions++; + } + dfe_done(t); + + // close instance + if(last_ria) { + storage_point_to_json(wb, WPT_INSTANCE, di, ii, ci, ni, &instance_aw, options, baseline); + instance_dun->exposed = true; + } + + // close context + if(last_rca) { + storage_point_to_json(wb, WPT_CONTEXT, di, ii, ci, ni, &context_aw, options, baseline); + context_dun->exposed = true; + } + + // close node + if(last_host) { + storage_point_to_json(wb, WPT_NODE, di, ii, ci, ni, &node_aw, options, baseline); + node_dun->exposed = true; + } + + buffer_json_array_close(wb); // points + + buffer_json_member_add_object(wb, "dictionaries"); + buffer_json_member_add_array(wb, "nodes"); + { + struct dict_unique_node *dun; + dfe_start_read(dict_nodes, dun) { + if(!dun->exposed) + continue; + + buffer_json_add_array_item_object(wb); + buffer_json_node_add_v2(wb, dun->host, dun->i, dun->duration_ut); + buffer_json_object_close(wb); + } + dfe_done(dun); + } + buffer_json_array_close(wb); + + buffer_json_member_add_array(wb, "contexts"); + { + struct dict_unique_name_units *dun; + dfe_start_read(dict_contexts, dun) { + if(!dun->exposed) + continue; + + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "id", dun_dfe.name); + buffer_json_member_add_string(wb, "units", dun->units); + buffer_json_member_add_int64(wb, "ci", dun->i); + buffer_json_object_close(wb); + } + dfe_done(dun); + } + buffer_json_array_close(wb); + + buffer_json_member_add_array(wb, "instances"); + { + struct dict_unique_id_name *dun; + dfe_start_read(dict_instances, dun) { + if(!dun->exposed) + continue; + + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "id", dun->id); + if(dun->id != dun->name) + buffer_json_member_add_string(wb, "nm", dun->name); + buffer_json_member_add_int64(wb, "ii", dun->i); + buffer_json_object_close(wb); + } + dfe_done(dun); + } + buffer_json_array_close(wb); + + buffer_json_member_add_array(wb, "dimensions"); + { + struct dict_unique_id_name *dun; + dfe_start_read(dict_dimensions, dun) { + if(!dun->exposed) + continue; + + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "id", dun->id); + if(dun->id != dun->name) + buffer_json_member_add_string(wb, "nm", dun->name); + buffer_json_member_add_int64(wb, "di", dun->i); + buffer_json_object_close(wb); + } + dfe_done(dun); + } + buffer_json_array_close(wb); + + buffer_json_object_close(wb); //dictionaries + + buffer_json_agents_array_v2(wb, &qwd->timings, 0); + buffer_json_member_add_uint64(wb, "correlated_dimensions", total_dimensions); + buffer_json_member_add_uint64(wb, "total_dimensions_count", examined_dimensions); + buffer_json_finalize(wb); + + dictionary_destroy(dict_nodes); + dictionary_destroy(dict_contexts); + dictionary_destroy(dict_instances); + dictionary_destroy(dict_dimensions); + + return total_dimensions; +} + +static size_t registered_results_to_json_multinode_group_by( + DICTIONARY *results, BUFFER *wb, + time_t after, time_t before, + time_t baseline_after, time_t baseline_before, + size_t points, WEIGHTS_METHOD method, + RRDR_TIME_GROUPING group, RRDR_OPTIONS options, uint32_t shifts, + size_t examined_dimensions, struct query_weights_data *qwd, + WEIGHTS_STATS *stats, + struct query_versions *versions) { + buffer_json_initialize(wb, "\"", "\"", 0, true, options & RRDR_OPTION_MINIFY); + buffer_json_member_add_uint64(wb, "api", 2); + + results_header_to_json_v2(results, wb, qwd, after, before, baseline_after, baseline_before, + points, method, group, options, shifts, examined_dimensions, + qwd->timings.executed_ut - qwd->timings.received_ut, stats, true); + + version_hashes_api_v2(wb, versions); + + bool baseline = method == WEIGHTS_METHOD_MC_KS2 || method == WEIGHTS_METHOD_MC_VOLUME; + multinode_data_schema(wb, options, "v_schema", baseline, true); + + DICTIONARY *group_by = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, + NULL, sizeof(struct aggregated_weight)); + + struct register_result *t; + size_t total_dimensions = 0; + BUFFER *key = buffer_create(0, NULL); + BUFFER *name = buffer_create(0, NULL); + dfe_start_read(results, t) { + + buffer_flush(key); + buffer_flush(name); + + if(qwd->qwr->group_by.group_by & RRDR_GROUP_BY_DIMENSION) { + buffer_strcat(key, rrdmetric_acquired_name(t->rma)); + buffer_strcat(name, rrdmetric_acquired_name(t->rma)); + } + if(qwd->qwr->group_by.group_by & RRDR_GROUP_BY_INSTANCE) { + if(buffer_strlen(key)) { + buffer_fast_strcat(key, ",", 1); + buffer_fast_strcat(name, ",", 1); + } + + buffer_strcat(key, rrdinstance_acquired_id(t->ria)); + buffer_strcat(name, rrdinstance_acquired_name(t->ria)); + + if(!(qwd->qwr->group_by.group_by & RRDR_GROUP_BY_NODE)) { + buffer_fast_strcat(key, "@", 1); + buffer_fast_strcat(name, "@", 1); + buffer_strcat(key, t->host->machine_guid); + buffer_strcat(name, rrdhost_hostname(t->host)); + } + } + if(qwd->qwr->group_by.group_by & RRDR_GROUP_BY_NODE) { + if(buffer_strlen(key)) { + buffer_fast_strcat(key, ",", 1); + buffer_fast_strcat(name, ",", 1); + } + + buffer_strcat(key, t->host->machine_guid); + buffer_strcat(name, rrdhost_hostname(t->host)); + } + if(qwd->qwr->group_by.group_by & RRDR_GROUP_BY_CONTEXT) { + if(buffer_strlen(key)) { + buffer_fast_strcat(key, ",", 1); + buffer_fast_strcat(name, ",", 1); + } + + buffer_strcat(key, rrdcontext_acquired_id(t->rca)); + buffer_strcat(name, rrdcontext_acquired_id(t->rca)); + } + if(qwd->qwr->group_by.group_by & RRDR_GROUP_BY_UNITS) { + if(buffer_strlen(key)) { + buffer_fast_strcat(key, ",", 1); + buffer_fast_strcat(name, ",", 1); + } + + buffer_strcat(key, rrdcontext_acquired_units(t->rca)); + buffer_strcat(name, rrdcontext_acquired_units(t->rca)); + } + + struct aggregated_weight *aw = dictionary_set(group_by, buffer_tostring(key), NULL, sizeof(struct aggregated_weight)); + if(!aw->name) { + aw->name = strdupz(buffer_tostring(name)); + aw->min = aw->max = aw->sum = t->value; + aw->count = 1; + aw->hsp = t->highlighted; + aw->bsp = t->baseline; + } + else + merge_into_aw(*aw, t); + + total_dimensions++; + } + dfe_done(t); + buffer_free(key); key = NULL; + buffer_free(name); name = NULL; + + struct aggregated_weight *aw; + buffer_json_member_add_array(wb, "result"); + dfe_start_read(group_by, aw) { + const char *k = aw_dfe.name; + const char *n = aw->name; + + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "id", k); + + if(strcmp(k, n) != 0) + buffer_json_member_add_string(wb, "nm", n); + + storage_point_to_json(wb, WPT_GROUP, 0, 0, 0, 0, aw, options, baseline); + buffer_json_object_close(wb); + + freez((void *)aw->name); + } + dfe_done(aw); + buffer_json_array_close(wb); // result + + buffer_json_agents_array_v2(wb, &qwd->timings, 0); + buffer_json_member_add_uint64(wb, "correlated_dimensions", total_dimensions); + buffer_json_member_add_uint64(wb, "total_dimensions_count", examined_dimensions); + buffer_json_finalize(wb); + + dictionary_destroy(group_by); return total_dimensions; } @@ -500,14 +1255,16 @@ NETDATA_DOUBLE *rrd2rrdr_ks2( ONEWAYALLOC *owa, RRDHOST *host, RRDCONTEXT_ACQUIRED *rca, RRDINSTANCE_ACQUIRED *ria, RRDMETRIC_ACQUIRED *rma, time_t after, time_t before, size_t points, RRDR_OPTIONS options, - RRDR_GROUPING group_method, const char *group_options, size_t tier, + RRDR_TIME_GROUPING time_group_method, const char *time_group_options, size_t tier, WEIGHTS_STATS *stats, - size_t *entries + size_t *entries, + STORAGE_POINT *sp ) { NETDATA_DOUBLE *ret = NULL; QUERY_TARGET_REQUEST qtr = { + .version = 1, .host = host, .rca = rca, .ria = ria, @@ -516,25 +1273,27 @@ NETDATA_DOUBLE *rrd2rrdr_ks2( .before = before, .points = points, .options = options, - .group_method = group_method, - .group_options = group_options, + .time_group_method = time_group_method, + .time_group_options = time_group_options, .tier = tier, .query_source = QUERY_SOURCE_API_WEIGHTS, - .priority = STORAGE_PRIORITY_NORMAL, + .priority = STORAGE_PRIORITY_SYNCHRONOUS, }; - RRDR *r = rrd2rrdr(owa, query_target_create(&qtr)); + QUERY_TARGET *qt = query_target_create(&qtr); + RRDR *r = rrd2rrdr(owa, qt); if(!r) goto cleanup; stats->db_queries++; - stats->result_points += r->internal.result_points_generated; - stats->db_points += r->internal.db_points_read; + stats->result_points += r->stats.result_points_generated; + stats->db_points += r->stats.db_points_read; for(size_t tr = 0; tr < storage_tiers ; tr++) - stats->db_points_per_tier[tr] += r->internal.tier_points_read[tr]; + stats->db_points_per_tier[tr] += r->internal.qt->db.tiers[tr].points; - if(r->d != 1) { - error("WEIGHTS: on query '%s' expected 1 dimension in RRDR but got %zu", r->internal.qt->id, r->d); + if(r->d != 1 || r->internal.qt->query.used != 1) { + error("WEIGHTS: on query '%s' expected 1 dimension in RRDR but got %zu r->d and %zu qt->query.used", + r->internal.qt->id, r->d, (size_t)r->internal.qt->query.used); goto cleanup; } @@ -553,6 +1312,9 @@ NETDATA_DOUBLE *rrd2rrdr_ks2( *entries = rrdr_rows(r); ret = onewayalloc_mallocz(owa, sizeof(NETDATA_DOUBLE) * rrdr_rows(r)); + if(sp) + *sp = r->internal.qt->query.array[0].query_points; + // copy the points of the dimension to a contiguous array // there is no need to check for empty values, since empty values are already zero // https://github.com/netdata/netdata/blob/6e3144683a73a2024d51425b20ecfd569034c858/web/api/queries/average/average.c#L41-L43 @@ -560,6 +1322,7 @@ NETDATA_DOUBLE *rrd2rrdr_ks2( cleanup: rrdr_free(owa, r); + query_target_release(qt); return ret; } @@ -570,27 +1333,30 @@ static void rrdset_metric_correlations_ks2( time_t baseline_after, time_t baseline_before, time_t after, time_t before, size_t points, RRDR_OPTIONS options, - RRDR_GROUPING group_method, const char *group_options, size_t tier, + RRDR_TIME_GROUPING time_group_method, const char *time_group_options, size_t tier, uint32_t shifts, WEIGHTS_STATS *stats, bool register_zero ) { options |= RRDR_OPTION_NATURAL_POINTS; + usec_t started_ut = now_monotonic_usec(); ONEWAYALLOC *owa = onewayalloc_create(16 * 1024); size_t high_points = 0; + STORAGE_POINT highlighted_sp; NETDATA_DOUBLE *highlight = rrd2rrdr_ks2( owa, host, rca, ria, rma, after, before, points, - options, group_method, group_options, tier, stats, &high_points); + options, time_group_method, time_group_options, tier, stats, &high_points, &highlighted_sp); if(!highlight) goto cleanup; size_t base_points = 0; + STORAGE_POINT baseline_sp; NETDATA_DOUBLE *baseline = rrd2rrdr_ks2( owa, host, rca, ria, rma, baseline_after, baseline_before, high_points << shifts, - options, group_method, group_options, tier, stats, &base_points); + options, time_group_method, time_group_options, tier, stats, &base_points, &baseline_sp); if(!baseline) goto cleanup; @@ -610,9 +1376,12 @@ static void rrdset_metric_correlations_ks2( prob = 1.0; } + usec_t ended_ut = now_monotonic_usec(); + // to spread the results evenly, 0.0 needs to be the less correlated and 1.0 the most correlated // so, we flip the result of kstwo() - register_result(results, rca, ria, rma, 1.0 - prob, RESULT_IS_BASE_HIGH_RATIO, stats, register_zero); + register_result(results, host, rca, ria, rma, 1.0 - prob, RESULT_IS_BASE_HIGH_RATIO, &highlighted_sp, + &baseline_sp, stats, register_zero, ended_ut - started_ut); } cleanup: @@ -622,8 +1391,8 @@ cleanup: // ---------------------------------------------------------------------------- // VOLUME algorithm functions -static void merge_query_value_to_stats(QUERY_VALUE *qv, WEIGHTS_STATS *stats) { - stats->db_queries++; +static void merge_query_value_to_stats(QUERY_VALUE *qv, WEIGHTS_STATS *stats, size_t queries) { + stats->db_queries += queries; stats->result_points += qv->result_points; stats->db_points += qv->points_read; for(size_t tier = 0; tier < storage_tiers ; tier++) @@ -636,16 +1405,16 @@ static void rrdset_metric_correlations_volume( DICTIONARY *results, time_t baseline_after, time_t baseline_before, time_t after, time_t before, - RRDR_OPTIONS options, RRDR_GROUPING group_method, const char *group_options, + RRDR_OPTIONS options, RRDR_TIME_GROUPING time_group_method, const char *time_group_options, size_t tier, WEIGHTS_STATS *stats, bool register_zero) { 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, STORAGE_PRIORITY_NORMAL); - merge_query_value_to_stats(&baseline_average, stats); + options, time_group_method, time_group_options, tier, 0, + QUERY_SOURCE_API_WEIGHTS, STORAGE_PRIORITY_SYNCHRONOUS); + merge_query_value_to_stats(&baseline_average, stats, 1); if(!netdata_double_isnumber(baseline_average.value)) { // this means no data for the baseline window, but we may have data for the highlighted one - assume zero @@ -653,9 +1422,9 @@ static void rrdset_metric_correlations_volume( } 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); + options, time_group_method, time_group_options, tier, 0, + QUERY_SOURCE_API_WEIGHTS, STORAGE_PRIORITY_SYNCHRONOUS); + merge_query_value_to_stats(&highlight_average, stats, 1); if(!netdata_double_isnumber(highlight_average.value)) return; @@ -665,12 +1434,17 @@ static void rrdset_metric_correlations_volume( return; } + if((options & RRDR_OPTION_ANOMALY_BIT) && highlight_average.value < baseline_average.value) { + // when working on anomaly bits, we are looking for an increase in the anomaly rate + return; + } + 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, STORAGE_PRIORITY_NORMAL); - merge_query_value_to_stats(&highlight_countif, stats); + QUERY_SOURCE_API_WEIGHTS, STORAGE_PRIORITY_SYNCHRONOUS); + merge_query_value_to_stats(&highlight_countif, stats, 1); if(!netdata_double_isnumber(highlight_countif.value)) { info("WEIGHTS: highlighted countif query failed, but highlighted average worked - strange..."); @@ -693,31 +1467,104 @@ static void rrdset_metric_correlations_volume( pcent = highlight_countif.value; } - register_result(results, rca, ria, rma, pcent, flags, stats, register_zero); + register_result(results, host, rca, ria, rma, pcent, flags, &highlight_average.sp, &baseline_average.sp, stats, + register_zero, baseline_average.duration_ut + highlight_average.duration_ut + highlight_countif.duration_ut); } // ---------------------------------------------------------------------------- -// ANOMALY RATE algorithm functions +// VALUE / ANOMALY RATE algorithm functions -static void rrdset_weights_anomaly_rate( +static void rrdset_weights_value( RRDHOST *host, RRDCONTEXT_ACQUIRED *rca, RRDINSTANCE_ACQUIRED *ria, RRDMETRIC_ACQUIRED *rma, DICTIONARY *results, time_t after, time_t before, - RRDR_OPTIONS options, RRDR_GROUPING group_method, const char *group_options, + RRDR_OPTIONS options, RRDR_TIME_GROUPING time_group_method, const char *time_group_options, size_t tier, WEIGHTS_STATS *stats, bool register_zero) { - options |= RRDR_OPTION_MATCH_IDS | RRDR_OPTION_ANOMALY_BIT | RRDR_OPTION_NATURAL_POINTS; + options |= RRDR_OPTION_MATCH_IDS | 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, STORAGE_PRIORITY_NORMAL); + options, time_group_method, time_group_options, tier, 0, + QUERY_SOURCE_API_WEIGHTS, STORAGE_PRIORITY_SYNCHRONOUS); - merge_query_value_to_stats(&qv, stats); + merge_query_value_to_stats(&qv, stats, 1); if(netdata_double_isnumber(qv.value)) - register_result(results, rca, ria, rma, qv.value, 0, stats, register_zero); + register_result(results, host, rca, ria, rma, qv.value, 0, &qv.sp, NULL, stats, register_zero, qv.duration_ut); +} + +static void rrdset_weights_multi_dimensional_value(struct query_weights_data *qwd) { + QUERY_TARGET_REQUEST qtr = { + .version = 1, + .scope_nodes = qwd->qwr->scope_nodes, + .scope_contexts = qwd->qwr->scope_contexts, + .nodes = qwd->qwr->nodes, + .contexts = qwd->qwr->contexts, + .instances = qwd->qwr->instances, + .dimensions = qwd->qwr->dimensions, + .labels = qwd->qwr->labels, + .alerts = qwd->qwr->alerts, + .after = qwd->qwr->after, + .before = qwd->qwr->before, + .points = 1, + .options = qwd->qwr->options | RRDR_OPTION_NATURAL_POINTS, + .time_group_method = qwd->qwr->time_group_method, + .time_group_options = qwd->qwr->time_group_options, + .tier = qwd->qwr->tier, + .timeout_ms = qwd->qwr->timeout_ms, + .query_source = QUERY_SOURCE_API_WEIGHTS, + .priority = STORAGE_PRIORITY_NORMAL, + }; + + ONEWAYALLOC *owa = onewayalloc_create(16 * 1024); + QUERY_TARGET *qt = query_target_create(&qtr); + RRDR *r = rrd2rrdr(owa, qt); + + if(!r || rrdr_rows(r) != 1 || !r->d || r->d != r->internal.qt->query.used) + goto cleanup; + + QUERY_VALUE qv = { + .after = r->view.after, + .before = r->view.before, + .points_read = r->stats.db_points_read, + .result_points = r->stats.result_points_generated, + }; + + size_t queries = 0; + for(size_t d = 0; d < r->d ;d++) { + if(!rrdr_dimension_should_be_exposed(r->od[d], qwd->qwr->options)) + continue; + + long i = 0; // only one row + NETDATA_DOUBLE *cn = &r->v[ i * r->d ]; + NETDATA_DOUBLE *ar = &r->ar[ i * r->d ]; + + qv.value = cn[d]; + qv.anomaly_rate = ar[d]; + storage_point_merge_to(qv.sp, r->internal.qt->query.array[d].query_points); + + if(netdata_double_isnumber(qv.value)) { + QUERY_METRIC *qm = query_metric(r->internal.qt, d); + QUERY_DIMENSION *qd = query_dimension(r->internal.qt, qm->link.query_dimension_id); + QUERY_INSTANCE *qi = query_instance(r->internal.qt, qm->link.query_instance_id); + QUERY_CONTEXT *qc = query_context(r->internal.qt, qm->link.query_context_id); + QUERY_NODE *qn = query_node(r->internal.qt, qm->link.query_node_id); + + register_result(qwd->results, qn->rrdhost, qc->rca, qi->ria, qd->rma, qv.value, 0, &qv.sp, + NULL, &qwd->stats, qwd->register_zero, qm->duration_ut); + } + + queries++; + } + + merge_query_value_to_stats(&qv, &qwd->stats, queries); + +cleanup: + rrdr_free(owa, r); + query_target_release(qt); + onewayalloc_destroy(owa); } // ---------------------------------------------------------------------------- @@ -765,13 +1612,15 @@ static size_t spread_results_evenly(DICTIONARY *results, WEIGHTS_STATS *stats) { NETDATA_DOUBLE slots[dimensions]; dimensions = 0; dfe_start_read(results, t) { - if(t->flags & (RESULT_IS_PERCENTAGE_OF_TIME)) + if(t->flags & RESULT_IS_PERCENTAGE_OF_TIME) t->value = t->value * stats->max_base_high_ratio; slots[dimensions++] = t->value; } dfe_done(t); + if(!dimensions) return 0; // Coverity fix + // sort the array with the values of all dimensions qsort(slots, dimensions, sizeof(NETDATA_DOUBLE), compare_netdata_doubles); @@ -805,60 +1654,184 @@ static size_t spread_results_evenly(DICTIONARY *results, WEIGHTS_STATS *stats) { // ---------------------------------------------------------------------------- // The main function -int web_api_v1_weights( - RRDHOST *host, BUFFER *wb, WEIGHTS_METHOD method, WEIGHTS_FORMAT format, - RRDR_GROUPING group, const char *group_options, - time_t baseline_after, time_t baseline_before, - time_t after, time_t before, - size_t points, RRDR_OPTIONS options, SIMPLE_PATTERN *contexts, size_t tier, size_t timeout) { +static ssize_t weights_for_rrdmetric(void *data, RRDHOST *host, RRDCONTEXT_ACQUIRED *rca, RRDINSTANCE_ACQUIRED *ria, RRDMETRIC_ACQUIRED *rma) { + struct query_weights_data *qwd = data; + QUERY_WEIGHTS_REQUEST *qwr = qwd->qwr; + + if(qwd->qwr->interrupt_callback && qwd->qwr->interrupt_callback(qwd->qwr->interrupt_callback_data)) { + qwd->interrupted = true; + return -1; + } + + qwd->examined_dimensions++; + + switch(qwr->method) { + case WEIGHTS_METHOD_VALUE: + rrdset_weights_value( + host, rca, ria, rma, + qwd->results, + qwr->after, qwr->before, + qwr->options, qwr->time_group_method, qwr->time_group_options, qwr->tier, + &qwd->stats, qwd->register_zero + ); + break; + + case WEIGHTS_METHOD_ANOMALY_RATE: + qwr->options |= RRDR_OPTION_ANOMALY_BIT; + rrdset_weights_value( + host, rca, ria, rma, + qwd->results, + qwr->after, qwr->before, + qwr->options, qwr->time_group_method, qwr->time_group_options, qwr->tier, + &qwd->stats, qwd->register_zero + ); + break; + + case WEIGHTS_METHOD_MC_VOLUME: + rrdset_metric_correlations_volume( + host, rca, ria, rma, + qwd->results, + qwr->baseline_after, qwr->baseline_before, + qwr->after, qwr->before, + qwr->options, qwr->time_group_method, qwr->time_group_options, qwr->tier, + &qwd->stats, qwd->register_zero + ); + break; + + default: + case WEIGHTS_METHOD_MC_KS2: + rrdset_metric_correlations_ks2( + host, rca, ria, rma, + qwd->results, + qwr->baseline_after, qwr->baseline_before, + qwr->after, qwr->before, qwr->points, + qwr->options, qwr->time_group_method, qwr->time_group_options, qwr->tier, qwd->shifts, + &qwd->stats, qwd->register_zero + ); + break; + } + + qwd->timings.executed_ut = now_monotonic_usec(); + if(qwd->timings.executed_ut - qwd->timings.received_ut > qwd->timeout_us) { + qwd->timed_out = true; + return -1; + } + + return 1; +} + +static ssize_t weights_do_context_callback(void *data, RRDCONTEXT_ACQUIRED *rca, bool queryable_context) { + if(!queryable_context) + return false; + + struct query_weights_data *qwd = data; + + bool has_retention = false; + switch(qwd->qwr->method) { + case WEIGHTS_METHOD_VALUE: + case WEIGHTS_METHOD_ANOMALY_RATE: + has_retention = rrdcontext_retention_match(rca, qwd->qwr->after, qwd->qwr->before); + break; + + case WEIGHTS_METHOD_MC_KS2: + case WEIGHTS_METHOD_MC_VOLUME: + has_retention = rrdcontext_retention_match(rca, qwd->qwr->after, qwd->qwr->before); + if(has_retention) + has_retention = rrdcontext_retention_match(rca, qwd->qwr->baseline_after, qwd->qwr->baseline_before); + break; + } + + if(!has_retention) + return 0; + + ssize_t ret = weights_foreach_rrdmetric_in_context(rca, + qwd->instances_sp, + NULL, + qwd->labels_sp, + qwd->alerts_sp, + qwd->dimensions_sp, + true, true, qwd->qwr->version, + weights_for_rrdmetric, qwd); + return ret; +} + +ssize_t weights_do_node_callback(void *data, RRDHOST *host, bool queryable) { + if(!queryable) + return 0; - WEIGHTS_STATS stats = {}; + struct query_weights_data *qwd = data; + + ssize_t ret = query_scope_foreach_context(host, qwd->qwr->scope_contexts, + qwd->scope_contexts_sp, qwd->contexts_sp, + weights_do_context_callback, queryable, qwd); + + return ret; +} + +int web_api_v12_weights(BUFFER *wb, QUERY_WEIGHTS_REQUEST *qwr) { - DICTIONARY *results = register_result_init(); - DICTIONARY *metrics = NULL; char *error = NULL; int resp = HTTP_RESP_OK; // if the user didn't give a timeout // assume 60 seconds - if(!timeout) - timeout = 60 * MSEC_PER_SEC; + if(!qwr->timeout_ms) + qwr->timeout_ms = 5 * 60 * MSEC_PER_SEC; // if the timeout is less than 1 second // make it at least 1 second - if(timeout < (long)(1 * MSEC_PER_SEC)) - timeout = 1 * MSEC_PER_SEC; - - usec_t timeout_usec = timeout * USEC_PER_MS; - usec_t started_usec = now_realtime_usec(); + if(qwr->timeout_ms < (long)(1 * MSEC_PER_SEC)) + qwr->timeout_ms = 1 * MSEC_PER_SEC; + + struct query_weights_data qwd = { + .qwr = qwr, + + .scope_nodes_sp = string_to_simple_pattern(qwr->scope_nodes), + .scope_contexts_sp = string_to_simple_pattern(qwr->scope_contexts), + .nodes_sp = string_to_simple_pattern(qwr->nodes), + .contexts_sp = string_to_simple_pattern(qwr->contexts), + .instances_sp = string_to_simple_pattern(qwr->instances), + .dimensions_sp = string_to_simple_pattern(qwr->dimensions), + .labels_sp = string_to_simple_pattern(qwr->labels), + .alerts_sp = string_to_simple_pattern(qwr->alerts), + .timeout_us = qwr->timeout_ms * USEC_PER_MS, + .timed_out = false, + .examined_dimensions = 0, + .register_zero = true, + .results = register_result_init(), + .stats = {}, + .shifts = 0, + .timings = { + .received_ut = now_monotonic_usec(), + } + }; - if(!rrdr_relative_window_to_absolute(&after, &before)) + if(!rrdr_relative_window_to_absolute(&qwr->after, &qwr->before, NULL)) buffer_no_cacheable(wb); - if (before <= after) { + if (qwr->before <= qwr->after) { resp = HTTP_RESP_BAD_REQUEST; error = "Invalid selected time-range."; goto cleanup; } - uint32_t shifts = 0; - if(method == WEIGHTS_METHOD_MC_KS2 || method == WEIGHTS_METHOD_MC_VOLUME) { - if(!points) points = 500; + if(qwr->method == WEIGHTS_METHOD_MC_KS2 || qwr->method == WEIGHTS_METHOD_MC_VOLUME) { + if(!qwr->points) qwr->points = 500; - if(baseline_before <= API_RELATIVE_TIME_MAX) - baseline_before += after; + if(qwr->baseline_before <= API_RELATIVE_TIME_MAX) + qwr->baseline_before += qwr->after; - rrdr_relative_window_to_absolute(&baseline_after, &baseline_before); + rrdr_relative_window_to_absolute(&qwr->baseline_after, &qwr->baseline_before, NULL); - if (baseline_before <= baseline_after) { + if (qwr->baseline_before <= qwr->baseline_after) { resp = HTTP_RESP_BAD_REQUEST; error = "Invalid baseline time-range."; goto cleanup; } // baseline should be a power of two multiple of highlight - long long base_delta = baseline_before - baseline_after; - long long high_delta = before - after; + long long base_delta = qwr->baseline_before - qwr->baseline_after; + long long high_delta = qwr->before - qwr->after; uint32_t multiplier = (uint32_t)round((double)base_delta / (double)high_delta); // check if the multiplier is a power of two @@ -880,138 +1853,146 @@ int web_api_v1_weights( // we need to do, to divide baseline numbers to match // the highlight ones while(multiplier > 1) { - shifts++; + qwd.shifts++; multiplier = multiplier >> 1; } // if the baseline size will not comply to MAX_POINTS // lower the window of the baseline - while(shifts && (points << shifts) > MAX_POINTS) - shifts--; + while(qwd.shifts && (qwr->points << qwd.shifts) > MAX_POINTS) + qwd.shifts--; // if the baseline size still does not comply to MAX_POINTS // lower the resolution of the highlight and the baseline - while((points << shifts) > MAX_POINTS) - points = points >> 1; + while((qwr->points << qwd.shifts) > MAX_POINTS) + qwr->points = qwr->points >> 1; - if(points < 15) { + if(qwr->points < 15) { resp = HTTP_RESP_BAD_REQUEST; error = "Too few points available, at least 15 are needed."; goto cleanup; } // adjust the baseline to be multiplier times bigger than the highlight - baseline_after = baseline_before - (high_delta << shifts); + qwr->baseline_after = qwr->baseline_before - (high_delta << qwd.shifts); } - size_t examined_dimensions = 0; + if(qwr->options & RRDR_OPTION_NONZERO) { + qwd.register_zero = false; - bool register_zero = true; - if(options & RRDR_OPTION_NONZERO) { - register_zero = false; - options &= ~RRDR_OPTION_NONZERO; + // remove it to run the queries without it + qwr->options &= ~RRDR_OPTION_NONZERO; } - metrics = rrdcontext_all_metrics_to_dict(host, contexts); - struct metric_entry *me; - - // for every metric_entry in the dictionary - dfe_start_read(metrics, me) { - usec_t now_usec = now_realtime_usec(); - if(now_usec - started_usec > timeout_usec) { - error = "timed out"; - resp = HTTP_RESP_GATEWAY_TIMEOUT; - goto cleanup; + if(qwr->host && qwr->version == 1) + weights_do_node_callback(&qwd, qwr->host, true); + else { + if((qwd.qwr->method == WEIGHTS_METHOD_VALUE || qwd.qwr->method == WEIGHTS_METHOD_ANOMALY_RATE) && (qwd.contexts_sp || qwd.scope_contexts_sp)) { + rrdset_weights_multi_dimensional_value(&qwd); } - - examined_dimensions++; - - switch(method) { - case WEIGHTS_METHOD_ANOMALY_RATE: - options |= RRDR_OPTION_ANOMALY_BIT; - rrdset_weights_anomaly_rate( - host, - me->rca, me->ria, me->rma, - results, - after, before, - options, group, group_options, tier, - &stats, register_zero - ); - break; - - case WEIGHTS_METHOD_MC_VOLUME: - rrdset_metric_correlations_volume( - host, - me->rca, me->ria, me->rma, - results, - baseline_after, baseline_before, - after, before, - options, group, group_options, tier, - &stats, register_zero - ); - break; - - default: - case WEIGHTS_METHOD_MC_KS2: - rrdset_metric_correlations_ks2( - host, - me->rca, me->ria, me->rma, - results, - baseline_after, baseline_before, - after, before, points, - options, group, group_options, tier, shifts, - &stats, register_zero - ); - break; + else { + query_scope_foreach_host(qwd.scope_nodes_sp, qwd.nodes_sp, + weights_do_node_callback, &qwd, + &qwd.versions, + NULL); } } - dfe_done(me); - if(!register_zero) - options |= RRDR_OPTION_NONZERO; + if(!qwd.register_zero) { + // put it back, to show it in the response + qwr->options |= RRDR_OPTION_NONZERO; + } + + if(qwd.timed_out) { + error = "timed out"; + resp = HTTP_RESP_GATEWAY_TIMEOUT; + goto cleanup; + } + + if(qwd.interrupted) { + error = "interrupted"; + resp = HTTP_RESP_BACKEND_FETCH_FAILED; + goto cleanup; + } + + if(!qwd.register_zero) + qwr->options |= RRDR_OPTION_NONZERO; - if(!(options & RRDR_OPTION_RETURN_RAW)) - spread_results_evenly(results, &stats); + if(!(qwr->options & RRDR_OPTION_RETURN_RAW) && qwr->method != WEIGHTS_METHOD_VALUE) + spread_results_evenly(qwd.results, &qwd.stats); - usec_t ended_usec = now_realtime_usec(); + usec_t ended_usec = qwd.timings.executed_ut = now_monotonic_usec(); // generate the json output we need buffer_flush(wb); size_t added_dimensions = 0; - switch(format) { + switch(qwr->format) { case WEIGHTS_FORMAT_CHARTS: added_dimensions = registered_results_to_json_charts( - results, wb, - after, before, - baseline_after, baseline_before, - points, method, group, options, shifts, - examined_dimensions, - ended_usec - started_usec, &stats); + qwd.results, wb, + qwr->after, qwr->before, + qwr->baseline_after, qwr->baseline_before, + qwr->points, qwr->method, qwr->time_group_method, qwr->options, qwd.shifts, + qwd.examined_dimensions, + ended_usec - qwd.timings.received_ut, &qwd.stats); break; - default: case WEIGHTS_FORMAT_CONTEXTS: added_dimensions = registered_results_to_json_contexts( - results, wb, - after, before, - baseline_after, baseline_before, - points, method, group, options, shifts, - examined_dimensions, - ended_usec - started_usec, &stats); + qwd.results, wb, + qwr->after, qwr->before, + qwr->baseline_after, qwr->baseline_before, + qwr->points, qwr->method, qwr->time_group_method, qwr->options, qwd.shifts, + qwd.examined_dimensions, + ended_usec - qwd.timings.received_ut, &qwd.stats); + break; + + default: + case WEIGHTS_FORMAT_MULTINODE: + // we don't support these groupings in weights + qwr->group_by.group_by &= ~(RRDR_GROUP_BY_LABEL|RRDR_GROUP_BY_SELECTED|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE); + if(qwr->group_by.group_by == RRDR_GROUP_BY_NONE) { + added_dimensions = + registered_results_to_json_multinode_no_group_by( + qwd.results, wb, + qwr->after, qwr->before, + qwr->baseline_after, qwr->baseline_before, + qwr->points, qwr->method, qwr->time_group_method, qwr->options, qwd.shifts, + qwd.examined_dimensions, + &qwd, &qwd.stats, &qwd.versions); + } + else { + added_dimensions = + registered_results_to_json_multinode_group_by( + qwd.results, wb, + qwr->after, qwr->before, + qwr->baseline_after, qwr->baseline_before, + qwr->points, qwr->method, qwr->time_group_method, qwr->options, qwd.shifts, + qwd.examined_dimensions, + &qwd, &qwd.stats, &qwd.versions); + } break; } - if(!added_dimensions) { + if(!added_dimensions && qwr->version < 2) { error = "no results produced."; resp = HTTP_RESP_NOT_FOUND; } cleanup: - if(metrics) dictionary_destroy(metrics); - if(results) register_result_destroy(results); + simple_pattern_free(qwd.scope_nodes_sp); + simple_pattern_free(qwd.scope_contexts_sp); + simple_pattern_free(qwd.nodes_sp); + simple_pattern_free(qwd.contexts_sp); + simple_pattern_free(qwd.instances_sp); + simple_pattern_free(qwd.dimensions_sp); + simple_pattern_free(qwd.labels_sp); + simple_pattern_free(qwd.alerts_sp); + + register_result_destroy(qwd.results); if(error) { buffer_flush(wb); diff --git a/web/api/queries/weights.h b/web/api/queries/weights.h index 50d8634e..66bea6ab 100644 --- a/web/api/queries/weights.h +++ b/web/api/queries/weights.h @@ -9,22 +9,57 @@ typedef enum { WEIGHTS_METHOD_MC_KS2 = 1, WEIGHTS_METHOD_MC_VOLUME = 2, WEIGHTS_METHOD_ANOMALY_RATE = 3, + WEIGHTS_METHOD_VALUE = 4, } WEIGHTS_METHOD; typedef enum { WEIGHTS_FORMAT_CHARTS = 1, WEIGHTS_FORMAT_CONTEXTS = 2, + WEIGHTS_FORMAT_MULTINODE = 3, } WEIGHTS_FORMAT; extern int enable_metric_correlations; extern int metric_correlations_version; extern WEIGHTS_METHOD default_metric_correlations_method; -int web_api_v1_weights (RRDHOST *host, BUFFER *wb, WEIGHTS_METHOD method, WEIGHTS_FORMAT format, - RRDR_GROUPING group, const char *group_options, - time_t baseline_after, time_t baseline_before, - time_t after, time_t before, - size_t points, RRDR_OPTIONS options, SIMPLE_PATTERN *contexts, size_t tier, size_t timeout); +typedef bool (*weights_interrupt_callback_t)(void *data); + +typedef struct query_weights_request { + size_t version; + RRDHOST *host; + const char *scope_nodes; + const char *scope_contexts; + const char *nodes; + const char *contexts; + const char *instances; + const char *dimensions; + const char *labels; + const char *alerts; + + struct { + RRDR_GROUP_BY group_by; + char *group_by_label; + RRDR_GROUP_BY_FUNCTION aggregation; + } group_by; + + WEIGHTS_METHOD method; + WEIGHTS_FORMAT format; + RRDR_TIME_GROUPING time_group_method; + const char *time_group_options; + time_t baseline_after; + time_t baseline_before; + time_t after; + time_t before; + size_t points; + RRDR_OPTIONS options; + size_t tier; + time_t timeout_ms; + + weights_interrupt_callback_t interrupt_callback; + void *interrupt_callback_data; +} QUERY_WEIGHTS_REQUEST; + +int web_api_v12_weights(BUFFER *wb, QUERY_WEIGHTS_REQUEST *qwr); WEIGHTS_METHOD weights_string_to_method(const char *method); const char *weights_method_to_string(WEIGHTS_METHOD method); diff --git a/web/api/web_api.c b/web/api/web_api.c new file mode 100644 index 00000000..7c1d0fa0 --- /dev/null +++ b/web/api/web_api.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "web_api.h" + +int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_path_endpoint, struct web_api_command *api_commands) { + if(unlikely(!url_path_endpoint || !*url_path_endpoint)) { + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, "Which API command?"); + return HTTP_RESP_BAD_REQUEST; + } + + uint32_t hash = simple_hash(url_path_endpoint); + + for(int i = 0; api_commands[i].command ; i++) { + if(unlikely(hash == api_commands[i].hash && !strcmp(url_path_endpoint, api_commands[i].command))) { + if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NOCHECK) && !(w->acl & api_commands[i].acl)) + return web_client_permission_denied(w); + + char *query_string = (char *)buffer_tostring(w->url_query_string_decoded); + + if(*query_string == '?') + query_string = &query_string[1]; + + return api_commands[i].callback(host, w, query_string); + } + } + + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "Unsupported API command: "); + buffer_strcat_htmlescape(w->response.data, url_path_endpoint); + return HTTP_RESP_NOT_FOUND; +} + +RRDCONTEXT_TO_JSON_OPTIONS rrdcontext_to_json_parse_options(char *o) { + RRDCONTEXT_TO_JSON_OPTIONS options = RRDCONTEXT_OPTION_NONE; + char *tok; + + while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) { + if(!*tok) continue; + + if(!strcmp(tok, "full") || !strcmp(tok, "all")) + options |= RRDCONTEXT_OPTIONS_ALL; + else if(!strcmp(tok, "charts") || !strcmp(tok, "instances")) + options |= RRDCONTEXT_OPTION_SHOW_INSTANCES; + else if(!strcmp(tok, "dimensions") || !strcmp(tok, "metrics")) + options |= RRDCONTEXT_OPTION_SHOW_METRICS; + else if(!strcmp(tok, "queue")) + options |= RRDCONTEXT_OPTION_SHOW_QUEUED; + else if(!strcmp(tok, "flags")) + options |= RRDCONTEXT_OPTION_SHOW_FLAGS; + else if(!strcmp(tok, "uuids")) + options |= RRDCONTEXT_OPTION_SHOW_UUIDS; + else if(!strcmp(tok, "deleted")) + options |= RRDCONTEXT_OPTION_SHOW_DELETED; + else if(!strcmp(tok, "labels")) + options |= RRDCONTEXT_OPTION_SHOW_LABELS; + else if(!strcmp(tok, "deepscan")) + options |= RRDCONTEXT_OPTION_DEEPSCAN; + else if(!strcmp(tok, "hidden")) + options |= RRDCONTEXT_OPTION_SHOW_HIDDEN; + } + + return options; +} + +int web_client_api_request_weights(RRDHOST *host, struct web_client *w, char *url, WEIGHTS_METHOD method, WEIGHTS_FORMAT format, size_t api_version) { + if (!netdata_ready) + return HTTP_RESP_BACKEND_FETCH_FAILED; + + time_t baseline_after = 0, baseline_before = 0, after = 0, before = 0; + size_t points = 0; + RRDR_OPTIONS options = 0; + RRDR_TIME_GROUPING time_group_method = RRDR_GROUPING_AVERAGE; + time_t timeout_ms = 0; + size_t tier = 0; + const char *time_group_options = NULL, *scope_contexts = NULL, *scope_nodes = NULL, *contexts = NULL, *nodes = NULL, + *instances = NULL, *dimensions = NULL, *labels = NULL, *alerts = NULL; + + struct group_by_pass group_by = { + .group_by = RRDR_GROUP_BY_NONE, + .group_by_label = NULL, + .aggregation = RRDR_GROUP_BY_FUNCTION_AVERAGE, + }; + + while (url) { + char *value = strsep_skip_consecutive_separators(&url, "&"); + if (!value || !*value) + continue; + + char *name = strsep_skip_consecutive_separators(&value, "="); + if (!name || !*name) + continue; + if (!value || !*value) + continue; + + if (!strcmp(name, "baseline_after")) + baseline_after = str2l(value); + + else if (!strcmp(name, "baseline_before")) + baseline_before = str2l(value); + + else if (!strcmp(name, "after") || !strcmp(name, "highlight_after")) + after = str2l(value); + + else if (!strcmp(name, "before") || !strcmp(name, "highlight_before")) + before = str2l(value); + + else if (!strcmp(name, "points") || !strcmp(name, "max_points")) + points = str2ul(value); + + else if (!strcmp(name, "timeout")) + timeout_ms = str2l(value); + + else if((api_version == 1 && !strcmp(name, "group")) || (api_version >= 2 && !strcmp(name, "time_group"))) + time_group_method = time_grouping_parse(value, RRDR_GROUPING_AVERAGE); + + else if((api_version == 1 && !strcmp(name, "group_options")) || (api_version >= 2 && !strcmp(name, "time_group_options"))) + time_group_options = value; + + else if(!strcmp(name, "options")) + options |= web_client_api_request_v1_data_options(value); + + else if(!strcmp(name, "method")) + method = weights_string_to_method(value); + + else if(api_version == 1 && (!strcmp(name, "context") || !strcmp(name, "contexts"))) + scope_contexts = value; + + else if(api_version >= 2 && !strcmp(name, "scope_nodes")) scope_nodes = value; + else if(api_version >= 2 && !strcmp(name, "scope_contexts")) scope_contexts = value; + else if(api_version >= 2 && !strcmp(name, "nodes")) nodes = value; + else if(api_version >= 2 && !strcmp(name, "contexts")) contexts = value; + else if(api_version >= 2 && !strcmp(name, "instances")) instances = value; + else if(api_version >= 2 && !strcmp(name, "dimensions")) dimensions = value; + else if(api_version >= 2 && !strcmp(name, "labels")) labels = value; + else if(api_version >= 2 && !strcmp(name, "alerts")) alerts = value; + else if(api_version >= 2 && (!strcmp(name, "group_by") || !strcmp(name, "group_by[0]"))) { + group_by.group_by = group_by_parse(value); + } + else if(api_version >= 2 && (!strcmp(name, "group_by_label") || !strcmp(name, "group_by_label[0]"))) { + group_by.group_by_label = value; + } + else if(api_version >= 2 && (!strcmp(name, "aggregation") || !strcmp(name, "aggregation[0]"))) { + group_by.aggregation = group_by_aggregate_function_parse(value); + } + + else if(!strcmp(name, "tier")) { + tier = str2ul(value); + if(tier < storage_tiers) + options |= RRDR_OPTION_SELECTED_TIER; + else + tier = 0; + } + } + + if(options == 0) + // the user did not set any options + options = RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NULL2ZERO | RRDR_OPTION_NONZERO; + else + // the user set some options, add also these + options |= RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NULL2ZERO; + + if(options & RRDR_OPTION_PERCENTAGE) + options |= RRDR_OPTION_ABSOLUTE; + + if(options & RRDR_OPTION_DEBUG) + options &= ~RRDR_OPTION_MINIFY; + + BUFFER *wb = w->response.data; + buffer_flush(wb); + wb->content_type = CT_APPLICATION_JSON; + + QUERY_WEIGHTS_REQUEST qwr = { + .version = api_version, + .host = (api_version == 1) ? NULL : host, + .scope_nodes = scope_nodes, + .scope_contexts = scope_contexts, + .nodes = nodes, + .contexts = contexts, + .instances = instances, + .dimensions = dimensions, + .labels = labels, + .alerts = alerts, + .group_by = { + .group_by = group_by.group_by, + .group_by_label = group_by.group_by_label, + .aggregation = group_by.aggregation, + }, + .method = method, + .format = format, + .time_group_method = time_group_method, + .time_group_options = time_group_options, + .baseline_after = baseline_after, + .baseline_before = baseline_before, + .after = after, + .before = before, + .points = points, + .options = options, + .tier = tier, + .timeout_ms = timeout_ms, + + .interrupt_callback = web_client_interrupt_callback, + .interrupt_callback_data = w, + }; + + return web_api_v12_weights(wb, &qwr); +} + +bool web_client_interrupt_callback(void *data) { + struct web_client *w = data; + + if(w->interrupt.callback) + return w->interrupt.callback(w, w->interrupt.callback_data); + + return sock_has_output_error(w->ofd); +} diff --git a/web/api/web_api.h b/web/api/web_api.h new file mode 100644 index 00000000..0ca91841 --- /dev/null +++ b/web/api/web_api.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_WEB_API_H +#define NETDATA_WEB_API_H 1 + +#include "daemon/common.h" +#include "web/api/badges/web_buffer_svg.h" +#include "web/api/formatters/rrd2json.h" +#include "web/api/health/health_cmdapi.h" +#include "web/api/queries/weights.h" + +struct web_api_command { + const char *command; + uint32_t hash; + WEB_CLIENT_ACL acl; + int (*callback)(RRDHOST *host, struct web_client *w, char *url); +}; + +struct web_client; + +int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_path_endpoint, struct web_api_command *api_commands); + +static inline void fix_google_param(char *s) { + if(unlikely(!s || !*s)) return; + + for( ; *s ;s++) { + if(!isalnum(*s) && *s != '.' && *s != '_' && *s != '-') + *s = '_'; + } +} + +int web_client_api_request_weights(RRDHOST *host, struct web_client *w, char *url, WEIGHTS_METHOD method, WEIGHTS_FORMAT format, size_t api_version); + +bool web_client_interrupt_callback(void *data); + +#include "web_api_v1.h" +#include "web_api_v2.h" + +#endif //NETDATA_WEB_API_H diff --git a/web/api/web_api_v1.c b/web/api/web_api_v1.c index 1b38a33b..6e23549d 100644 --- a/web/api/web_api_v1.c +++ b/web/api/web_api_v1.c @@ -41,7 +41,12 @@ 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} + , {"details" , 0 , RRDR_OPTION_SHOW_DETAILS} + , {"debug" , 0 , RRDR_OPTION_DEBUG} + , {"plan" , 0 , RRDR_OPTION_DEBUG} + , {"minify" , 0 , RRDR_OPTION_MINIFY} + , {"group-by-labels" , 0 , RRDR_OPTION_GROUP_BY_LABELS} + , {"label-quotes" , 0 , RRDR_OPTION_LABEL_QUOTES} , {NULL , 0 , 0} }; @@ -53,6 +58,7 @@ static struct { { DATASOURCE_FORMAT_DATATABLE_JSON , 0 , DATASOURCE_DATATABLE_JSON} , {DATASOURCE_FORMAT_DATATABLE_JSONP, 0 , DATASOURCE_DATATABLE_JSONP} , {DATASOURCE_FORMAT_JSON , 0 , DATASOURCE_JSON} + , {DATASOURCE_FORMAT_JSON2 , 0 , DATASOURCE_JSON2} , {DATASOURCE_FORMAT_JSONP , 0 , DATASOURCE_JSONP} , {DATASOURCE_FORMAT_SSV , 0 , DATASOURCE_SSV} , {DATASOURCE_FORMAT_CSV , 0 , DATASOURCE_CSV} @@ -63,7 +69,9 @@ static struct { , {DATASOURCE_FORMAT_SSV_COMMA , 0 , DATASOURCE_SSV_COMMA} , {DATASOURCE_FORMAT_CSV_JSON_ARRAY , 0 , DATASOURCE_CSV_JSON_ARRAY} , {DATASOURCE_FORMAT_CSV_MARKDOWN , 0 , DATASOURCE_CSV_MARKDOWN} - , { NULL, 0, 0} + + // terminator + , {NULL, 0, 0} }; static struct { @@ -92,7 +100,7 @@ void web_client_api_v1_init(void) { for(i = 0; api_v1_data_google_formats[i].name ; i++) api_v1_data_google_formats[i].hash = simple_hash(api_v1_data_google_formats[i].name); - web_client_api_v1_init_grouping(); + time_grouping_init(); uuid_t uuid; @@ -170,7 +178,7 @@ inline RRDR_OPTIONS web_client_api_request_v1_data_options(char *o) { RRDR_OPTIONS ret = 0x00000000; char *tok; - while(o && *o && (tok = mystrsep(&o, ", |"))) { + while(o && *o && (tok = strsep_skip_consecutive_separators(&o, ", |"))) { if(!*tok) continue; uint32_t hash = simple_hash(tok); @@ -186,20 +194,20 @@ inline RRDR_OPTIONS web_client_api_request_v1_data_options(char *o) { return ret; } -void web_client_api_request_v1_data_options_to_buffer(BUFFER *wb, RRDR_OPTIONS options) { +void web_client_api_request_v1_data_options_to_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options) { + buffer_json_member_add_array(wb, key); + RRDR_OPTIONS used = 0; // to prevent adding duplicates - int added = 0; for(int i = 0; api_v1_data_options[i].name ; i++) { if (unlikely((api_v1_data_options[i].value & options) && !(api_v1_data_options[i].value & used))) { const char *name = api_v1_data_options[i].name; used |= api_v1_data_options[i].value; - if(added) buffer_strcat(wb, ","); - buffer_strcat(wb, name); - - added++; + buffer_json_add_array_item_string(wb, name); } } + + buffer_json_array_close(wb); } void web_client_api_request_v1_data_options_to_string(char *buf, size_t size, RRDR_OPTIONS options) { @@ -254,11 +262,11 @@ inline uint32_t web_client_api_request_v1_data_google_format(char *name) { int web_client_api_request_v1_alarms_select (char *url) { int all = 0; while(url) { - char *value = mystrsep(&url, "&"); + char *value = strsep_skip_consecutive_separators(&url, "&"); if (!value || !*value) continue; - if(!strcmp(value, "all")) all = 1; - else if(!strcmp(value, "active")) all = 0; + if(!strcmp(value, "all") || !strcmp(value, "all=true")) all = 1; + else if(!strcmp(value, "active") || !strcmp(value, "active=true")) all = 0; } return all; @@ -268,7 +276,7 @@ inline int web_client_api_request_v1_alarms(RRDHOST *host, struct web_client *w, int all = web_client_api_request_v1_alarms_select(url); buffer_flush(w->response.data); - w->response.data->contenttype = CT_APPLICATION_JSON; + w->response.data->content_type = CT_APPLICATION_JSON; health_alarms2json(host, w->response.data, all); buffer_no_cacheable(w->response.data); return HTTP_RESP_OK; @@ -278,7 +286,7 @@ inline int web_client_api_request_v1_alarms_values(RRDHOST *host, struct web_cli int all = web_client_api_request_v1_alarms_select(url); buffer_flush(w->response.data); - w->response.data->contenttype = CT_APPLICATION_JSON; + w->response.data->content_type = CT_APPLICATION_JSON; health_alarms_values2json(host, w->response.data, all); buffer_no_cacheable(w->response.data); return HTTP_RESP_OK; @@ -292,10 +300,10 @@ inline int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_clien buffer_sprintf(w->response.data, "["); while(url) { - char *value = mystrsep(&url, "&"); + char *value = strsep_skip_consecutive_separators(&url, "&"); if(!value || !*value) continue; - char *name = mystrsep(&value, "="); + char *name = strsep_skip_consecutive_separators(&value, "="); if(!name || !*name) continue; if(!value || !*value) continue; @@ -321,7 +329,7 @@ inline int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_clien health_aggregate_alarms(host, w->response.data, contexts, status); buffer_sprintf(w->response.data, "]\n"); - w->response.data->contenttype = CT_APPLICATION_JSON; + w->response.data->content_type = CT_APPLICATION_JSON; buffer_no_cacheable(w->response.data); buffer_free(contexts); @@ -333,10 +341,10 @@ inline int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client char *chart = NULL; while(url) { - char *value = mystrsep(&url, "&"); + char *value = strsep_skip_consecutive_separators(&url, "&"); if (!value || !*value) continue; - char *name = mystrsep(&value, "="); + char *name = strsep_skip_consecutive_separators(&value, "="); if(!name || !*name) continue; if(!value || !*value) continue; @@ -345,7 +353,7 @@ inline int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client } buffer_flush(w->response.data); - w->response.data->contenttype = CT_APPLICATION_JSON; + w->response.data->content_type = CT_APPLICATION_JSON; health_alarm_log2json(host, w->response.data, after, chart); return HTTP_RESP_OK; } @@ -357,10 +365,10 @@ inline int web_client_api_request_single_chart(RRDHOST *host, struct web_client buffer_flush(w->response.data); while(url) { - char *value = mystrsep(&url, "&"); + char *value = strsep_skip_consecutive_separators(&url, "&"); if(!value || !*value) continue; - char *name = mystrsep(&value, "="); + char *name = strsep_skip_consecutive_separators(&value, "="); if(!name || !*name) continue; if(!value || !*value) continue; @@ -388,7 +396,7 @@ inline int web_client_api_request_single_chart(RRDHOST *host, struct web_client goto cleanup; } - w->response.data->contenttype = CT_APPLICATION_JSON; + w->response.data->content_type = CT_APPLICATION_JSON; st->last_accessed_time_s = now_realtime_sec(); callback(st, w->response.data); return HTTP_RESP_OK; @@ -401,38 +409,6 @@ inline int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_c return web_client_api_request_single_chart(host, w, url, health_api_v1_chart_variables2json); } -static RRDCONTEXT_TO_JSON_OPTIONS rrdcontext_to_json_parse_options(char *o) { - RRDCONTEXT_TO_JSON_OPTIONS options = RRDCONTEXT_OPTION_NONE; - char *tok; - - while(o && *o && (tok = mystrsep(&o, ", |"))) { - if(!*tok) continue; - - if(!strcmp(tok, "full") || !strcmp(tok, "all")) - options |= RRDCONTEXT_OPTIONS_ALL; - else if(!strcmp(tok, "charts") || !strcmp(tok, "instances")) - options |= RRDCONTEXT_OPTION_SHOW_INSTANCES; - else if(!strcmp(tok, "dimensions") || !strcmp(tok, "metrics")) - options |= RRDCONTEXT_OPTION_SHOW_METRICS; - else if(!strcmp(tok, "queue")) - options |= RRDCONTEXT_OPTION_SHOW_QUEUED; - else if(!strcmp(tok, "flags")) - options |= RRDCONTEXT_OPTION_SHOW_FLAGS; - else if(!strcmp(tok, "uuids")) - options |= RRDCONTEXT_OPTION_SHOW_UUIDS; - else if(!strcmp(tok, "deleted")) - options |= RRDCONTEXT_OPTION_SHOW_DELETED; - else if(!strcmp(tok, "labels")) - options |= RRDCONTEXT_OPTION_SHOW_LABELS; - else if(!strcmp(tok, "deepscan")) - options |= RRDCONTEXT_OPTION_DEEPSCAN; - else if(!strcmp(tok, "hidden")) - options |= RRDCONTEXT_OPTION_SHOW_HIDDEN; - } - - return options; -} - static int web_client_api_request_v1_context(RRDHOST *host, struct web_client *w, char *url) { char *context = NULL; RRDCONTEXT_TO_JSON_OPTIONS options = RRDCONTEXT_OPTION_NONE; @@ -443,10 +419,10 @@ static int web_client_api_request_v1_context(RRDHOST *host, struct web_client *w buffer_flush(w->response.data); while(url) { - char *value = mystrsep(&url, "&"); + char *value = strsep_skip_consecutive_separators(&url, "&"); if(!value || !*value) continue; - char *name = mystrsep(&value, "="); + char *name = strsep_skip_consecutive_separators(&value, "="); if(!name || !*name) continue; if(!value || !*value) continue; @@ -476,17 +452,19 @@ static int web_client_api_request_v1_context(RRDHOST *host, struct web_client *w SIMPLE_PATTERN *chart_dimensions_pattern = NULL; if(chart_label_key) - chart_label_key_pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT); + chart_label_key_pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, true); if(chart_labels_filter) - chart_labels_filter_pattern = simple_pattern_create(chart_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT); + chart_labels_filter_pattern = simple_pattern_create(chart_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, + true); if(dimensions) { - chart_dimensions_pattern = simple_pattern_create(buffer_tostring(dimensions), ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT); + chart_dimensions_pattern = simple_pattern_create(buffer_tostring(dimensions), ",|\t\r\n\f\v", + SIMPLE_PATTERN_EXACT, true); buffer_free(dimensions); } - w->response.data->contenttype = CT_APPLICATION_JSON; + w->response.data->content_type = CT_APPLICATION_JSON; int ret = rrdcontext_to_json(host, w->response.data, after, before, options, context, chart_label_key_pattern, chart_labels_filter_pattern, chart_dimensions_pattern); simple_pattern_free(chart_label_key_pattern); @@ -505,10 +483,10 @@ static int web_client_api_request_v1_contexts(RRDHOST *host, struct web_client * buffer_flush(w->response.data); while(url) { - char *value = mystrsep(&url, "&"); + char *value = strsep_skip_consecutive_separators(&url, "&"); if(!value || !*value) continue; - char *name = mystrsep(&value, "="); + char *name = strsep_skip_consecutive_separators(&value, "="); if(!name || !*name) continue; if(!value || !*value) continue; @@ -532,17 +510,19 @@ static int web_client_api_request_v1_contexts(RRDHOST *host, struct web_client * SIMPLE_PATTERN *chart_dimensions_pattern = NULL; if(chart_label_key) - chart_label_key_pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT); + chart_label_key_pattern = simple_pattern_create(chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, true); if(chart_labels_filter) - chart_labels_filter_pattern = simple_pattern_create(chart_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT); + chart_labels_filter_pattern = simple_pattern_create(chart_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT, + true); if(dimensions) { - chart_dimensions_pattern = simple_pattern_create(buffer_tostring(dimensions), ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT); + chart_dimensions_pattern = simple_pattern_create(buffer_tostring(dimensions), ",|\t\r\n\f\v", + SIMPLE_PATTERN_EXACT, true); buffer_free(dimensions); } - w->response.data->contenttype = CT_APPLICATION_JSON; + w->response.data->content_type = CT_APPLICATION_JSON; int ret = rrdcontexts_to_json(host, w->response.data, after, before, options, chart_label_key_pattern, chart_labels_filter_pattern, chart_dimensions_pattern); simple_pattern_free(chart_label_key_pattern); @@ -556,7 +536,7 @@ inline int web_client_api_request_v1_charts(RRDHOST *host, struct web_client *w, (void)url; buffer_flush(w->response.data); - w->response.data->contenttype = CT_APPLICATION_JSON; + w->response.data->content_type = CT_APPLICATION_JSON; charts2json(host, w->response.data, 0, 0); return HTTP_RESP_OK; } @@ -565,18 +545,8 @@ inline int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, return web_client_api_request_single_chart(host, w, url, rrd_stats_api_v1_chart); } -void fix_google_param(char *s) { - if(unlikely(!s)) return; - - for( ; *s ;s++) { - if(!isalnum(*s) && *s != '.' && *s != '_' && *s != '-') - *s = '_'; - } -} - - // returns the HTTP code -inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url) { +static inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, char *url) { debug(D_WEB_CLIENT, "%llu: API v1 data with URL '%s'", w->id, url); int ret = HTTP_RESP_BAD_REQUEST; @@ -604,15 +574,15 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c char *chart_labels_filter = NULL; char *group_options = NULL; size_t tier = 0; - RRDR_GROUPING group = RRDR_GROUPING_AVERAGE; + RRDR_TIME_GROUPING group = RRDR_GROUPING_AVERAGE; DATASOURCE_FORMAT format = DATASOURCE_JSON; RRDR_OPTIONS options = 0; while(url) { - char *value = mystrsep(&url, "&"); + char *value = strsep_skip_consecutive_separators(&url, "&"); if(!value || !*value) continue; - char *name = mystrsep(&value, "="); + char *name = strsep_skip_consecutive_separators(&value, "="); if(!name || !*name) continue; if(!value || !*value) continue; @@ -638,7 +608,7 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c else if(!strcmp(name, "gtime")) group_time_str = value; else if(!strcmp(name, "group_options")) group_options = value; else if(!strcmp(name, "group")) { - group = web_client_api_request_v1_data_group(value, RRDR_GROUPING_AVERAGE); + group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE); } else if(!strcmp(name, "format")) { format = web_client_api_request_v1_data_format(value); @@ -658,10 +628,10 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c char *tqx_name, *tqx_value; while(value) { - tqx_value = mystrsep(&value, ";"); + tqx_value = strsep_skip_consecutive_separators(&value, ";"); if(!tqx_value || !*tqx_value) continue; - tqx_name = mystrsep(&tqx_value, ":"); + tqx_name = strsep_skip_consecutive_separators(&tqx_value, ":"); if(!tqx_name || !*tqx_name) continue; if(!tqx_value || !*tqx_value) continue; @@ -722,26 +692,29 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c long group_time = (group_time_str && *group_time_str)?str2l(group_time_str):0; QUERY_TARGET_REQUEST qtr = { + .version = 1, .after = after, .before = before, .host = host, .st = st, - .hosts = NULL, + .nodes = NULL, .contexts = context, - .charts = chart, + .instances = chart, .dimensions = (dimensions)?buffer_tostring(dimensions):NULL, - .timeout = timeout, + .timeout_ms = timeout, .points = points, .format = format, .options = options, - .group_method = group, - .group_options = group_options, + .time_group_method = group, + .time_group_options = group_options, .resampling_time = group_time, .tier = tier, .chart_label_key = chart_label_key, - .charts_labels_filter = chart_labels_filter, + .labels = chart_labels_filter, .query_source = QUERY_SOURCE_API_DATA, .priority = STORAGE_PRIORITY_NORMAL, + .interrupt_callback = web_client_interrupt_callback, + .interrupt_callback_data = w, }; qt = query_target_create(&qtr); @@ -751,22 +724,12 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c goto cleanup; } - if (timeout) { - struct timeval now; - now_realtime_timeval(&now); - int inqueue = (int)dt_usec(&w->tv_in, &now) / 1000; - timeout -= inqueue; - if (timeout <= 0) { - buffer_flush(w->response.data); - buffer_strcat(w->response.data, "Query timeout exceeded"); - ret = HTTP_RESP_BACKEND_FETCH_FAILED; - goto cleanup; - } + web_client_timeout_checkpoint_set(w, timeout); + if(web_client_timeout_checkpoint_and_check(w, NULL)) { + ret = w->response.code; + goto cleanup; } - debug(D_WEB_CLIENT, "%llu: API command 'data' for chart '%s', dimensions '%s', after '%lld', before '%lld', points '%d', group '%u', format '%u', options '0x%08x'" - , w->id, chart, (dimensions)?buffer_tostring(dimensions):"", after, before , points, group, format, options); - if(outFileName && *outFileName) { buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName); debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName); @@ -814,10 +777,7 @@ inline int web_client_api_request_v1_data(RRDHOST *host, struct web_client *w, c buffer_strcat(w->response.data, ");"); cleanup: - if(qt && qt->used) { - internal_error(true, "QUERY_TARGET: left non-released on query '%s'", qt->id); - query_target_release(qt); - } + query_target_release(qt); onewayalloc_destroy(owa); buffer_free(dimensions); return ret; @@ -886,10 +846,10 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client * buffer_no_cacheable(w->response.data); while(url) { - char *value = mystrsep(&url, "&"); + char *value = strsep_skip_consecutive_separators(&url, "&"); if (!value || !*value) continue; - char *name = mystrsep(&value, "="); + char *name = strsep_skip_consecutive_separators(&value, "="); if (!name || !*name) continue; if (!value || !*value) continue; @@ -1013,8 +973,10 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client * } } -static inline void web_client_api_request_v1_info_summary_alarm_statuses(RRDHOST *host, BUFFER *wb) { - int alarm_normal = 0, alarm_warn = 0, alarm_crit = 0; +void web_client_api_request_v1_info_summary_alarm_statuses(RRDHOST *host, BUFFER *wb, const char *key) { + buffer_json_member_add_object(wb, key); + + size_t normal = 0, warning = 0, critical = 0; RRDCALC *rc; foreach_rrdcalc_in_rrdhost_read(host, rc) { if(unlikely(!rc->rrdset || !rc->rrdset->last_collected_time.tv_sec)) @@ -1022,274 +984,217 @@ static inline void web_client_api_request_v1_info_summary_alarm_statuses(RRDHOST switch(rc->status) { case RRDCALC_STATUS_WARNING: - alarm_warn++; + warning++; break; case RRDCALC_STATUS_CRITICAL: - alarm_crit++; + critical++; break; default: - alarm_normal++; + normal++; } } foreach_rrdcalc_in_rrdhost_done(rc); - buffer_sprintf(wb, "\t\t\"normal\": %d,\n", alarm_normal); - buffer_sprintf(wb, "\t\t\"warning\": %d,\n", alarm_warn); - buffer_sprintf(wb, "\t\t\"critical\": %d\n", alarm_crit); + + buffer_json_member_add_uint64(wb, "normal", normal); + buffer_json_member_add_uint64(wb, "warning", warning); + buffer_json_member_add_uint64(wb, "critical", critical); + + buffer_json_object_close(wb); +} + +static inline void web_client_api_request_v1_info_mirrored_hosts_status(BUFFER *wb, RRDHOST *host) { + buffer_json_add_array_item_object(wb); + + buffer_json_member_add_string(wb, "hostname", rrdhost_hostname(host)); + buffer_json_member_add_uint64(wb, "hops", host->system_info ? host->system_info->hops : (host == localhost) ? 0 : 1); + buffer_json_member_add_boolean(wb, "reachable", (host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))); + + buffer_json_member_add_string(wb, "guid", host->machine_guid); + buffer_json_member_add_uuid(wb, "node_id", host->node_id); + rrdhost_aclk_state_lock(host); + buffer_json_member_add_string(wb, "claim_id", host->aclk_state.claimed_id); + rrdhost_aclk_state_unlock(host); + + buffer_json_object_close(wb); } static inline void web_client_api_request_v1_info_mirrored_hosts(BUFFER *wb) { RRDHOST *host; - int count = 0; - buffer_strcat(wb, "\t\"mirrored_hosts\": [\n"); rrd_rdlock(); - rrdhost_foreach_read(host) { - if (count > 0) - buffer_strcat(wb, ",\n"); - buffer_sprintf(wb, "\t\t\"%s\"", rrdhost_hostname(host)); - count++; - } - - buffer_strcat(wb, "\n\t],\n\t\"mirrored_hosts_status\": [\n"); - count = 0; + buffer_json_member_add_array(wb, "mirrored_hosts"); rrdhost_foreach_read(host) - { - if (count > 0) - buffer_strcat(wb, ",\n"); + buffer_json_add_array_item_string(wb, rrdhost_hostname(host)); + buffer_json_array_close(wb); - buffer_sprintf( - wb, "\t\t{ \"guid\": \"%s\", \"hostname\": \"%s\", \"reachable\": %s, \"hops\": %d" - , host->machine_guid - , rrdhost_hostname(host) - , (host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN)) ? "true" : "false" - , host->system_info ? host->system_info->hops : (host == localhost) ? 0 : 1 - ); - - rrdhost_aclk_state_lock(host); - if (host->aclk_state.claimed_id) - buffer_sprintf(wb, ", \"claim_id\": \"%s\"", host->aclk_state.claimed_id); - else - buffer_strcat(wb, ", \"claim_id\": null"); - rrdhost_aclk_state_unlock(host); - - if (host->node_id) { - char node_id_str[GUID_LEN + 1]; - uuid_unparse_lower(*host->node_id, node_id_str); - buffer_sprintf(wb, ", \"node_id\": \"%s\" }", node_id_str); - } else - buffer_strcat(wb, ", \"node_id\": null }"); - - count++; + buffer_json_member_add_array(wb, "mirrored_hosts_status"); + rrdhost_foreach_read(host) { + if ((host == localhost || !rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) { + web_client_api_request_v1_info_mirrored_hosts_status(wb, host); + } } + rrdhost_foreach_read(host) { + if ((host != localhost && rrdhost_flag_check(host, RRDHOST_FLAG_ORPHAN))) { + web_client_api_request_v1_info_mirrored_hosts_status(wb, host); + } + } + buffer_json_array_close(wb); + rrd_unlock(); +} - buffer_strcat(wb, "\n\t],\n"); +void host_labels2json(RRDHOST *host, BUFFER *wb, const char *key) { + buffer_json_member_add_object(wb, key); + rrdlabels_to_buffer_json_members(host->rrdlabels, wb); + buffer_json_object_close(wb); } -inline void host_labels2json(RRDHOST *host, BUFFER *wb, size_t indentation) { - char tabs[11]; +static void host_collectors(RRDHOST *host, BUFFER *wb) { + buffer_json_member_add_array(wb, "collectors"); + + DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE); + RRDSET *st; + char name[500]; - if (indentation > 10) - indentation = 10; + time_t now = now_realtime_sec(); - tabs[0] = '\0'; - while (indentation) { - strcat(tabs, "\t"); - indentation--; + rrdset_foreach_read(st, host) { + if (!rrdset_is_available_for_viewers(st)) + continue; + + sprintf(name, "%s:%s", rrdset_plugin_name(st), rrdset_module_name(st)); + + bool old = 0; + bool *set = dictionary_set(dict, name, &old, sizeof(bool)); + if(!*set) { + *set = true; + st->last_accessed_time_s = now; + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "plugin", rrdset_plugin_name(st)); + buffer_json_member_add_string(wb, "module", rrdset_module_name(st)); + buffer_json_object_close(wb); + } } + rrdset_foreach_done(st); + dictionary_destroy(dict); - rrdlabels_to_buffer(host->rrdlabels, wb, tabs, ":", "\"", ",\n", NULL, NULL, NULL, NULL); - buffer_strcat(wb, "\n"); + buffer_json_array_close(wb); } extern int aclk_connected; -inline int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb) -{ - buffer_strcat(wb, "{\n"); - buffer_sprintf(wb, "\t\"version\": \"%s\",\n", rrdhost_program_version(host)); - buffer_sprintf(wb, "\t\"uid\": \"%s\",\n", host->machine_guid); +inline int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb) { + buffer_json_initialize(wb, "\"", "\"", 0, true, false); + buffer_json_member_add_string(wb, "version", rrdhost_program_version(host)); + buffer_json_member_add_string(wb, "uid", host->machine_guid); + + buffer_json_member_add_uint64(wb, "hosts-available", rrdhost_hosts_available()); web_client_api_request_v1_info_mirrored_hosts(wb); - buffer_strcat(wb, "\t\"alarms\": {\n"); - web_client_api_request_v1_info_summary_alarm_statuses(host, wb); - buffer_strcat(wb, "\t},\n"); - - buffer_sprintf(wb, "\t\"os_name\": \"%s\",\n", (host->system_info->host_os_name) ? host->system_info->host_os_name : ""); - buffer_sprintf(wb, "\t\"os_id\": \"%s\",\n", (host->system_info->host_os_id) ? host->system_info->host_os_id : ""); - buffer_sprintf(wb, "\t\"os_id_like\": \"%s\",\n", (host->system_info->host_os_id_like) ? host->system_info->host_os_id_like : ""); - buffer_sprintf(wb, "\t\"os_version\": \"%s\",\n", (host->system_info->host_os_version) ? host->system_info->host_os_version : ""); - buffer_sprintf(wb, "\t\"os_version_id\": \"%s\",\n", (host->system_info->host_os_version_id) ? host->system_info->host_os_version_id : ""); - buffer_sprintf(wb, "\t\"os_detection\": \"%s\",\n", (host->system_info->host_os_detection) ? host->system_info->host_os_detection : ""); - buffer_sprintf(wb, "\t\"cores_total\": \"%s\",\n", (host->system_info->host_cores) ? host->system_info->host_cores : ""); - buffer_sprintf(wb, "\t\"total_disk_space\": \"%s\",\n", (host->system_info->host_disk_space) ? host->system_info->host_disk_space : ""); - buffer_sprintf(wb, "\t\"cpu_freq\": \"%s\",\n", (host->system_info->host_cpu_freq) ? host->system_info->host_cpu_freq : ""); - buffer_sprintf(wb, "\t\"ram_total\": \"%s\",\n", (host->system_info->host_ram_total) ? host->system_info->host_ram_total : ""); - - if (host->system_info->container_os_name) - buffer_sprintf(wb, "\t\"container_os_name\": \"%s\",\n", host->system_info->container_os_name); - if (host->system_info->container_os_id) - buffer_sprintf(wb, "\t\"container_os_id\": \"%s\",\n", host->system_info->container_os_id); - if (host->system_info->container_os_id_like) - buffer_sprintf(wb, "\t\"container_os_id_like\": \"%s\",\n", host->system_info->container_os_id_like); - if (host->system_info->container_os_version) - buffer_sprintf(wb, "\t\"container_os_version\": \"%s\",\n", host->system_info->container_os_version); - if (host->system_info->container_os_version_id) - buffer_sprintf(wb, "\t\"container_os_version_id\": \"%s\",\n", host->system_info->container_os_version_id); - if (host->system_info->container_os_detection) - buffer_sprintf(wb, "\t\"container_os_detection\": \"%s\",\n", host->system_info->container_os_detection); - if (host->system_info->is_k8s_node) - buffer_sprintf(wb, "\t\"is_k8s_node\": \"%s\",\n", host->system_info->is_k8s_node); - - buffer_sprintf(wb, "\t\"kernel_name\": \"%s\",\n", (host->system_info->kernel_name) ? host->system_info->kernel_name : ""); - buffer_sprintf(wb, "\t\"kernel_version\": \"%s\",\n", (host->system_info->kernel_version) ? host->system_info->kernel_version : ""); - buffer_sprintf(wb, "\t\"architecture\": \"%s\",\n", (host->system_info->architecture) ? host->system_info->architecture : ""); - buffer_sprintf(wb, "\t\"virtualization\": \"%s\",\n", (host->system_info->virtualization) ? host->system_info->virtualization : ""); - buffer_sprintf(wb, "\t\"virt_detection\": \"%s\",\n", (host->system_info->virt_detection) ? host->system_info->virt_detection : ""); - buffer_sprintf(wb, "\t\"container\": \"%s\",\n", (host->system_info->container) ? host->system_info->container : ""); - buffer_sprintf(wb, "\t\"container_detection\": \"%s\",\n", (host->system_info->container_detection) ? host->system_info->container_detection : ""); - - if (host->system_info->cloud_provider_type) - buffer_sprintf(wb, "\t\"cloud_provider_type\": \"%s\",\n", host->system_info->cloud_provider_type); - if (host->system_info->cloud_instance_type) - buffer_sprintf(wb, "\t\"cloud_instance_type\": \"%s\",\n", host->system_info->cloud_instance_type); - if (host->system_info->cloud_instance_region) - buffer_sprintf(wb, "\t\"cloud_instance_region\": \"%s\",\n", host->system_info->cloud_instance_region); - - buffer_strcat(wb, "\t\"host_labels\": {\n"); - host_labels2json(host, wb, 2); - buffer_strcat(wb, "\t},\n"); - - buffer_strcat(wb, "\t\"functions\": {\n"); - host_functions2json(host, wb, 2, "\"", "\""); - buffer_strcat(wb, "\t},\n"); - - buffer_strcat(wb, "\t\"collectors\": ["); - chartcollectors2json(host, wb); - buffer_strcat(wb, "\n\t],\n"); + web_client_api_request_v1_info_summary_alarm_statuses(host, wb, "alarms"); + + buffer_json_member_add_string_or_empty(wb, "os_name", host->system_info->host_os_name); + buffer_json_member_add_string_or_empty(wb, "os_id", host->system_info->host_os_id); + buffer_json_member_add_string_or_empty(wb, "os_id_like", host->system_info->host_os_id_like); + buffer_json_member_add_string_or_empty(wb, "os_version", host->system_info->host_os_version); + buffer_json_member_add_string_or_empty(wb, "os_version_id", host->system_info->host_os_version_id); + buffer_json_member_add_string_or_empty(wb, "os_detection", host->system_info->host_os_detection); + buffer_json_member_add_string_or_empty(wb, "cores_total", host->system_info->host_cores); + buffer_json_member_add_string_or_empty(wb, "total_disk_space", host->system_info->host_disk_space); + buffer_json_member_add_string_or_empty(wb, "cpu_freq", host->system_info->host_cpu_freq); + buffer_json_member_add_string_or_empty(wb, "ram_total", host->system_info->host_ram_total); + + buffer_json_member_add_string_or_omit(wb, "container_os_name", host->system_info->container_os_name); + buffer_json_member_add_string_or_omit(wb, "container_os_id", host->system_info->container_os_id); + buffer_json_member_add_string_or_omit(wb, "container_os_id_like", host->system_info->container_os_id_like); + buffer_json_member_add_string_or_omit(wb, "container_os_version", host->system_info->container_os_version); + buffer_json_member_add_string_or_omit(wb, "container_os_version_id", host->system_info->container_os_version_id); + buffer_json_member_add_string_or_omit(wb, "container_os_detection", host->system_info->container_os_detection); + buffer_json_member_add_string_or_omit(wb, "is_k8s_node", host->system_info->is_k8s_node); + + buffer_json_member_add_string_or_empty(wb, "kernel_name", host->system_info->kernel_name); + buffer_json_member_add_string_or_empty(wb, "kernel_version", host->system_info->kernel_version); + buffer_json_member_add_string_or_empty(wb, "architecture", host->system_info->architecture); + buffer_json_member_add_string_or_empty(wb, "virtualization", host->system_info->virtualization); + buffer_json_member_add_string_or_empty(wb, "virt_detection", host->system_info->virt_detection); + buffer_json_member_add_string_or_empty(wb, "container", host->system_info->container); + buffer_json_member_add_string_or_empty(wb, "container_detection", host->system_info->container_detection); + + buffer_json_member_add_string_or_omit(wb, "cloud_provider_type", host->system_info->cloud_provider_type); + buffer_json_member_add_string_or_omit(wb, "cloud_instance_type", host->system_info->cloud_instance_type); + buffer_json_member_add_string_or_omit(wb, "cloud_instance_region", host->system_info->cloud_instance_region); + + host_labels2json(host, wb, "host_labels"); + host_functions2json(host, wb); + host_collectors(host, wb); #ifdef DISABLE_CLOUD - buffer_strcat(wb, "\t\"cloud-enabled\": false,\n"); + buffer_json_member_add_boolean(wb, "cloud-enabled", false); #else - buffer_sprintf(wb, "\t\"cloud-enabled\": %s,\n", - appconfig_get_boolean(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", 1) ? "true" : "false"); + buffer_json_member_add_boolean(wb, "cloud-enabled", + appconfig_get_boolean(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", true)); #endif #ifdef ENABLE_ACLK - buffer_strcat(wb, "\t\"cloud-available\": true,\n"); + buffer_json_member_add_boolean(wb, "cloud-available", true); #else - buffer_strcat(wb, "\t\"cloud-available\": false,\n"); + buffer_json_member_add_boolean(wb, "cloud-available", false); #endif + char *agent_id = get_agent_claimid(); - if (agent_id == NULL) - buffer_strcat(wb, "\t\"agent-claimed\": false,\n"); - else { - buffer_strcat(wb, "\t\"agent-claimed\": true,\n"); - freez(agent_id); - } + buffer_json_member_add_boolean(wb, "agent-claimed", agent_id != NULL); + freez(agent_id); + #ifdef ENABLE_ACLK - if (aclk_connected) { - buffer_strcat(wb, "\t\"aclk-available\": true,\n"); - } - else + buffer_json_member_add_boolean(wb, "aclk-available", aclk_connected); +#else + buffer_json_member_add_boolean(wb, "aclk-available", false); #endif - buffer_strcat(wb, "\t\"aclk-available\": false,\n"); // Intentionally valid with/without #ifdef above - - buffer_strcat(wb, "\t\"memory-mode\": "); - analytics_get_data(analytics_data.netdata_config_memory_mode, wb); - buffer_strcat(wb, ",\n"); - - buffer_strcat(wb, "\t\"multidb-disk-quota\": "); - analytics_get_data(analytics_data.netdata_config_multidb_disk_quota, wb); - buffer_strcat(wb, ",\n"); - buffer_strcat(wb, "\t\"page-cache-size\": "); - analytics_get_data(analytics_data.netdata_config_page_cache_size, wb); - buffer_strcat(wb, ",\n"); - - buffer_strcat(wb, "\t\"stream-enabled\": "); - analytics_get_data(analytics_data.netdata_config_stream_enabled, wb); - buffer_strcat(wb, ",\n"); + buffer_json_member_add_string(wb, "memory-mode", rrd_memory_mode_name(host->rrd_memory_mode)); +#ifdef ENABLE_DBENGINE + buffer_json_member_add_uint64(wb, "multidb-disk-quota", default_multidb_disk_quota_mb); + buffer_json_member_add_uint64(wb, "page-cache-size", default_rrdeng_page_cache_mb); +#endif // ENABLE_DBENGINE + buffer_json_member_add_boolean(wb, "web-enabled", web_server_mode != WEB_SERVER_MODE_NONE); + buffer_json_member_add_boolean(wb, "stream-enabled", default_rrdpush_enabled); #ifdef ENABLE_COMPRESSION - if(host->sender){ - buffer_strcat(wb, "\t\"stream-compression\": "); - buffer_strcat(wb, stream_has_capability(host->sender, STREAM_CAP_COMPRESSION) ? "true" : "false"); - buffer_strcat(wb, ",\n"); - }else{ - buffer_strcat(wb, "\t\"stream-compression\": null,\n"); - } + buffer_json_member_add_boolean(wb, "stream-compression", + host->sender && stream_has_capability(host->sender, STREAM_CAP_COMPRESSION)); #else - buffer_strcat(wb, "\t\"stream-compression\": null,\n"); -#endif //ENABLE_COMPRESSION - - buffer_strcat(wb, "\t\"hosts-available\": "); - analytics_get_data(analytics_data.netdata_config_hosts_available, wb); - buffer_strcat(wb, ",\n"); - - buffer_strcat(wb, "\t\"https-enabled\": "); - analytics_get_data(analytics_data.netdata_config_https_enabled, wb); - buffer_strcat(wb, ",\n"); + buffer_json_member_add_boolean(wb, "stream-compression", false); +#endif //ENABLE_COMPRESSION - buffer_strcat(wb, "\t\"buildinfo\": "); - analytics_get_data(analytics_data.netdata_buildinfo, wb); - buffer_strcat(wb, ",\n"); - - buffer_strcat(wb, "\t\"release-channel\": "); - analytics_get_data(analytics_data.netdata_config_release_channel, wb); - buffer_strcat(wb, ",\n"); - - buffer_strcat(wb, "\t\"web-enabled\": "); - analytics_get_data(analytics_data.netdata_config_web_enabled, wb); - buffer_strcat(wb, ",\n"); - - buffer_strcat(wb, "\t\"notification-methods\": "); - analytics_get_data(analytics_data.netdata_notification_methods, wb); - buffer_strcat(wb, ",\n"); - - buffer_strcat(wb, "\t\"exporting-enabled\": "); - analytics_get_data(analytics_data.netdata_config_exporting_enabled, wb); - buffer_strcat(wb, ",\n"); - - buffer_strcat(wb, "\t\"exporting-connectors\": "); - analytics_get_data(analytics_data.netdata_exporting_connectors, wb); - buffer_strcat(wb, ",\n"); - - buffer_strcat(wb, "\t\"allmetrics-prometheus-used\": "); - analytics_get_data(analytics_data.netdata_allmetrics_prometheus_used, wb); - buffer_strcat(wb, ",\n"); - - buffer_strcat(wb, "\t\"allmetrics-shell-used\": "); - analytics_get_data(analytics_data.netdata_allmetrics_shell_used, wb); - buffer_strcat(wb, ",\n"); +#ifdef ENABLE_HTTPS + buffer_json_member_add_boolean(wb, "https-enabled", true); +#else + buffer_json_member_add_boolean(wb, "https-enabled", false); +#endif - buffer_strcat(wb, "\t\"allmetrics-json-used\": "); - analytics_get_data(analytics_data.netdata_allmetrics_json_used, wb); - buffer_strcat(wb, ",\n"); + buffer_json_member_add_quoted_string(wb, "buildinfo", analytics_data.netdata_buildinfo); + buffer_json_member_add_quoted_string(wb, "release-channel", analytics_data.netdata_config_release_channel); + buffer_json_member_add_quoted_string(wb, "notification-methods", analytics_data.netdata_notification_methods); - buffer_strcat(wb, "\t\"dashboard-used\": "); - analytics_get_data(analytics_data.netdata_dashboard_used, wb); - buffer_strcat(wb, ",\n"); + buffer_json_member_add_boolean(wb, "exporting-enabled", analytics_data.exporting_enabled); + buffer_json_member_add_quoted_string(wb, "exporting-connectors", analytics_data.netdata_exporting_connectors); - buffer_strcat(wb, "\t\"charts-count\": "); - analytics_get_data(analytics_data.netdata_charts_count, wb); - buffer_strcat(wb, ",\n"); + buffer_json_member_add_uint64(wb, "allmetrics-prometheus-used", analytics_data.prometheus_hits); + buffer_json_member_add_uint64(wb, "allmetrics-shell-used", analytics_data.shell_hits); + buffer_json_member_add_uint64(wb, "allmetrics-json-used", analytics_data.json_hits); + buffer_json_member_add_uint64(wb, "dashboard-used", analytics_data.dashboard_hits); - buffer_strcat(wb, "\t\"metrics-count\": "); - analytics_get_data(analytics_data.netdata_metrics_count, wb); + buffer_json_member_add_uint64(wb, "charts-count", analytics_data.charts_count); + buffer_json_member_add_uint64(wb, "metrics-count", analytics_data.metrics_count); #if defined(ENABLE_ML) - buffer_strcat(wb, ",\n"); - char *ml_info = ml_get_host_info(host); - - buffer_strcat(wb, "\t\"ml-info\": "); - buffer_strcat(wb, ml_info); - - freez(ml_info); + buffer_json_member_add_object(wb, "ml-info"); + ml_host_get_info(host, wb); + buffer_json_object_close(wb); #endif - buffer_strcat(wb, "\n}"); + buffer_json_finalize(wb); return 0; } @@ -1300,17 +1205,16 @@ int web_client_api_request_v1_ml_info(RRDHOST *host, struct web_client *w, char if (!netdata_ready) return HTTP_RESP_BACKEND_FETCH_FAILED; - char *s = ml_get_host_runtime_info(host); - if (!s) - s = strdupz("{\"error\": \"json string is empty\" }\n"); - BUFFER *wb = w->response.data; buffer_flush(wb); - wb->contenttype = CT_APPLICATION_JSON; - buffer_strcat(wb, s); + wb->content_type = CT_APPLICATION_JSON; + + buffer_json_initialize(wb, "\"", "\"", 0, true, false); + ml_host_get_detection_info(host, wb); + buffer_json_finalize(wb); + buffer_no_cacheable(wb); - freez(s); return HTTP_RESP_OK; } @@ -1320,27 +1224,22 @@ int web_client_api_request_v1_ml_models(RRDHOST *host, struct web_client *w, cha if (!netdata_ready) return HTTP_RESP_BACKEND_FETCH_FAILED; - char *s = ml_get_host_models(host); - if (!s) - s = strdupz("{\"error\": \"json string is empty\" }\n"); - BUFFER *wb = w->response.data; buffer_flush(wb); - wb->contenttype = CT_APPLICATION_JSON; - buffer_strcat(wb, s); + wb->content_type = CT_APPLICATION_JSON; + ml_host_get_models(host, wb); buffer_no_cacheable(wb); - freez(s); return HTTP_RESP_OK; } -#endif +#endif // ENABLE_ML inline int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url) { (void)url; if (!netdata_ready) return HTTP_RESP_BACKEND_FETCH_FAILED; BUFFER *wb = w->response.data; buffer_flush(wb); - wb->contenttype = CT_APPLICATION_JSON; + wb->content_type = CT_APPLICATION_JSON; web_client_api_request_v1_info_fill_buffer(host, wb); @@ -1360,94 +1259,19 @@ static int web_client_api_request_v1_aclk_state(RRDHOST *host, struct web_client buffer_strcat(wb, str); freez(str); - wb->contenttype = CT_APPLICATION_JSON; + wb->content_type = CT_APPLICATION_JSON; buffer_no_cacheable(wb); return HTTP_RESP_OK; } -static int web_client_api_request_v1_weights_internal(RRDHOST *host, struct web_client *w, char *url, WEIGHTS_METHOD method, WEIGHTS_FORMAT format) { - if (!netdata_ready) - return HTTP_RESP_BACKEND_FETCH_FAILED; - - long long baseline_after = 0, baseline_before = 0, after = 0, before = 0, points = 0; - RRDR_OPTIONS options = RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NONZERO | RRDR_OPTION_NULL2ZERO; - int options_count = 0; - RRDR_GROUPING group = RRDR_GROUPING_AVERAGE; - int timeout = 0; - size_t tier = 0; - const char *group_options = NULL, *contexts_str = NULL; - - while (url) { - char *value = mystrsep(&url, "&"); - if (!value || !*value) - continue; - - char *name = mystrsep(&value, "="); - if (!name || !*name) - continue; - if (!value || !*value) - continue; - - if (!strcmp(name, "baseline_after")) - baseline_after = (long long) strtoul(value, NULL, 0); - - else if (!strcmp(name, "baseline_before")) - baseline_before = (long long) strtoul(value, NULL, 0); - - else if (!strcmp(name, "after") || !strcmp(name, "highlight_after")) - after = (long long) strtoul(value, NULL, 0); - - else if (!strcmp(name, "before") || !strcmp(name, "highlight_before")) - before = (long long) strtoul(value, NULL, 0); - - else if (!strcmp(name, "points") || !strcmp(name, "max_points")) - points = (long long) strtoul(value, NULL, 0); - - else if (!strcmp(name, "timeout")) - timeout = (int) strtoul(value, NULL, 0); - - else if(!strcmp(name, "group")) - group = web_client_api_request_v1_data_group(value, RRDR_GROUPING_AVERAGE); - - else if(!strcmp(name, "options")) { - if(!options_count) options = RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NULL2ZERO; - options |= web_client_api_request_v1_data_options(value); - options_count++; - } - - else if(!strcmp(name, "method")) - method = weights_string_to_method(value); - - else if(!strcmp(name, "context") || !strcmp(name, "contexts")) - contexts_str = value; - - else if(!strcmp(name, "tier")) { - tier = str2ul(value); - if(tier < storage_tiers) - options |= RRDR_OPTION_SELECTED_TIER; - else - tier = 0; - } - } - - BUFFER *wb = w->response.data; - buffer_flush(wb); - wb->contenttype = CT_APPLICATION_JSON; - - SIMPLE_PATTERN *contexts = (contexts_str) ? simple_pattern_create(contexts_str, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL; - - int ret = web_api_v1_weights(host, wb, method, format, group, group_options, baseline_after, baseline_before, after, before, points, options, contexts, tier, timeout); - - simple_pattern_free(contexts); - return ret; -} - int web_client_api_request_v1_metric_correlations(RRDHOST *host, struct web_client *w, char *url) { - return web_client_api_request_v1_weights_internal(host, w, url, default_metric_correlations_method, WEIGHTS_FORMAT_CHARTS); + return web_client_api_request_weights(host, w, url, default_metric_correlations_method, + WEIGHTS_FORMAT_CHARTS, 1); } int web_client_api_request_v1_weights(RRDHOST *host, struct web_client *w, char *url) { - return web_client_api_request_v1_weights_internal(host, w, url, WEIGHTS_METHOD_ANOMALY_RATE, WEIGHTS_FORMAT_CONTEXTS); + return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_ANOMALY_RATE, + WEIGHTS_FORMAT_CONTEXTS, 1); } int web_client_api_request_v1_function(RRDHOST *host, struct web_client *w, char *url) { @@ -1458,11 +1282,11 @@ int web_client_api_request_v1_function(RRDHOST *host, struct web_client *w, char const char *function = NULL; while (url) { - char *value = mystrsep(&url, "&"); + char *value = strsep_skip_consecutive_separators(&url, "&"); if (!value || !*value) continue; - char *name = mystrsep(&value, "="); + char *name = strsep_skip_consecutive_separators(&value, "="); if (!name || !*name) continue; @@ -1475,7 +1299,7 @@ int web_client_api_request_v1_function(RRDHOST *host, struct web_client *w, char BUFFER *wb = w->response.data; buffer_flush(wb); - wb->contenttype = CT_APPLICATION_JSON; + wb->content_type = CT_APPLICATION_JSON; buffer_no_cacheable(wb); return rrd_call_function_and_wait(host, wb, timeout, function); @@ -1487,12 +1311,12 @@ int web_client_api_request_v1_functions(RRDHOST *host, struct web_client *w, cha BUFFER *wb = w->response.data; buffer_flush(wb); - wb->contenttype = CT_APPLICATION_JSON; + wb->content_type = CT_APPLICATION_JSON; buffer_no_cacheable(wb); - buffer_strcat(wb, "{\n"); - host_functions2json(host, wb, 1, "\"", "\""); - buffer_strcat(wb, "}"); + buffer_json_initialize(wb, "\"", "\"", 0, true, false); + host_functions2json(host, wb); + buffer_json_finalize(wb); return HTTP_RESP_OK; } @@ -1576,7 +1400,7 @@ int web_client_api_request_v1_dbengine_stats(RRDHOST *host __maybe_unused, struc return HTTP_RESP_NOT_FOUND; } - wb->contenttype = CT_APPLICATION_JSON; + wb->content_type = CT_APPLICATION_JSON; buffer_no_cacheable(wb); buffer_strcat(wb, "{"); for(size_t tier = 0; tier < storage_tiers ;tier++) { @@ -1596,85 +1420,55 @@ int web_client_api_request_v1_dbengine_stats(RRDHOST *host __maybe_unused, struc #define ACL_DEV_OPEN_ACCESS 0 #endif -static struct api_command { - const char *command; - uint32_t hash; - WEB_CLIENT_ACL acl; - int (*callback)(RRDHOST *host, struct web_client *w, char *url); -} api_commands[] = { - { "info", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_info }, - { "data", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_data }, - { "chart", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_chart }, - { "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 }, +static struct web_api_command api_commands_v1[] = { + { "info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_info }, + { "data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_data }, + { "chart", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_chart }, + { "charts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_charts }, + { "context", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_context }, + { "contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_contexts }, // registry checks the ACL by itself, so we allow everything { "registry", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v1_registry }, // badges can be fetched with both dashboard and badge permissions - { "badge.svg", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_BADGE | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_badge }, + { "badge.svg", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC | WEB_CLIENT_ACL_BADGE, web_client_api_request_v1_badge }, - { "alarms", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_alarms }, - { "alarms_values", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_alarms_values }, - { "alarm_log", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_alarm_log }, - { "alarm_variables", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_alarm_variables }, - { "alarm_count", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_alarm_count }, - { "allmetrics", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_allmetrics }, + { "alarms", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarms }, + { "alarms_values", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarms_values }, + { "alarm_log", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_log }, + { "alarm_variables", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_variables }, + { "alarm_count", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_count }, + { "allmetrics", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_allmetrics }, #if defined(ENABLE_ML) - { "ml_info", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_ml_info }, - { "ml_models", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_ml_models }, + { "ml_info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_ml_info }, + { "ml_models", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_ml_models }, #endif { "manage/health", 0, WEB_CLIENT_ACL_MGMT | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_mgmt_health }, - { "aclk", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_aclk_state }, - { "metric_correlations", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_metric_correlations }, - { "weights", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_weights }, + { "aclk", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_aclk_state }, + { "metric_correlations", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_metric_correlations }, + { "weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_weights }, { "function", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_function }, { "functions", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_functions }, - { "dbengine_stats", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v1_dbengine_stats }, + { "dbengine_stats", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_dbengine_stats }, // terminator { NULL, 0, WEB_CLIENT_ACL_NONE, NULL }, }; -inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url) { +inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url_path_endpoint) { static int initialized = 0; - int i; if(unlikely(initialized == 0)) { initialized = 1; - for(i = 0; api_commands[i].command ; i++) - api_commands[i].hash = simple_hash(api_commands[i].command); + for(int i = 0; api_commands_v1[i].command ; i++) + api_commands_v1[i].hash = simple_hash(api_commands_v1[i].command); } - // get the command - if(url) { - debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, url); - uint32_t hash = simple_hash(url); - - for(i = 0; api_commands[i].command ;i++) { - if(unlikely(hash == api_commands[i].hash && !strcmp(url, api_commands[i].command))) { - if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NOCHECK) && !(w->acl & api_commands[i].acl)) - return web_client_permission_denied(w); - - //return api_commands[i].callback(host, w, url); - return api_commands[i].callback(host, w, (w->decoded_query_string + 1)); - } - } - - buffer_flush(w->response.data); - buffer_strcat(w->response.data, "Unsupported v1 API command: "); - buffer_strcat_htmlescape(w->response.data, url); - return HTTP_RESP_NOT_FOUND; - } - else { - buffer_flush(w->response.data); - buffer_sprintf(w->response.data, "Which API v1 command?"); - return HTTP_RESP_BAD_REQUEST; - } + return web_client_api_request_vX(host, w, url_path_endpoint, api_commands_v1); } diff --git a/web/api/web_api_v1.h b/web/api/web_api_v1.h index 9dd6a1c2..6fa8de01 100644 --- a/web/api/web_api_v1.h +++ b/web/api/web_api_v1.h @@ -3,14 +3,12 @@ #ifndef NETDATA_WEB_API_V1_H #define NETDATA_WEB_API_V1_H 1 -#include "daemon/common.h" -#include "web/api/badges/web_buffer_svg.h" -#include "web/api/formatters/rrd2json.h" -#include "web/api/health/health_cmdapi.h" -#include "web/api/queries/weights.h" +#include "web_api.h" + +struct web_client; 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_buffer_json_array(BUFFER *wb, const char *key, RRDR_OPTIONS options); void web_client_api_request_v1_data_options_to_string(char *buf, size_t size, RRDR_OPTIONS options); uint32_t web_client_api_request_v1_data_format(char *name); @@ -24,16 +22,17 @@ int web_client_api_request_v1_alarm_variables(RRDHOST *host, struct web_client * 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_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); int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url); -int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url); +int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url_path_endpoint); int web_client_api_request_v1_info_fill_buffer(RRDHOST *host, BUFFER *wb); -void host_labels2json(RRDHOST *host, BUFFER *wb, size_t indentation); void web_client_api_v1_init(void); void web_client_api_v1_management_init(void); +void host_labels2json(RRDHOST *host, BUFFER *wb, const char *key); +void web_client_api_request_v1_info_summary_alarm_statuses(RRDHOST *host, BUFFER *wb, const char *key); + extern char *api_secret; #endif //NETDATA_WEB_API_V1_H diff --git a/web/api/web_api_v2.c b/web/api/web_api_v2.c new file mode 100644 index 00000000..7280c042 --- /dev/null +++ b/web/api/web_api_v2.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "web_api_v2.h" +#include "../rtc/webrtc.h" + +static int web_client_api_request_v2_contexts_internal(RRDHOST *host __maybe_unused, struct web_client *w, char *url, CONTEXTS_V2_OPTIONS options) { + struct api_v2_contexts_request req = { 0 }; + + while(url) { + char *value = strsep_skip_consecutive_separators(&url, "&"); + if(!value || !*value) continue; + + char *name = strsep_skip_consecutive_separators(&value, "="); + if(!name || !*name) continue; + if(!value || !*value) continue; + + // name and value are now the parameters + // they are not null and not empty + + if(!strcmp(name, "scope_nodes")) req.scope_nodes = value; + else if((options & (CONTEXTS_V2_NODES | CONTEXTS_V2_CONTEXTS)) && !strcmp(name, "nodes")) req.nodes = value; + else if((options & CONTEXTS_V2_CONTEXTS) && !strcmp(name, "scope_contexts")) req.scope_contexts = value; + else if((options & CONTEXTS_V2_CONTEXTS) && !strcmp(name, "contexts")) req.contexts = value; + else if((options & CONTEXTS_V2_SEARCH) && !strcmp(name, "q")) req.q = value; + else if(!strcmp(name, "timeout")) req.timeout_ms = str2l(value); + } + + options |= CONTEXTS_V2_DEBUG; + + buffer_flush(w->response.data); + buffer_no_cacheable(w->response.data); + return rrdcontext_to_json_v2(w->response.data, &req, options); +} + +static int web_client_api_request_v2_q(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_SEARCH | CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES); +} + +static int web_client_api_request_v2_contexts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_CONTEXTS); +} + +static int web_client_api_request_v2_nodes(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_DETAILED); +} + +static int web_client_api_request_v2_weights(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_VALUE, + WEIGHTS_FORMAT_MULTINODE, 2); +} + +#define GROUP_BY_KEY_MAX_LENGTH 30 +static struct { + char group_by[GROUP_BY_KEY_MAX_LENGTH + 1]; + char aggregation[GROUP_BY_KEY_MAX_LENGTH + 1]; + char group_by_label[GROUP_BY_KEY_MAX_LENGTH + 1]; +} group_by_keys[MAX_QUERY_GROUP_BY_PASSES]; + +__attribute__((constructor)) void initialize_group_by_keys(void) { + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) { + snprintfz(group_by_keys[g].group_by, GROUP_BY_KEY_MAX_LENGTH, "group_by[%zu]", g); + snprintfz(group_by_keys[g].aggregation, GROUP_BY_KEY_MAX_LENGTH, "aggregation[%zu]", g); + snprintfz(group_by_keys[g].group_by_label, GROUP_BY_KEY_MAX_LENGTH, "group_by_label[%zu]", g); + } +} + +static int web_client_api_request_v2_data(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + usec_t received_ut = now_monotonic_usec(); + + int ret = HTTP_RESP_BAD_REQUEST; + + buffer_flush(w->response.data); + + char *google_version = "0.6", + *google_reqId = "0", + *google_sig = "0", + *google_out = "json", + *responseHandler = NULL, + *outFileName = NULL; + + time_t last_timestamp_in_data = 0, google_timestamp = 0; + + char *scope_nodes = NULL; + char *scope_contexts = NULL; + char *nodes = NULL; + char *contexts = NULL; + char *instances = NULL; + char *dimensions = NULL; + char *before_str = NULL; + char *after_str = NULL; + char *resampling_time_str = NULL; + char *points_str = NULL; + char *timeout_str = NULL; + char *labels = NULL; + char *alerts = NULL; + char *time_group_options = NULL; + char *tier_str = NULL; + size_t tier = 0; + RRDR_TIME_GROUPING time_group = RRDR_GROUPING_AVERAGE; + DATASOURCE_FORMAT format = DATASOURCE_JSON2; + RRDR_OPTIONS options = RRDR_OPTION_VIRTUAL_POINTS | RRDR_OPTION_JSON_WRAP | RRDR_OPTION_RETURN_JWAR; + + struct group_by_pass group_by[MAX_QUERY_GROUP_BY_PASSES] = { + { + .group_by = RRDR_GROUP_BY_DIMENSION, + .group_by_label = NULL, + .aggregation = RRDR_GROUP_BY_FUNCTION_AVERAGE, + }, + }; + + size_t group_by_idx = 0, group_by_label_idx = 0, aggregation_idx = 0; + + while(url) { + char *value = strsep_skip_consecutive_separators(&url, "&"); + if(!value || !*value) continue; + + char *name = strsep_skip_consecutive_separators(&value, "="); + if(!name || !*name) continue; + if(!value || !*value) continue; + + // name and value are now the parameters + // they are not null and not empty + + if(!strcmp(name, "scope_nodes")) scope_nodes = value; + else if(!strcmp(name, "scope_contexts")) scope_contexts = value; + else if(!strcmp(name, "nodes")) nodes = value; + else if(!strcmp(name, "contexts")) contexts = value; + else if(!strcmp(name, "instances")) instances = value; + else if(!strcmp(name, "dimensions")) dimensions = value; + else if(!strcmp(name, "labels")) labels = value; + else if(!strcmp(name, "alerts")) alerts = value; + else if(!strcmp(name, "after")) after_str = value; + else if(!strcmp(name, "before")) before_str = value; + else if(!strcmp(name, "points")) points_str = value; + else if(!strcmp(name, "timeout")) timeout_str = value; + else if(!strcmp(name, "group_by")) { + group_by[group_by_idx++].group_by = group_by_parse(value); + if(group_by_idx >= MAX_QUERY_GROUP_BY_PASSES) + group_by_idx = MAX_QUERY_GROUP_BY_PASSES - 1; + } + else if(!strcmp(name, "group_by_label")) { + group_by[group_by_label_idx++].group_by_label = value; + if(group_by_label_idx >= MAX_QUERY_GROUP_BY_PASSES) + group_by_label_idx = MAX_QUERY_GROUP_BY_PASSES - 1; + } + else if(!strcmp(name, "aggregation")) { + group_by[aggregation_idx++].aggregation = group_by_aggregate_function_parse(value); + if(aggregation_idx >= MAX_QUERY_GROUP_BY_PASSES) + aggregation_idx = MAX_QUERY_GROUP_BY_PASSES - 1; + } + else if(!strcmp(name, "format")) format = web_client_api_request_v1_data_format(value); + else if(!strcmp(name, "options")) options |= web_client_api_request_v1_data_options(value); + else if(!strcmp(name, "time_group")) time_group = time_grouping_parse(value, RRDR_GROUPING_AVERAGE); + else if(!strcmp(name, "time_group_options")) time_group_options = value; + else if(!strcmp(name, "time_resampling")) resampling_time_str = value; + else if(!strcmp(name, "tier")) tier_str = value; + else if(!strcmp(name, "callback")) responseHandler = value; + else if(!strcmp(name, "filename")) outFileName = value; + else if(!strcmp(name, "tqx")) { + // parse Google Visualization API options + // https://developers.google.com/chart/interactive/docs/dev/implementing_data_source + char *tqx_name, *tqx_value; + + while(value) { + tqx_value = strsep_skip_consecutive_separators(&value, ";"); + if(!tqx_value || !*tqx_value) continue; + + tqx_name = strsep_skip_consecutive_separators(&tqx_value, ":"); + if(!tqx_name || !*tqx_name) continue; + if(!tqx_value || !*tqx_value) continue; + + if(!strcmp(tqx_name, "version")) + google_version = tqx_value; + else if(!strcmp(tqx_name, "reqId")) + google_reqId = tqx_value; + else if(!strcmp(tqx_name, "sig")) { + google_sig = tqx_value; + google_timestamp = strtoul(google_sig, NULL, 0); + } + else if(!strcmp(tqx_name, "out")) { + google_out = tqx_value; + format = web_client_api_request_v1_data_google_format(google_out); + } + else if(!strcmp(tqx_name, "responseHandler")) + responseHandler = tqx_value; + else if(!strcmp(tqx_name, "outFileName")) + outFileName = tqx_value; + } + } + else { + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) { + if(!strcmp(name, group_by_keys[g].group_by)) + group_by[g].group_by = group_by_parse(value); + else if(!strcmp(name, group_by_keys[g].group_by_label)) + group_by[g].group_by_label = value; + else if(!strcmp(name, group_by_keys[g].aggregation)) + group_by[g].aggregation = group_by_aggregate_function_parse(value); + } + } + } + + // validate the google parameters given + fix_google_param(google_out); + fix_google_param(google_sig); + fix_google_param(google_reqId); + fix_google_param(google_version); + fix_google_param(responseHandler); + fix_google_param(outFileName); + + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) { + if (group_by[g].group_by_label && *group_by[g].group_by_label) + group_by[g].group_by |= RRDR_GROUP_BY_LABEL; + } + + if(group_by[0].group_by == RRDR_GROUP_BY_NONE) + group_by[0].group_by = RRDR_GROUP_BY_DIMENSION; + + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) { + if ((group_by[g].group_by & ~(RRDR_GROUP_BY_DIMENSION)) || (options & RRDR_OPTION_PERCENTAGE)) { + options |= RRDR_OPTION_ABSOLUTE; + break; + } + } + + if(options & RRDR_OPTION_DEBUG) + options &= ~RRDR_OPTION_MINIFY; + + if(tier_str && *tier_str) { + tier = str2ul(tier_str); + if(tier < storage_tiers) + options |= RRDR_OPTION_SELECTED_TIER; + else + tier = 0; + } + + time_t before = (before_str && *before_str)?str2l(before_str):0; + time_t after = (after_str && *after_str) ?str2l(after_str):-600; + size_t points = (points_str && *points_str)?str2u(points_str):0; + int timeout = (timeout_str && *timeout_str)?str2i(timeout_str): 0; + time_t resampling_time = (resampling_time_str && *resampling_time_str) ? str2l(resampling_time_str) : 0; + + QUERY_TARGET_REQUEST qtr = { + .version = 2, + .scope_nodes = scope_nodes, + .scope_contexts = scope_contexts, + .after = after, + .before = before, + .host = NULL, + .st = NULL, + .nodes = nodes, + .contexts = contexts, + .instances = instances, + .dimensions = dimensions, + .alerts = alerts, + .timeout_ms = timeout, + .points = points, + .format = format, + .options = options, + .time_group_method = time_group, + .time_group_options = time_group_options, + .resampling_time = resampling_time, + .tier = tier, + .chart_label_key = NULL, + .labels = labels, + .query_source = QUERY_SOURCE_API_DATA, + .priority = STORAGE_PRIORITY_NORMAL, + .received_ut = received_ut, + + .interrupt_callback = web_client_interrupt_callback, + .interrupt_callback_data = w, + }; + + for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) + qtr.group_by[g] = group_by[g]; + + QUERY_TARGET *qt = query_target_create(&qtr); + ONEWAYALLOC *owa = NULL; + + if(!qt) { + buffer_sprintf(w->response.data, "Failed to prepare the query."); + ret = HTTP_RESP_INTERNAL_SERVER_ERROR; + goto cleanup; + } + + web_client_timeout_checkpoint_set(w, timeout); + if(web_client_timeout_checkpoint_and_check(w, NULL)) { + ret = w->response.code; + goto cleanup; + } + + if(outFileName && *outFileName) { + buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName); + debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName); + } + + if(format == DATASOURCE_DATATABLE_JSONP) { + if(responseHandler == NULL) + responseHandler = "google.visualization.Query.setResponse"; + + debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'", + w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName + ); + + buffer_sprintf( + w->response.data, + "%s({version:'%s',reqId:'%s',status:'ok',sig:'%"PRId64"',table:", + responseHandler, + google_version, + google_reqId, + (int64_t)now_realtime_sec()); + } + else if(format == DATASOURCE_JSONP) { + if(responseHandler == NULL) + responseHandler = "callback"; + + buffer_strcat(w->response.data, responseHandler); + buffer_strcat(w->response.data, "("); + } + + owa = onewayalloc_create(0); + ret = data_query_execute(owa, w->response.data, qt, &last_timestamp_in_data); + + if(format == DATASOURCE_DATATABLE_JSONP) { + if(google_timestamp < last_timestamp_in_data) + buffer_strcat(w->response.data, "});"); + + else { + // the client already has the latest data + buffer_flush(w->response.data); + buffer_sprintf(w->response.data, + "%s({version:'%s',reqId:'%s',status:'error',errors:[{reason:'not_modified',message:'Data not modified'}]});", + responseHandler, google_version, google_reqId); + } + } + else if(format == DATASOURCE_JSONP) + buffer_strcat(w->response.data, ");"); + +cleanup: + query_target_release(qt); + onewayalloc_destroy(owa); + return ret; +} + +static int web_client_api_request_v2_webrtc(RRDHOST *host __maybe_unused, struct web_client *w, char *url __maybe_unused) { + return webrtc_new_connection(w->post_payload, w->response.data); +} + +static struct web_api_command api_commands_v2[] = { + {"data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_data}, + {"nodes", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_nodes}, + {"contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_contexts}, + {"weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_weights}, + {"q", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_q}, + + {"rtc_offer", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v2_webrtc}, + + // terminator + {NULL, 0, WEB_CLIENT_ACL_NONE, NULL}, +}; + +inline int web_client_api_request_v2(RRDHOST *host, struct web_client *w, char *url_path_endpoint) { + static int initialized = 0; + + if(unlikely(initialized == 0)) { + initialized = 1; + + for(int i = 0; api_commands_v2[i].command ; i++) + api_commands_v2[i].hash = simple_hash(api_commands_v2[i].command); + } + + return web_client_api_request_vX(host, w, url_path_endpoint, api_commands_v2); +} diff --git a/web/api/web_api_v2.h b/web/api/web_api_v2.h new file mode 100644 index 00000000..4a1893bd --- /dev/null +++ b/web/api/web_api_v2.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_WEB_API_V2_H +#define NETDATA_WEB_API_V2_H 1 + +#include "web_api.h" + +struct web_client; + +int web_client_api_request_v2(RRDHOST *host, struct web_client *w, char *url_path_endpoint); + +#endif //NETDATA_WEB_API_V2_H diff --git a/web/gui/README.md b/web/gui/README.md index fbd7da4d..eefeb6d0 100644 --- a/web/gui/README.md +++ b/web/gui/README.md @@ -2,6 +2,9 @@ title: "Local Agent dashboard" description: "The local Netdata Agent dashboard is the heart of health monitoring and performance troubleshooting, with hundreds of real-time charts." custom_edit_url: https://github.com/netdata/netdata/edit/master/web/gui/README.md +sidebar_label: "Local Agent dashboard" +learn_status: "Published" +learn_rel_path: "Operations" --> # Local Agent dashboard @@ -28,10 +31,14 @@ behind an [Nginx proxy](https://github.com/netdata/netdata/blob/master/docs/Runn Beyond charts, the local dashboard can be broken down into three key areas: -1. [**Sections**](#sections) -2. [**Time & date picker**](#time--date-picker) -3. [**Metrics menus/submenus**](#metrics-menus) -4. [**Netdata Cloud menus: Spaces, War Rooms, and Visited nodes)**](#cloud-menus-spaces-war-rooms-and-visited-nodes) +- [Local Agent dashboard](#local-agent-dashboard) + - [Navigating the local dashboard](#navigating-the-local-dashboard) + - [Sections](#sections) + - [Time \& date picker](#time--date-picker) + - [Metrics menus](#metrics-menus) + - [Cloud menus (Spaces, War Rooms, and Visited nodes)](#cloud-menus-spaces-war-rooms-and-visited-nodes) + - [Customizing the local dashboard](#customizing-the-local-dashboard) + - [Custom dashboards](#custom-dashboards) ![Annotated screenshot of the local Agent dashboard](https://user-images.githubusercontent.com/1153921/101509403-f7e59400-3935-11eb-9abd-cbecfa3ee49a.png) @@ -99,9 +106,6 @@ a War Room's name to jump to the Netdata Cloud web interface. ![A screenshot of the Cloud 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](https://github.com/netdata/netdata/blob/master/docs/agent-cloud.md). - ## Customizing the local dashboard Netdata stores information about individual charts in the `dashboard_info.js` diff --git a/web/gui/confluence/README.md b/web/gui/confluence/README.md index 9e7b8025..219a48cc 100644 --- a/web/gui/confluence/README.md +++ b/web/gui/confluence/README.md @@ -1,6 +1,10 @@ # Atlassian Confluence dashboards diff --git a/web/gui/custom/README.md b/web/gui/custom/README.md index 0751f208..2924a321 100644 --- a/web/gui/custom/README.md +++ b/web/gui/custom/README.md @@ -1,24 +1,10 @@ - - # Custom dashboards -You can: - -- create your own dashboards using simple HTML (no javascript is required for - basic dashboards) -- utilize any or all of the available chart libraries, on the same dashboard -- use data from one or more Netdata servers, on the same dashboard -- host your dashboard HTML page on any web server, anywhere +You can build custom Netdata dashboards just with some basic HTML knowledge. -You can also add Netdata charts to existing web pages. +Custom dashboards allow you to utilize any or all of the available chart libraries, on the same dashboard. +You can use data from one or more Netdata servers, on the same dashboard and host your dashboard HTML page on +any web server, anywhere. You can also add Netdata charts to existing web pages. Check this **[very simple working example of a custom dashboard](http://netdata.firehol.org/demo.html)**. @@ -30,7 +16,8 @@ If you plan to put the dashboard on TV, check out [tv.html](http://netdata.firehol.org/tv.html). Here's is a screenshot of it, monitoring two servers on the same page: -![image](https://cloud.githubusercontent.com/assets/2662304/14252187/d8d5f78e-fa8e-11e5-990d-99821d38c874.png) +image + diff --git a/web/gui/dashboard/Makefile.am b/web/gui/dashboard/Makefile.am index f939a7ea..e761167e 100644 --- a/web/gui/dashboard/Makefile.am +++ b/web/gui/dashboard/Makefile.am @@ -26,7 +26,7 @@ dist_web_DATA = \ $(srcdir)/index.html \ $(srcdir)/infographic.html \ $(srcdir)/manifest.json \ - $(srcdir)/precache-manifest.5fec6109084644adf7bf854243e1a044.js \ + $(srcdir)/precache-manifest.21dcd7c609bff6504512face054c360f.js \ $(srcdir)/refresh-badges.js \ $(srcdir)/robots.txt \ $(srcdir)/service-worker.js \ @@ -147,9 +147,9 @@ webstaticjsdir=$(webdir)/static/js dist_webstaticjs_DATA = \ $(srcdir)/static/js/10.a5cd7d0e.chunk.js \ $(srcdir)/static/js/10.a5cd7d0e.chunk.js.map \ - $(srcdir)/static/js/2.92ca8446.chunk.js \ - $(srcdir)/static/js/2.92ca8446.chunk.js.LICENSE \ - $(srcdir)/static/js/2.92ca8446.chunk.js.map \ + $(srcdir)/static/js/2.ae48988e.chunk.js \ + $(srcdir)/static/js/2.ae48988e.chunk.js.LICENSE \ + $(srcdir)/static/js/2.ae48988e.chunk.js.map \ $(srcdir)/static/js/3.f137faca.chunk.js \ $(srcdir)/static/js/3.f137faca.chunk.js.map \ $(srcdir)/static/js/4.2dbcd906.chunk.js \ @@ -165,9 +165,9 @@ dist_webstaticjs_DATA = \ $(srcdir)/static/js/8.b4161ea2.chunk.js.map \ $(srcdir)/static/js/9.a4363968.chunk.js \ $(srcdir)/static/js/9.a4363968.chunk.js.map \ - $(srcdir)/static/js/main.7d1bdca1.chunk.js \ - $(srcdir)/static/js/main.7d1bdca1.chunk.js.LICENSE \ - $(srcdir)/static/js/main.7d1bdca1.chunk.js.map \ + $(srcdir)/static/js/main.76dfe4de.chunk.js \ + $(srcdir)/static/js/main.76dfe4de.chunk.js.LICENSE \ + $(srcdir)/static/js/main.76dfe4de.chunk.js.map \ $(srcdir)/static/js/runtime-main.08abed8f.js \ $(srcdir)/static/js/runtime-main.08abed8f.js.map \ $(NULL) diff --git a/web/gui/dashboard/asset-manifest.json b/web/gui/dashboard/asset-manifest.json index 58440241..25b8cded 100644 --- a/web/gui/dashboard/asset-manifest.json +++ b/web/gui/dashboard/asset-manifest.json @@ -1,13 +1,13 @@ { "files": { "main.css": "./static/css/main.53ba10f1.chunk.css", - "main.js": "./static/js/main.7d1bdca1.chunk.js", - "main.js.map": "./static/js/main.7d1bdca1.chunk.js.map", + "main.js": "./static/js/main.76dfe4de.chunk.js", + "main.js.map": "./static/js/main.76dfe4de.chunk.js.map", "runtime-main.js": "./static/js/runtime-main.08abed8f.js", "runtime-main.js.map": "./static/js/runtime-main.08abed8f.js.map", "static/css/2.c454aab8.chunk.css": "./static/css/2.c454aab8.chunk.css", - "static/js/2.92ca8446.chunk.js": "./static/js/2.92ca8446.chunk.js", - "static/js/2.92ca8446.chunk.js.map": "./static/js/2.92ca8446.chunk.js.map", + "static/js/2.ae48988e.chunk.js": "./static/js/2.ae48988e.chunk.js", + "static/js/2.ae48988e.chunk.js.map": "./static/js/2.ae48988e.chunk.js.map", "static/js/3.f137faca.chunk.js": "./static/js/3.f137faca.chunk.js", "static/js/3.f137faca.chunk.js.map": "./static/js/3.f137faca.chunk.js.map", "static/css/4.a36e3b73.chunk.css": "./static/css/4.a36e3b73.chunk.css", @@ -26,22 +26,22 @@ "static/js/10.a5cd7d0e.chunk.js": "./static/js/10.a5cd7d0e.chunk.js", "static/js/10.a5cd7d0e.chunk.js.map": "./static/js/10.a5cd7d0e.chunk.js.map", "index.html": "./index.html", - "precache-manifest.5fec6109084644adf7bf854243e1a044.js": "./precache-manifest.5fec6109084644adf7bf854243e1a044.js", + "precache-manifest.21dcd7c609bff6504512face054c360f.js": "./precache-manifest.21dcd7c609bff6504512face054c360f.js", "service-worker.js": "./service-worker.js", "static/css/2.c454aab8.chunk.css.map": "./static/css/2.c454aab8.chunk.css.map", "static/css/4.a36e3b73.chunk.css.map": "./static/css/4.a36e3b73.chunk.css.map", "static/css/main.53ba10f1.chunk.css.map": "./static/css/main.53ba10f1.chunk.css.map", - "static/js/2.92ca8446.chunk.js.LICENSE": "./static/js/2.92ca8446.chunk.js.LICENSE", + "static/js/2.ae48988e.chunk.js.LICENSE": "./static/js/2.ae48988e.chunk.js.LICENSE", "static/js/5.2f783a54.chunk.js.LICENSE": "./static/js/5.2f783a54.chunk.js.LICENSE", - "static/js/main.7d1bdca1.chunk.js.LICENSE": "./static/js/main.7d1bdca1.chunk.js.LICENSE", + "static/js/main.76dfe4de.chunk.js.LICENSE": "./static/js/main.76dfe4de.chunk.js.LICENSE", "static/media/index.css": "./static/media/ibm-plex-sans-latin-700italic.72e9af40.woff", "static/media/fonts.css": "./static/media/material-icons.0509ab09.woff2" }, "entrypoints": [ "static/js/runtime-main.08abed8f.js", "static/css/2.c454aab8.chunk.css", - "static/js/2.92ca8446.chunk.js", + "static/js/2.ae48988e.chunk.js", "static/css/main.53ba10f1.chunk.css", - "static/js/main.7d1bdca1.chunk.js" + "static/js/main.76dfe4de.chunk.js" ] } \ No newline at end of file diff --git a/web/gui/dashboard/dashboard-react.js b/web/gui/dashboard/dashboard-react.js index 99f868f4..8dbe6c3a 100644 --- a/web/gui/dashboard/dashboard-react.js +++ b/web/gui/dashboard/dashboard-react.js @@ -175,7 +175,7 @@ NETDATA.options = { sync_selection: true, // enable or disable selection sync - pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart + pan_and_zoom_delay: 50, // when panning or zooming, how often to update the chart sync_pan_and_zoom: true, // enable or disable pan and zoom sync @@ -356,7 +356,7 @@ NETDATA.localStorage = { // todo temporary stuff which was originally in dashboard.js -// but needs to be refractored +// but needs to be refactored NETDATA.name2id = function (s) { return s .replace(/ /g, '_') diff --git a/web/gui/dashboard/dashboard.js b/web/gui/dashboard/dashboard.js index 83f7b584..3d470a65 100644 --- a/web/gui/dashboard/dashboard.js +++ b/web/gui/dashboard/dashboard.js @@ -1468,7 +1468,7 @@ NETDATA.options = { sync_selection: true, // enable or disable selection sync - pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart + pan_and_zoom_delay: 50, // when panning or zooming, how often to update the chart sync_pan_and_zoom: true, // enable or disable pan and zoom sync diff --git a/web/gui/dashboard/index.html b/web/gui/dashboard/index.html index db46d079..d2d09ce7 100644 --- a/web/gui/dashboard/index.html +++ b/web/gui/dashboard/index.html @@ -13,4 +13,4 @@ } const overlayEl = document.getElementById('loadOverlay'); overlayEl.innerHTML = 'netdata
Real-time performance monitoring, done right!
'; - overlayEl.style = theme == 'slate' ? "background-color: #272b30; color: #373b40;" : "background-color: #fff; color: #ddd;";
\ No newline at end of file + overlayEl.style = theme == 'slate' ? "background-color: #272b30; color: #373b40;" : "background-color: #fff; color: #ddd;";
\ No newline at end of file diff --git a/web/gui/dashboard/precache-manifest.21dcd7c609bff6504512face054c360f.js b/web/gui/dashboard/precache-manifest.21dcd7c609bff6504512face054c360f.js new file mode 100644 index 00000000..71c86640 --- /dev/null +++ b/web/gui/dashboard/precache-manifest.21dcd7c609bff6504512face054c360f.js @@ -0,0 +1,190 @@ +self.__precacheManifest = (self.__precacheManifest || []).concat([ + { + "revision": "e3c4d364a9ff58ad73696ca1489b2f53", + "url": "./index.html" + }, + { + "revision": "74af867d0b165be1d3e9", + "url": "./static/css/2.c454aab8.chunk.css" + }, + { + "revision": "3f3e6e773062ffa97127", + "url": "./static/css/4.a36e3b73.chunk.css" + }, + { + "revision": "16fe102ed87c5308ae90", + "url": "./static/css/main.53ba10f1.chunk.css" + }, + { + "revision": "2e1518ba242fafff36c2", + "url": "./static/js/10.a5cd7d0e.chunk.js" + }, + { + "revision": "74af867d0b165be1d3e9", + "url": "./static/js/2.ae48988e.chunk.js" + }, + { + "revision": "202231b589b831e707a2050ef8b04086", + "url": "./static/js/2.ae48988e.chunk.js.LICENSE" + }, + { + "revision": "9d949a4191279169d355", + "url": "./static/js/3.f137faca.chunk.js" + }, + { + "revision": "3f3e6e773062ffa97127", + "url": "./static/js/4.2dbcd906.chunk.js" + }, + { + "revision": "0a44fcc2aa7ee04633c0", + "url": "./static/js/5.2f783a54.chunk.js" + }, + { + "revision": "f05f27d89effd681fe0717b6a67b9a0d", + "url": "./static/js/5.2f783a54.chunk.js.LICENSE" + }, + { + "revision": "38dacb6fd8c4687e55a7", + "url": "./static/js/6.e1951239.chunk.js" + }, + { + "revision": "31df05c0a16aeb47134f", + "url": "./static/js/7.c2417fb0.chunk.js" + }, + { + "revision": "5a726e66eb5aa05866c5", + "url": "./static/js/8.b4161ea2.chunk.js" + }, + { + "revision": "79862bcda7c7424c10fc", + "url": "./static/js/9.a4363968.chunk.js" + }, + { + "revision": "16fe102ed87c5308ae90", + "url": "./static/js/main.76dfe4de.chunk.js" + }, + { + "revision": "19356475904bddb45614eb6ff7f6cd44", + "url": "./static/js/main.76dfe4de.chunk.js.LICENSE" + }, + { + "revision": "a99878aeea4445b20339", + "url": "./static/js/runtime-main.08abed8f.js" + }, + { + "revision": "245539db8ee56425757ef728eda8194e", + "url": "./static/media/ibm-plex-sans-latin-100.245539db.woff2" + }, + { + "revision": "9a582f3a304f421eca4027517706843c", + "url": "./static/media/ibm-plex-sans-latin-100.9a582f3a.woff" + }, + { + "revision": "1ea7c5d21b5956b602bdf9656cfb353f", + "url": "./static/media/ibm-plex-sans-latin-100italic.1ea7c5d2.woff" + }, + { + "revision": "3c34cf080b38f5fb1d4c59ffa45b3967", + "url": "./static/media/ibm-plex-sans-latin-100italic.3c34cf08.woff2" + }, + { + "revision": "67524c36348a323f78f2845e3aafc2d4", + "url": "./static/media/ibm-plex-sans-latin-200.67524c36.woff" + }, + { + "revision": "bf72c8412ab06c393f52efc5beb26ea7", + "url": "./static/media/ibm-plex-sans-latin-200.bf72c841.woff2" + }, + { + "revision": "52df25607ec284ca361ae50ba24b3580", + "url": "./static/media/ibm-plex-sans-latin-200italic.52df2560.woff" + }, + { + "revision": "bbc2d55223638ce450424a917e1104b2", + "url": "./static/media/ibm-plex-sans-latin-200italic.bbc2d552.woff2" + }, + { + "revision": "10bb6a0ae6dc8000d999ab622a45e281", + "url": "./static/media/ibm-plex-sans-latin-300.10bb6a0a.woff" + }, + { + "revision": "9e1c48af24191f6ea8aede14957c5d01", + "url": "./static/media/ibm-plex-sans-latin-300.9e1c48af.woff2" + }, + { + "revision": "c76f2ab53673e964b6e6734c1c455761", + "url": "./static/media/ibm-plex-sans-latin-300italic.c76f2ab5.woff2" + }, + { + "revision": "d3566d5bb4f31d86bfb9fda09563b416", + "url": "./static/media/ibm-plex-sans-latin-300italic.d3566d5b.woff" + }, + { + "revision": "263d6267533501f58c33b12b382e3abb", + "url": "./static/media/ibm-plex-sans-latin-400.263d6267.woff2" + }, + { + "revision": "a2c56f946488a9a267ba6ba21471a217", + "url": "./static/media/ibm-plex-sans-latin-400.a2c56f94.woff" + }, + { + "revision": "272f86114c980c52c131dfc3b4ae3276", + "url": "./static/media/ibm-plex-sans-latin-400italic.272f8611.woff" + }, + { + "revision": "89a93a1bdde48c7bb104150de88affce", + "url": "./static/media/ibm-plex-sans-latin-400italic.89a93a1b.woff2" + }, + { + "revision": "0866c24487514ad726738fb24f8e015b", + "url": "./static/media/ibm-plex-sans-latin-500.0866c244.woff2" + }, + { + "revision": "f6d5c5d5b849796d6a8f5a2953b60753", + "url": "./static/media/ibm-plex-sans-latin-500.f6d5c5d5.woff" + }, + { + "revision": "ccd41bd1a5bfa8bad2cd8d35fdaeb3d1", + "url": "./static/media/ibm-plex-sans-latin-500italic.ccd41bd1.woff" + }, + { + "revision": "ffd12d59339823b8cf53b9f99b47d87c", + "url": "./static/media/ibm-plex-sans-latin-500italic.ffd12d59.woff2" + }, + { + "revision": "337b16517a230dc830b84dc6e6167b68", + "url": "./static/media/ibm-plex-sans-latin-600.337b1651.woff" + }, + { + "revision": "7852d4dc26ef44df58e23dc0b9722d6f", + "url": "./static/media/ibm-plex-sans-latin-600.7852d4dc.woff2" + }, + { + "revision": "17e5379fd9a99b9bcb26ea983f391b6a", + "url": "./static/media/ibm-plex-sans-latin-600italic.17e5379f.woff2" + }, + { + "revision": "6f4ba6aa87fa99d5bc2b90a7b40a0ded", + "url": "./static/media/ibm-plex-sans-latin-600italic.6f4ba6aa.woff" + }, + { + "revision": "b8809d619a33eb825b0450281ff752e7", + "url": "./static/media/ibm-plex-sans-latin-700.b8809d61.woff" + }, + { + "revision": "c9983d3d04f3ed6c2eafee1db1d24e06", + "url": "./static/media/ibm-plex-sans-latin-700.c9983d3d.woff2" + }, + { + "revision": "02954beec9e742bb1f3ae27b7e7cb71f", + "url": "./static/media/ibm-plex-sans-latin-700italic.02954bee.woff2" + }, + { + "revision": "72e9af409ddafc63a5dd380e34758560", + "url": "./static/media/ibm-plex-sans-latin-700italic.72e9af40.woff" + }, + { + "revision": "0509ab09c1b0d2200a4135803c91d6ce", + "url": "./static/media/material-icons.0509ab09.woff2" + } +]); \ No newline at end of file diff --git a/web/gui/dashboard/precache-manifest.5fec6109084644adf7bf854243e1a044.js b/web/gui/dashboard/precache-manifest.5fec6109084644adf7bf854243e1a044.js deleted file mode 100644 index 2816bb2a..00000000 --- a/web/gui/dashboard/precache-manifest.5fec6109084644adf7bf854243e1a044.js +++ /dev/null @@ -1,190 +0,0 @@ -self.__precacheManifest = (self.__precacheManifest || []).concat([ - { - "revision": "928c945fab999ac6b49d381d496fd4fa", - "url": "./index.html" - }, - { - "revision": "9ff07d20678a36d676fa", - "url": "./static/css/2.c454aab8.chunk.css" - }, - { - "revision": "3f3e6e773062ffa97127", - "url": "./static/css/4.a36e3b73.chunk.css" - }, - { - "revision": "1ede0c6b7366f2db76cd", - "url": "./static/css/main.53ba10f1.chunk.css" - }, - { - "revision": "2e1518ba242fafff36c2", - "url": "./static/js/10.a5cd7d0e.chunk.js" - }, - { - "revision": "9ff07d20678a36d676fa", - "url": "./static/js/2.92ca8446.chunk.js" - }, - { - "revision": "202231b589b831e707a2050ef8b04086", - "url": "./static/js/2.92ca8446.chunk.js.LICENSE" - }, - { - "revision": "9d949a4191279169d355", - "url": "./static/js/3.f137faca.chunk.js" - }, - { - "revision": "3f3e6e773062ffa97127", - "url": "./static/js/4.2dbcd906.chunk.js" - }, - { - "revision": "0a44fcc2aa7ee04633c0", - "url": "./static/js/5.2f783a54.chunk.js" - }, - { - "revision": "f05f27d89effd681fe0717b6a67b9a0d", - "url": "./static/js/5.2f783a54.chunk.js.LICENSE" - }, - { - "revision": "38dacb6fd8c4687e55a7", - "url": "./static/js/6.e1951239.chunk.js" - }, - { - "revision": "31df05c0a16aeb47134f", - "url": "./static/js/7.c2417fb0.chunk.js" - }, - { - "revision": "5a726e66eb5aa05866c5", - "url": "./static/js/8.b4161ea2.chunk.js" - }, - { - "revision": "79862bcda7c7424c10fc", - "url": "./static/js/9.a4363968.chunk.js" - }, - { - "revision": "1ede0c6b7366f2db76cd", - "url": "./static/js/main.7d1bdca1.chunk.js" - }, - { - "revision": "19356475904bddb45614eb6ff7f6cd44", - "url": "./static/js/main.7d1bdca1.chunk.js.LICENSE" - }, - { - "revision": "a99878aeea4445b20339", - "url": "./static/js/runtime-main.08abed8f.js" - }, - { - "revision": "245539db8ee56425757ef728eda8194e", - "url": "./static/media/ibm-plex-sans-latin-100.245539db.woff2" - }, - { - "revision": "9a582f3a304f421eca4027517706843c", - "url": "./static/media/ibm-plex-sans-latin-100.9a582f3a.woff" - }, - { - "revision": "1ea7c5d21b5956b602bdf9656cfb353f", - "url": "./static/media/ibm-plex-sans-latin-100italic.1ea7c5d2.woff" - }, - { - "revision": "3c34cf080b38f5fb1d4c59ffa45b3967", - "url": "./static/media/ibm-plex-sans-latin-100italic.3c34cf08.woff2" - }, - { - "revision": "67524c36348a323f78f2845e3aafc2d4", - "url": "./static/media/ibm-plex-sans-latin-200.67524c36.woff" - }, - { - "revision": "bf72c8412ab06c393f52efc5beb26ea7", - "url": "./static/media/ibm-plex-sans-latin-200.bf72c841.woff2" - }, - { - "revision": "52df25607ec284ca361ae50ba24b3580", - "url": "./static/media/ibm-plex-sans-latin-200italic.52df2560.woff" - }, - { - "revision": "bbc2d55223638ce450424a917e1104b2", - "url": "./static/media/ibm-plex-sans-latin-200italic.bbc2d552.woff2" - }, - { - "revision": "10bb6a0ae6dc8000d999ab622a45e281", - "url": "./static/media/ibm-plex-sans-latin-300.10bb6a0a.woff" - }, - { - "revision": "9e1c48af24191f6ea8aede14957c5d01", - "url": "./static/media/ibm-plex-sans-latin-300.9e1c48af.woff2" - }, - { - "revision": "c76f2ab53673e964b6e6734c1c455761", - "url": "./static/media/ibm-plex-sans-latin-300italic.c76f2ab5.woff2" - }, - { - "revision": "d3566d5bb4f31d86bfb9fda09563b416", - "url": "./static/media/ibm-plex-sans-latin-300italic.d3566d5b.woff" - }, - { - "revision": "263d6267533501f58c33b12b382e3abb", - "url": "./static/media/ibm-plex-sans-latin-400.263d6267.woff2" - }, - { - "revision": "a2c56f946488a9a267ba6ba21471a217", - "url": "./static/media/ibm-plex-sans-latin-400.a2c56f94.woff" - }, - { - "revision": "272f86114c980c52c131dfc3b4ae3276", - "url": "./static/media/ibm-plex-sans-latin-400italic.272f8611.woff" - }, - { - "revision": "89a93a1bdde48c7bb104150de88affce", - "url": "./static/media/ibm-plex-sans-latin-400italic.89a93a1b.woff2" - }, - { - "revision": "0866c24487514ad726738fb24f8e015b", - "url": "./static/media/ibm-plex-sans-latin-500.0866c244.woff2" - }, - { - "revision": "f6d5c5d5b849796d6a8f5a2953b60753", - "url": "./static/media/ibm-plex-sans-latin-500.f6d5c5d5.woff" - }, - { - "revision": "ccd41bd1a5bfa8bad2cd8d35fdaeb3d1", - "url": "./static/media/ibm-plex-sans-latin-500italic.ccd41bd1.woff" - }, - { - "revision": "ffd12d59339823b8cf53b9f99b47d87c", - "url": "./static/media/ibm-plex-sans-latin-500italic.ffd12d59.woff2" - }, - { - "revision": "337b16517a230dc830b84dc6e6167b68", - "url": "./static/media/ibm-plex-sans-latin-600.337b1651.woff" - }, - { - "revision": "7852d4dc26ef44df58e23dc0b9722d6f", - "url": "./static/media/ibm-plex-sans-latin-600.7852d4dc.woff2" - }, - { - "revision": "17e5379fd9a99b9bcb26ea983f391b6a", - "url": "./static/media/ibm-plex-sans-latin-600italic.17e5379f.woff2" - }, - { - "revision": "6f4ba6aa87fa99d5bc2b90a7b40a0ded", - "url": "./static/media/ibm-plex-sans-latin-600italic.6f4ba6aa.woff" - }, - { - "revision": "b8809d619a33eb825b0450281ff752e7", - "url": "./static/media/ibm-plex-sans-latin-700.b8809d61.woff" - }, - { - "revision": "c9983d3d04f3ed6c2eafee1db1d24e06", - "url": "./static/media/ibm-plex-sans-latin-700.c9983d3d.woff2" - }, - { - "revision": "02954beec9e742bb1f3ae27b7e7cb71f", - "url": "./static/media/ibm-plex-sans-latin-700italic.02954bee.woff2" - }, - { - "revision": "72e9af409ddafc63a5dd380e34758560", - "url": "./static/media/ibm-plex-sans-latin-700italic.72e9af40.woff" - }, - { - "revision": "0509ab09c1b0d2200a4135803c91d6ce", - "url": "./static/media/material-icons.0509ab09.woff2" - } -]); \ No newline at end of file diff --git a/web/gui/dashboard/service-worker.js b/web/gui/dashboard/service-worker.js index b7eba5a4..6b953f46 100644 --- a/web/gui/dashboard/service-worker.js +++ b/web/gui/dashboard/service-worker.js @@ -14,7 +14,7 @@ importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); importScripts( - "./precache-manifest.5fec6109084644adf7bf854243e1a044.js" + "./precache-manifest.21dcd7c609bff6504512face054c360f.js" ); self.addEventListener('message', (event) => { diff --git a/web/gui/dashboard/static/js/2.92ca8446.chunk.js b/web/gui/dashboard/static/js/2.92ca8446.chunk.js deleted file mode 100644 index 9a810127..00000000 --- a/web/gui/dashboard/static/js/2.92ca8446.chunk.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! For license information please see 2.92ca8446.chunk.js.LICENSE */ -(this["webpackJsonp@netdata/dashboard"]=this["webpackJsonp@netdata/dashboard"]||[]).push([[2],[function(e,t,n){"use strict";e.exports=n(325)},function(e,t,n){(function(t){var n;n=function(){"use strict";var e=function(e){var t=e.id,n=e.viewBox,r=e.content;this.id=t,this.viewBox=n,this.content=r};function n(e,t){return e(t={exports:{}},t.exports),t.exports}e.prototype.stringify=function(){return this.content},e.prototype.toString=function(){return this.stringify()},e.prototype.destroy=function(){var e=this;["id","viewBox","content"].forEach((function(t){return delete e[t]}))},"undefined"!==typeof window?window:"undefined"!==typeof t||"undefined"!==typeof self&&self;var r=n((function(e,t){e.exports=function(){function e(e){return e&&"object"===typeof e&&"[object RegExp]"!==Object.prototype.toString.call(e)&&"[object Date]"!==Object.prototype.toString.call(e)}function t(t,n){var o;return n&&!0===n.clone&&e(t)?r((o=t,Array.isArray(o)?[]:{}),t,n):t}function n(n,o,i){var a=n.slice();return o.forEach((function(o,c){"undefined"===typeof a[c]?a[c]=t(o,i):e(o)?a[c]=r(n[c],o,i):-1===n.indexOf(o)&&a.push(t(o,i))})),a}function r(o,i,a){var c=Array.isArray(i),s=(a||{arrayMerge:n}).arrayMerge||n;return c?Array.isArray(o)?s(o,i,a):t(i,a):function(n,o,i){var a={};return e(n)&&Object.keys(n).forEach((function(e){a[e]=t(n[e],i)})),Object.keys(o).forEach((function(c){e(o[c])&&n[c]?a[c]=r(n[c],o[c],i):a[c]=t(o[c],i)})),a}(o,i,a)}return r.all=function(e,t){if(!Array.isArray(e)||e.length<2)throw new Error("first argument should be an array with at least two elements");return e.reduce((function(e,n){return r(e,n,t)}))},r}()})),o=n((function(e,t){t.default={svg:{name:"xmlns",uri:"http://www.w3.org/2000/svg"},xlink:{name:"xmlns:xlink",uri:"http://www.w3.org/1999/xlink"}},e.exports=t.default})),i=o.svg,a=o.xlink,c={};c[i.name]=i.uri,c[a.name]=a.uri;var s=function(e,t){return void 0===e&&(e=""),""+e+""};return function(e){function t(){e.apply(this,arguments)}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={isMounted:{}};return n.isMounted.get=function(){return!!this.node},t.createFromExistingNode=function(e){return new t({id:e.getAttribute("id"),viewBox:e.getAttribute("viewBox"),content:e.outerHTML})},t.prototype.destroy=function(){this.isMounted&&this.unmount(),e.prototype.destroy.call(this)},t.prototype.mount=function(e){if(this.isMounted)return this.node;var t="string"===typeof e?document.querySelector(e):e,n=this.render();return this.node=n,t.appendChild(n),n},t.prototype.render=function(){var e=this.stringify();return function(e){var t=!!document.importNode,n=(new DOMParser).parseFromString(e,"image/svg+xml").documentElement;return t?document.importNode(n,!0):n}(s(e)).childNodes[0]},t.prototype.unmount=function(){this.node.parentNode.removeChild(this.node)},Object.defineProperties(t.prototype,n),t}(e)},e.exports=n()}).call(this,n(86))},function(e,t,n){(function(t){var n;n=function(){"use strict";function e(e,t){return e(t={exports:{}},t.exports),t.exports}"undefined"!==typeof window?window:"undefined"!==typeof t||"undefined"!==typeof self&&self;var n=e((function(e,t){e.exports=function(){function e(e){return e&&"object"===typeof e&&"[object RegExp]"!==Object.prototype.toString.call(e)&&"[object Date]"!==Object.prototype.toString.call(e)}function t(t,n){var o;return n&&!0===n.clone&&e(t)?r((o=t,Array.isArray(o)?[]:{}),t,n):t}function n(n,o,i){var a=n.slice();return o.forEach((function(o,c){"undefined"===typeof a[c]?a[c]=t(o,i):e(o)?a[c]=r(n[c],o,i):-1===n.indexOf(o)&&a.push(t(o,i))})),a}function r(o,i,a){var c=Array.isArray(i),s=(a||{arrayMerge:n}).arrayMerge||n;return c?Array.isArray(o)?s(o,i,a):t(i,a):function(n,o,i){var a={};return e(n)&&Object.keys(n).forEach((function(e){a[e]=t(n[e],i)})),Object.keys(o).forEach((function(c){e(o[c])&&n[c]?a[c]=r(n[c],o[c],i):a[c]=t(o[c],i)})),a}(o,i,a)}return r.all=function(e,t){if(!Array.isArray(e)||e.length<2)throw new Error("first argument should be an array with at least two elements");return e.reduce((function(e,n){return r(e,n,t)}))},r}()})),r=e((function(e,t){t.default={svg:{name:"xmlns",uri:"http://www.w3.org/2000/svg"},xlink:{name:"xmlns:xlink",uri:"http://www.w3.org/1999/xlink"}},e.exports=t.default})),o=r.svg,i=r.xlink,a={};a[o.name]=o.uri,a[i.name]=i.uri;var c,s=function(e,t){return void 0===e&&(e=""),""+e+""},l=r.svg,u=r.xlink,f={attrs:(c={style:["position: absolute","width: 0","height: 0"].join("; ")},c[l.name]=l.uri,c[u.name]=u.uri,c)},d=function(e){this.config=n(f,e||{}),this.symbols=[]};d.prototype.add=function(e){var t=this.symbols,n=this.find(e.id);return n?(t[t.indexOf(n)]=e,!1):(t.push(e),!0)},d.prototype.remove=function(e){var t=this.symbols,n=this.find(e);return!!n&&(t.splice(t.indexOf(n),1),n.destroy(),!0)},d.prototype.find=function(e){return this.symbols.filter((function(t){return t.id===e}))[0]||null},d.prototype.has=function(e){return null!==this.find(e)},d.prototype.stringify=function(){var e=this.config.attrs,t=this.symbols.map((function(e){return e.stringify()})).join("");return s(t,e)},d.prototype.toString=function(){return this.stringify()},d.prototype.destroy=function(){this.symbols.forEach((function(e){return e.destroy()}))};var h=function(e){var t=e.id,n=e.viewBox,r=e.content;this.id=t,this.viewBox=n,this.content=r};h.prototype.stringify=function(){return this.content},h.prototype.toString=function(){return this.stringify()},h.prototype.destroy=function(){var e=this;["id","viewBox","content"].forEach((function(t){return delete e[t]}))};var p=function(e){var t=!!document.importNode,n=(new DOMParser).parseFromString(e,"image/svg+xml").documentElement;return t?document.importNode(n,!0):n},z=function(e){function t(){e.apply(this,arguments)}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={isMounted:{}};return n.isMounted.get=function(){return!!this.node},t.createFromExistingNode=function(e){return new t({id:e.getAttribute("id"),viewBox:e.getAttribute("viewBox"),content:e.outerHTML})},t.prototype.destroy=function(){this.isMounted&&this.unmount(),e.prototype.destroy.call(this)},t.prototype.mount=function(e){if(this.isMounted)return this.node;var t="string"===typeof e?document.querySelector(e):e,n=this.render();return this.node=n,t.appendChild(n),n},t.prototype.render=function(){var e=this.stringify();return p(s(e)).childNodes[0]},t.prototype.unmount=function(){this.node.parentNode.removeChild(this.node)},Object.defineProperties(t.prototype,n),t}(h),v={autoConfigure:!0,mountTo:"body",syncUrlsWithBaseTag:!1,listenLocationChangeEvent:!0,locationChangeEvent:"locationChange",locationChangeAngularEmitter:!1,usagesToUpdate:"use[*|href]",moveGradientsOutsideSymbol:!1},g=function(e){return Array.prototype.slice.call(e,0)},m={isChrome:function(){return/chrome/i.test(navigator.userAgent)},isFirefox:function(){return/firefox/i.test(navigator.userAgent)},isIE:function(){return/msie/i.test(navigator.userAgent)||/trident/i.test(navigator.userAgent)},isEdge:function(){return/edge/i.test(navigator.userAgent)}},y=function(e){var t=[];return g(e.querySelectorAll("style")).forEach((function(e){e.textContent+="",t.push(e)})),t},b=function(e){return(e||window.location.href).split("#")[0]},w=function(e){angular.module("ng").run(["$rootScope",function(t){t.$on("$locationChangeSuccess",(function(t,n,r){!function(e,t){var n=document.createEvent("CustomEvent");n.initCustomEvent(e,!1,!1,t),window.dispatchEvent(n)}(e,{oldUrl:r,newUrl:n})}))}])},k=function(e,t){return void 0===t&&(t="linearGradient, radialGradient, pattern"),g(e.querySelectorAll("symbol")).forEach((function(e){g(e.querySelectorAll(t)).forEach((function(t){e.parentNode.insertBefore(t,e)}))})),e},x=r.xlink.uri,j="xlink:href",M=/[{}|\\\^\[\]`"<>]/g;function C(e){return e.replace(M,(function(e){return"%"+e[0].charCodeAt(0).toString(16).toUpperCase()}))}var _,q=["clipPath","colorProfile","src","cursor","fill","filter","marker","markerStart","markerMid","markerEnd","mask","stroke","style"],S=q.map((function(e){return"["+e+"]"})).join(","),O=function(e,t,n,r){var o=C(n),i=C(r);(function(e,t){return g(e).reduce((function(e,n){if(!n.attributes)return e;var r=g(n.attributes),o=t?r.filter(t):r;return e.concat(o)}),[])})(e.querySelectorAll(S),(function(e){var t=e.localName,n=e.value;return-1!==q.indexOf(t)&&-1!==n.indexOf("url("+o)})).forEach((function(e){return e.value=e.value.replace(new RegExp(o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),"g"),i)})),function(e,t,n){g(e).forEach((function(e){var r=e.getAttribute(j);if(r&&0===r.indexOf(t)){var o=r.replace(t,n);e.setAttributeNS(x,j,o)}}))}(t,o,i)},E={MOUNT:"mount",SYMBOL_MOUNT:"symbol_mount"},T=function(e){function t(t){var r=this;void 0===t&&(t={}),e.call(this,n(v,t));var o,i=(o=o||Object.create(null),{on:function(e,t){(o[e]||(o[e]=[])).push(t)},off:function(e,t){o[e]&&o[e].splice(o[e].indexOf(t)>>>0,1)},emit:function(e,t){(o[e]||[]).map((function(e){e(t)})),(o["*"]||[]).map((function(n){n(e,t)}))}});this._emitter=i,this.node=null;var a=this.config;if(a.autoConfigure&&this._autoConfigure(t),a.syncUrlsWithBaseTag){var c=document.getElementsByTagName("base")[0].getAttribute("href");i.on(E.MOUNT,(function(){return r.updateUrls("#",c)}))}var s=this._handleLocationChange.bind(this);this._handleLocationChange=s,a.listenLocationChangeEvent&&window.addEventListener(a.locationChangeEvent,s),a.locationChangeAngularEmitter&&w(a.locationChangeEvent),i.on(E.MOUNT,(function(e){a.moveGradientsOutsideSymbol&&k(e)})),i.on(E.SYMBOL_MOUNT,(function(e){a.moveGradientsOutsideSymbol&&k(e.parentNode),(m.isIE()||m.isEdge())&&y(e)}))}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var r={isMounted:{}};return r.isMounted.get=function(){return!!this.node},t.prototype._autoConfigure=function(e){var t=this.config;"undefined"===typeof e.syncUrlsWithBaseTag&&(t.syncUrlsWithBaseTag="undefined"!==typeof document.getElementsByTagName("base")[0]),"undefined"===typeof e.locationChangeAngularEmitter&&(t.locationChangeAngularEmitter="angular"in window),"undefined"===typeof e.moveGradientsOutsideSymbol&&(t.moveGradientsOutsideSymbol=m.isFirefox())},t.prototype._handleLocationChange=function(e){var t=e.detail,n=t.oldUrl,r=t.newUrl;this.updateUrls(n,r)},t.prototype.add=function(t){var n=e.prototype.add.call(this,t);return this.isMounted&&n&&(t.mount(this.node),this._emitter.emit(E.SYMBOL_MOUNT,t.node)),n},t.prototype.attach=function(e){var t=this,n=this;if(n.isMounted)return n.node;var r="string"===typeof e?document.querySelector(e):e;return n.node=r,this.symbols.forEach((function(e){e.mount(n.node),t._emitter.emit(E.SYMBOL_MOUNT,e.node)})),g(r.querySelectorAll("symbol")).forEach((function(e){var t=z.createFromExistingNode(e);t.node=e,n.add(t)})),this._emitter.emit(E.MOUNT,r),r},t.prototype.destroy=function(){var e=this.config,t=this.symbols,n=this._emitter;t.forEach((function(e){return e.destroy()})),n.off("*"),window.removeEventListener(e.locationChangeEvent,this._handleLocationChange),this.isMounted&&this.unmount()},t.prototype.mount=function(e,t){if(void 0===e&&(e=this.config.mountTo),void 0===t&&(t=!1),this.isMounted)return this.node;var n="string"===typeof e?document.querySelector(e):e,r=this.render();return this.node=r,t&&n.childNodes[0]?n.insertBefore(r,n.childNodes[0]):n.appendChild(r),this._emitter.emit(E.MOUNT,r),r},t.prototype.render=function(){return p(this.stringify())},t.prototype.unmount=function(){this.node.parentNode.removeChild(this.node)},t.prototype.updateUrls=function(e,t){if(!this.isMounted)return!1;var n=document.querySelectorAll(this.config.usagesToUpdate);return O(this.node,n,b(e)+"#",b(t)+"#"),!0},Object.defineProperties(t.prototype,r),t}(d),A=e((function(e){e.exports=function(){var e,t=[],n=document,r=n.documentElement.doScroll,o=(r?/^loaded|^c/:/^loaded|^i|^c/).test(n.readyState);return o||n.addEventListener("DOMContentLoaded",e=function(){for(n.removeEventListener("DOMContentLoaded",e),o=1;e=t.shift();)e()}),function(e){o?setTimeout(e,0):t.push(e)}}()}));window.__SVG_SPRITE__?_=window.__SVG_SPRITE__:(_=new T({attrs:{id:"__SVG_SPRITE_NODE__"}}),window.__SVG_SPRITE__=_);var L=function(){var e=document.getElementById("__SVG_SPRITE_NODE__");e?_.attach(e):_.mount(document.body,!0)};return document.body?L():A(L),_},e.exports=n()}).call(this,n(86))},function(e,t,n){"use strict";var r={};n.r(r),n.d(r,"SIZE_SUB_UNIT",(function(){return h})),n.d(r,"SIZE_UNIT",(function(){return p})),n.d(r,"GUTTER_HEIGHT",(function(){return z}));var o={};n.r(o),n.d(o,"findFilterValues",(function(){return Uy})),n.d(o,"removeSingleFilterValue",(function(){return Wy})),n.d(o,"markSelectedFacetValuesFromFilters",(function(){return Gy})),n.d(o,"doFilterValuesMatch",(function(){return Yy})),n.d(o,"mergeFilters",(function(){return $y})),n.d(o,"isFilterValueRange",(function(){return Zy}));var i={};n.r(i),n.d(i,"addFilter",(function(){return Wb})),n.d(i,"trackAutocompleteClickThrough",(function(){return Gb})),n.d(i,"clearFilters",(function(){return Yb})),n.d(i,"removeFilter",(function(){return $b})),n.d(i,"reset",(function(){return Zb})),n.d(i,"setCurrent",(function(){return Xb})),n.d(i,"setFilter",(function(){return Kb})),n.d(i,"setResultsPerPage",(function(){return Qb})),n.d(i,"setSearchTerm",(function(){return Jb})),n.d(i,"setSort",(function(){return ew})),n.d(i,"trackClickThrough",(function(){return tw})),n.d(i,"a11yNotify",(function(){return cw}));var a=n(0),c=n.n(a),s=n(4),l={transparent:{full:"rgba(255, 255, 255, 0.0)",semi:"rgba(255, 255, 255, 0.5)",popover:"rgba(18, 36, 50, 0.9)"},green:{chateau:"#42B861",netdata:"#00AB44",deyork:"#68C47D",vista:"#96D4A2",fringyFlower:"#BFE5C6",frostee:"#E5F5E8",limeGreen:"#48E499"},red:{pomegranate:"#FF4136",carnation:"#F95251",apricot:"#ED7374",wewak:"#F59B9B",pastelpink:"#FFCED3",lavender:"#FFEBEF"},yellow:{amber:"#FFC300",sunglow:"#FFCC26",seaBuckthorn:"#F9A825",mustard:"#FFD74F",salomie:"#FFE182",buttermilk:"#FFEDB3",ginfizz:"#FFF8E1"},neutral:{white:"#FFFFFF",black:"#000000",limedSpruce:"#35414A",regentgrey:"#8F9EAA",blackhaze:"#F7F8F8",iron:"#CFD5DA",porcelain:"#ECEEEF",bluebayoux:"#536775",shark:"#1C1E22",tuna:"#383B40",outerSpace:"#2B3136"},purple:{mauve:"#DB94F4",daisy:"#563D7C",lilac:"#B596F8"},blue:{aquamarine:"#19C89E",indigo:"#5790FF",cyan:"#00BAE2"}},u=function(){return(u=Object.assign||function(e){for(var t,n=1,r=arguments.length;n=arguments.length)?c=t[a]:(c=arguments[o],o+=1),r[a]=c,x(c)||(i-=1),a+=1}return i<=0?n.apply(this,r):C(i,_(e,r,n))}}var q=M((function(e,t){return 1===e?j(t):C(e,_(e,[],t))}));function S(e){return function t(n,r,o){switch(arguments.length){case 0:return t;case 1:return x(n)?t:M((function(t,r){return e(n,t,r)}));case 2:return x(n)&&x(r)?t:x(n)?M((function(t,n){return e(t,r,n)})):x(r)?M((function(t,r){return e(n,t,r)})):j((function(t){return e(n,r,t)}));default:return x(n)&&x(r)&&x(o)?t:x(n)&&x(r)?M((function(t,n){return e(t,n,o)})):x(n)&&x(o)?M((function(t,n){return e(t,r,n)})):x(r)&&x(o)?M((function(t,r){return e(n,t,r)})):x(n)?j((function(t){return e(t,r,o)})):x(r)?j((function(t){return e(n,t,o)})):x(o)?j((function(t){return e(n,r,t)})):e(n,r,o)}}}var O=Array.isArray||function(e){return null!=e&&e.length>=0&&"[object Array]"===Object.prototype.toString.call(e)};function E(e){return null!=e&&"function"===typeof e["@@transducer/step"]}function T(e,t,n){return function(){if(0===arguments.length)return n();var r=arguments[arguments.length-1];if(!O(r)){for(var o=0;o0&&(e.hasOwnProperty(0)&&e.hasOwnProperty(e.length-1)))))})),P=function(){function e(e){this.f=e}return e.prototype["@@transducer/init"]=function(){throw new Error("init not implemented on XWrap")},e.prototype["@@transducer/result"]=function(e){return e},e.prototype["@@transducer/step"]=function(e,t){return this.f(e,t)},e}();function V(e){return new P(e)}var R=M((function(e,t){return C(e.length,(function(){return e.apply(t,arguments)}))}));function I(e,t,n){for(var r=n.next();!r.done;){if((t=e["@@transducer/step"](t,r.value))&&t["@@transducer/reduced"]){t=t["@@transducer/value"];break}r=n.next()}return e["@@transducer/result"](t)}function N(e,t,n,r){return e["@@transducer/result"](n[r](R(e["@@transducer/step"],e),t))}var F="undefined"!==typeof Symbol?Symbol.iterator:"@@iterator";function B(e,t,n){if("function"===typeof e&&(e=V(e)),D(n))return function(e,t,n){for(var r=0,o=n.length;r=0;)G(t=X[n],e)&&!Q(r,t)&&(r[r.length]=t),n-=1;return r})):j((function(e){return Object(e)!==e?[]:Object.keys(e)})),ee=M(T(["fantasy-land/map","map"],W,(function(e,t){switch(Object.prototype.toString.call(t)){case"[object Function]":return q(t.length,(function(){return e.call(this,t.apply(this,arguments))}));case"[object Object]":return B((function(n,r){return n[r]=e(t[r]),n}),{},J(t));default:return L(e,t)}}))),te=Number.isInteger||function(e){return e<<0===e},ne=M((function(e,t){var n=e<0?t.length+e:e;return H(t)?t.charAt(n):t[n]})),re=S(B);function oe(e){return function t(n){for(var r,o,i,a=[],c=0,s=n.length;c=0;){if(n[i]===e)return r[i]===t;i-=1}switch(o){case"Map":return e.size===t.size&&ve(e.entries(),t.entries(),n.concat([e]),r.concat([t]));case"Set":return e.size===t.size&&ve(e.values(),t.values(),n.concat([e]),r.concat([t]));case"Arguments":case"Array":case"Object":case"Boolean":case"Number":case"String":case"Date":case"Error":case"RegExp":case"Int8Array":case"Uint8Array":case"Uint8ClampedArray":case"Int16Array":case"Uint16Array":case"Int32Array":case"Uint32Array":case"Float32Array":case"Float64Array":case"ArrayBuffer":break;default:return!1}var a=J(e);if(a.length!==J(t).length)return!1;var c=n.concat([e]),s=r.concat([t]);for(i=a.length-1;i>=0;){var l=a[i];if(!G(l,t)||!ge(t[l],e[l],c,s))return!1;i-=1}return!0}var me=M((function(e,t){return ge(e,t,[],[])}));function ye(e,t,n){var r,o;if("function"===typeof e.indexOf)switch(typeof t){case"number":if(0===t){for(r=1/t;n=0}var we=function(e){return(e<10?"0":"")+e};Date.prototype.toISOString;var ke=M((function(e,t){return null==t||t!==t?e:t}));function xe(e,t,n){var r,o=typeof e;switch(o){case"string":case"number":return 0===e&&1/e===-1/0?!!n._items["-0"]||(t&&(n._items["-0"]=!0),!1):null!==n._nativeSet?t?(r=n._nativeSet.size,n._nativeSet.add(e),n._nativeSet.size===r):n._nativeSet.has(e):o in n._items?e in n._items[o]||(t&&(n._items[o][e]=!0),!1):(t&&(n._items[o]={},n._items[o][e]=!0),!1);case"boolean":if(o in n._items){var i=e?1:0;return!!n._items[o][i]||(t&&(n._items[o][i]=!0),!1)}return t&&(n._items[o]=e?[!1,!0]:[!0,!1]),!1;case"function":return null!==n._nativeSet?t?(r=n._nativeSet.size,n._nativeSet.add(e),n._nativeSet.size===r):n._nativeSet.has(e):o in n._items?!!be(e,n._items[o])||(t&&n._items[o].push(e),!1):(t&&(n._items[o]=[e]),!1);case"undefined":return!!n._items[o]||(t&&(n._items[o]=!0),!1);case"object":if(null===e)return!!n._items.null||(t&&(n._items.null=!0),!1);default:return(o=Object.prototype.toString.call(e))in n._items?!!be(e,n._items[o])||(t&&n._items[o].push(e),!1):(t&&(n._items[o]=[e]),!1)}}var je=function(){function e(){this._nativeSet="function"===typeof Set?new Set:null,this._items={}}return e.prototype.add=function(e){return!xe(e,!0,this)},e.prototype.has=function(e){return xe(e,!1,this)},e}();var Me=j(oe(!0)),Ce=function(){function e(e,t){this.xf=t,this.f=e,this.set=new je}return e.prototype["@@transducer/init"]=A.init,e.prototype["@@transducer/result"]=A.result,e.prototype["@@transducer/step"]=function(e,t){return this.set.add(this.f(t))?this.xf["@@transducer/step"](e,t):e},e}(),_e=M(T([],M((function(e,t){return new Ce(e,t)})),(function(e,t){for(var n,r,o=new je,i=[],a=0;a>8&255,c=255&i;return"rgba(".concat(i>>16&255,", ").concat(a,", ").concat(c,", ").concat(t,")")}},Ie=function(e){return void 0===e&&(e=1),function(t){var n=(Pe(t)||0)*e;return"".concat(n,"px")}},Ne=function(e,t){return void 0===e&&(e="border"),void 0===t&&(t="disabled"),function(n){var r=n.theme,o=n.success,i=n.error,a=n.disabled;return o?Ve(["success"])({theme:r}):i?Ve(["error"])({theme:r}):a?Ve([t])({theme:r}):Ve([e])({theme:r})}},Fe=function(e,t){return"number"===typeof t?0===(n=e.constants.SIZE_SUB_UNIT*t)?"0":"".concat(n,"px"):"auto";var n},Be=function(e,t){return t.map((function(t){return Fe(e,t)})).join(" ")},Ue=function(e){var t=e.theme,n=e.margin;return n?Array.isArray(n)&&n.length>=1&&n.length<=4?"margin: ".concat(Be(t,n),";"):(console.error("Please provide an array (max 4 elements) for `margin` style helper."),""):""},We=function(e){var t=e.theme,n=e.padding;return n?Array.isArray(n)&&n.length>=1&&n.length<=4?"padding: ".concat(Be(t,n),";"):(console.error("Please provide an array (max 4 elements) for `padding` style helper."),""):""},Ge={end:"flex-end",start:"flex-start",center:"center",stretch:"stretch"},Ye=function(e){var t=e.alignSelf;return t in Ge&&"align-self: ".concat(Ge[t],";")},$e={none:"none",capitalize:"capitalize",uppercase:"uppercase",lowercase:"lowercase",firstLetter:"firstLetter",fullWidth:"full-width"},Ze=function(e){var t=(void 0===e?{}:e).textTransform,n=void 0===t?$e.none:t;return n===$e.firstLetter?"text-transform: lowercase;\n &::first-letter {\n text-transform: uppercase;\n }\n":"text-transform: ".concat(n in $e?$e[n]:$e.none,";")},Xe=function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e},Ke=function(){return(Ke=Object.assign||function(e){for(var t,n=1,r=arguments.length;n span {\n ",";\n margin-left: ",";\n }\n\n &:hover {\n border-color: ",";\n background-color: ",";\n color: ",";\n text-decoration: none;\n\n .button-icon {\n fill: ",";\n }\n }\n\n &:active {\n ","\n }\n ","\n\n &:focus {\n outline: none;\n }\n\n .button-icon {\n height: ",";\n width: ",";\n fill: ",";\n }\n\n .ntd-spinner {\n fill: none;\n stroke-width: 17px;\n stroke-dasharray: 100;\n stroke-dashoffset: 100;\n animation: ntd-draw 1s linear infinite;\n stroke: ",";\n width: 24px;\n }\n\n .path {\n stroke: ",";\n }\n\n @keyframes ntd-draw {\n to {\n stroke-dashoffset: 0;\n }\n }\n }\n"],["\n && {\n display: flex;\n justify-content: center;\n align-items: center;\n position: relative;\n ",";\n\n width: ",";\n height: ",";\n\n font-weight: 500;\n font-size: ",";\n line-height: ",";\n white-space: nowrap;\n word-break: keep-all;\n\n cursor: pointer;\n opacity: ",";\n pointer-events: ",";\n\n ","\n ","\n transition: all 150ms;\n\n background-color: ",";\n color: ",";\n\n border-width: 1px;\n border-style: solid;\n border-color: ",";\n border-radius: 4px;\n box-sizing: border-box;\n\n text-decoration: none;\n & > span {\n ",";\n margin-left: ",";\n }\n\n &:hover {\n border-color: ",";\n background-color: ",";\n color: ",";\n text-decoration: none;\n\n .button-icon {\n fill: ",";\n }\n }\n\n &:active {\n ","\n }\n ","\n\n &:focus {\n outline: none;\n }\n\n .button-icon {\n height: ",";\n width: ",";\n fill: ",";\n }\n\n .ntd-spinner {\n fill: none;\n stroke-width: 17px;\n stroke-dasharray: 100;\n stroke-dashoffset: 100;\n animation: ntd-draw 1s linear infinite;\n stroke: ",";\n width: 24px;\n }\n\n .path {\n stroke: ",";\n }\n\n @keyframes ntd-draw {\n to {\n stroke-dashoffset: 0;\n }\n }\n }\n"])),Ye,(function(e){return e.width?e.width:e.hasLabel?Ie(16):Ie(e.tiny?2.75:e.small?3:4)}),(function(e){return e.hasLabel?Ie(e.small?4:5):Ie(e.tiny?2.75:e.small?3:4)}),(function(e){var t=e.small;return e.tiny?"10px":t?"12px":"14px"}),Ie(2),(function(e){var t=e.disabled;return e.neutral?1:t?.4:1}),(function(e){return e.disabled?"none":"auto"}),Ue,We,(function(e){return e.colors.bg(e)}),(function(e){return e.colors.color(e)}),(function(e){return e.colors.border(e)}),Ze,(function(e){return e.hasIcon?Ie(1.5):"0px"}),(function(e){return e.colors.borderHover(e)}),(function(e){return e.colors.bgHover(e)}),(function(e){return e.colors.colorHover(e)}),(function(e){return e.colors.colorHover(e)}),Je,(function(e){return e.active&&"\n ".concat(Je,"\n ")}),Ie(2),Ie(2),(function(e){return e.colors.color(e)}),(function(e){return e.colors.color(e)}),(function(e){return e.colors.color(e)})),lt=function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e},ut=s.d.svg.withConfig({displayName:"loader__StyledSvg",componentId:"sc-1mq98qd-0"})(Ae||(Ae=lt(["\n fill: none;\n stroke-width: 17px;\n stroke-dasharray: 100;\n stroke-dashoffset: 100;\n animation: ntd-draw 1s linear infinite;\n stroke: ",";\n width: 24px;\n .path {\n stroke: ",";\n }\n\n @keyframes ntd-draw {\n to {\n stroke-dashoffset: 0;\n }\n }\n"],["\n fill: none;\n stroke-width: 17px;\n stroke-dasharray: 100;\n stroke-dashoffset: 100;\n animation: ntd-draw 1s linear infinite;\n stroke: ",";\n width: 24px;\n .path {\n stroke: ",";\n }\n\n @keyframes ntd-draw {\n to {\n stroke-dashoffset: 0;\n }\n }\n"])),Ve("bright"),Ve("bright")),ft=function(e){var t=e.className;return c.a.createElement(ut,{className:t,viewBox:"0 0 21 17",version:"1.1",xmlns:"http://www.w3.org/2000/svg"},c.a.createElement("g",{className:"path",stroke:"none",strokeWidth:"1",fill:"none",fillRule:"evenodd"},c.a.createElement("path",{d:"M2,1 C8.25086152,1 11.9367136,1 13.0575562,1 C14.73882,1 19.6834591,2 19.9614325,7.72050108 C20.239406,13.4410022 15.7459591,15.1224845 13.6463763,15.1224845 C12.2466545,15.1224845 10.0279195,15.1224845 6.9901715,15.1224845 L2,1 Z",id:"Path-2",strokeWidth:"2"})))},dt=n(1),ht=n.n(dt),pt=n(2),zt=n.n(pt),vt=new ht.a({id:"add_node",use:"add_node-usage",viewBox:"0 0 18 18",content:''}),gt=(zt.a.add(vt),vt),mt=new ht.a({id:"add_user",use:"add_user-usage",viewBox:"0 0 15 16",content:''}),yt=(zt.a.add(mt),mt),bt=new ht.a({id:"aggregation_avg",use:"aggregation_avg-usage",viewBox:"0 0 16 12",content:''}),wt=(zt.a.add(bt),bt),kt=new ht.a({id:"aggregation_max",use:"aggregation_max-usage",viewBox:"0 0 15 16",content:''}),xt=(zt.a.add(kt),kt),jt=new ht.a({id:"aggregation_med",use:"aggregation_med-usage",viewBox:"0 0 14 14",content:''}),Mt=(zt.a.add(jt),jt),Ct=new ht.a({id:"aggregation_min",use:"aggregation_min-usage",viewBox:"0 0 15 16",content:''}),_t=(zt.a.add(Ct),Ct),qt=new ht.a({id:"aggregation_sum",use:"aggregation_sum-usage",viewBox:"0 0 12 14",content:''}),St=(zt.a.add(qt),qt),Ot=new ht.a({id:"aggregation_sum_abs",use:"aggregation_sum_abs-usage",viewBox:"0 0 14 14",content:''}),Et=(zt.a.add(Ot),Ot),Tt=new ht.a({id:"alarm",use:"alarm-usage",viewBox:"0 0 18 21",content:''}),At=(zt.a.add(Tt),Tt),Lt=new ht.a({id:"alarm_c",use:"alarm_c-usage",viewBox:"0 0 24 24",content:''}),Ht=(zt.a.add(Lt),Lt),Dt=new ht.a({id:"alarm_cw",use:"alarm_cw-usage",viewBox:"0 0 24 24",content:''}),Pt=(zt.a.add(Dt),Dt),Vt=new ht.a({id:"alarm_w",use:"alarm_w-usage",viewBox:"0 0 24 24",content:''}),Rt=(zt.a.add(Vt),Vt),It=new ht.a({id:"alarm_bell",use:"alarm_bell-usage",viewBox:"0 0 12 14",content:''}),Nt=(zt.a.add(It),It),Ft=new ht.a({id:"alarms_new",use:"alarms_new-usage",viewBox:"0 0 22 20",content:''}),Bt=(zt.a.add(Ft),Ft),Ut=new ht.a({id:"alarm_off",use:"alarm_off-usage",viewBox:"0 0 15 16",content:''}),Wt=(zt.a.add(Ut),Ut),Gt=new ht.a({id:"anomalies_brain",use:"anomalies_brain-usage",viewBox:"0 0 18 18",content:''}),Yt=(zt.a.add(Gt),Gt),$t=new ht.a({id:"anomalies_lens",use:"anomalies_lens-usage",viewBox:"0 0 18 18",content:''}),Zt=(zt.a.add($t),$t),Xt=new ht.a({id:"applications_hollow",use:"applications_hollow-usage",viewBox:"0 0 18 18",content:''}),Kt=(zt.a.add(Xt),Xt),Qt=new ht.a({id:"around_clock",use:"around_clock-usage",viewBox:"0 0 16 16",content:''}),Jt=(zt.a.add(Qt),Qt),en=new ht.a({id:"arrow_down",use:"arrow_down-usage",viewBox:"0 0 16 16",content:''}),tn=(zt.a.add(en),en),nn=new ht.a({id:"arrow_w_line_left",use:"arrow_w_line_left-usage",viewBox:"0 0 26 24",content:''}),rn=(zt.a.add(nn),nn),on=new ht.a({id:"arrow_w_line_right",use:"arrow_w_line_right-usage",viewBox:"0 0 24 13",content:''}),an=(zt.a.add(on),on),cn=new ht.a({id:"arrow_left",use:"arrow_left-usage",viewBox:"0 0 24 24",content:''}),sn=(zt.a.add(cn),cn),ln=new ht.a({id:"arrow-s_down",use:"arrow-s_down-usage",viewBox:"0 0 8 9",content:''}),un=(zt.a.add(ln),ln),fn=new ht.a({id:"arrow-s_left",use:"arrow-s_left-usage",viewBox:"0 0 8 9",content:''}),dn=(zt.a.add(fn),fn),hn=new ht.a({id:"arrows_vertical",use:"arrows_vertical-usage",viewBox:"0 0 6 10",content:''}),pn=(zt.a.add(hn),hn),zn=new ht.a({id:"bookmark",use:"bookmark-usage",viewBox:"0 0 12 14",content:''}),vn=(zt.a.add(zn),zn),gn=new ht.a({id:"bullet_one",use:"bullet_one-usage",viewBox:"0 0 10 10",content:''}),mn=(zt.a.add(gn),gn),yn=new ht.a({id:"bullet_three",use:"bullet_three-usage",viewBox:"0 0 10 10",content:''}),bn=(zt.a.add(yn),yn),wn=new ht.a({id:"bullet_two",use:"bullet_two-usage",viewBox:"0 0 10 10",content:''}),kn=(zt.a.add(wn),wn),xn=new ht.a({id:"calendar_full",use:"calendar_full-usage",viewBox:"0 0 18 18",content:''}),jn=(zt.a.add(xn),xn),Mn=new ht.a({id:"calendar_full_press",use:"calendar_full_press-usage",viewBox:"0 0 18 18",content:''}),Cn=(zt.a.add(Mn),Mn),_n=new ht.a({id:"chart_added",use:"chart_added-usage",viewBox:"0 0 17 17",content:''}),qn=(zt.a.add(_n),_n),Sn=new ht.a({id:"charts",use:"charts-usage",viewBox:"0 0 20 20",content:''}),On=(zt.a.add(Sn),Sn),En=new ht.a({id:"check",use:"check-usage",viewBox:"0 0 24 24",content:''}),Tn=(zt.a.add(En),En),An=new ht.a({id:"checkmark_partial_s",use:"checkmark_partial_s-usage",viewBox:"0 0 16 16",content:''}),Ln=(zt.a.add(An),An),Hn=new ht.a({id:"checkmark_s",use:"checkmark_s-usage",viewBox:"0 0 16 16",content:''}),Dn=(zt.a.add(Hn),Hn),Pn=new ht.a({id:"checkmark",use:"checkmark-usage",viewBox:"0 0 168 168",content:''}),Vn=(zt.a.add(Pn),Pn),Rn=new ht.a({id:"chevron_double",use:"chevron_double-usage",viewBox:"0 0 6 10",content:''}),In=(zt.a.add(Rn),Rn),Nn=new ht.a({id:"chevron_down",use:"chevron_down-usage",viewBox:"0 0 12 12",content:''}),Fn=(zt.a.add(Nn),Nn),Bn=new ht.a({id:"chevron_left",use:"chevron_left-usage",viewBox:"0 0 24 24",content:''}),Un=(zt.a.add(Bn),Bn),Wn=new ht.a({id:"chevron_right_s",use:"chevron_right_s-usage",viewBox:"0 0 5 6",content:''}),Gn=(zt.a.add(Wn),Wn),Yn=new ht.a({id:"chevron_right_small",use:"chevron_right_small-usage",viewBox:"0 0 5 6",content:''}),$n=(zt.a.add(Yn),Yn),Zn=new ht.a({id:"chevron_left_small",use:"chevron_left_small-usage",viewBox:"0 0 5 6",content:''}),Xn=(zt.a.add(Zn),Zn),Kn=new ht.a({id:"class_error",use:"class_error-usage",viewBox:"0 0 21 22",content:''}),Qn=(zt.a.add(Kn),Kn),Jn=new ht.a({id:"class_latency",use:"class_latency-usage",viewBox:"0 0 21 20",content:''}),er=(zt.a.add(Jn),Jn),tr=new ht.a({id:"class_utilization",use:"class_utilization-usage",viewBox:"0 0 25 19",content:''}),nr=(zt.a.add(tr),tr),rr=new ht.a({id:"class_workload",use:"class_workload-usage",viewBox:"0 0 22 21",content:''}),or=(zt.a.add(rr),rr),ir=new ht.a({id:"clock_hollow",use:"clock_hollow-usage",viewBox:"0 0 24 24",content:''}),ar=(zt.a.add(ir),ir),cr=new ht.a({id:"clock_5_min",use:"clock_5_min-usage",viewBox:"0 0 18 18",content:''}),sr=(zt.a.add(cr),cr),lr=new ht.a({id:"clock_5_min_press",use:"clock_5_min_press-usage",viewBox:"0 0 18 18",content:''}),ur=(zt.a.add(lr),lr),fr=new ht.a({id:"close_circle",use:"close_circle-usage",viewBox:"0 0 10 10",content:''}),dr=(zt.a.add(fr),fr),hr=new ht.a({id:"cluster",use:"cluster-usage",viewBox:"0 0 22 22",content:''}),pr=(zt.a.add(hr),hr),zr=new ht.a({id:"cluster_spaces",use:"cluster_spaces-usage",viewBox:"0 0 22 22",content:''}),vr=(zt.a.add(zr),zr),gr=new ht.a({id:"code",use:"code-usage",viewBox:"0 0 16 16",content:''}),mr=(zt.a.add(gr),gr),yr=new ht.a({id:"collapse",use:"collapse-usage",viewBox:"0 0 16 2",content:''}),br=(zt.a.add(yr),yr),wr=new ht.a({id:"community",use:"community-usage",viewBox:"0 0 18 18",content:''}),kr=(zt.a.add(wr),wr),xr=new ht.a({id:"connectivity_status_live",use:"connectivity_status_live-usage",viewBox:"0 0 18 18",content:''}),jr=(zt.a.add(xr),xr),Mr=new ht.a({id:"connectivity_status_offline",use:"connectivity_status_offline-usage",viewBox:"0 0 18 18",content:''}),Cr=(zt.a.add(Mr),Mr),_r=new ht.a({id:"connectivity_status_stale",use:"connectivity_status_stale-usage",viewBox:"0 0 18 18",content:''}),qr=(zt.a.add(_r),_r),Sr=new ht.a({id:"container",use:"container-usage",viewBox:"0 0 22 22",content:''}),Or=(zt.a.add(Sr),Sr),Er=new ht.a({id:"controller_kind",use:"controller_kind-usage",viewBox:"0 0 22 22",content:''}),Tr=(zt.a.add(Er),Er),Ar=new ht.a({id:"controller_name",use:"controller_name-usage",viewBox:"0 0 22 22",content:''}),Lr=(zt.a.add(Ar),Ar),Hr=new ht.a({id:"copy",use:"copy-usage",viewBox:"0 0 14 14",content:''}),Dr=(zt.a.add(Hr),Hr),Pr=new ht.a({id:"correlation",use:"correlation-usage",viewBox:"0 0 28 28",content:''}),Vr=(zt.a.add(Pr),Pr),Rr=new ht.a({id:"correlation_inv",use:"correlation_inv-usage",viewBox:"0 0 24 24",content:''}),Ir=(zt.a.add(Rr),Rr),Nr=new ht.a({id:"cpu",use:"cpu-usage",viewBox:"0 0 18 18",content:''}),Fr=(zt.a.add(Nr),Nr),Br=new ht.a({id:"cross_s",use:"cross_s-usage",viewBox:"0 0 16 16",content:''}),Ur=(zt.a.add(Br),Br),Wr=new ht.a({id:"data_retention",use:"data_retention-usage",viewBox:"0 0 18 18",content:''}),Gr=(zt.a.add(Wr),Wr),Yr=new ht.a({id:"database",use:"database-usage",viewBox:"0 0 24 24",content:''}),$r=(zt.a.add(Yr),Yr),Zr=new ht.a({id:"dashboard",use:"dashboard-usage",viewBox:"0 0 22 18",content:''}),Xr=(zt.a.add(Zr),Zr),Kr=new ht.a({id:"dashboard_add",use:"dashboard_add-usage",viewBox:"0 0 16 16",content:''}),Qr=(zt.a.add(Kr),Kr),Jr=new ht.a({id:"dashboards",use:"dashboards-usage",viewBox:"0 0 16 10",content:''}),eo=(zt.a.add(Jr),Jr),to=new ht.a({id:"disk",use:"disk-usage",viewBox:"0 0 18 18",content:''}),no=(zt.a.add(to),to),ro=new ht.a({id:"documentation",use:"documentation-usage",viewBox:"0 0 24 24",content:''}),oo=(zt.a.add(ro),ro),io=new ht.a({id:"dot",use:"dot-usage",viewBox:"0 0 10 10",content:''}),ao=(zt.a.add(io),io),co=new ht.a({id:"dots_2x3",use:"dots_2x3-usage",viewBox:"0 0 6 10",content:''}),so=(zt.a.add(co),co),lo=new ht.a({id:"download",use:"download-usage",viewBox:"0 0 20 20",content:''}),uo=(zt.a.add(lo),lo),fo=new ht.a({id:"error",use:"error-usage",viewBox:"0 0 24 24",content:''}),ho=(zt.a.add(fo),fo),po=new ht.a({id:"exclamation",use:"exclamation-usage",viewBox:"0 0 24 24",content:''}),zo=(zt.a.add(po),po),vo=new ht.a({id:"expand",use:"expand-usage",viewBox:"0 0 24 24",content:''}),go=(zt.a.add(vo),vo),mo=new ht.a({id:"filterList",use:"filterList-usage",viewBox:"0 0 18 18",content:''}),yo=(zt.a.add(mo),mo),bo=new ht.a({id:"force_play",use:"force_play-usage",viewBox:"0 0 18 18",content:''}),wo=(zt.a.add(bo),bo),ko=new ht.a({id:"force_play_outline",use:"force_play_outline-usage",viewBox:"0 0 18 18",content:''}),xo=(zt.a.add(ko),ko),jo=new ht.a({id:"gear",use:"gear-usage",viewBox:"0 0 20 20",content:''}),Mo=(zt.a.add(jo),jo),Co=new ht.a({id:"github",use:"github-usage",viewBox:"0 0 24 24",content:''}),_o=(zt.a.add(Co),Co),qo=new ht.a({id:"go_to_node",use:"go_to_node-usage",viewBox:"0 0 18 18",content:''}),So=(zt.a.add(qo),qo),Oo=new ht.a({id:"google",use:"google-usage",viewBox:"0 0 24 24",content:''}),Eo=(zt.a.add(Oo),Oo),To=new ht.a({id:"group_by",use:"group_by-usage",viewBox:"0 0 18 18",content:''}),Ao=(zt.a.add(To),To),Lo=new ht.a({id:"hamburger",use:"hamburger-usage",viewBox:"0 0 24 24",content:''}),Ho=(zt.a.add(Lo),Lo),Do=new ht.a({id:"help",use:"help-usage",viewBox:"0 0 20 21",content:''}),Po=(zt.a.add(Do),Do),Vo=new ht.a({id:"hide",use:"hide-usage",viewBox:"0 0 18 18",content:''}),Ro=(zt.a.add(Vo),Vo),Io=new ht.a({id:"highlight_area",use:"highlight_area-usage",viewBox:"0 0 16 16",content:''}),No=(zt.a.add(Io),Io),Fo=new ht.a({id:"holder",use:"holder-usage",viewBox:"0 0 24 24",content:''}),Bo=(zt.a.add(Fo),Fo),Uo=new ht.a({id:"incident_manager",use:"incident_manager-usage",viewBox:"0 0 18 18",content:''}),Wo=(zt.a.add(Uo),Uo),Go=new ht.a({id:"information",use:"information-usage",viewBox:"0 0 18 18",content:''}),Yo=(zt.a.add(Go),Go),$o=new ht.a({id:"information_press",use:"information_press-usage",viewBox:"0 0 18 18",content:''}),Zo=(zt.a.add($o),$o),Xo=new ht.a({id:"insights",use:"insights-usage",viewBox:"0 0 18 18",content:''}),Ko=(zt.a.add(Xo),Xo),Qo=new ht.a({id:"integrations",use:"integrations-usage",viewBox:"0 0 16 16",content:''}),Jo=(zt.a.add(Qo),Qo),ei=new ht.a({id:"ipNetworking",use:"ipNetworking-usage",viewBox:"0 0 16 16",content:''}),ti=(zt.a.add(ei),ei),ni=new ht.a({id:"ipNetworkingPress",use:"ipNetworkingPress-usage",viewBox:"0 0 16 16",content:''}),ri=(zt.a.add(ni),ni),oi=new ht.a({id:"last_week",use:"last_week-usage",viewBox:"0 0 18 18",content:''}),ii=(zt.a.add(oi),oi),ai=new ht.a({id:"line_chart",use:"line_chart-usage",viewBox:"0 0 15 15",content:''}),ci=(zt.a.add(ai),ai),si=new ht.a({id:"logo_s",use:"logo_s-usage",viewBox:"0 0 14 13",content:''}),li=(zt.a.add(si),si),ui=new ht.a({id:"loading",use:"loading-usage",viewBox:"0 0 24 24",content:''}),fi=(zt.a.add(ui),ui),di=new ht.a({id:"magnify",use:"magnify-usage",viewBox:"0 0 24 24",content:''}),hi=(zt.a.add(di),di),pi=new ht.a({id:"metrics",use:"metrics-usage",viewBox:"0 0 24 24",content:''}),zi=(zt.a.add(pi),pi),vi=new ht.a({id:"metrics_explorer",use:"metrics_explorer-usage",viewBox:"0 0 18 18",content:''}),gi=(zt.a.add(vi),vi),mi=new ht.a({id:"monitoring",use:"monitoring-usage",viewBox:"0 0 20 20",content:''}),yi=(zt.a.add(mi),mi),bi=new ht.a({id:"more",use:"more-usage",viewBox:"0 0 18 4",content:''}),wi=(zt.a.add(bi),bi),ki=new ht.a({id:"nav_left",use:"nav_left-usage",viewBox:"0 0 8 10",content:''}),xi=(zt.a.add(ki),ki),ji=new ht.a({id:"nav_right",use:"nav_right-usage",viewBox:"0 0 8 10",content:''}),Mi=(zt.a.add(ji),ji),Ci=new ht.a({id:"nav_arrow_goto",use:"nav_arrow_goto-usage",viewBox:"0 0 10 10",content:''}),_i=(zt.a.add(Ci),Ci),qi=new ht.a({id:"nav_dots",use:"nav_dots-usage",viewBox:"0 0 24 24",content:''}),Si=(zt.a.add(qi),qi),Oi=new ht.a({id:"netdata",use:"netdata-usage",viewBox:"0 0 24 24",content:''}),Ei=(zt.a.add(Oi),Oi),Ti=new ht.a({id:"netdata-press",use:"netdata-press-usage",viewBox:"0 0 18 18",content:''}),Ai=(zt.a.add(Ti),Ti),Li=new ht.a({id:"node",use:"node-usage",viewBox:"0 0 24 24",content:''}),Hi=(zt.a.add(Li),Li),Di=new ht.a({id:"node_child",use:"node_child-usage",viewBox:"0 0 18 18",content:''}),Pi=(zt.a.add(Di),Di),Vi=new ht.a({id:"node_default_l",use:"node_default_l-usage",viewBox:"0 0 40 40",content:''}),Ri=(zt.a.add(Vi),Vi),Ii=new ht.a({id:"node_hollow",use:"node_hollow-usage",viewBox:"0 0 22 12",content:''}),Ni=(zt.a.add(Ii),Ii),Fi=new ht.a({id:"node_import_export",use:"node_import_export-usage",viewBox:"0 0 24 24",content:''}),Bi=(zt.a.add(Fi),Fi),Ui=new ht.a({id:"node_notification_l",use:"node_notification_l-usage",viewBox:"0 0 40 40",content:''}),Wi=(zt.a.add(Ui),Ui),Gi=new ht.a({id:"node_parent",use:"node_parent-usage",viewBox:"0 0 18 18",content:''}),Yi=(zt.a.add(Gi),Gi),$i=new ht.a({id:"node_selected_l",use:"node_selected_l-usage",viewBox:"0 0 40 40",content:''}),Zi=(zt.a.add($i),$i),Xi=new ht.a({id:"nodes",use:"nodes-usage",viewBox:"0 0 16 16",content:''}),Ki=(zt.a.add(Xi),Xi),Qi=new ht.a({id:"nodes_hollow",use:"nodes_hollow-usage",viewBox:"0 0 18 18",content:''}),Ji=(zt.a.add(Qi),Qi),ea=new ht.a({id:"none_selected",use:"none_selected-usage",viewBox:"0 0 16 16",content:''}),ta=(zt.a.add(ea),ea),na=new ht.a({id:"os",use:"os-usage",viewBox:"0 0 18 18",content:''}),ra=(zt.a.add(na),na),oa=new ht.a({id:"alpine_linux",use:"alpine_linux-usage",viewBox:"0 0 18 18",content:''}),ia=(zt.a.add(oa),oa),aa=new ht.a({id:"amazon_linux",use:"amazon_linux-usage",viewBox:"0 0 18 18",content:''}),ca=(zt.a.add(aa),aa),sa=new ht.a({id:"arch_linux",use:"arch_linux-usage",viewBox:"0 0 18 18",content:''}),la=(zt.a.add(sa),sa),ua=new ht.a({id:"celarOS",use:"celarOS-usage",viewBox:"0 0 18 18",content:''}),fa=(zt.a.add(ua),ua),da=new ht.a({id:"centos",use:"centos-usage",viewBox:"0 0 18 18",content:''}),ha=(zt.a.add(da),da),pa=new ht.a({id:"centos_color",use:"centos_color-usage",viewBox:"0 0 18 18",content:''}),za=(zt.a.add(pa),pa),va=new ht.a({id:"coreOS",use:"coreOS-usage",viewBox:"0 0 18 18",content:''}),ga=(zt.a.add(va),va),ma=new ht.a({id:"debian",use:"debian-usage",viewBox:"0 0 18 18",content:''}),ya=(zt.a.add(ma),ma),ba=new ht.a({id:"debian_color",use:"debian_color-usage",viewBox:"0 0 18 18",content:''}),wa=(zt.a.add(ba),ba),ka=new ht.a({id:"fedora",use:"fedora-usage",viewBox:"0 0 18 18",content:''}),xa=(zt.a.add(ka),ka),ja=new ht.a({id:"freeBSD",use:"freeBSD-usage",viewBox:"0 0 18 18",content:''}),Ma=(zt.a.add(ja),ja),Ca=new ht.a({id:"gentoo",use:"gentoo-usage",viewBox:"0 0 18 18",content:''}),_a=(zt.a.add(Ca),Ca),qa=new ht.a({id:"linux",use:"linux-usage",viewBox:"0 0 18 18",content:''}),Sa=(zt.a.add(qa),qa),Oa=new ht.a({id:"linux_color",use:"linux_color-usage",viewBox:"0 0 18 18",content:''}),Ea=(zt.a.add(Oa),Oa),Ta=new ht.a({id:"macOSX",use:"macOSX-usage",viewBox:"0 0 18 18",content:''}),Aa=(zt.a.add(Ta),Ta),La=new ht.a({id:"oracle",use:"oracle-usage",viewBox:"0 0 18 18",content:''}),Ha=(zt.a.add(La),La),Da=new ht.a({id:"oracle_color",use:"oracle_color-usage",viewBox:"0 0 18 18",content:''}),Pa=(zt.a.add(Da),Da),Va=new ht.a({id:"os_press",use:"os_press-usage",viewBox:"0 0 18 18",content:''}),Ra=(zt.a.add(Va),Va),Ia=new ht.a({id:"raspbian",use:"raspbian-usage",viewBox:"0 0 18 18",content:''}),Na=(zt.a.add(Ia),Ia),Fa=new ht.a({id:"red_hat",use:"red_hat-usage",viewBox:"0 0 18 18",content:''}),Ba=(zt.a.add(Fa),Fa),Ua=new ht.a({id:"suse_linux",use:"suse_linux-usage",viewBox:"0 0 18 18",content:''}),Wa=(zt.a.add(Ua),Ua),Ga=new ht.a({id:"ubuntu",use:"ubuntu-usage",viewBox:"0 0 18 18",content:''}),Ya=(zt.a.add(Ga),Ga),$a=new ht.a({id:"ubuntu_color",use:"ubuntu_color-usage",viewBox:"0 0 18 18",content:''}),Za=(zt.a.add($a),$a),Xa=new ht.a({id:"notification",use:"notification-usage",viewBox:"0 0 40 24",content:''}),Ka=(zt.a.add(Xa),Xa),Qa=new ht.a({id:"padlock",use:"padlock-usage",viewBox:"0 0 18 18",content:''}),Ja=(zt.a.add(Qa),Qa),ec=new ht.a({id:"pause_outline",use:"pause_outline-usage",viewBox:"0 0 18 18",content:''}),tc=(zt.a.add(ec),ec),nc=new ht.a({id:"pause_solid",use:"pause_solid-usage",viewBox:"0 0 24 24",content:''}),rc=(zt.a.add(nc),nc),oc=new ht.a({id:"pencil_outline",use:"pencil_outline-usage",viewBox:"0 0 14 14",content:''}),ic=(zt.a.add(oc),oc),ac=new ht.a({id:"pencil_solid",use:"pencil_solid-usage",viewBox:"0 0 19 19",content:''}),cc=(zt.a.add(ac),ac),sc=new ht.a({id:"pie_chart_skeleton",use:"pie_chart_skeleton-usage",viewBox:"0 0 100 100",content:''}),lc=(zt.a.add(sc),sc),uc=new ht.a({id:"pin_element",use:"pin_element-usage",viewBox:"0 0 14 14",content:''}),fc=(zt.a.add(uc),uc),dc=new ht.a({id:"play_outline",use:"play_outline-usage",viewBox:"0 0 18 18",content:''}),hc=(zt.a.add(dc),dc),pc=new ht.a({id:"play_solid",use:"play_solid-usage",viewBox:"0 0 24 24",content:''}),zc=(zt.a.add(pc),pc),vc=new ht.a({id:"plus",use:"plus-usage",viewBox:"0 0 24 24",content:''}),gc=(zt.a.add(vc),vc),mc=new ht.a({id:"plus_mini_s",use:"plus_mini_s-usage",viewBox:"0 0 24 24",content:''}),yc=(zt.a.add(mc),mc),bc=new ht.a({id:"pod",use:"pod-usage",viewBox:"0 0 22 22",content:''}),wc=(zt.a.add(bc),bc),kc=new ht.a({id:"pricing",use:"pricing-usage",viewBox:"0 0 16 16",content:''}),xc=(zt.a.add(kc),kc),jc=new ht.a({id:"print",use:"print-usage",viewBox:"0 0 21 20",content:''}),Mc=(zt.a.add(jc),jc),Cc=new ht.a({id:"privacy",use:"privacy-usage",viewBox:"0 0 16 16",content:''}),_c=(zt.a.add(Cc),Cc),qc=new ht.a({id:"question",use:"question-usage",viewBox:"0 0 20 20",content:''}),Sc=(zt.a.add(qc),qc),Oc=new ht.a({id:"questionFilled",use:"questionFilled-usage",viewBox:"0 0 24 24",content:''}),Ec=(zt.a.add(Oc),Oc),Tc=new ht.a({id:"ram",use:"ram-usage",viewBox:"0 0 18 18",content:''}),Ac=(zt.a.add(Tc),Tc),Lc=new ht.a({id:"refresh",use:"refresh-usage",viewBox:"0 0 18 19",content:''}),Hc=(zt.a.add(Lc),Lc),Dc=new ht.a({id:"reload",use:"reload-usage",viewBox:"0 0 24 24",content:''}),Pc=(zt.a.add(Dc),Dc),Vc=new ht.a({id:"remove_node",use:"remove_node-usage",viewBox:"0 0 18 18",content:''}),Rc=(zt.a.add(Vc),Vc),Ic=new ht.a({id:"resize_handler",use:"resize_handler-usage",viewBox:"0 0 16 16",content:''}),Nc=(zt.a.add(Ic),Ic),Fc=new ht.a({id:"room",use:"room-usage",viewBox:"0 0 24 24",content:''}),Bc=(zt.a.add(Fc),Fc),Uc=new ht.a({id:"room_home",use:"room_home-usage",viewBox:"0 0 14 12",content:''}),Wc=(zt.a.add(Uc),Uc),Gc=new ht.a({id:"room_new",use:"room_new-usage",viewBox:"0 0 20 20",content:''}),Yc=(zt.a.add(Gc),Gc),$c=new ht.a({id:"room_overview",use:"room_overview-usage",viewBox:"0 0 24 25",content:''}),Zc=(zt.a.add($c),$c),Xc=new ht.a({id:"sad",use:"sad-usage",viewBox:"0 0 24 24",content:''}),Kc=(zt.a.add(Xc),Xc),Qc=new ht.a({id:"save",use:"save-usage",viewBox:"0 0 14 14",content:''}),Jc=(zt.a.add(Qc),Qc),es=new ht.a({id:"search",use:"search-usage",viewBox:"0 0 18 18",content:''}),ts=(zt.a.add(es),es),ns=new ht.a({id:"search_s",use:"search_s-usage",viewBox:"0 0 14 14",content:''}),rs=(zt.a.add(ns),ns),os=new ht.a({id:"search_press",use:"search_press-usage",viewBox:"0 0 18 18",content:''}),is=(zt.a.add(os),os),as=new ht.a({id:"apache",use:"apache-usage",viewBox:"0 0 18 18",content:''}),cs=(zt.a.add(as),as),ss=new ht.a({id:"apache_tomcat",use:"apache_tomcat-usage",viewBox:"0 0 18 18",content:''}),ls=(zt.a.add(ss),ss),us=new ht.a({id:"beanstalk",use:"beanstalk-usage",viewBox:"0 0 18 18",content:''}),fs=(zt.a.add(us),us),ds=new ht.a({id:"couchDB",use:"couchDB-usage",viewBox:"0 0 18 18",content:''}),hs=(zt.a.add(ds),ds),ps=new ht.a({id:"database",use:"database-usage",viewBox:"0 0 18 18",content:''}),zs=(zt.a.add(ps),ps),vs=new ht.a({id:"docker_hub",use:"docker_hub-usage",viewBox:"0 0 18 18",content:''}),gs=(zt.a.add(vs),vs),ms=new ht.a({id:"docker_hub_press",use:"docker_hub_press-usage",viewBox:"0 0 18 18",content:''}),ys=(zt.a.add(ms),ms),bs=new ht.a({id:"eBPF",use:"eBPF-usage",viewBox:"0 0 18 18",content:''}),ws=(zt.a.add(bs),bs),ks=new ht.a({id:"elasticSearch",use:"elasticSearch-usage",viewBox:"0 0 18 18",content:''}),xs=(zt.a.add(ks),ks),js=new ht.a({id:"freeNAS",use:"freeNAS-usage",viewBox:"0 0 18 18",content:''}),Ms=(zt.a.add(js),js),Cs=new ht.a({id:"haProxy",use:"haProxy-usage",viewBox:"0 0 18 18",content:''}),_s=(zt.a.add(Cs),Cs),qs=new ht.a({id:"httpCheck",use:"httpCheck-usage",viewBox:"0 0 18 18",content:''}),Ss=(zt.a.add(qs),qs),Os=new ht.a({id:"iceCast",use:"iceCast-usage",viewBox:"0 0 18 18",content:''}),Es=(zt.a.add(Os),Os),Ts=new ht.a({id:"influxDB",use:"influxDB-usage",viewBox:"0 0 18 18",content:''}),As=(zt.a.add(Ts),Ts),Ls=new ht.a({id:"ipfs",use:"ipfs-usage",viewBox:"0 0 18 18",content:''}),Hs=(zt.a.add(Ls),Ls),Ds=new ht.a({id:"ipvs",use:"ipvs-usage",viewBox:"0 0 18 18",content:''}),Ps=(zt.a.add(Ds),Ds),Vs=new ht.a({id:"kubermetes",use:"kubermetes-usage",viewBox:"0 0 18 18",content:''}),Rs=(zt.a.add(Vs),Vs),Is=new ht.a({id:"lighthttpd",use:"lighthttpd-usage",viewBox:"0 0 18 18",content:''}),Ns=(zt.a.add(Is),Is),Fs=new ht.a({id:"lighthttpd2",use:"lighthttpd2-usage",viewBox:"0 0 18 18",content:''}),Bs=(zt.a.add(Fs),Fs),Us=new ht.a({id:"liteSpeed",use:"liteSpeed-usage",viewBox:"0 0 18 18",content:''}),Ws=(zt.a.add(Us),Us),Gs=new ht.a({id:"lxc",use:"lxc-usage",viewBox:"0 0 18 18",content:''}),Ys=(zt.a.add(Gs),Gs),$s=new ht.a({id:"mariaDB",use:"mariaDB-usage",viewBox:"0 0 18 18",content:''}),Zs=(zt.a.add($s),$s),Xs=new ht.a({id:"memCached",use:"memCached-usage",viewBox:"0 0 18 18",content:''}),Ks=(zt.a.add(Xs),Xs),Qs=new ht.a({id:"mongoDB",use:"mongoDB-usage",viewBox:"0 0 18 18",content:''}),Js=(zt.a.add(Qs),Qs),el=new ht.a({id:"mySQL",use:"mySQL-usage",viewBox:"0 0 18 18",content:''}),tl=(zt.a.add(el),el),nl=new ht.a({id:"mySQL_press",use:"mySQL_press-usage",viewBox:"0 0 18 18",content:''}),rl=(zt.a.add(nl),nl),ol=new ht.a({id:"nginx",use:"nginx-usage",viewBox:"0 0 18 18",content:''}),il=(zt.a.add(ol),ol),al=new ht.a({id:"nginx_local",use:"nginx_local-usage",viewBox:"0 0 18 18",content:''}),cl=(zt.a.add(al),al),sl=new ht.a({id:"nginx_plus",use:"nginx_plus-usage",viewBox:"0 0 18 18",content:''}),ll=(zt.a.add(sl),sl),ul=new ht.a({id:"ntpd",use:"ntpd-usage",viewBox:"0 0 18 18",content:''}),fl=(zt.a.add(ul),ul),dl=new ht.a({id:"ntpd_press",use:"ntpd_press-usage",viewBox:"0 0 18 18",content:''}),hl=(zt.a.add(dl),dl),pl=new ht.a({id:"openStack",use:"openStack-usage",viewBox:"0 0 18 18",content:''}),zl=(zt.a.add(pl),pl),vl=new ht.a({id:"openWrt",use:"openWrt-usage",viewBox:"0 0 18 18",content:''}),gl=(zt.a.add(vl),vl),ml=new ht.a({id:"pan",use:"pan-usage",viewBox:"0 0 18 18",content:''}),yl=(zt.a.add(ml),ml),bl=new ht.a({id:"percona",use:"percona-usage",viewBox:"0 0 18 18",content:''}),wl=(zt.a.add(bl),bl),kl=new ht.a({id:"pfSense",use:"pfSense-usage",viewBox:"0 0 18 18",content:''}),xl=(zt.a.add(kl),kl),jl=new ht.a({id:"php_fpm",use:"php_fpm-usage",viewBox:"0 0 18 18",content:''}),Ml=(zt.a.add(jl),jl),Cl=new ht.a({id:"postgreSQL",use:"postgreSQL-usage",viewBox:"0 0 18 18",content:''}),_l=(zt.a.add(Cl),Cl),ql=new ht.a({id:"rabbitMQ",use:"rabbitMQ-usage",viewBox:"0 0 18 18",content:''}),Sl=(zt.a.add(ql),ql),Ol=new ht.a({id:"redis",use:"redis-usage",viewBox:"0 0 18 18",content:''}),El=(zt.a.add(Ol),Ol),Tl=new ht.a({id:"rethinkDB",use:"rethinkDB-usage",viewBox:"0 0 18 18",content:''}),Al=(zt.a.add(Tl),Tl),Ll=new ht.a({id:"retroShare",use:"retroShare-usage",viewBox:"0 0 18 18",content:''}),Hl=(zt.a.add(Ll),Ll),Dl=new ht.a({id:"services",use:"services-usage",viewBox:"0 0 18 18",content:''}),Pl=(zt.a.add(Dl),Dl),Vl=new ht.a({id:"selected_area",use:"selected_area-usage",viewBox:"0 0 18 18",content:''}),Rl=(zt.a.add(Vl),Vl),Il=new ht.a({id:"solr",use:"solr-usage",viewBox:"0 0 18 18",content:''}),Nl=(zt.a.add(Il),Il),Fl=new ht.a({id:"squid",use:"squid-usage",viewBox:"0 0 18 18",content:''}),Bl=(zt.a.add(Fl),Fl),Ul=new ht.a({id:"summary_statistic",use:"summary_statistic-usage",viewBox:"0 0 18 18",content:''}),Wl=(zt.a.add(Ul),Ul),Gl=new ht.a({id:"traefik",use:"traefik-usage",viewBox:"0 0 18 18",content:''}),Yl=(zt.a.add(Gl),Gl),$l=new ht.a({id:"varnish",use:"varnish-usage",viewBox:"0 0 18 18",content:''}),Zl=(zt.a.add($l),$l),Xl=new ht.a({id:"webLog",use:"webLog-usage",viewBox:"0 0 18 18",content:''}),Kl=(zt.a.add(Xl),Xl),Ql=new ht.a({id:"webLog_nginx",use:"webLog_nginx-usage",viewBox:"0 0 18 18",content:''}),Jl=(zt.a.add(Ql),Ql),eu=new ht.a({id:"x509_check",use:"x509_check-usage",viewBox:"0 0 18 18",content:''}),tu=(zt.a.add(eu),eu),nu=new ht.a({id:"xen",use:"xen-usage",viewBox:"0 0 18 18",content:''}),ru=(zt.a.add(nu),nu),ou=new ht.a({id:"settings",use:"settings-usage",viewBox:"0 0 17 15",content:''}),iu=(zt.a.add(ou),ou),au=new ht.a({id:"settings_h",use:"settings_h-usage",viewBox:"0 0 14 14",content:''}),cu=(zt.a.add(au),au),su=new ht.a({id:"sorting_vertical",use:"sorting_vertical-usage",viewBox:"0 0 19 18",content:''}),lu=(zt.a.add(su),su),uu=new ht.a({id:"sorting_desc",use:"sorting_desc-usage",viewBox:"0 0 8 9",content:''}),fu=(zt.a.add(uu),uu),du=new ht.a({id:"sorting_asc",use:"sorting_asc-usage",viewBox:"0 0 8 9",content:''}),hu=(zt.a.add(du),du),pu=new ht.a({id:"space",use:"space-usage",viewBox:"0 0 24 24",content:''}),zu=(zt.a.add(pu),pu),vu=new ht.a({id:"space_new",use:"space_new-usage",viewBox:"0 0 20 20",content:''}),gu=(zt.a.add(vu),vu),mu=new ht.a({id:"switch_off",use:"switch_off-usage",viewBox:"0 0 14 15",content:''}),yu=(zt.a.add(mu),mu),bu=new ht.a({id:"system_overview",use:"system_overview-usage",viewBox:"0 0 32 32",content:''}),wu=(zt.a.add(bu),bu),ku=new ht.a({id:"text_add",use:"text_add-usage",viewBox:"0 0 16 16",content:''}),xu=(zt.a.add(ku),ku),ju=new ht.a({id:"thumb_down",use:"thumb_down-usage",viewBox:"0 0 24 24",content:''}),Mu=(zt.a.add(ju),ju),Cu=new ht.a({id:"thumb_up",use:"thumb_up-usage",viewBox:"0 0 24 24",content:''}),_u=(zt.a.add(Cu),Cu),qu=new ht.a({id:"tiny_buttons",use:"tiny_buttons-usage",viewBox:"0 0 22 22",content:''}),Su=(zt.a.add(qu),qu),Ou=new ht.a({id:"training",use:"training-usage",viewBox:"0 0 16 16",content:''}),Eu=(zt.a.add(Ou),Ou),Tu=new ht.a({id:"trashcan",use:"trashcan-usage",viewBox:"0 0 14 15",content:''}),Au=(zt.a.add(Tu),Tu),Lu=new ht.a({id:"triangle",use:"triangle-usage",viewBox:"0 0 24 24",content:''}),Hu=(zt.a.add(Lu),Lu),Du=new ht.a({id:"triangle_down",use:"triangle_down-usage",viewBox:"0 0 10 5",content:''}),Pu=(zt.a.add(Du),Du),Vu=new ht.a({id:"unknownError",use:"unknownError-usage",viewBox:"0 0 16 16",content:''}),Ru=(zt.a.add(Vu),Vu),Iu=new ht.a({id:"universe",use:"universe-usage",viewBox:"0 0 18 18",content:''}),Nu=(zt.a.add(Iu),Iu),Fu=new ht.a({id:"unreachable",use:"unreachable-usage",viewBox:"0 0 12 14",content:''}),Bu=(zt.a.add(Fu),Fu),Uu=new ht.a({id:"unreachableNode",use:"unreachableNode-usage",viewBox:"0 0 231 230",content:''}),Wu=(zt.a.add(Uu),Uu),Gu=new ht.a({id:"update",use:"update-usage",viewBox:"0 0 20 20",content:''}),Yu=(zt.a.add(Gu),Gu),$u=new ht.a({id:"update_pending",use:"update_pending-usage",viewBox:"0 0 20 20",content:''}),Zu=(zt.a.add($u),$u),Xu=new ht.a({id:"upload",use:"upload-usage",viewBox:"0 0 20 21",content:''}),Ku=(zt.a.add(Xu),Xu),Qu=new ht.a({id:"user",use:"user-usage",viewBox:"0 0 16 18",content:''}),Ju=(zt.a.add(Qu),Qu),ef=new ht.a({id:"users",use:"users-usage",viewBox:"0 0 14 14",content:''}),tf=(zt.a.add(ef),ef),nf=new ht.a({id:"view_list",use:"view_list-usage",viewBox:"0 0 24 24",content:''}),rf=(zt.a.add(nf),nf),of=new ht.a({id:"single_node_view",use:"single_node_view-usage",viewBox:"0 0 18 18",content:''}),af=(zt.a.add(of),of),cf=new ht.a({id:"single_node_view_press",use:"single_node_view_press-usage",viewBox:"0 0 18 18",content:''}),sf=(zt.a.add(cf),cf),lf=new ht.a({id:"virtualization",use:"virtualization-usage",viewBox:"0 0 16 16",content:''}),uf=(zt.a.add(lf),lf),ff=new ht.a({id:"warning",use:"warning-usage",viewBox:"0 0 24 24",content:''}),df=(zt.a.add(ff),ff),hf=new ht.a({id:"warning_triangle",use:"warning_triangle-usage",viewBox:"0 0 12 10",content:''}),pf=(zt.a.add(hf),hf),zf=new ht.a({id:"warning_triangle_hollow",use:"warning_triangle_hollow-usage",viewBox:"0 0 24 24",content:''}),vf=(zt.a.add(zf),zf),gf=new ht.a({id:"x",use:"x-usage",viewBox:"0 0 24 24",content:''}),mf=(zt.a.add(gf),gf),yf=new ht.a({id:"firewall_solid",use:"firewall_solid-usage",viewBox:"0 0 24 24",content:''}),bf=(zt.a.add(yf),yf),wf=new ht.a({id:"qualityOfService_solid",use:"qualityOfService_solid-usage",viewBox:"0 0 24 24",content:''}),kf=(zt.a.add(wf),wf),xf=new ht.a({id:"applications_solid",use:"applications_solid-usage",viewBox:"0 0 24 24",content:''}),jf=(zt.a.add(xf),xf),Mf=new ht.a({id:"networking_stack",use:"networking_stack-usage",viewBox:"0 0 18 18",content:''}),Cf=(zt.a.add(Mf),Mf),_f=new ht.a({id:"charts_view",use:"charts_view-usage",viewBox:"0 0 16 15",content:''}),qf=(zt.a.add(_f),_f),Sf=new ht.a({id:"nodes_update",use:"nodes_update-usage",viewBox:"0 0 40 40",content:''}),Of=(zt.a.add(Sf),{add_node:gt,add_user:yt,aggregation_avg:wt,aggregation_max:xt,aggregation_med:Mt,aggregation_min:_t,aggregation_sum:St,aggregation_sum_abs:Et,alarm:At,alarmCritical:Ht,alarmCriticalWarning:Pt,alarmWarning:Rt,alarm_bell:Nt,alarms_new:Bt,alarm_off:Wt,anomaliesBrain:Yt,anomaliesLens:Zt,applications_hollow:Kt,applicationsSolid:jf,around_clock:Jt,arrow_down:tn,arrow_w_line_left:rn,arrow_w_line_right:an,arrow_left:sn,arrow_s_down:un,arrow_s_left:dn,arrows_vertical:pn,bookmark:vn,bullet_one:mn,bullet_three:bn,bullet_two:kn,calendarFull:jn,calendarFullPress:Cn,chart_added:qn,charts:On,charts_view:qf,check:Tn,checkmark_partial_s:Ln,checkmark_s:Dn,checkmark:Vn,chevron_double:In,chevron_down:Fn,chevron_left:Un,chevron_right_s:Gn,chevron_right_small:$n,chevron_left_small:Xn,classError:Qn,classLatency:er,classUtilization:nr,classWorkload:or,clock_hollow:ar,clock5Min:sr,clock5MinPress:ur,close_circle:dr,cluster:pr,cluster_spaces:vr,code:mr,collapse:br,community:kr,connectivityStatusLive:jr,connectivityStatusOffline:Cr,connectivityStatusStale:qr,container:Or,controller_kind:Tr,controller_name:Lr,copy:Dr,correlation:Vr,correlation_inv:Ir,cpu:Fr,cross_s:Ur,data_retention:Gr,database:$r,dashboard:Xr,dashboard_add:Qr,dashboards:eo,disk:no,documentation:oo,dot:ao,dots_2x3:so,download:uo,error:ho,exclamation:zo,expand:go,filterList:yo,firewallSolid:bf,forcePlay:wo,forcePlayOutline:xo,gear:Mo,github:_o,google:Eo,goToNode:So,group_by:Ao,hamburger:Ho,help:Po,hide:Ro,highlightArea:No,holder:Bo,incident_manager:Wo,information:Yo,informationPress:Zo,insights:Ko,integrations:Jo,ipNetworking:ti,ipNetworkingPress:ri,last_week:ii,line_chart:ci,logo_s:li,loading:fi,magnify:hi,metrics:zi,metrics_explorer:gi,monitoring:yi,more:wi,navLeft:xi,navRight:Mi,nav_arrow_goto:_i,nav_dots:Si,networkingStack:Cf,netdata:Ei,netdataPress:Ai,node:Hi,node_child:Pi,node_default_l:Ri,node_hollow:Ni,node_import_export:Bi,node_notification_l:Wi,node_parent:Yi,node_selected_l:Zi,nodes:Ki,nodes_hollow:Ji,none_selected:ta,nodes_update:Sf,notification:Ka,os:ra,osAlpineLinux:ia,osAmazonLinux:ca,osArchLinux:la,osCelarOS:fa,osCentos:ha,osCentosColor:za,osCoreOS:ga,osDebian:ya,osDebianColor:wa,osFedora:xa,osFreeBSD:Ma,osGentoo:_a,osLinux:Sa,osLinuxColor:Ea,osMacOSX:Aa,osOracle:Ha,osOracleColor:Pa,osPress:Ra,osRaspbian:Na,osRedHat:Ba,osSuseLinux:Wa,osUbuntu:Ya,osUbuntuColor:Za,padlock:Ja,pauseOutline:tc,pauseSolid:rc,pencilSolid:cc,pencilOutline:ic,pie_chart_skeleton:lc,pin_element:fc,playOutline:hc,playSolid:zc,plus:gc,plus_mini_s:yc,pod:wc,pricing:xc,print:Mc,privacy:_c,ram:Ac,qualityOfServiceSolid:kf,question:Sc,questionFilled:Ec,refresh:Hc,reload:Pc,removeNode:Rc,resize_handler:Nc,room:Bc,room_home:Wc,room_new:Yc,room_overview:Zc,sad:Kc,save:Jc,search:ts,search_s:rs,searchPress:is,serviceApache:cs,serviceApacheTomcat:ls,serviceBeanstalk:fs,serviceCouchDB:hs,serviceDatabase:zs,serviceDockerHub:gs,serviceDockerHubPress:ys,serviceEBPF:ws,serviceElasticSearch:xs,serviceFreeNAS:Ms,serviceHAProxy:_s,serviceHTTPCheck:Ss,serviceIceCast:Es,serviceInfluxDB:As,serviceIPFS:Hs,serviceIPVS:Ps,serviceKubernetes:Rs,serviceLighthttpd:Ns,serviceLighthttpd2:Bs,serviceLiteSpeed:Ws,serviceLxc:Ys,serviceMariaDB:Zs,serviceMemCached:Ks,serviceMongoDB:Js,serviceMySQL:tl,serviceMySQLPress:rl,serviceNginx:il,serviceNginxLocal:cl,serviceNginxPlus:ll,serviceNtpd:fl,serviceNtpdPress:hl,serviceOpenStack:zl,serviceOpenWrt:gl,servicePan:yl,servicePercona:wl,servicePfSense:xl,servicePhpFpm:Ml,servicePostgreSQL:_l,serviceProxySQL:_l,serviceRabbitMQ:Sl,serviceRedis:El,serviceRethinkDB:Al,serviceRetroShare:Hl,services:Pl,serviceSelectedArea:Rl,serviceSolr:Nl,serviceSquid:Bl,serviceSummaryStatistic:Wl,serviceTraefik:Yl,serviceVarnish:Zl,serviceWebLog:Kl,serviceWebLogNginx:Jl,serviceX509Check:tu,serviceXen:ru,settings:iu,settings_h:cu,sorting_vertical:lu,sorting_asc:hu,sorting_desc:fu,space:zu,space_new:gu,switch_off:yu,system_overview:wu,text_add:xu,thumb_down:Mu,thumb_up:_u,tiny_buttons:Su,training:Eu,trashcan:Au,triangle:Hu,triangle_down:Pu,unknownError:Ru,universe:Nu,unreachable:Bu,unreachableNode:Wu,update:Yu,update_pending:Zu,upload:Ku,user:Ju,users:tf,view_list:rf,viewSingleNode:af,viewSingleNodePress:sf,virtualization:uf,warning:df,warning_triangle:pf,warning_triangle_hollow:vf,x:mf}),Ef=function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e},Tf={small:"16px",medium:"24px",large:"40px"},Af=s.d.svg.withConfig({displayName:"styled__StyledIcon",componentId:"sc-1pjn63w-0"})(Le||(Le=Ef(["\n height: ",";\n width: ",";\n opacity: ",";\n pointer-events: ",";\n ","\n ","\n ","\n ","\n ","\n"],["\n height: ",";\n width: ",";\n opacity: ",";\n pointer-events: ",";\n ","\n ","\n ","\n ","\n ","\n"])),(function(e){var t=e.size;return e.height||Tf[t]}),(function(e){var t=e.size;return e.width||Tf[t]}),(function(e){return e.disabled?.3:1}),(function(e){return e.disabled?"none":"unset"}),(function(e){var t=e.rotate;return!isNaN(t)&&"transform: rotate(".concat(90*t,"deg);")}),(function(e){var t=e.theme,n=e.color;return n&&"fill: ".concat(Ve(n)({theme:t}),";")}),(function(e){var t=e.theme,n=e.hoverColor;return n&&"&:hover { fill: ".concat(Ve(n)({theme:t}),"; }")}),Ue,Ye),Lf=function(){return(Lf=Object.assign||function(e){for(var t,n=1,r=arguments.length;n *:not(:last-child) {\n margin-".concat(a,": ").concat(t*n,"px;\n }\n ")},ud=function(e){var t=function(e,t,n){return e?"column":t?"column-reverse":n?"row-reverse":"row"}(e.column,e.columnReverse,e.rowReverse);return"flex-direction: ".concat(t,";")},fd=function(){return(fd=Object.assign||function(e){for(var t,n=1,r=arguments.length;n &",_invalid:"&[aria-invalid=true]",_pressed:"&[aria-pressed=true]",_readOnly:"&[aria-readonly=true], &[readonly]",_first:"&:first-of-type",_last:"&:last-of-type",_expanded:"&[aria-expanded=true]",_grabbed:"&[aria-grabbed=true]",_notFirst:"&:not(:first-of-type)",_notLast:"&:not(:last-of-type)",_groupHover:"[role=group]:hover &",_autofill:"&:-webkit-autofill",_placeholder:"&::placeholder"},bd=function(e){var t=e.theme,n=gd(e,["theme"]),r="";for(var o in n)if(o in yd){var i=o,a=n[i],c=md(zd,pd,ed)(vd({theme:t},a));r+="\n ".concat(yd[i],"{ \n ").concat(c," \n }")}return r.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,"")};function wd(){return(wd=Object.assign||function(e){for(var t=1;t=0)return xd(e,t,t);var n=Math.abs(t),r=xd(e,n,n);return"string"===typeof r?"-"+r:-1*r},Od=["margin","marginTop","marginRight","marginBottom","marginLeft","marginX","marginY","top","bottom","left","right"].reduce((function(e,t){var n;return wd({},e,((n={})[t]=Sd,n))}),{}),Ed=function e(t){return function(n){void 0===n&&(n={});var r=wd({},Md,{},n.theme||n),o={},i=function(e){return function(t){var n={},r=xd(t,"breakpoints",jd),o=[null].concat(r.map((function(e){return"@media screen and (min-width: "+e+")"})));for(var i in e){var a="function"===typeof e[i]?e[i](t):e[i];if(null!=a)if(Array.isArray(a))for(var c=0;c tr th {\n border-spacing: 0;\n border-bottom: 1px solid #aeb3b7;\n padding-bottom: 5px;\n }\n"],["\n & > tr th {\n border-spacing: 0;\n border-bottom: 1px solid #aeb3b7;\n padding-bottom: 5px;\n }\n"]))),pz=function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e},zz=function(){return(zz=Object.assign||function(e){for(var t,n=1,r=arguments.length;n=t?e.call(null):r.id=requestAnimationFrame(o)}))};return r}var Dz=null;function Pz(e){if(void 0===e&&(e=!1),null===Dz||e){var t=document.createElement("div"),n=t.style;n.width="50px",n.height="50px",n.overflow="scroll",n.direction="rtl";var r=document.createElement("div"),o=r.style;return o.width="100px",o.height="100px",t.appendChild(r),document.body.appendChild(t),t.scrollLeft>0?Dz="positive-descending":(t.scrollLeft=1,Dz=0===t.scrollLeft?"negative":"positive-ascending"),document.body.removeChild(t),Dz}return Dz}var Vz=150,Rz=function(e,t){return e};function Iz(e){var t,n=e.getItemOffset,r=e.getEstimatedTotalSize,o=e.getItemSize,i=e.getOffsetForIndexAndAlignment,c=e.getStartIndexForOffset,s=e.getStopIndexForStartIndex,l=e.initInstanceProps,u=e.shouldResetStyleCacheOnItemSizeChange,f=e.validateProps;return(t=function(e){function t(t){var r;return(r=e.call(this,t)||this)._instanceProps=l(r.props,Object(qz.a)(r)),r._outerRef=void 0,r._resetIsScrollingTimeoutId=null,r.state={instance:Object(qz.a)(r),isScrolling:!1,scrollDirection:"forward",scrollOffset:"number"===typeof r.props.initialScrollOffset?r.props.initialScrollOffset:0,scrollUpdateWasRequested:!1},r._callOnItemsRendered=void 0,r._callOnItemsRendered=Tz((function(e,t,n,o){return r.props.onItemsRendered({overscanStartIndex:e,overscanStopIndex:t,visibleStartIndex:n,visibleStopIndex:o})})),r._callOnScroll=void 0,r._callOnScroll=Tz((function(e,t,n){return r.props.onScroll({scrollDirection:e,scrollOffset:t,scrollUpdateWasRequested:n})})),r._getItemStyle=void 0,r._getItemStyle=function(e){var t,i=r.props,a=i.direction,c=i.itemSize,s=i.layout,l=r._getItemStyleCache(u&&c,u&&s,u&&a);if(l.hasOwnProperty(e))t=l[e];else{var f=n(r.props,e,r._instanceProps),d=o(r.props,e,r._instanceProps),h="horizontal"===a||"horizontal"===s,p="rtl"===a,z=h?f:0;l[e]=t={position:"absolute",left:p?void 0:z,right:p?z:void 0,top:h?0:f,height:h?"100%":d,width:h?d:"100%"}}return t},r._getItemStyleCache=void 0,r._getItemStyleCache=Tz((function(e,t,n){return{}})),r._onScrollHorizontal=function(e){var t=e.currentTarget,n=t.clientWidth,o=t.scrollLeft,i=t.scrollWidth;r.setState((function(e){if(e.scrollOffset===o)return null;var t=r.props.direction,a=o;if("rtl"===t)switch(Pz()){case"negative":a=-o;break;case"positive-descending":a=i-n-o}return a=Math.max(0,Math.min(a,i-n)),{isScrolling:!0,scrollDirection:e.scrollOffset0)for(var _=j;_<=M;_++)C.push(Object(a.createElement)(t,{data:f,key:h(_,f),index:_,isScrolling:m?b:void 0,style:this._getItemStyle(_)}));var q=r(this.props,this._instanceProps);return Object(a.createElement)(z||v||"div",{className:n,onScroll:k,ref:this._outerRefSetter,style:Object(_z.a)({position:"relative",height:i,width:y,overflow:"auto",WebkitOverflowScrolling:"touch",willChange:"transform",direction:o},g)},Object(a.createElement)(s||l||"div",{children:C,ref:c,style:{height:w?"100%":q,pointerEvents:b?"none":void 0,width:w?q:"100%"}}))},d._callPropsCallbacks=function(){if("function"===typeof this.props.onItemsRendered&&this.props.itemCount>0){var e=this._getRangeToRender(),t=e[0],n=e[1],r=e[2],o=e[3];this._callOnItemsRendered(t,n,r,o)}if("function"===typeof this.props.onScroll){var i=this.state,a=i.scrollDirection,c=i.scrollOffset,s=i.scrollUpdateWasRequested;this._callOnScroll(a,c,s)}},d._getRangeToRender=function(){var e=this.props,t=e.itemCount,n=e.overscanCount,r=this.state,o=r.isScrolling,i=r.scrollDirection,a=r.scrollOffset;if(0===t)return[0,0,0,0];var l=c(this.props,a,this._instanceProps),u=s(this.props,l,a,this._instanceProps),f=o&&"backward"!==i?1:Math.max(1,n),d=o&&"forward"!==i?1:Math.max(1,n);return[Math.max(0,l-f),Math.max(0,Math.min(t-1,u+d)),l,u]},t}(a.PureComponent)).defaultProps={direction:"ltr",itemData:void 0,layout:"vertical",overscanCount:2,useIsScrolling:!1},t}var Nz=function(e,t){e.children,e.direction,e.height,e.layout,e.innerTagName,e.outerTagName,e.width,t.instance},Fz=function(e,t,n){var r=e.itemSize,o=n.itemMetadataMap,i=n.lastMeasuredIndex;if(t>i){var a=0;if(i>=0){var c=o[i];a=c.offset+c.size}for(var s=i+1;s<=t;s++){var l=r(s);o[s]={offset:a,size:l},a+=l}n.lastMeasuredIndex=t}return o[t]},Bz=function(e,t,n,r,o){for(;r<=n;){var i=r+Math.floor((n-r)/2),a=Fz(e,i,t).offset;if(a===o)return i;ao&&(n=i-1)}return r>0?r-1:0},Uz=function(e,t,n,r){for(var o=e.itemCount,i=1;n=n&&(i=n-1),i>=0){var c=r[i];a=c.offset+c.size}return a+(n-i-1)*o},Gz=Iz({getItemOffset:function(e,t,n){return Fz(e,t,n).offset},getItemSize:function(e,t,n){return n.itemMetadataMap[t].size},getEstimatedTotalSize:Wz,getOffsetForIndexAndAlignment:function(e,t,n,r,o){var i=e.direction,a=e.height,c=e.layout,s=e.width,l="horizontal"===i||"horizontal"===c?s:a,u=Fz(e,t,o),f=Wz(e,o),d=Math.max(0,Math.min(f-l,u.offset)),h=Math.max(0,u.offset-l+u.size);switch("smart"===n&&(n=r>=h-l&&r<=d+l?"auto":"center"),n){case"start":return d;case"end":return h;case"center":return Math.round(h+(d-h)/2);case"auto":default:return r>=h&&r<=d?r:r0?r[o].offset:0)>=n?Bz(e,t,o,0,n):Uz(e,t,Math.max(0,o),n)}(e,n,t)},getStopIndexForStartIndex:function(e,t,n,r){for(var o=e.direction,i=e.height,a=e.itemCount,c=e.layout,s=e.width,l="horizontal"===o||"horizontal"===c?s:i,u=Fz(e,t,r),f=n+l,d=u.offset+u.size,h=t;h=h-u&&r<=d+u?"auto":"center"),n){case"start":return d;case"end":return h;case"center":var p=Math.round(h+(d-h)/2);return pf+Math.floor(u/2)?f:p;case"auto":default:return r>=h&&r<=d?r:r span {\n font-weight: ",";\n }\n"],["\n border-bottom: "," solid\n ",";\n box-sizing: border-box;\n\n min-width: ",";\n max-width: ",";\n height: ",";\n color: ",";\n font-weight: ",";\n\n cursor: pointer;\n opacity: ",";\n pointer-events: ",";\n\n &:hover {\n border-bottom: "," solid ",";\n }\n\n & > span {\n font-weight: ",";\n }\n"])),(function(e){return e.small?"2px":"4px"}),(function(e){var t=e.active;return Ve(t?"accent":["transparent","full"])}),(function(e){var t=e.minWidth;return null!==t&&void 0!==t?t:Ie(10)}),(function(e){var t=e.maxWidth;return null!==t&&void 0!==t?t:Ie(26)}),(function(e){var t=e.small;return Ie(t?4:6)}),Ve("text"),(function(e){return e.active?"bold":"normal"}),(function(e){return e.disabled?.4:1}),(function(e){return e.disabled?"none":"auto"}),(function(e){return e.small?"2px":"4px"}),Ve("primary"),(function(e){return e.active?"bold":"normal"})),Vv=function(){return(Vv=Object.assign||function(e){for(var t,n=1,r=arguments.length;ne.clientHeight}));return n.forEach((function(e){return e.addEventListener("scroll",t,{capture:!1,passive:!0})})),function(){return n.forEach((function(e){return e.removeEventListener("scroll",t)}))}};n=r();var o=function(){n(),n=r(),t()};return window.addEventListener("resize",o),function(){n(),window.removeEventListener("resize",o)}}),[e,t])}(o,b),og(m,f,o),tg(d);var w=eg();return Gd.a.createPortal(r?c.a.createElement(c.a.Fragment,null,c.a.createElement(dg,zg({ref:y,width:{max:"100%"},column:!0,"data-testid":"drop"},v),h),c.a.createElement(gg,null)):c.a.createElement(dg,zg({ref:y,width:{max:"100%"},column:!0,"data-testid":"drop"},v),h),w)})),bg=function(){return(bg=Object.assign||function(e){for(var t,n=1,r=arguments.length;n1&&o.forEach((function(n){var o;r[n]=e(((o={})[n]=t[n],o))})),r},lm=function(e,t,n,r,o){var i={};return r.slice(0,e.length).forEach((function(r,a){var c,s=e[a],l=t(r,n,o);s?nm()(i,((c={})[s]=nm()({},i[s],l),c)):nm()(i,l)})),i},um=function(e,t,n,r,o){var i={};for(var a in r){var c=e[a],s=t(r[a],n,o);if(c){var l,u=im(c);nm()(i,((l={})[u]=nm()({},i[u],s),l))}else nm()(i,s)}return i},fm=function(e){var t=e.properties,n=e.property,r=e.scale,o=e.transform,i=void 0===o?am:o,a=e.defaultScale;t=t||[n];var c=function(e,n,r){var o={},a=i(e,n,r);if(null!==a)return t.forEach((function(e){o[e]=a})),o};return c.scale=r,c.defaults=a,c},dm=function(e){void 0===e&&(e={});var t={};return Object.keys(e).forEach((function(n){var r=e[n];t[n]=!0!==r?"function"!==typeof r?fm(r):r:fm({property:n,scale:n})})),sm(t)},hm=dm({width:{property:"width",scale:"sizes",transform:function(e,t){return cm(t,e,!function(e){return"number"===typeof e&&!isNaN(e)}(e)||e>1?e:100*e+"%")}},height:{property:"height",scale:"sizes"},minWidth:{property:"minWidth",scale:"sizes"},minHeight:{property:"minHeight",scale:"sizes"},maxWidth:{property:"maxWidth",scale:"sizes"},maxHeight:{property:"maxHeight",scale:"sizes"},size:{properties:["width","height"],scale:"sizes"},overflow:!0,overflowX:!0,overflowY:!0,display:!0,verticalAlign:!0}),pm={color:{property:"color",scale:"colors"},backgroundColor:{property:"backgroundColor",scale:"colors"},opacity:!0};pm.bg=pm.backgroundColor;var zm=dm(pm),vm=dm({fontFamily:{property:"fontFamily",scale:"fonts"},fontSize:{property:"fontSize",scale:"fontSizes",defaultScale:[12,14,16,20,24,32,48,64,72]},fontWeight:{property:"fontWeight",scale:"fontWeights"},lineHeight:{property:"lineHeight",scale:"lineHeights"},letterSpacing:{property:"letterSpacing",scale:"letterSpacings"},textAlign:!0,fontStyle:!0}),gm=dm({alignItems:!0,alignContent:!0,justifyItems:!0,justifyContent:!0,flexWrap:!0,flexDirection:!0,flex:!0,flexGrow:!0,flexShrink:!0,flexBasis:!0,justifySelf:!0,alignSelf:!0,order:!0}),mm={space:[0,4,8,16,32,64,128,256,512]},ym=dm({gridGap:{property:"gridGap",scale:"space",defaultScale:mm.space},gridColumnGap:{property:"gridColumnGap",scale:"space",defaultScale:mm.space},gridRowGap:{property:"gridRowGap",scale:"space",defaultScale:mm.space},gridColumn:!0,gridRow:!0,gridAutoFlow:!0,gridAutoColumns:!0,gridAutoRows:!0,gridTemplateColumns:!0,gridTemplateRows:!0,gridTemplateAreas:!0,gridArea:!0}),bm={border:{property:"border",scale:"borders"},borderWidth:{property:"borderWidth",scale:"borderWidths"},borderStyle:{property:"borderStyle",scale:"borderStyles"},borderColor:{property:"borderColor",scale:"colors"},borderRadius:{property:"borderRadius",scale:"radii"},borderTop:{property:"borderTop",scale:"borders"},borderTopLeftRadius:{property:"borderTopLeftRadius",scale:"radii"},borderTopRightRadius:{property:"borderTopRightRadius",scale:"radii"},borderRight:{property:"borderRight",scale:"borders"},borderBottom:{property:"borderBottom",scale:"borders"},borderBottomLeftRadius:{property:"borderBottomLeftRadius",scale:"radii"},borderBottomRightRadius:{property:"borderBottomRightRadius",scale:"radii"},borderLeft:{property:"borderLeft",scale:"borders"},borderX:{properties:["borderLeft","borderRight"],scale:"borders"},borderY:{properties:["borderTop","borderBottom"],scale:"borders"},borderTopWidth:{property:"borderTopWidth",scale:"borderWidths"},borderTopColor:{property:"borderTopColor",scale:"colors"},borderTopStyle:{property:"borderTopStyle",scale:"borderStyles"}};bm.borderTopLeftRadius={property:"borderTopLeftRadius",scale:"radii"},bm.borderTopRightRadius={property:"borderTopRightRadius",scale:"radii"},bm.borderBottomWidth={property:"borderBottomWidth",scale:"borderWidths"},bm.borderBottomColor={property:"borderBottomColor",scale:"colors"},bm.borderBottomStyle={property:"borderBottomStyle",scale:"borderStyles"},bm.borderBottomLeftRadius={property:"borderBottomLeftRadius",scale:"radii"},bm.borderBottomRightRadius={property:"borderBottomRightRadius",scale:"radii"},bm.borderLeftWidth={property:"borderLeftWidth",scale:"borderWidths"},bm.borderLeftColor={property:"borderLeftColor",scale:"colors"},bm.borderLeftStyle={property:"borderLeftStyle",scale:"borderStyles"},bm.borderRightWidth={property:"borderRightWidth",scale:"borderWidths"},bm.borderRightColor={property:"borderRightColor",scale:"colors"},bm.borderRightStyle={property:"borderRightStyle",scale:"borderStyles"};var wm=dm(bm),km={background:!0,backgroundImage:!0,backgroundSize:!0,backgroundPosition:!0,backgroundRepeat:!0};km.bgImage=km.backgroundImage,km.bgSize=km.backgroundSize,km.bgPosition=km.backgroundPosition,km.bgRepeat=km.backgroundRepeat;var xm=dm(km),jm={space:[0,4,8,16,32,64,128,256,512]},Mm=dm({position:!0,zIndex:{property:"zIndex",scale:"zIndices"},top:{property:"top",scale:"space",defaultScale:jm.space},right:{property:"right",scale:"space",defaultScale:jm.space},bottom:{property:"bottom",scale:"space",defaultScale:jm.space},left:{property:"left",scale:"space",defaultScale:jm.space}}),Cm=Mm,_m={space:[0,4,8,16,32,64,128,256,512]},qm=function(e){return"number"===typeof e&&!isNaN(e)},Sm=function(e,t){if(!qm(e))return cm(t,e,e);var n=e<0,r=Math.abs(e),o=cm(t,r,r);return qm(o)?o*(n?-1:1):n?"-"+o:o},Om={};Om.margin={margin:{property:"margin",scale:"space",transform:Sm,defaultScale:_m.space},marginTop:{property:"marginTop",scale:"space",transform:Sm,defaultScale:_m.space},marginRight:{property:"marginRight",scale:"space",transform:Sm,defaultScale:_m.space},marginBottom:{property:"marginBottom",scale:"space",transform:Sm,defaultScale:_m.space},marginLeft:{property:"marginLeft",scale:"space",transform:Sm,defaultScale:_m.space},marginX:{properties:["marginLeft","marginRight"],scale:"space",transform:Sm,defaultScale:_m.space},marginY:{properties:["marginTop","marginBottom"],scale:"space",transform:Sm,defaultScale:_m.space}},Om.margin.m=Om.margin.margin,Om.margin.mt=Om.margin.marginTop,Om.margin.mr=Om.margin.marginRight,Om.margin.mb=Om.margin.marginBottom,Om.margin.ml=Om.margin.marginLeft,Om.margin.mx=Om.margin.marginX,Om.margin.my=Om.margin.marginY,Om.padding={padding:{property:"padding",scale:"space",defaultScale:_m.space},paddingTop:{property:"paddingTop",scale:"space",defaultScale:_m.space},paddingRight:{property:"paddingRight",scale:"space",defaultScale:_m.space},paddingBottom:{property:"paddingBottom",scale:"space",defaultScale:_m.space},paddingLeft:{property:"paddingLeft",scale:"space",defaultScale:_m.space},paddingX:{properties:["paddingLeft","paddingRight"],scale:"space",defaultScale:_m.space},paddingY:{properties:["paddingTop","paddingBottom"],scale:"space",defaultScale:_m.space}},Om.padding.p=Om.padding.padding,Om.padding.pt=Om.padding.paddingTop,Om.padding.pr=Om.padding.paddingRight,Om.padding.pb=Om.padding.paddingBottom,Om.padding.pl=Om.padding.paddingLeft,Om.padding.px=Om.padding.paddingX,Om.padding.py=Om.padding.paddingY;(function(){for(var e={},t=arguments.length,n=new Array(t),r=0;r4)return Fm;var n=t.map((function(t){return Fe(e,t)}));return 1===n.length?{top:n[0],right:n[0],bottom:n[0],left:n[0]}:2===n.length?{top:n[0],right:n[1],bottom:n[0],left:n[1]}:3===n.length?{top:n[0],right:n[1],bottom:n[2],left:n[1]}:{top:n[0],right:n[1],bottom:n[2],left:n[3]}},Um=function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e},Wm=function(e,t){return"0"!==e&&"0"!==t?"calc((100% - ".concat(e,") - ").concat(t,")"):"0"===e&&"0"===t?"100%":"calc(100% - ".concat("0"===e?t:e,")")},Gm=new Set(["top","center","bottom"]),Ym=new Set(["bottom-left","left","top-left"]),$m=new Set(["right","center","left"]),Zm=new Set(["top-left","top","top-right"]),Xm=new Set(["top-right","right","bottom-right"]),Km=new Set(["bottom-right","bottom","bottom-left"]),Qm=s.d.div.attrs((function(e){var t=e.theme,n=e.margin;return{marginDimensions:Bm(t,n)}})).withConfig({displayName:"container__Container",componentId:"sc-7g83tw-0"})(Tm||(Tm=Um(["\n position: ",";\n display: flex;\n z-index: 35;\n outline: none;\n pointer-events: all;\n\n ","\n ","\n ","\n ","\n ","\n ","\n ","\n\n ","\n"],["\n position: ",";\n display: flex;\n z-index: 35;\n outline: none;\n pointer-events: all;\n\n ","\n ","\n ","\n ","\n ","\n ","\n ","\n\n ","\n"])),(function(e){return e.isAbsolute?"absolute":"fixed"}),(function(e){var t=e.marginDimensions,n=t.top,r=t.bottom;return"max-height: ".concat(Wm(n,r),";")}),(function(e){var t=e.marginDimensions,n=t.right,r=t.left;return"max-width: ".concat(Wm(r,n),";")}),(function(e){var t=e.position,n=e.full,r=e.marginDimensions;return"vertical"===n||!0===n||Zm.has(t)?"top: ".concat(r.top,";"):$m.has(t)?"top: 50%;":""}),(function(e){var t=e.position,n=e.full,r=e.marginDimensions;return"horizontal"===n||!0===n||Xm.has(t)?"right: ".concat(r.right,";"):""}),(function(e){var t=e.position,n=e.full,r=e.marginDimensions;return"vertical"===n||!0===n||Km.has(t)?"bottom: ".concat(r.bottom,";"):""}),(function(e){var t=e.position,n=e.full,r=e.marginDimensions;return"horizontal"===n||!0===n||Ym.has(t)?"left: ".concat(r.left,";"):Gm.has(t)?"left: 50%;":""}),(function(e){var t=e.full,n=e.position,r=function(){var e=!0!==t&&"horizontal"!==t&&Gm.has(n),r=!0!==t&&"vertical"!==t&&$m.has(n);return e||r?e&&!r?"translateX(-50%)":!e&&r?"translateY(-50%)":"translate(-50%, -50%)":""}();return r&&"transform: ".concat(r,";")}),(function(e){return e.borderShadow&&"box-shadow: 0px 2px 68px rgba(0, 0, 0, 0.288);"})),Jm=function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e},ey=function(){return(ey=Object.assign||function(e){for(var t,n=1,r=arguments.length;n * {\n min-width: initial;\n max-width: initial;\n }\n"],["\n width: 100%;\n\n .tabs > * {\n min-width: initial;\n max-width: initial;\n }\n"]))),Ty=function(){return c.a.createElement(Hd,{overflow:{vertical:"auto"},"data-testid":"dashboard"},c.a.createElement(Ey,null,c.a.createElement(Iv,{label:"Using a Mouse"},c.a.createElement(qy,null)),c.a.createElement(Iv,{label:"Using Touch"},c.a.createElement(Sy,null))))},Ay=n(21),Ly=n.n(Ay);function Hy(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function Dy(e,t){for(var n=0;n0?e.concat(Object.assign({field:i,values:l,type:c},s)):e}return e.concat(o)}),[])}function Gy(e,t,n,r){var o=e.data,i=Uy(t,n,r)||[];return Object.assign(Object.assign({},e),{data:o.map((function(e){return Object.assign(Object.assign({},e),{selected:i.some((function(t){return Yy(t,e.value)}))})}))})}function Yy(e,t){return!!(e&&e.name&&t&&t.name&&e.name===t.name)||Fy()(e,t,{strict:!0})}function $y(e,t){return t?t.reduce((function(e,t){return e.find((function(e){return e.type===t.type&&e.field===t.field}))?e:[].concat(Object(Iy.a)(e),[t])}),e):e}function Zy(e){return void 0!==e.name}var Xy=Object.assign({},o),Ky=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o1)return console.warn("search-ui-site-search-connector: Cannot apply more than 1 none-value filters to a single field"),e;var o=r[0];if(Xy.isFilterValueRange(o)){o.name;var i=Ky(o,["name"]);return e[n]=Object.assign({type:"range"},i),e}return e}return e[n]=Object.assign(Object.assign({},"any"===t.type?{}:{type:"and"}),{values:r}),e}),{})}(void 0!==t.filters?t.filters:e.filters),l=void 0!==t.current?t.current:e.current,u=void 0!==t.resultsPerPage?t.resultsPerPage:e.resultsPerPage,f=void 0!==t.sortDirection?t.sortDirection:e.sortDirection,d=void 0!==t.sortField?t.sortField:e.sortField,h=void 0!==t.sortList?t.sortList:e.sortList,p=(i=t.result_fields)?[Object.keys(i),Object.entries(i).reduce((function(e,t){var n=Object(Vy.a)(t,2),r=n[0],o=n[1];return o.snippet?Object.assign(Object.assign({},e),Object(Ry.a)({},r,o.snippet)):e}),{})]:[],z=Object(Vy.a)(p,2),v=z[0],g=z[1],m=(a=t.search_fields)?Object.keys(a):[],y=e.searchTerm;return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},u&&{per_page:u}),l&&{page:l}),f&&{sort_direction:Object(Ry.a)({},n,f)}),d&&{sort_field:Object(Ry.a)({},n,d)}),h&&{sort_list:Object(Ry.a)({},n,h)}),s&&{filters:Object(Ry.a)({},n,s)}),c&&{facets:Object(Ry.a)({},n,c)}),v&&{fetch_fields:Object(Ry.a)({},n,v)}),g&&{highlight_fields:Object(Ry.a)({},n,g)}),m&&!!m.length&&{search_fields:Object(Ry.a)({},n,m)}),{q:y})}var Jy=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o0&&{facets:a})}var nb=function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{s(r.next(e))}catch(t){i(t)}}function c(e){try{s(r.throw(e))}catch(t){i(t)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,c)}s((r=r.apply(e,t||[])).next())}))};function rb(e,t,n,r){return nb(this,void 0,void 0,Ly.a.mark((function o(){var i,a,c,s;return Ly.a.wrap((function(o){for(;;)switch(o.prev=o.next){case 0:return i=new Headers({"Content-Type":"application/json"}),o.next=3,fetch("https://search-api.swiftype.com/api/v1/public/".concat(n),{method:t,headers:i,body:JSON.stringify(Object.assign({engine_key:e},r)),credentials:"include"});case 3:return a=o.sent,o.prev=4,o.next=7,a.json();case 7:c=o.sent,o.next=12;break;case 10:o.prev=10,o.t0=o.catch(4);case 12:if(!(a.status>=200&&a.status<300)){o.next=16;break}return o.abrupt("return",c);case 16:throw s=c&&c.errors&&Object.entries(c.errors).length>0?JSON.stringify(c.errors):a.status,new Error("".concat(s));case 18:case"end":return o.stop()}}),o,null,[[4,10]])})))}var ob=function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{s(r.next(e))}catch(t){i(t)}}function c(e){try{s(r.throw(e))}catch(t){i(t)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,c)}s((r=r.apply(e,t||[])).next())}))};function ib(e,t,n){var r=Object.entries(Object.assign({engine_key:e},n)).map((function(e){var t=Object(Vy.a)(e,2),n=t[0],r=t[1];return"".concat(n,"=").concat(encodeURIComponent(r))})).join("&");return fetch("https://search-api.swiftype.com/api/v1/public/".concat(t,"?").concat(r),{method:"GET",credentials:"include"})}var ab=function(){function e(t){var n=t.documentType,r=t.engineKey,o=t.beforeSearchCall,i=void 0===o?function(e,t){return t(e)}:o,a=t.beforeAutocompleteResultsCall,c=void 0===a?function(e,t){return t(e)}:a;Hy(this,e),this.documentType=n,this.engineKey=r,this.beforeSearchCall=i,this.beforeAutocompleteResultsCall=c,this.request=rb.bind(this,r),this._get=ib.bind(this,r)}return Py(e,[{key:"onResultClick",value:function(e){var t=e.query,n=e.documentId,r=e.tags;r&&r.length>0&&console.warn("search-ui-site-search-connector: Site Search does not support tags on click"),this._get("analytics/pc",{t:(new Date).getTime(),q:t,doc_id:n})}},{key:"onAutocompleteResultClick",value:function(e){var t=e.query,n=e.documentId;e.tags&&console.warn("search-ui-site-search-connector: Site Search does not support tags on autocompleteClick"),this._get("analytics/pas",{t:(new Date).getTime(),q:t,doc_id:n})}},{key:"onSearch",value:function(e,t){var n=this,r=Qy(e,t,this.documentType);return this.beforeSearchCall(r,(function(e){return n.request("POST","engines/search.json",e).then((function(e){return tb(e,n.documentType)}))}))}},{key:"onAutocomplete",value:function(e,t){var n=e.searchTerm;return ob(this,void 0,void 0,Ly.a.mark((function e(){var r,o=this;return Ly.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!t.results){e.next=3;break}return r=Qy({searchTerm:n},t.results,this.documentType),e.abrupt("return",this.beforeAutocompleteResultsCall(r,(function(e){return o.request("POST","engines/suggest.json",e).then((function(e){return{autocompletedResults:tb(e,o.documentType).results}}))})));case 3:t.suggestions&&console.warn("search-ui-site-search-connector: Site Search does support query suggestions on autocomplete");case 4:case"end":return e.stop()}}),e,this)})))}}]),e}();function cb(e){return"/"===e.charAt(0)}function sb(e,t){for(var n=t,r=n+1,o=e.length;r=0;u--){var f=o[u];"."===f?sb(o,u):".."===f?(sb(o,u),l++):l&&(sb(o,u),l--)}if(!c)for(;l--;l)o.unshift("..");!c||""===o[0]||o[0]&&cb(o[0])||o.unshift("");var d=o.join("/");return n&&"/"!==d.substr(-1)&&(d+="/"),d};var ub=!0,fb="Invariant failed";function db(e,t){if(!e){if(ub)throw new Error(fb);var n="function"===typeof t?t():t;throw new Error(n?fb+": "+n:fb)}}function hb(e){return"/"===e.charAt(0)?e:"/"+e}function pb(e,t){return function(e,t){return 0===e.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(t.length))}(e,t)?e.substr(t.length):e}function zb(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function vb(e){var t=e.pathname,n=e.search,r=e.hash,o=t||"/";return n&&"?"!==n&&(o+="?"===n.charAt(0)?n:"?"+n),r&&"#"!==r&&(o+="#"===r.charAt(0)?r:"#"+r),o}function gb(e,t,n,r){var o;"string"===typeof e?(o=function(e){var t=e||"/",n="",r="",o=t.indexOf("#");-1!==o&&(r=t.substr(o),t=t.substr(0,o));var i=t.indexOf("?");return-1!==i&&(n=t.substr(i),t=t.substr(0,i)),{pathname:t,search:"?"===n?"":n,hash:"#"===r?"":r}}(e)).state=t:(void 0===(o=Object(_z.a)({},e)).pathname&&(o.pathname=""),o.search?"?"!==o.search.charAt(0)&&(o.search="?"+o.search):o.search="",o.hash?"#"!==o.hash.charAt(0)&&(o.hash="#"+o.hash):o.hash="",void 0!==t&&void 0===o.state&&(o.state=t));try{o.pathname=decodeURI(o.pathname)}catch(i){throw i instanceof URIError?new URIError('Pathname "'+o.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):i}return n&&(o.key=n),r?o.pathname?"/"!==o.pathname.charAt(0)&&(o.pathname=lb(o.pathname,r.pathname)):o.pathname=r.pathname:o.pathname||(o.pathname="/"),o}function mb(){var e=null;var t=[];return{setPrompt:function(t){return e=t,function(){e===t&&(e=null)}},confirmTransitionTo:function(t,n,r,o){if(null!=e){var i="function"===typeof e?e(t,n):e;"string"===typeof i?"function"===typeof r?r(i,o):o(!0):o(!1!==i)}else o(!0)},appendListener:function(e){var n=!0;function r(){n&&e.apply(void 0,arguments)}return t.push(r),function(){n=!1,t=t.filter((function(e){return e!==r}))}},notifyListeners:function(){for(var e=arguments.length,n=new Array(e),r=0;r1&&(s.current=n),t&&(s.q=t),o&&(s.size=o),r&&r.length>0&&(s.filters=r),c&&c.length>0?s.sort=c:a&&(s["sort-field"]=a,s["sort-direction"]=i),s}(e))}var Ib=function(){function e(){Hy(this,e),this.history="undefined"!==typeof window?jb():function(e){void 0===e&&(e={});var t=e,n=t.getUserConfirmation,r=t.initialEntries,o=void 0===r?["/"]:r,i=t.initialIndex,a=void 0===i?0:i,c=t.keyLength,s=void 0===c?6:c,l=mb();function u(e){Object(_z.a)(v,e),v.length=v.entries.length,l.notifyListeners(v.location,v.action)}function f(){return Math.random().toString(36).substr(2,s)}var d=Mb(a,0,o.length-1),h=o.map((function(e){return gb(e,void 0,"string"===typeof e?f():e.key||f())})),p=vb;function z(e){var t=Mb(v.index+e,0,v.entries.length-1),r=v.entries[t];l.confirmTransitionTo(r,"POP",n,(function(e){e?u({action:"POP",location:r,index:t}):u()}))}var v={length:h.length,action:"POP",location:h[d],index:d,entries:h,createHref:p,push:function(e,t){var r=gb(e,t,f(),v.location);l.confirmTransitionTo(r,"PUSH",n,(function(e){if(e){var t=v.index+1,n=v.entries.slice(0);n.length>t?n.splice(t,n.length-t,r):n.push(r),u({action:"PUSH",location:r,index:t,entries:n})}}))},replace:function(e,t){var r=gb(e,t,f(),v.location);l.confirmTransitionTo(r,"REPLACE",n,(function(e){e&&(v.entries[v.index]=r,u({action:"REPLACE",location:r}))}))},go:z,goBack:function(){z(-1)},goForward:function(){z(1)},canGo:function(e){var t=v.index+e;return t>=0&&t1&&void 0!==arguments[1]?arguments[1]:{},n=t.replaceUrl,r=void 0!==n&&n,o=Rb(e);this.lastPushSearchString=o;var i=r?this.history.replace:this.history.push;i({search:"?".concat(o)})}},{key:"onURLStateChange",value:function(e){var t=this;this.unlisten=this.history.listen((function(n){"?".concat(t.lastPushSearchString)!==n.search&&(t.lastPushSearchString="",e(Vb(Sb.parse(n.search))))}))}},{key:"tearDown",value:function(){this.unlisten()}}]),e}(),Nb=function(){function e(){Hy(this,e),this.requestSequence=0,this.lastCompleted=0}return Py(e,[{key:"next",value:function(){return++this.requestSequence}},{key:"isOldRequest",value:function(e){return e3?r-3:0),i=3;i2&&void 0!==arguments[2]?arguments[2]:"all";this.debug&&(n=console).log.apply(n,["Search UI: Action","addFilter"].concat(Array.prototype.slice.call(arguments)));var o=this.state.filters,i=o.find((function(t){return t.field===e&&t.type===r}))||{},a=o.filter((function(t){return t.field!==e||t.type!==r}))||[],c=i.values||[],s=c.find((function(e){return Yy(e,t)}))?c:c.concat(t);this._updateSearchResults({current:1,filters:[].concat(Object(Iy.a)(a),[{field:e,values:s,type:r}])})}function Gb(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[];this.debug&&(t=console).log.apply(t,["Search UI: Action","trackAutocompleteClickThrough"].concat(Array.prototype.slice.call(arguments)));var r=this.state,o=r.autocompletedResultsRequestId,i=r.searchTerm,a=r.autocompletedResults,c=a.findIndex((function(t){return t._meta.id===e})),s=a[c];this.events.autocompleteResultClick({query:i,documentId:e,requestId:o,tags:n,result:s,resultIndex:c})}function Yb(){var e,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.debug&&(e=console).log.apply(e,["Search UI: Action","clearFilters"].concat(Array.prototype.slice.call(arguments)));var n=this.state.filters.filter((function(e){var n=e.field;return t.includes(n)}));this._updateSearchResults({current:1,filters:n})}function $b(e,t,n){var r;this.debug&&(r=console).log.apply(r,["Search UI: Action","removeFilter"].concat(Array.prototype.slice.call(arguments)));var o=this.state.filters,i=o;i=!t&&n?o.filter((function(t){return!(t.field===e&&t.type===n)})):t?Wy(o,e,t,n):o.filter((function(t){return t.field!==e})),this._updateSearchResults({current:1,filters:i})}function Zb(){var e;this.debug&&(e=console).log.apply(e,["Search UI: Action","reset"].concat(Array.prototype.slice.call(arguments))),this._setState(this.startingState),this.trackUrlState&&this.URLManager.pushStateToURL(this.state)}function Xb(e){var t;this.debug&&(t=console).log.apply(t,["Search UI: Action","setCurrent"].concat(Array.prototype.slice.call(arguments))),this._updateSearchResults({current:e})}function Kb(e,t){var n,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"all";this.debug&&(n=console).log.apply(n,["Search UI: Action","setFilter"].concat(Array.prototype.slice.call(arguments)));var o=this.state.filters;o=o.filter((function(t){return t.field!==e||t.type!==r})),this._updateSearchResults({current:1,filters:[].concat(Object(Iy.a)(o),[{field:e,values:[t],type:r}])})}function Qb(e){var t;this.debug&&(t=console).log.apply(t,["Search UI: Action","setResultsPerPage"].concat(Array.prototype.slice.call(arguments))),this._updateSearchResults({current:1,resultsPerPage:e})}function Jb(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=n.autocompleteMinimumCharacters,o=void 0===r?0:r,i=n.autocompleteResults,a=void 0!==i&&i,c=n.autocompleteSuggestions,s=void 0!==c&&c,l=n.shouldClearFilters,u=void 0===l||l,f=n.refresh,d=void 0===f||f,h=n.debounce,p=void 0===h?0:h;this.debug&&(t=console).log.apply(t,["Search UI: Action","setSearchTerm"].concat(Array.prototype.slice.call(arguments))),this._setState({searchTerm:e}),d&&this.debounceManager.runWithDebounce(p,"_updateSearchResults",this._updateSearchResults,Object.assign({current:1},u&&{filters:[]})),(a||s)&&e.length>=o&&this.debounceManager.runWithDebounce(p,"_updateAutocomplete",this._updateAutocomplete,e,{autocompleteResults:a,autocompleteSuggestions:s})}function ew(e,t){var n;this.debug&&(n=console).log.apply(n,["Search UI: Action","setSort"].concat(Array.prototype.slice.call(arguments)));var r={current:1,sortList:null,sortField:null,sortDirection:null};Array.isArray(e)?r.sortList=e:(r.sortField=e,r.sortDirection=t),this._updateSearchResults(r)}function tw(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[];this.debug&&(t=console).log.apply(t,["Search UI: Action","trackClickThrough"].concat(Array.prototype.slice.call(arguments)));var r=this.state,o=r.requestId,i=r.searchTerm,a=r.results,c=r.current,s=r.resultsPerPage,l=a.findIndex((function(t){return t._meta.id===e})),u=a[l];this.events.resultClick({query:i,documentId:e,requestId:o,tags:n,result:u,page:c,resultsPerPage:s,resultIndexOnPage:l})}var nw="search-ui-screen-reader-notifications",rw="undefined"!==typeof document,ow=function(){if(!rw)return null;var e=document.getElementById(nw);return e||((e=document.createElement("div")).id=nw,e.setAttribute("role","status"),e.setAttribute("aria-live","polite"),e.style.position="absolute",e.style.width="1px",e.style.height="1px",e.style.margin="-1px",e.style.padding="0",e.style.border="0",e.style.overflow="hidden",e.style.clip="rect(0 0 0 0)",document.body.appendChild(e),e)},iw=function(e){var t=ow();t&&(t.textContent=e)},aw={searchResults:function(e){var t=e.start,n=e.end,r=e.totalResults,o=e.searchTerm,i="Showing ".concat(t," to ").concat(n," results out of ").concat(r);return o&&(i+=', searching for "'.concat(o,'".')),i}};function cw(e,t){if(this.hasA11yNotifications){var n=this.a11yNotificationMessages[e];if(n){var r=n(t);iw(r),this.debug&&console.log("Search UI: Action","a11yNotify",{messageFunc:e,messageArgs:t,message:r})}else{var o='Could not find corresponding message function in a11yNotificationMessages: "'.concat(e,'"');console.warn("Action","a11yNotify",o)}}}function sw(e,t,n){if(n){if(t){var r=t[e].bind(t);return function(){for(var e=arguments.length,t=new Array(e),o=0;o0&&void 0!==arguments[0]?arguments[0]:{},n=t.apiConnector,r=t.onSearch,o=t.onAutocomplete,i=t.onResultClick,a=t.onAutocompleteResultClick;Hy(this,e),this.search=sw("onSearch",n,r),this.autocomplete=sw("onAutocomplete",n,o),this.resultClick=sw("onResultClick",n,i),this.autocompleteResultClick=sw("onAutocompleteResultClick",n,a)},uw="Invalid credentials",fw=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[];return Object.entries(e).reduce((function(e,r){var o=Object(Vy.a)(r,2),i=o[0],a=o[1];return t[i]&&"function"===typeof t[i]&&!t[i]({filters:n})?e:(e[i]=a,e)}),{})}var zw=function(){function e(t){var n,r=this,o=t.apiConnector,a=t.autocompleteQuery,c=void 0===a?{}:a,s=t.debug,l=t.initialState,u=t.onSearch,f=t.onAutocomplete,d=t.onResultClick,h=t.onAutocompleteResultClick,p=t.searchQuery,z=void 0===p?{}:p,v=t.trackUrlState,g=void 0===v||v,m=t.urlPushDebounceLength,y=void 0===m?500:m,b=t.hasA11yNotifications,w=void 0!==b&&b,k=t.a11yNotificationMessages,x=void 0===k?{}:k,j=t.alwaysSearchOnInitialLoad,M=void 0!==j&&j;Hy(this,e),this.state=hw,this._updateAutocomplete=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.autocompleteResults,o=t.autocompleteSuggestions,i=r.autocompleteRequestSequencer.next(),a=Object.assign(Object.assign({},n&&{results:r.autocompleteQuery.results||{}}),o&&{suggestions:r.autocompleteQuery.suggestions||{}});return r.events.autocomplete({searchTerm:e},a).then((function(e){r.autocompleteRequestSequencer.isOldRequest(i)||(r.autocompleteRequestSequencer.completed(i),r._setState(e))}))},this._updateSearchResults=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.skipPushToUrl,o=void 0!==n&&n,i=t.replaceUrl,a=void 0!==i&&i,c=Object.assign(Object.assign({},r.state),e),s=c.current,l=c.filters,u=c.resultsPerPage,f=c.searchTerm,d=c.sortDirection,h=c.sortField,p=c.sortList;r.debounceManager.cancelByName("_updateSearchResults"),r._setState({current:s,error:"",filters:l,resultsPerPage:u,searchTerm:f,sortDirection:d,sortField:h,sortList:p}),r._makeSearchRequest({skipPushToUrl:o,replaceUrl:a})},this._makeSearchRequest=Ub.debounce(0,(function(e){var t=e.skipPushToUrl,n=e.replaceUrl,o=r.state,i=o.current,a=o.filters,c=o.resultsPerPage,s=o.searchTerm,l=o.sortDirection,u=o.sortField,f=o.sortList;r._setState({isLoading:!0});var d=r.searchRequestSequencer.next(),h=r.searchQuery,p=h.conditionalFacets,z=fw(h,["conditionalFacets"]),v=Object.assign(Object.assign({},z),{facets:pw(r.searchQuery.facets,p,a)}),g=Object.assign(Object.assign({},dw(r.state)),{filters:$y(a,r.searchQuery.filters)});return r.events.search(g,v).then((function(e){if(!r.searchRequestSequencer.isOldRequest(d)){r.searchRequestSequencer.completed(d);var o=e.totalResults,h=0===o?0:(i-1)*c+1,p=o0||this.alwaysSearchOnInitialLoad)&&this._updateSearchResults(C,{replaceUrl:!0})}return Py(e,[{key:"_setState",value:function(e){var t=Object.assign(Object.assign({},this.state),e);this.debug&&console.log("Search UI: State Update",e,t),this.state=t,this.subscriptions.forEach((function(e){return e(t)}))}},{key:"setSearchQuery",value:function(e){this.searchQuery=e,this._updateSearchResults({})}},{key:"setAutocompleteQuery",value:function(e){this.autocompleteQuery=e}},{key:"subscribeToStateChanges",value:function(e){this.subscriptions.push(e)}},{key:"unsubscribeToStateChanges",value:function(e){this.subscriptions=this.subscriptions.filter((function(t){return t!==e}))}},{key:"tearDown",value:function(){this.subscriptions=[],this.URLManager&&this.URLManager.tearDown()}},{key:"getActions",value:function(){return this.actions}},{key:"getState",value:function(){return Object.assign({},this.state)}}]),e}(),vw=c.a.createContext(null),gw={moreFilters:function(e){var t=e.visibleOptionsCount,n=e.showingAll?"All ":"";return n+="".concat(t," options shown.")}},mw=function(e){var t=e.children,n=e.config,r=e.driver,o=Object(a.useState)(null),i=Object(Vy.a)(o,2),s=i[0],l=i[1];if(Object(a.useEffect)((function(){var e=r||new zw(Object.assign(Object.assign({},n),{a11yNotificationMessages:Object.assign(Object.assign({},gw),n.a11yNotificationMessages)}));return l(e),function(){e.tearDown()}}),[]),Object(a.useEffect)((function(){s&&s.setSearchQuery(n.searchQuery)}),[n.searchQuery]),Object(a.useEffect)((function(){s&&s.setAutocompleteQuery(n.autocompleteQuery)}),[n.autocompleteQuery]),!s)return null;var u={driver:s};return c.a.createElement(vw.Provider,{value:u},t)};function yw(e){return(yw="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function bw(e){return(bw="function"===typeof Symbol&&"symbol"===yw(Symbol.iterator)?function(e){return yw(e)}:function(e){return e&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":yw(e)})(e)}function ww(e,t){return!t||"object"!==bw(t)&&"function"!==typeof t?function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e):t}function kw(e){return(kw=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function xw(e,t){return(xw=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var jw=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"===typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(e);o0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]1&&e}));return e[n]=e[n]||[],e[n].push(t),e}),{})},Gw=function(){return(Gw=Object.assign||function(e){for(var t,n=1,r=arguments.length;n * {\n min-width: 160px;\n max-width: 100%;\n }\n"],["\n width: 100%;\n\n .tabs > * {\n min-width: 160px;\n max-width: 100%;\n }\n"]))),rk=["learn","community"],ok={learn:"learn.netdata",community:"discourse","github-cloud":"netdata-cloud","github-agent":"netdata"},ik={learn:"Documentation",community:"Community","github-cloud":"Github / Cloud","github-agent":"Github / Agent"},ak=function(e){var t=e.results;return c.a.createElement(Hd,{overflow:{vertical:"auto"},"data-testid":"searchResults",flex:!0,width:"1000px",height:"60vh"},c.a.createElement(nk,null,rk.map((function(e){var n=t[ok[e]],r=null===n||void 0===n?void 0:n.length;return c.a.createElement(Iv,{key:e,label:"".concat(ik[e]).concat(r?" (".concat(r,")"):"")},c.a.createElement(tk,null,r?n.map((function(e){var t=e.id,n=e.url,r=e.title,o=e.description;return c.a.createElement(ek,{key:t.raw,url:n.raw,title:r,description:o})})):c.a.createElement(Hd,{padding:[4]},c.a.createElement(xh,{strong:!0},"No results"))))}))))},ck=function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e},sk=Object(s.d)(Hd).attrs({padding:[6],background:"dropdown",gap:6,column:!0,round:!0,overflow:{vertical:"auto"}}).withConfig({displayName:"documentation__Container",componentId:"sc-3qq6g2-0"})(Ow||(Ow=ck(["\n box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);\n"],["\n box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);\n"]))),lk=function(e){var t=e.children,n=e.onClose;return c.a.createElement(Hd,{width:"100%",alignItems:"center",justifyContent:"between",padding:[0,0,4],border:{side:"bottom",color:"disabled"}},c.a.createElement(Hd,{gap:2,alignItems:"center"},t),c.a.createElement(Vd,{icon:"x",neutral:!0,small:!0,onClick:n,flavour:"borderless","data-testid":"documentation-help-close"}))},uk="general",fk="dashboard",dk="search",hk={general:"Need help?",dashboard:"Need help?"},pk=function(e){var t=e.app,n=void 0===t?"cloud":t,r=e.onCloseClick,o=e.onVisitDocumentClick,i=e.onOpenIssueClick,s=e.onOpenBugClick,l=e.onContributeClick,u=e.onSupportClick,f=e.children,d=vy()(),h=d[0],p=d[1],z=Object(a.useState)(uk),v=z[0],g=z[1],m=v===uk,y=Object(a.useCallback)((function(){return g(fk)}),[]),b=Object(a.useCallback)((function(){return g(uk)}),[]),w=Object(a.useCallback)((function(){return g(dk)}),[]),k=Object(a.useCallback)((function(){p(),r&&r()}),[]);return c.a.createElement(a.Fragment,null,f(p,h),h&&c.a.createElement(iy,{position:"bottom-left",backdrop:!0,margin:[5,17],onClickOutside:p,onEsc:p},c.a.createElement(Zw,null,(function(e){var t=e.searchTerm,r=e.setSearchTerm,f=e.results,d=e.reset;return c.a.createElement(a.Fragment,null,c.a.createElement(sk,{width:{max:m?"325px":v===fk?"600px":"100%"},"data-testid":"documentation-layer"},c.a.createElement(lk,{onClose:k},m&&c.a.createElement(Df,{color:"text",name:"questionFilled",width:"18px",height:"18px"}),!m&&c.a.createElement(Vd,{icon:"arrow_left",neutral:!0,small:!0,onClick:function(){b(),d()},flavour:"borderless","data-testid":"dashboard-back"}),c.a.createElement(mh,{margin:[0]},hk[v]||hk.general)),v!==fk&&c.a.createElement(Xw,{defaultValue:t,setSearchTerm:r,setSearchView:w}),m&&c.a.createElement(Hd,{gap:6,overflow:{vertical:"auto"},column:!0,padding:[1]},c.a.createElement(ky,{app:n,onDashboardClick:y,onVisitDocumentClick:o,onOpenIssueClick:i,onOpenBugClick:s,onContributeClick:l,onSupportClick:u})),v===fk&&c.a.createElement(Ty,null),v===dk&&c.a.createElement(ak,{results:f})))}))))},zk=n(291),vk=n.n(zk),gk=function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e},mk=Object(s.d)(Hd).attrs({overflow:{vertical:"auto"},padding:[0,4,0,0]}).withConfig({displayName:"container__Container",componentId:"sc-1v3y9uu-0"})(Ew||(Ew=gk(["\n ","\n"],["\n ","\n"])),jp),yk=function(e){var t=e.onClose;return c.a.createElement(Hd,{border:{side:"bottom",color:"selected"},justifyContent:"between",alignItems:"center",padding:[0,0,4,0]},c.a.createElement(Hd,{gap:2},c.a.createElement(Df,{color:"text",name:"insights"}),c.a.createElement(jh,{strong:!0},"Netdata News")),c.a.createElement(Vd,{flavour:"borderless",neutral:!0,icon:"x",title:"close news",onClick:t}))},bk=function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e},wk=Object(s.d)(Hd).attrs({as:"img"}).withConfig({displayName:"image__Image",componentId:"sc-1l0yjz3-0"})(Tw||(Tw=bk(["\n object-fit: cover;\n"],["\n object-fit: cover;\n"]))),kk=function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e},xk=Object(s.d)(Hd).attrs({as:"a"}).withConfig({displayName:"anchor__Anchor",componentId:"sc-5t4sos-0"})(Aw||(Aw=kk(["\n text-decoration: none;\n & :hover {\n text-decoration: none;\n }\n"],["\n text-decoration: none;\n & :hover {\n text-decoration: none;\n }\n"]))),jk=function(e){var t=e.item,n=t.last_publication_date,r=t.data,o=r.title,i=r.description,a=r.url,s=r.image,l=r.label,u=s&&s.url,f=new Date(n);return c.a.createElement(Hd,{column:!0,gap:2},c.a.createElement(Hd,{gap:4},u&&c.a.createElement(wk,{src:u,width:"160px"}),c.a.createElement(Hd,{column:!0,gap:2},c.a.createElement(xh,{strong:!0},o),c.a.createElement(xh,null,i))),c.a.createElement(Hd,{justifyContent:"between",alignItems:"center"},c.a.createElement(kh,null,f.toLocaleDateString()),c.a.createElement(xk,{href:a,target:"_blank",rel:"noopener noreferrer",gap:1,alignItems:"center"},c.a.createElement(xh,{color:"success",strong:!0},l),c.a.createElement(Df,{color:"success",rotate:2,name:"arrow_left"}))))},Mk=n(290),Ck=function(e,t){return(Ck=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)};function _k(e,t){function n(){this.constructor=e}Ck(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var qk=function(){return(qk=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0?this.running[0]:null},e.prototype.refFromCookie=function(e){if(!e||""===e.trim())return null;var t=e.trim().split(" ");if(t.length<2)return null;var n=t[0],r=parseInt(t[1],10),o=this.running.filter((function(e){return e.googleId()===n&&e.variations.length>r}))[0];return o?o.variations[r].ref():null},e}(),Tk=function(){function e(e,t){for(var n in this.httpClient=t,this.form=e,this.data={},e.fields)e.fields[n].default&&(this.data[n]=[e.fields[n].default])}return e.prototype.set=function(e,t){var n=this.form.fields[e];if(!n)throw new Error("Unknown field "+e);var r=""===t||void 0===t?null:t,o=this.data[e]||[];o=n.multiple?r?o.concat([r]):o:r?[r]:o,this.data[e]=o},e.prototype.url=function(){var e=this.form.action;if(this.data){var t=e.indexOf("?")>-1?"&":"?";for(var n in this.data)if(Object.prototype.hasOwnProperty.call(this.data,n)){var r=this.data[n];if(r)for(var o=0;o0&&(this.url+=function(e){return e.indexOf("?")>-1?"&":"?"}(e)+n.join("&")),this.apiDataTTL=this.options.apiDataTTL||5,this.httpClient=new wx(this.options.requestHandler,this.options.apiCache,this.options.proxyAgent,this.options.timeoutInMs)}return e.prototype.get=function(e){var t=this;return this.httpClient.cachedRequest(this.url,{ttl:this.apiDataTTL}).then((function(n){var r=new gx(n,t.httpClient,t.options);return e&&e(null,r),r})).catch((function(t){throw e&&e(t),t}))},e}(),xx=function(){function e(e,t){this.id=e,this.api=t,this.fields={}}return e.prototype.set=function(e,t){return this.fields[e]=t,this},e.prototype.ref=function(e){return this.set("ref",e)},e.prototype.query=function(e){return this.set("q",e)},e.prototype.pageSize=function(e){return this.set("pageSize",e)},e.prototype.graphQuery=function(e){return this.set("graphQuery",e)},e.prototype.lang=function(e){return this.set("lang",e)},e.prototype.page=function(e){return this.set("page",e)},e.prototype.after=function(e){return this.set("after",e)},e.prototype.orderings=function(e){return this.set("orderings",e)},e.prototype.url=function(){var t=this;return this.api.get().then((function(n){return e.toSearchForm(t,n).url()}))},e.prototype.submit=function(t){var n=this;return this.api.get().then((function(r){return e.toSearchForm(n,r).submit(t)}))},e.toSearchForm=function(e,t){var n=t.searchForm(e.id);if(n)return Object.keys(e.fields).reduce((function(t,n){var r=e.fields[n];return"q"===n?t.query(r):"pageSize"===n?t.pageSize(r):"graphQuery"===n?t.graphQuery(r):"lang"===n?t.lang(r):"page"===n?t.page(r):"after"===n?t.after(r):"orderings"===n?t.orderings(r):t.set(n,r)}),n);throw new Error("Unable to access to form "+e.id)},e}(),jx=function(){function e(e,t){this.api=new kx(e,t)}return e.prototype.getApi=function(){return this.api.get()},e.prototype.everything=function(){return this.form("everything")},e.prototype.form=function(e){return new xx(e,this.api)},e.prototype.query=function(e,t,n){return this.getApi().then((function(r){return r.query(e,t,n)}))},e.prototype.queryFirst=function(e,t,n){return this.getApi().then((function(r){return r.queryFirst(e,t,n)}))},e.prototype.getByID=function(e,t,n){return this.getApi().then((function(r){return r.getByID(e,t,n)}))},e.prototype.getByIDs=function(e,t,n){return this.getApi().then((function(r){return r.getByIDs(e,t,n)}))},e.prototype.getByUID=function(e,t,n,r){return this.getApi().then((function(o){return o.getByUID(e,t,n,r)}))},e.prototype.getSingle=function(e,t,n){return this.getApi().then((function(r){return r.getSingle(e,t,n)}))},e.prototype.getBookmark=function(e,t,n){return this.getApi().then((function(r){return r.getBookmark(e,t,n)}))},e.prototype.getTags=function(){return this.getApi().then((function(e){return e.getTags()}))},e.prototype.getPreviewResolver=function(e,t){var n=this;return vx(e,t,(function(e,t){return n.getApi().then((function(n){return n.getByID(e,t)}))}))},e.getApi=function(e,t){return new kx(e,t).get()},e}();function Mx(e,t){return jx.getApi(e,t)}var Cx={experimentCookie:"io.prismic.experiment",previewCookie:"io.prismic.preview",Predicates:hx,predicates:hx,Experiments:Ek,Api:kx,client:function(e,t){return new jx(e,t)},getApi:Mx,api:function(e,t){return Mx(e,t)}},_x=Cx.client("https://netdata-news.cdn.prismic.io/api/v2"),qx=[],Sx=function(e){var t=e.app,n=void 0===t?"cloud":t,r=e.onCloseClick,o=e.children,i=vk()("news_last_seen"),s=i[0],l=i[1],u=Object(a.useState)(qx),f=u[0],d=u[1],h=Object(a.useState)(),p=h[0],z=h[1],v=vy()(),g=v[0],m=v[1];Object(a.useEffect)((function(){!function(e,t,n){_x.query(Cx.Predicates.at("document.tags",[e]),{pageSize:100,orderings:"[document.last_publication_date desc]"}).then(t).catch(n)}(n,(function(e){var t=e.results;return d(t)}),(function(){return z(!0)}))}),[]);var y=Object(a.useMemo)((function(){if(!f.length)return!0;var e=f[0].last_publication_date;return new Date(s)>=new Date(e)}),[s,f]),b=Object(a.useCallback)((function(){m(),l(new Date),r&&r()}),[r]);return c.a.createElement(a.Fragment,null,o({toggle:m,isOpen:g,upToDate:y}),g&&c.a.createElement(iy,{backdrop:!0,onClickOutside:b,onEsc:b},c.a.createElement(Hd,{background:"dropdown",round:!0,padding:[6],width:"640px",height:{max:"640px"},gap:4,column:!0},c.a.createElement(yk,{onClose:b}),c.a.createElement(mk,{column:!0,gap:6},p&&c.a.createElement(kh,{textAlign:"center"},"Something went wrong \ud83d\ude14"),!p&&!f.length&&c.a.createElement(kh,{textAlign:"center"},"There are no latest news"),!p&&f.length>0&&f.map((function(e){return c.a.createElement(jk,{key:e.id,item:e})}))))))},Ox=function(){return(Ox=Object.assign||function(e){for(var t,n=1,r=arguments.length;n=0||(o[n]=e[n]);return o}var Qx=n(223),Jx=n.n(Qx),ej=n(292),tj=["getDisplayName","methodName","renderCountProp","shouldHandleStateChanges","storeKey","withRef","forwardRef","context"],nj=["reactReduxForwardedRef"],rj=[],oj=[null,null];function ij(e,t){var n=e[1];return[t.payload,n+1]}function aj(e,t,n){Zx((function(){return e.apply(void 0,t)}),n)}function cj(e,t,n,r,o,i,a){e.current=r,t.current=o,n.current=!1,i.current&&(i.current=null,a())}function sj(e,t,n,r,o,i,a,c,s,l){if(e){var u=!1,f=null,d=function(){if(!u){var e,n,d=t.getState();try{e=r(d,o.current)}catch(h){n=h,f=h}n||(f=null),e===i.current?a.current||s():(i.current=e,c.current=e,a.current=!0,l({type:"STORE_UPDATED",payload:{error:n}}))}};n.onStateChange=d,n.trySubscribe(),d();return function(){if(u=!0,n.tryUnsubscribe(),n.onStateChange=null,f)throw f}}}var lj=function(){return[null,0]};function uj(e,t){void 0===t&&(t={});var n=t,r=n.getDisplayName,o=void 0===r?function(e){return"ConnectAdvanced("+e+")"}:r,i=n.methodName,s=void 0===i?"connectAdvanced":i,l=n.renderCountProp,u=void 0===l?void 0:l,f=n.shouldHandleStateChanges,d=void 0===f||f,h=n.storeKey,p=void 0===h?"store":h,z=(n.withRef,n.forwardRef),v=void 0!==z&&z,g=n.context,m=void 0===g?Ux:g,y=Kx(n,tj),b=m;return function(t){var n=t.displayName||t.name||"Component",r=o(n),i=Fx({},y,{getDisplayName:o,methodName:s,renderCountProp:u,shouldHandleStateChanges:d,storeKey:p,displayName:r,wrappedComponentName:n,WrappedComponent:t}),l=y.pure;var f=l?a.useMemo:function(e){return e()};function h(n){var r=Object(a.useMemo)((function(){var e=n.reactReduxForwardedRef,t=Kx(n,nj);return[n.context,e,t]}),[n]),o=r[0],s=r[1],l=r[2],u=Object(a.useMemo)((function(){return o&&o.Consumer&&Object(ej.isContextConsumer)(c.a.createElement(o.Consumer,null))?o:b}),[o,b]),h=Object(a.useContext)(u),p=Boolean(n.store)&&Boolean(n.store.getState)&&Boolean(n.store.dispatch);Boolean(h)&&Boolean(h.store);var z=p?n.store:h.store,v=Object(a.useMemo)((function(){return function(t){return e(t.dispatch,i)}(z)}),[z]),g=Object(a.useMemo)((function(){if(!d)return oj;var e=$x(z,p?null:h.subscription),t=e.notifyNestedSubs.bind(e);return[e,t]}),[z,p,h]),m=g[0],y=g[1],w=Object(a.useMemo)((function(){return p?h:Fx({},h,{subscription:m})}),[p,h,m]),k=Object(a.useReducer)(ij,rj,lj),x=k[0][0],j=k[1];if(x&&x.error)throw x.error;var M=Object(a.useRef)(),C=Object(a.useRef)(l),_=Object(a.useRef)(),q=Object(a.useRef)(!1),S=f((function(){return _.current&&l===C.current?_.current:v(z.getState(),l)}),[z,x,l]);aj(cj,[C,M,q,l,S,_,y]),aj(sj,[d,z,m,v,C,M,q,_,y,j],[z,m,v]);var O=Object(a.useMemo)((function(){return c.a.createElement(t,Fx({},S,{ref:s}))}),[s,t,S]);return Object(a.useMemo)((function(){return d?c.a.createElement(u.Provider,{value:w},O):O}),[u,O,w])}var z=l?c.a.memo(h):h;if(z.WrappedComponent=t,z.displayName=h.displayName=r,v){var g=c.a.forwardRef((function(e,t){return c.a.createElement(z,Fx({},e,{reactReduxForwardedRef:t}))}));return g.displayName=r,g.WrappedComponent=t,Jx()(g,t)}return Jx()(z,t)}}function fj(e,t){return e===t?0!==e||0!==t||1/e===1/t:e!==e&&t!==t}function dj(e,t){if(fj(e,t))return!0;if("object"!==typeof e||null===e||"object"!==typeof t||null===t)return!1;var n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(var o=0;o=0;r--){var o=t[r](e);if(o)return o}return function(t,r){throw new Error("Invalid value of type "+typeof e+" for "+n+" argument when connecting component "+r.wrappedComponentName+".")}}function Cj(e,t){return e===t}function _j(e){var t=void 0===e?{}:e,n=t.connectHOC,r=void 0===n?uj:n,o=t.mapStateToPropsFactories,i=void 0===o?gj:o,a=t.mapDispatchToPropsFactories,c=void 0===a?vj:a,s=t.mergePropsFactories,l=void 0===s?yj:s,u=t.selectorFactory,f=void 0===u?xj:u;return function(e,t,n,o){void 0===o&&(o={});var a=o,s=a.pure,u=void 0===s||s,d=a.areStatesEqual,h=void 0===d?Cj:d,p=a.areOwnPropsEqual,z=void 0===p?dj:p,v=a.areStatePropsEqual,g=void 0===v?dj:v,m=a.areMergedPropsEqual,y=void 0===m?dj:m,b=Kx(a,jj),w=Mj(e,i,"mapStateToProps"),k=Mj(t,c,"mapDispatchToProps"),x=Mj(n,l,"mergeProps");return r(f,Fx({methodName:"connect",getDisplayName:function(e){return"Connect("+e+")"},shouldHandleStateChanges:Boolean(e),initMapStateToProps:w,initMapDispatchToProps:k,initMergeProps:x,pure:u,areStatesEqual:h,areOwnPropsEqual:z,areStatePropsEqual:g,areMergedPropsEqual:y},b))}}var qj=_j();var Sj;function Oj(e,t){var n=Object(a.useState)((function(){return{inputs:t,result:e()}}))[0],r=Object(a.useRef)(!0),o=Object(a.useRef)(n),i=r.current||Boolean(t&&o.current.inputs&&function(e,t){if(e.length!==t.length)return!1;for(var n=0;n");return t.callbacks},t.setCallbacks=function(e){t.callbacks=e},t}Nx(t,e);var n=t.prototype;return n.componentDidMount=function(){this.unbind=Jj(window,[{eventName:"error",fn:this.onWindowError}])},n.componentDidCatch=function(e){if(!(e instanceof nM))throw e;this.setState({})},n.componentWillUnmount=function(){this.unbind()},n.render=function(){return this.props.children(this.setCallbacks)},t}(c.a.Component),iM=function(e){return e+1},aM=function(e,t){var n=e.droppableId===t.droppableId,r=iM(e.index),o=iM(t.index);return n?"\n You have moved the item from position "+r+"\n to position "+o+"\n ":"\n You have moved the item from position "+r+"\n in list "+e.droppableId+"\n to list "+t.droppableId+"\n in position "+o+"\n "},cM=function(e,t,n){return t.droppableId===n.droppableId?"\n The item "+e+"\n has been combined with "+n.draggableId:"\n The item "+e+"\n in list "+t.droppableId+"\n has been combined with "+n.draggableId+"\n in list "+n.droppableId+"\n "},sM=function(e){return"\n The item has returned to its starting position\n of "+iM(e.index)+"\n"},lM={dragHandleUsageInstructions:"\n Press space bar to start a drag.\n When dragging you can use the arrow keys to move the item around and escape to cancel.\n Some screen readers may require you to be in focus mode or to use your pass through key\n",onDragStart:function(e){return"\n You have lifted an item in position "+iM(e.source.index)+"\n"},onDragUpdate:function(e){var t=e.destination;if(t)return aM(e.source,t);var n=e.combine;return n?cM(e.draggableId,e.source,n):"You are over an area that cannot be dropped on"},onDragEnd:function(e){if("CANCEL"===e.reason)return"\n Movement cancelled.\n "+sM(e.source)+"\n ";var t=e.destination,n=e.combine;return t?"\n You have dropped the item.\n "+aM(e.source,t)+"\n ":n?"\n You have dropped the item.\n "+cM(e.draggableId,e.source,n)+"\n ":"\n The item has been dropped while not over a drop area.\n "+sM(e.source)+"\n "}},uM={x:0,y:0},fM=function(e,t){return{x:e.x+t.x,y:e.y+t.y}},dM=function(e,t){return{x:e.x-t.x,y:e.y-t.y}},hM=function(e,t){return e.x===t.x&&e.y===t.y},pM=function(e){return{x:0!==e.x?-e.x:0,y:0!==e.y?-e.y:0}},zM=function(e,t,n){var r;return void 0===n&&(n=0),(r={})[e]=t,r["x"===e?"y":"x"]=n,r},vM=function(e,t){return Math.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2))},gM=function(e,t){return Math.min.apply(Math,t.map((function(t){return vM(e,t)})))},mM=function(e){return function(t){return{x:e(t.x),y:e(t.y)}}},yM=function(e,t){return{top:e.top+t.y,left:e.left+t.x,bottom:e.bottom+t.y,right:e.right+t.x}},bM=function(e){return[{x:e.left,y:e.top},{x:e.right,y:e.top},{x:e.left,y:e.bottom},{x:e.right,y:e.bottom}]},wM=function(e,t){return t&&t.shouldClipSubject?function(e,t){var n=Aj({top:Math.max(t.top,e.top),right:Math.min(t.right,e.right),bottom:Math.min(t.bottom,e.bottom),left:Math.max(t.left,e.left)});return n.width<=0||n.height<=0?null:n}(t.pageMarginBox,e):Aj(e)},kM=function(e){var t=e.page,n=e.withPlaceholder,r=e.axis,o=e.frame,i=function(e,t,n){var r;return n&&n.increasedBy?Fx({},e,((r={})[t.end]=e[t.end]+n.increasedBy[t.line],r)):e}(function(e,t){return t?yM(e,t.scroll.diff.displacement):e}(t.marginBox,o),r,n);return{page:t,withPlaceholder:n,active:wM(i,o)}},xM=function(e,t){e.frame||rM(!1);var n=e.frame,r=dM(t,n.scroll.initial),o=pM(r),i=Fx({},n,{scroll:{initial:n.scroll.initial,current:t,diff:{value:r,displacement:o},max:n.scroll.max}});return Fx({},e,{frame:i,subject:kM({page:e.subject.page,withPlaceholder:e.subject.withPlaceholder,axis:e.axis,frame:i})})};function jM(e){return Object.values?Object.values(e):Object.keys(e).map((function(t){return e[t]}))}function MM(e,t){if(e.findIndex)return e.findIndex(t);for(var n=0;ne.bottom,c=r.lefte.right;return!(!a||!c)||(a&&i||c&&o)}},FM=function(e){var t=IM(e.top,e.bottom),n=IM(e.left,e.right);return function(e){return t(e.top)&&t(e.bottom)&&n(e.left)&&n(e.right)}},BM={direction:"vertical",line:"y",crossAxisLine:"x",start:"top",end:"bottom",size:"height",crossAxisStart:"left",crossAxisEnd:"right",crossAxisSize:"width"},UM={direction:"horizontal",line:"x",crossAxisLine:"y",start:"left",end:"right",size:"width",crossAxisStart:"top",crossAxisEnd:"bottom",crossAxisSize:"height"},WM=function(e){var t=e.target,n=e.destination,r=e.viewport,o=e.withDroppableDisplacement,i=e.isVisibleThroughFrameFn,a=o?function(e,t){var n=t.frame?t.frame.scroll.diff.displacement:uM;return yM(e,n)}(t,n):t;return function(e,t,n){return!!t.subject.active&&n(t.subject.active)(e)}(a,n,i)&&function(e,t,n){return n(t)(e)}(a,r,i)},GM=function(e){return WM(Fx({},e,{isVisibleThroughFrameFn:NM}))},YM=function(e){return WM(Fx({},e,{isVisibleThroughFrameFn:FM}))},$M=function(e,t,n){if("boolean"===typeof n)return n;if(!t)return!0;var r=t.invisible,o=t.visible;if(r[e])return!1;var i=o[e];return!i||i.shouldAnimate};function ZM(e){var t=e.afterDragging,n=e.destination,r=e.displacedBy,o=e.viewport,i=e.forceShouldAnimate,a=e.last;return t.reduce((function(e,t){var c=function(e,t){var n=e.page.marginBox,r={top:t.point.y,right:0,bottom:0,left:t.point.x};return Aj(Lj(n,r))}(t,r),s=t.descriptor.id;if(e.all.push(s),!GM({target:c,destination:n,viewport:o,withDroppableDisplacement:!0}))return e.invisible[t.descriptor.id]=!0,e;var l={draggableId:s,shouldAnimate:$M(s,a,i)};return e.visible[s]=l,e}),{all:[],visible:{},invisible:{}})}function XM(e){var t=e.insideDestination,n=e.inHomeList,r=e.displacedBy,o=e.destination,i=function(e,t){if(!e.length)return 0;var n=e[e.length-1].descriptor.index;return t.inHomeList?n:n+1}(t,{inHomeList:n});return{displaced:VM,displacedBy:r,at:{type:"REORDER",destination:{droppableId:o.descriptor.id,index:i}}}}function KM(e){var t=e.draggable,n=e.insideDestination,r=e.destination,o=e.viewport,i=e.displacedBy,a=e.last,c=e.index,s=e.forceShouldAnimate,l=DM(t,r);if(null==c)return XM({insideDestination:n,inHomeList:l,displacedBy:i,destination:r});var u=CM(n,(function(e){return e.descriptor.index===c}));if(!u)return XM({insideDestination:n,inHomeList:l,displacedBy:i,destination:r});var f=HM(t,n),d=n.indexOf(u);return{displaced:ZM({afterDragging:f.slice(d),destination:r,displacedBy:i,last:a,viewport:o.frame,forceShouldAnimate:s}),displacedBy:i,at:{type:"REORDER",destination:{droppableId:r.descriptor.id,index:c}}}}function QM(e,t){return Boolean(t.effected[e])}var JM=function(e){var t=e.isMovingForward,n=e.isInHomeList,r=e.draggable,o=e.draggables,i=e.destination,a=e.insideDestination,c=e.previousImpact,s=e.viewport,l=e.afterCritical,u=c.at;if(u||rM(!1),"REORDER"===u.type){var f=function(e){var t=e.isMovingForward,n=e.isInHomeList,r=e.insideDestination,o=e.location;if(!r.length)return null;var i=o.index,a=t?i+1:i-1,c=r[0].descriptor.index,s=r[r.length-1].descriptor.index;return a(n?s:s+1)?null:a}({isMovingForward:t,isInHomeList:n,location:u.destination,insideDestination:a});return null==f?null:KM({draggable:r,insideDestination:a,destination:i,viewport:s,last:c.displaced,displacedBy:c.displacedBy,index:f})}var d=function(e){var t=e.isMovingForward,n=e.destination,r=e.draggables,o=e.combine,i=e.afterCritical;if(!n.isCombineEnabled)return null;var a=o.draggableId,c=r[a].descriptor.index;return QM(a,i)?t?c:c-1:t?c+1:c}({isMovingForward:t,destination:i,displaced:c.displaced,draggables:o,combine:u.combine,afterCritical:l});return null==d?null:KM({draggable:r,insideDestination:a,destination:i,viewport:s,last:c.displaced,displacedBy:c.displacedBy,index:d})},eC=function(e){var t=e.afterCritical,n=e.impact,r=e.draggables,o=LM(n);o||rM(!1);var i=o.draggableId,a=r[i].page.borderBox.center,c=function(e){var t=e.displaced,n=e.afterCritical,r=e.combineWith,o=e.displacedBy,i=Boolean(t.visible[r]||t.invisible[r]);return QM(r,n)?i?uM:pM(o.point):i?o.point:uM}({displaced:n.displaced,afterCritical:t,combineWith:i,displacedBy:n.displacedBy});return fM(a,c)},tC=function(e,t){return t.margin[e.start]+t.borderBox[e.size]/2},nC=function(e,t,n){return t[e.crossAxisStart]+n.margin[e.crossAxisStart]+n.borderBox[e.crossAxisSize]/2},rC=function(e){var t=e.axis,n=e.moveRelativeTo,r=e.isMoving;return zM(t.line,n.marginBox[t.end]+tC(t,r),nC(t,n.marginBox,r))},oC=function(e){var t=e.axis,n=e.moveRelativeTo,r=e.isMoving;return zM(t.line,n.marginBox[t.start]-function(e,t){return t.margin[e.end]+t.borderBox[e.size]/2}(t,r),nC(t,n.marginBox,r))},iC=function(e){var t=e.impact,n=e.draggable,r=e.draggables,o=e.droppable,i=e.afterCritical,a=TM(o.descriptor.id,r),c=n.page,s=o.axis;if(!a.length)return function(e){var t=e.axis,n=e.moveInto,r=e.isMoving;return zM(t.line,n.contentBox[t.start]+tC(t,r),nC(t,n.contentBox,r))}({axis:s,moveInto:o.page,isMoving:c});var l=t.displaced,u=t.displacedBy,f=l.all[0];if(f){var d=r[f];if(QM(f,i))return oC({axis:s,moveRelativeTo:d.page,isMoving:c});var h=Rj(d.page,u.point);return oC({axis:s,moveRelativeTo:h,isMoving:c})}var p=a[a.length-1];if(p.descriptor.id===n.descriptor.id)return c.borderBox.center;if(QM(p.descriptor.id,i)){var z=Rj(p.page,pM(i.displacedBy.point));return rC({axis:s,moveRelativeTo:z,isMoving:c})}return rC({axis:s,moveRelativeTo:p.page,isMoving:c})},aC=function(e,t){var n=e.frame;return n?fM(t,n.scroll.diff.displacement):t},cC=function(e){var t=function(e){var t=e.impact,n=e.draggable,r=e.droppable,o=e.draggables,i=e.afterCritical,a=n.page.borderBox.center,c=t.at;return r&&c?"REORDER"===c.type?iC({impact:t,draggable:n,draggables:o,droppable:r,afterCritical:i}):eC({impact:t,draggables:o,afterCritical:i}):a}(e),n=e.droppable;return n?aC(n,t):t},sC=function(e,t){var n=dM(t,e.scroll.initial),r=pM(n);return{frame:Aj({top:t.y,bottom:t.y+e.frame.height,left:t.x,right:t.x+e.frame.width}),scroll:{initial:e.scroll.initial,max:e.scroll.max,current:t,diff:{value:n,displacement:r}}}};function lC(e,t){return e.map((function(e){return t[e]}))}var uC=function(e){var t=e.pageBorderBoxCenter,n=e.draggable,r=function(e,t){return fM(e.scroll.diff.displacement,t)}(e.viewport,t),o=dM(r,n.page.borderBox.center);return fM(n.client.borderBox.center,o)},fC=function(e){var t=e.draggable,n=e.destination,r=e.newPageBorderBoxCenter,o=e.viewport,i=e.withDroppableDisplacement,a=e.onlyOnMainAxis,c=void 0!==a&&a,s=dM(r,t.page.borderBox.center),l={target:yM(t.page.borderBox,s),destination:n,withDroppableDisplacement:i,viewport:o};return c?function(e){return WM(Fx({},e,{isVisibleThroughFrameFn:(t=e.destination.axis,function(e){var n=IM(e.top,e.bottom),r=IM(e.left,e.right);return function(e){return t===BM?n(e.top)&&n(e.bottom):r(e.left)&&r(e.right)}})}));var t}(l):YM(l)},dC=function(e){var t=e.isMovingForward,n=e.draggable,r=e.destination,o=e.draggables,i=e.previousImpact,a=e.viewport,c=e.previousPageBorderBoxCenter,s=e.previousClientSelection,l=e.afterCritical;if(!r.isEnabled)return null;var u=TM(r.descriptor.id,o),f=DM(n,r),d=function(e){var t=e.isMovingForward,n=e.draggable,r=e.destination,o=e.insideDestination,i=e.previousImpact;if(!r.isCombineEnabled)return null;if(!AM(i))return null;function a(e){var t={type:"COMBINE",combine:{draggableId:e,droppableId:r.descriptor.id}};return Fx({},i,{at:t})}var c=i.displaced.all,s=c.length?c[0]:null;if(t)return s?a(s):null;var l=HM(n,o);if(!s)return l.length?a(l[l.length-1].descriptor.id):null;var u=MM(l,(function(e){return e.descriptor.id===s}));-1===u&&rM(!1);var f=u-1;return f<0?null:a(l[f].descriptor.id)}({isMovingForward:t,draggable:n,destination:r,insideDestination:u,previousImpact:i})||JM({isMovingForward:t,isInHomeList:f,draggable:n,draggables:o,destination:r,insideDestination:u,previousImpact:i,viewport:a,afterCritical:l});if(!d)return null;var h=cC({impact:d,draggable:n,droppable:r,draggables:o,afterCritical:l});if(fC({draggable:n,destination:r,newPageBorderBoxCenter:h,viewport:a.frame,withDroppableDisplacement:!1,onlyOnMainAxis:!0}))return{clientSelection:uC({pageBorderBoxCenter:h,draggable:n,viewport:a}),impact:d,scrollJumpRequest:null};var p=dM(h,c);return{clientSelection:s,impact:function(e){var t=e.impact,n=e.viewport,r=e.destination,o=e.draggables,i=e.maxScrollChange,a=sC(n,fM(n.scroll.current,i)),c=r.frame?xM(r,fM(r.frame.scroll.current,i)):r,s=t.displaced,l=ZM({afterDragging:lC(s.all,o),destination:r,displacedBy:t.displacedBy,viewport:a.frame,last:s,forceShouldAnimate:!1}),u=ZM({afterDragging:lC(s.all,o),destination:c,displacedBy:t.displacedBy,viewport:n.frame,last:s,forceShouldAnimate:!1}),f={},d={},h=[s,l,u];return s.all.forEach((function(e){var t=function(e,t){for(var n=0;n1?u.sort((function(e,t){return hC(e)[c.start]-hC(t)[c.start]}))[0]:l.sort((function(e,t){var r=gM(n,bM(hC(e))),o=gM(n,bM(hC(t)));return r!==o?r-o:hC(e)[c.start]-hC(t)[c.start]}))[0]}({isMovingForward:t,pageBorderBoxCenter:n,source:o,droppables:a,viewport:c});if(!l)return null;var u=TM(l.descriptor.id,i),f=function(e){var t=e.previousPageBorderBoxCenter,n=e.moveRelativeTo,r=e.insideDestination,o=e.draggable,i=e.draggables,a=e.destination,c=e.viewport,s=e.afterCritical;if(!n){if(r.length)return null;var l={displaced:VM,displacedBy:PM,at:{type:"REORDER",destination:{droppableId:a.descriptor.id,index:0}}},u=cC({impact:l,draggable:o,droppable:a,draggables:i,afterCritical:s}),f=DM(o,a)?a:mC(a,o,i);return fC({draggable:o,destination:f,newPageBorderBoxCenter:u,viewport:c.frame,withDroppableDisplacement:!1,onlyOnMainAxis:!0})?l:null}var d=Boolean(t[a.axis.line]<=n.page.borderBox.center[a.axis.line]),h=function(){var e=n.descriptor.index;return n.descriptor.id===o.descriptor.id?e:d?e:e+1}(),p=vC(a.axis,o.displaceBy);return KM({draggable:o,insideDestination:r,destination:a,viewport:c,displacedBy:p,last:VM,index:h})}({previousPageBorderBoxCenter:n,destination:l,draggable:r,draggables:i,moveRelativeTo:function(e){var t=e.pageBorderBoxCenter,n=e.viewport,r=e.destination,o=e.insideDestination,i=e.afterCritical;return o.filter((function(e){return YM({target:zC(e,i),destination:r,viewport:n.frame,withDroppableDisplacement:!0})})).sort((function(e,n){var o=vM(t,aC(r,pC(e,i))),a=vM(t,aC(r,pC(n,i)));return or.left&&n.topr.top))return!1;if(xC(o)(t.center))return!0;var i=e.axis,a=o.center[i.crossAxisLine],c=t[i.crossAxisStart],s=t[i.crossAxisEnd],l=IM(o[i.crossAxisStart],o[i.crossAxisEnd]),u=l(c),f=l(s);return!u&&!f||(u?ca)}));return o.length?1===o.length?o[0].descriptor.id:function(e){var t=e.pageBorderBox,n=e.draggable,r=e.candidates,o=n.page.borderBox.center,i=r.map((function(e){var n=e.axis,r=zM(e.axis.line,t.center[n.line],e.page.borderBox.center[n.crossAxisLine]);return{id:e.descriptor.id,distance:vM(o,r)}})).sort((function(e,t){return t.distance-e.distance}));return i[0]?i[0].id:null}({pageBorderBox:t,draggable:n,candidates:o}):null}var MC=function(e,t){return Aj(yM(e,t))};function CC(e){var t=e.displaced,n=e.id;return Boolean(t.visible[n]||t.invisible[n])}var _C=function(e){var t=e.pageOffset,n=e.draggable,r=e.draggables,o=e.droppables,i=e.previousImpact,a=e.viewport,c=e.afterCritical,s=MC(n.page.borderBox,t),l=jC({pageBorderBox:s,draggable:n,droppables:o});if(!l)return RM;var u=o[l],f=TM(u.descriptor.id,r),d=function(e,t){var n=e.frame;return n?MC(t,n.scroll.diff.value):t}(u,s);return function(e){var t=e.draggable,n=e.pageBorderBoxWithDroppableScroll,r=e.previousImpact,o=e.destination,i=e.insideDestination,a=e.afterCritical;if(!o.isCombineEnabled)return null;var c=o.axis,s=vC(o.axis,t.displaceBy),l=s.value,u=n[c.start],f=n[c.end],d=CM(HM(t,i),(function(e){var t=e.descriptor.id,n=e.page.borderBox,o=n[c.size]/4,i=QM(t,a),s=CC({displaced:r.displaced,id:t});return i?s?f>n[c.start]+o&&fn[c.start]-l+o&&un[c.start]+l+o&&fn[c.start]+o&&ut.descriptor.index?n.descriptor.index-1:n.descriptor.index:null}({draggable:n,closest:CM(HM(n,o),(function(e){var t=e.descriptor.id,n=e.page.borderBox.center[s.line],r=QM(t,c),o=CC({displaced:i,id:t});return r?o?d<=n:f=1500)return h_;var i=d_+p_*(o/1500);return Number(("CANCEL"===r?.6*i:i).toFixed(2))}({current:o.current.client.offset,destination:v,reason:i});n(function(e){return{type:"DROP_ANIMATE",payload:e}}({newHomeClientOffset:v,dropDuration:m,completed:g}))}else n(t_({completed:g}))}}else n(function(e){return{type:"DROP_PENDING",payload:e}}({reason:i}))}else e(r)}}},v_=function(){return{x:window.pageXOffset,y:window.pageYOffset}};function g_(e){var t=e.onWindowScroll;var n,r=Bj((function(){t(v_())})),o=(n=r,{eventName:"scroll",options:{passive:!0,capture:!1},fn:function(e){e.target!==window&&e.target!==window.document||n()}}),i=Qj;function a(){return i!==Qj}return{start:function(){a()&&rM(!1),i=Jj(window,[o])},stop:function(){a()||rM(!1),r.cancel(),i(),i=Qj},isActive:a}}var m_=function(e){var t=g_({onWindowScroll:function(t){e.dispatch({type:"MOVE_BY_WINDOW_SCROLL",payload:{newScroll:t}})}});return function(e){return function(n){t.isActive()||"INITIAL_PUBLISH"!==n.type||t.start(),t.isActive()&&function(e){return"DROP_COMPLETE"===e.type||"DROP_ANIMATE"===e.type||"FLUSH"===e.type}(n)&&t.stop(),e(n)}}},y_=function(){var e=[];return{add:function(t){var n=setTimeout((function(){return function(t){var n=MM(e,(function(e){return e.timerId===t}));-1===n&&rM(!1),e.splice(n,1)[0].callback()}(n)})),r={timerId:n,callback:t};e.push(r)},flush:function(){if(e.length){var t=[].concat(e);e.length=0,t.forEach((function(e){clearTimeout(e.timerId),e.callback()}))}}}},b_=function(e,t){HC(),t(),DC()},w_=function(e,t){return{draggableId:e.draggable.id,type:e.droppable.type,source:{droppableId:e.droppable.id,index:e.draggable.index},mode:t}},k_=function(e,t,n,r){if(e){var o=function(e){var t=!1,n=!1,r=setTimeout((function(){n=!0})),o=function(o){t||n||(t=!0,e(o),clearTimeout(r))};return o.wasCalled=function(){return t},o}(n);e(t,{announce:o}),o.wasCalled()||n(r(t))}else n(r(t))},x_=function(e,t){var n=function(e,t){var n=y_(),r=null,o=function(n){r||rM(!1),r=null,b_(0,(function(){return k_(e().onDragEnd,n,t,lM.onDragEnd)}))};return{beforeCapture:function(t,n){r&&rM(!1),b_(0,(function(){var r=e().onBeforeCapture;r&&r({draggableId:t,mode:n})}))},beforeStart:function(t,n){r&&rM(!1),b_(0,(function(){var r=e().onBeforeDragStart;r&&r(w_(t,n))}))},start:function(o,i){r&&rM(!1);var a=w_(o,i);r={mode:i,lastCritical:o,lastLocation:a.source,lastCombine:null},n.add((function(){b_(0,(function(){return k_(e().onDragStart,a,t,lM.onDragStart)}))}))},update:function(o,i){var a=AM(i),c=LM(i);r||rM(!1);var s=!function(e,t){if(e===t)return!0;var n=e.draggable.id===t.draggable.id&&e.draggable.droppableId===t.draggable.droppableId&&e.draggable.type===t.draggable.type&&e.draggable.index===t.draggable.index,r=e.droppable.id===t.droppable.id&&e.droppable.type===t.droppable.type;return n&&r}(o,r.lastCritical);s&&(r.lastCritical=o);var l,u,f=(l=r.lastLocation,u=a,!(null==l&&null==u||null!=l&&null!=u&&l.droppableId===u.droppableId&&l.index===u.index));f&&(r.lastLocation=a);var d=!function(e,t){return null==e&&null==t||null!=e&&null!=t&&(e.draggableId===t.draggableId&&e.droppableId===t.droppableId)}(r.lastCombine,c);if(d&&(r.lastCombine=c),s||f||d){var h=Fx({},w_(o,r.mode),{combine:c,destination:a});n.add((function(){b_(0,(function(){return k_(e().onDragUpdate,h,t,lM.onDragUpdate)}))}))}},flush:function(){r||rM(!1),n.flush()},drop:o,abort:function(){if(r){var e=Fx({},w_(r.lastCritical,r.mode),{combine:null,destination:null,reason:"CANCEL"});o(e)}}}}(e,t);return function(e){return function(t){return function(r){if("BEFORE_INITIAL_CAPTURE"!==r.type){if("INITIAL_PUBLISH"===r.type){var o=r.payload.critical;return n.beforeStart(o,r.payload.movementMode),t(r),void n.start(o,r.payload.movementMode)}if("DROP_COMPLETE"===r.type){var i=r.payload.completed.result;return n.flush(),t(r),void n.drop(i)}if(t(r),"FLUSH"!==r.type){var a=e.getState();"DRAGGING"===a.phase&&n.update(a.critical,a.impact)}else n.abort()}else n.beforeCapture(r.payload.draggableId,r.payload.movementMode)}}}},j_=function(e){return function(t){return function(n){if("DROP_ANIMATION_FINISHED"===n.type){var r=e.getState();"DROP_ANIMATING"!==r.phase&&rM(!1),e.dispatch(t_({completed:r.completed}))}else t(n)}}},M_=function(e){var t=null,n=null;return function(r){return function(o){if("FLUSH"!==o.type&&"DROP_COMPLETE"!==o.type&&"DROP_ANIMATION_FINISHED"!==o.type||(n&&(cancelAnimationFrame(n),n=null),t&&(t(),t=null)),r(o),"DROP_ANIMATE"===o.type){var i={eventName:"scroll",options:{capture:!0,passive:!1,once:!0},fn:function(){"DROP_ANIMATING"===e.getState().phase&&e.dispatch({type:"DROP_ANIMATION_FINISHED",payload:null})}};n=requestAnimationFrame((function(){n=null,t=Jj(window,[i])}))}}}},C_=function(e){return function(t){return function(n){if(t(n),"PUBLISH_WHILE_DRAGGING"===n.type){var r=e.getState();"DROP_PENDING"===r.phase&&(r.isWaiting||e.dispatch(n_({reason:r.reason})))}}}},__=Bx.d,q_=function(e){var t,n=e.dimensionMarshal,r=e.focusMarshal,o=e.styleMarshal,i=e.getResponders,a=e.announce,c=e.autoScroller;return Object(Bx.e)(FC,__(Object(Bx.a)((t=o,function(){return function(e){return function(n){"INITIAL_PUBLISH"===n.type&&t.dragging(),"DROP_ANIMATE"===n.type&&t.dropping(n.payload.completed.result.reason),"FLUSH"!==n.type&&"DROP_COMPLETE"!==n.type||t.resting(),e(n)}}}),function(e){return function(){return function(t){return function(n){"DROP_COMPLETE"!==n.type&&"FLUSH"!==n.type&&"DROP_ANIMATE"!==n.type||e.stopPublishing(),t(n)}}}}(n),function(e){return function(t){var n=t.getState,r=t.dispatch;return function(t){return function(o){if("LIFT"===o.type){var i=o.payload,a=i.id,c=i.clientSelection,s=i.movementMode,l=n();"DROP_ANIMATING"===l.phase&&r(t_({completed:l.completed})),"IDLE"!==n().phase&&rM(!1),r(e_()),r({type:"BEFORE_INITIAL_CAPTURE",payload:{draggableId:a,movementMode:s}});var u={draggableId:a,scrollOptions:{shouldPublishImmediately:"SNAP"===s}},f=e.startPublishing(u),d=f.critical,h=f.dimensions,p=f.viewport;r(function(e){return{type:"INITIAL_PUBLISH",payload:e}}({critical:d,dimensions:h,clientSelection:c,movementMode:s,viewport:p}))}else t(o)}}}}(n),z_,j_,M_,C_,function(e){return function(t){return function(n){return function(r){if(function(e){return"DROP_COMPLETE"===e.type||"DROP_ANIMATE"===e.type||"FLUSH"===e.type}(r))return e.stop(),void n(r);if("INITIAL_PUBLISH"===r.type){n(r);var o=t.getState();return"DRAGGING"!==o.phase&&rM(!1),void e.start(o)}n(r),e.scroll(t.getState())}}}}(c),m_,function(e){var t=!1;return function(){return function(n){return function(r){if("INITIAL_PUBLISH"===r.type)return t=!0,e.tryRecordFocus(r.payload.critical.draggable.id),n(r),void e.tryRestoreFocusRecorded();if(n(r),t){if("FLUSH"===r.type)return t=!1,void e.tryRestoreFocusRecorded();if("DROP_COMPLETE"===r.type){t=!1;var o=r.payload.completed.result;o.combine&&e.tryShiftRecord(o.draggableId,o.combine.draggableId),e.tryRestoreFocusRecorded()}}}}}}(r),x_(i,a))))},S_=function(){return{additions:{},removals:{},modified:{}}};var O_=function(e){var t=e.scrollHeight,n=e.scrollWidth,r=e.height,o=e.width,i=dM({x:n,y:t},{x:o,y:r});return{x:Math.max(0,i.x),y:Math.max(0,i.y)}},E_=function(){var e=document.documentElement;return e||rM(!1),e},T_=function(){var e=E_();return O_({scrollHeight:e.scrollHeight,scrollWidth:e.scrollWidth,width:e.clientWidth,height:e.clientHeight})},A_=function(e){var t=e.critical,n=e.scrollOptions,r=e.registry;HC();var o=function(){var e=v_(),t=T_(),n=e.y,r=e.x,o=E_(),i=o.clientWidth,a=o.clientHeight;return{frame:Aj({top:n,left:r,right:r+i,bottom:n+a}),scroll:{initial:e,current:e,max:t,diff:{value:uM,displacement:uM}}}}(),i=o.scroll.current,a=t.droppable,c=r.droppable.getAllByType(a.type).map((function(e){return e.callbacks.getDimensionAndWatchScroll(i,n)})),s=r.draggable.getAllByType(t.draggable.type).map((function(e){return e.getDimension(i)})),l={draggables:SM(s),droppables:qM(c)};return DC(),{dimensions:l,critical:t,viewport:o}};function L_(e,t,n){return n.descriptor.id!==t.id&&(n.descriptor.type===t.type&&"virtual"===e.droppable.getById(n.descriptor.droppableId).descriptor.mode)}var H_=function(e,t){var n=null,r=function(e){var t=e.registry,n=e.callbacks,r=S_(),o=null,i=function(){o||(n.collectionStarting(),o=requestAnimationFrame((function(){o=null,HC();var e=r,i=e.additions,a=e.removals,c=e.modified,s=Object.keys(i).map((function(e){return t.draggable.getById(e).getDimension(uM)})).sort((function(e,t){return e.descriptor.index-t.descriptor.index})),l=Object.keys(c).map((function(e){return{droppableId:e,scroll:t.droppable.getById(e).callbacks.getScrollWhileDragging()}})),u={additions:s,removals:Object.keys(a),modified:l};r=S_(),DC(),n.publish(u)})))};return{add:function(e){var t=e.descriptor.id;r.additions[t]=e,r.modified[e.descriptor.droppableId]=!0,r.removals[t]&&delete r.removals[t],i()},remove:function(e){var t=e.descriptor;r.removals[t.id]=!0,r.modified[t.droppableId]=!0,r.additions[t.id]&&delete r.additions[t.id],i()},stop:function(){o&&(cancelAnimationFrame(o),o=null,r=S_())}}}({callbacks:{publish:t.publishWhileDragging,collectionStarting:t.collectionStarting},registry:e}),o=function(t){n||rM(!1);var o=n.critical.draggable;"ADDITION"===t.type&&L_(e,o,t.value)&&r.add(t.value),"REMOVAL"===t.type&&L_(e,o,t.value)&&r.remove(t.value)};return{updateDroppableIsEnabled:function(r,o){e.droppable.exists(r)||rM(!1),n&&t.updateDroppableIsEnabled({id:r,isEnabled:o})},updateDroppableIsCombineEnabled:function(r,o){n&&(e.droppable.exists(r)||rM(!1),t.updateDroppableIsCombineEnabled({id:r,isCombineEnabled:o}))},scrollDroppable:function(t,r){n&&e.droppable.getById(t).callbacks.scroll(r)},updateDroppableScroll:function(r,o){n&&(e.droppable.exists(r)||rM(!1),t.updateDroppableScroll({id:r,newScroll:o}))},startPublishing:function(t){n&&rM(!1);var r=e.draggable.getById(t.draggableId),i=e.droppable.getById(r.descriptor.droppableId),a={draggable:r.descriptor,droppable:i.descriptor},c=e.subscribe(o);return n={critical:a,unsubscribe:c},A_({critical:a,registry:e,scrollOptions:t.scrollOptions})},stopPublishing:function(){if(n){r.stop();var t=n.critical.droppable;e.droppable.getAllByType(t.type).forEach((function(e){return e.callbacks.dragStopped()})),n.unsubscribe(),n=null}}}},D_=function(e,t){return"IDLE"===e.phase||"DROP_ANIMATING"===e.phase&&(e.completed.result.draggableId!==t&&"DROP"===e.completed.result.reason)},P_=function(e){window.scrollBy(e.x,e.y)},V_=Tz((function(e){return OM(e).filter((function(e){return!!e.isEnabled&&!!e.frame}))})),R_=function(e){var t=e.center,n=e.destination,r=e.droppables;if(n){var o=r[n];return o.frame?o:null}return function(e,t){return CM(V_(t),(function(t){return t.frame||rM(!1),xC(t.frame.pageMarginBox)(e)}))}(t,r)},I_=.25,N_=.05,F_=28,B_=function(e){return Math.pow(e,2)},U_={stopDampeningAt:1200,accelerateAt:360},W_=function(e){var t=e.startOfRange,n=e.endOfRange,r=e.current,o=n-t;return 0===o?0:(r-t)/o},G_=U_.accelerateAt,Y_=U_.stopDampeningAt,$_=function(e){var t=e.distanceToEdge,n=e.thresholds,r=e.dragStartTime,o=e.shouldUseTimeDampening,i=function(e,t){if(e>t.startScrollingFrom)return 0;if(e<=t.maxScrollValueAt)return F_;if(e===t.startScrollingFrom)return 1;var n=W_({startOfRange:t.maxScrollValueAt,endOfRange:t.startScrollingFrom,current:e}),r=F_*B_(1-n);return Math.ceil(r)}(t,n);return 0===i?0:o?Math.max(function(e,t){var n=t,r=Y_,o=Date.now()-n;if(o>=Y_)return e;if(ot.height,i=n.width>t.width;return i||o?i&&o?null:{x:i?0:r.x,y:o?0:r.y}:r}({container:n,subject:r,proposedScroll:l});return u?hM(u,uM)?null:u:null},Q_=mM((function(e){return 0===e?0:e>0?1:-1})),J_=function(){var e=function(e,t){return e<0?e:e>t?e-t:0};return function(t){var n=t.current,r=t.max,o=t.change,i=fM(n,o),a={x:e(i.x,r.x),y:e(i.y,r.y)};return hM(a,uM)?null:a}}(),eq=function(e){var t=e.max,n=e.current,r=e.change,o={x:Math.max(n.x,t.x),y:Math.max(n.y,t.y)},i=Q_(r),a=J_({max:o,current:n,change:i});return!a||(0!==i.x&&0===a.x||0!==i.y&&0===a.y)},tq=function(e,t){return eq({current:e.scroll.current,max:e.scroll.max,change:t})},nq=function(e,t){var n=e.frame;return!!n&&eq({current:n.scroll.current,max:n.scroll.max,change:t})},rq=function(e){var t=e.state,n=e.dragStartTime,r=e.shouldUseTimeDampening,o=e.scrollWindow,i=e.scrollDroppable,a=t.current.page.borderBoxCenter,c=t.dimensions.draggables[t.critical.draggable.id].page.marginBox;if(t.isWindowScrollAllowed){var s=function(e){var t=e.viewport,n=e.subject,r=e.center,o=e.dragStartTime,i=e.shouldUseTimeDampening,a=K_({dragStartTime:o,container:t.frame,subject:n,center:r,shouldUseTimeDampening:i});return a&&tq(t,a)?a:null}({dragStartTime:n,viewport:t.viewport,subject:c,center:a,shouldUseTimeDampening:r});if(s)return void o(s)}var l=R_({center:a,destination:bC(t.impact),droppables:t.dimensions.droppables});if(l){var u=function(e){var t=e.droppable,n=e.subject,r=e.center,o=e.dragStartTime,i=e.shouldUseTimeDampening,a=t.frame;if(!a)return null;var c=K_({dragStartTime:o,container:a.pageMarginBox,subject:n,center:r,shouldUseTimeDampening:i});return c&&nq(t,c)?c:null}({dragStartTime:n,droppable:l,subject:c,center:a,shouldUseTimeDampening:r});u&&i(l.descriptor.id,u)}},oq=function(e){var t=e.move,n=e.scrollDroppable,r=e.scrollWindow,o=function(e,t){if(!nq(e,t))return t;var r=function(e,t){var n=e.frame;return n&&nq(e,t)?J_({current:n.scroll.current,max:n.scroll.max,change:t}):null}(e,t);if(!r)return n(e.descriptor.id,t),null;var o=dM(t,r);return n(e.descriptor.id,o),dM(t,o)},i=function(e,t,n){if(!e)return n;if(!tq(t,n))return n;var o=function(e,t){if(!tq(e,t))return null;var n=e.scroll.max,r=e.scroll.current;return J_({current:r,max:n,change:t})}(t,n);if(!o)return r(n),null;var i=dM(n,o);return r(i),dM(n,i)};return function(e){var n=e.scrollJumpRequest;if(n){var r=bC(e.impact);r||rM(!1);var a=o(e.dimensions.droppables[r],n);if(a){var c=e.viewport,s=i(e.isWindowScrollAllowed,c,a);s&&function(e,n){var r=fM(e.current.client.selection,n);t({client:r})}(e,s)}}}},iq=function(e){var t=e.scrollDroppable,n=e.scrollWindow,r=e.move,o=function(e){var t=e.scrollWindow,n=e.scrollDroppable,r=Bj(t),o=Bj(n),i=null,a=function(e){i||rM(!1);var t=i,n=t.shouldUseTimeDampening,a=t.dragStartTime;rq({state:e,scrollWindow:r,scrollDroppable:o,dragStartTime:a,shouldUseTimeDampening:n})};return{start:function(e){HC(),i&&rM(!1);var t=Date.now(),n=!1,r=function(){n=!0};rq({state:e,dragStartTime:0,shouldUseTimeDampening:!1,scrollWindow:r,scrollDroppable:r}),i={dragStartTime:t,shouldUseTimeDampening:n},DC(),n&&a(e)},stop:function(){i&&(r.cancel(),o.cancel(),i=null)},scroll:a}}({scrollWindow:n,scrollDroppable:t}),i=oq({move:r,scrollWindow:n,scrollDroppable:t});return{scroll:function(e){"DRAGGING"===e.phase&&("FLUID"!==e.movementMode?e.scrollJumpRequest&&i(e):o.scroll(e))},start:o.start,stop:o.stop}},aq="data-rbd",cq=function(){var e=aq+"-drag-handle";return{base:e,draggableId:e+"-draggable-id",contextId:e+"-context-id"}}(),sq=function(){var e=aq+"-draggable";return{base:e,contextId:e+"-context-id",id:e+"-id"}}(),lq=function(){var e=aq+"-droppable";return{base:e,contextId:e+"-context-id",id:e+"-id"}}(),uq={contextId:aq+"-scroll-container-context-id"},fq=function(e,t){return e.map((function(e){var n=e.styles[t];return n?e.selector+" { "+n+" }":""})).join(" ")},dq=function(e){var t,n=(t=e,function(e){return"["+e+'="'+t+'"]'}),r=function(){var e="\n cursor: -webkit-grab;\n cursor: grab;\n ";return{selector:n(cq.contextId),styles:{always:"\n -webkit-touch-callout: none;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n touch-action: manipulation;\n ",resting:e,dragging:"pointer-events: none;",dropAnimating:e}}}(),o=[function(){var e="\n transition: "+l_.outOfTheWay+";\n ";return{selector:n(sq.contextId),styles:{dragging:e,dropAnimating:e,userCancel:e}}}(),r,{selector:n(lq.contextId),styles:{always:"overflow-anchor: none;"}},{selector:"body",styles:{dragging:"\n cursor: grabbing;\n cursor: -webkit-grabbing;\n user-select: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n overflow-anchor: none;\n "}}];return{always:fq(o,"always"),resting:fq(o,"resting"),dragging:fq(o,"dragging"),dropAnimating:fq(o,"dropAnimating"),userCancel:fq(o,"userCancel")}},hq="undefined"!==typeof window&&"undefined"!==typeof window.document&&"undefined"!==typeof window.document.createElement?a.useLayoutEffect:a.useEffect,pq=function(){var e=document.querySelector("head");return e||rM(!1),e},zq=function(e){var t=document.createElement("style");return e&&t.setAttribute("nonce",e),t.type="text/css",t};var vq=function(e){return e&&e.ownerDocument?e.ownerDocument.defaultView:window};function gq(e){return e instanceof vq(e).HTMLElement}function mq(e,t){var n="["+cq.contextId+'="'+e+'"]',r=_M(document.querySelectorAll(n));if(!r.length)return null;var o=CM(r,(function(e){return e.getAttribute(cq.draggableId)===t}));return o&&gq(o)?o:null}function yq(){var e={draggables:{},droppables:{}},t=[];function n(e){t.length&&t.forEach((function(t){return t(e)}))}function r(t){return e.draggables[t]||null}function o(t){return e.droppables[t]||null}return{draggable:{register:function(t){e.draggables[t.descriptor.id]=t,n({type:"ADDITION",value:t})},update:function(t,n){var r=e.draggables[n.descriptor.id];r&&r.uniqueId===t.uniqueId&&(delete e.draggables[n.descriptor.id],e.draggables[t.descriptor.id]=t)},unregister:function(t){var o=t.descriptor.id,i=r(o);i&&t.uniqueId===i.uniqueId&&(delete e.draggables[o],n({type:"REMOVAL",value:t}))},getById:function(e){var t=r(e);return t||rM(!1),t},findById:r,exists:function(e){return Boolean(r(e))},getAllByType:function(t){return jM(e.draggables).filter((function(e){return e.descriptor.type===t}))}},droppable:{register:function(t){e.droppables[t.descriptor.id]=t},unregister:function(t){var n=o(t.descriptor.id);n&&t.uniqueId===n.uniqueId&&delete e.droppables[t.descriptor.id]},getById:function(e){var t=o(e);return t||rM(!1),t},findById:o,exists:function(e){return Boolean(o(e))},getAllByType:function(t){return jM(e.droppables).filter((function(e){return e.descriptor.type===t}))}},subscribe:function(e){return t.push(e),function(){var n=t.indexOf(e);-1!==n&&t.splice(n,1)}},clean:function(){e.draggables={},e.droppables={},t.length=0}}}var bq=c.a.createContext(null),wq=function(){var e=document.body;return e||rM(!1),e},kq={position:"absolute",width:"1px",height:"1px",margin:"-1px",border:"0",padding:"0",overflow:"hidden",clip:"rect(0 0 0 0)","clip-path":"inset(100%)"},xq=function(e){return"rbd-announcement-"+e};var jq=0,Mq={separator:"::"};function Cq(e,t){return void 0===t&&(t=Mq),Ej((function(){return""+e+t.separator+jq++}),[t.separator,e])}var _q=c.a.createContext(null);function qq(e){0}function Sq(e,t){qq()}function Oq(e){var t=Object(a.useRef)(e);return Object(a.useEffect)((function(){t.current=e})),t}var Eq,Tq=27,Aq=32,Lq=37,Hq=38,Dq=39,Pq=40,Vq=((Eq={})[13]=!0,Eq[9]=!0,Eq),Rq=function(e){Vq[e.keyCode]&&e.preventDefault()},Iq=function(){var e="visibilitychange";return"undefined"===typeof document?e:CM([e,"ms"+e,"webkit"+e,"moz"+e,"o"+e],(function(e){return"on"+e in document}))||e}(),Nq=0,Fq=5;var Bq,Uq={type:"IDLE"};function Wq(e){var t=e.cancel,n=e.completed,r=e.getPhase,o=e.setPhase;return[{eventName:"mousemove",fn:function(e){var t=e.button,n=e.clientX,i=e.clientY;if(t===Nq){var a={x:n,y:i},c=r();if("DRAGGING"===c.type)return e.preventDefault(),void c.actions.move(a);"PENDING"!==c.type&&rM(!1);var s=c.point;if(l=s,u=a,Math.abs(u.x-l.x)>=Fq||Math.abs(u.y-l.y)>=Fq){var l,u;e.preventDefault();var f=c.actions.fluidLift(a);o({type:"DRAGGING",actions:f})}}}},{eventName:"mouseup",fn:function(e){var o=r();"DRAGGING"===o.type?(e.preventDefault(),o.actions.drop({shouldBlockNextClick:!0}),n()):t()}},{eventName:"mousedown",fn:function(e){"DRAGGING"===r().type&&e.preventDefault(),t()}},{eventName:"keydown",fn:function(e){if("PENDING"!==r().type)return e.keyCode===Tq?(e.preventDefault(),void t()):void Rq(e);t()}},{eventName:"resize",fn:t},{eventName:"scroll",options:{passive:!0,capture:!1},fn:function(){"PENDING"===r().type&&t()}},{eventName:"webkitmouseforcedown",fn:function(e){var n=r();"IDLE"===n.type&&rM(!1),n.actions.shouldRespectForcePress()?t():e.preventDefault()}},{eventName:Iq,fn:t}]}function Gq(){}var Yq=((Bq={})[34]=!0,Bq[33]=!0,Bq[36]=!0,Bq[35]=!0,Bq);function $q(e,t){function n(){t(),e.cancel()}return[{eventName:"keydown",fn:function(r){return r.keyCode===Tq?(r.preventDefault(),void n()):r.keyCode===Aq?(r.preventDefault(),t(),void e.drop()):r.keyCode===Pq?(r.preventDefault(),void e.moveDown()):r.keyCode===Hq?(r.preventDefault(),void e.moveUp()):r.keyCode===Dq?(r.preventDefault(),void e.moveRight()):r.keyCode===Lq?(r.preventDefault(),void e.moveLeft()):void(Yq[r.keyCode]?r.preventDefault():Rq(r))}},{eventName:"mousedown",fn:n},{eventName:"mouseup",fn:n},{eventName:"click",fn:n},{eventName:"touchstart",fn:n},{eventName:"resize",fn:n},{eventName:"wheel",fn:n,options:{passive:!0}},{eventName:Iq,fn:n}]}var Zq={type:"IDLE"},Xq=120,Kq=.15;var Qq={input:!0,button:!0,textarea:!0,select:!0,option:!0,optgroup:!0,video:!0,audio:!0};function Jq(e,t){var n=t.target;return!!gq(n)&&function e(t,n){if(null==n)return!1;if(Boolean(Qq[n.tagName.toLowerCase()]))return!0;var r=n.getAttribute("contenteditable");return"true"===r||""===r||n!==t&&e(t,n.parentElement)}(e,n)}var eS=function(e){return Aj(e.getBoundingClientRect()).center};var tS="undefined"===typeof document?"matches":CM(["matches","msMatchesSelector","webkitMatchesSelector"],(function(e){return e in Element.prototype}))||"matches";function nS(e,t){return e.closest?e.closest(t):function e(t,n){return null==t?null:t[tS](n)?t:e(t.parentElement,n)}(e,t)}function rS(e,t){var n,r=t.target;if(!((n=r)instanceof vq(n).Element))return null;var o=nS(r,function(e){return"["+cq.contextId+'="'+e+'"]'}(e));return o&&gq(o)?o:null}function oS(e){e.preventDefault()}function iS(e){var t=e.expected,n=e.phase,r=e.isLockActive;e.shouldWarn;return!!r()&&t===n}function aS(e){var t=e.lockAPI,n=e.store,r=e.registry,o=e.draggableId;if(t.isClaimed())return!1;var i=r.draggable.findById(o);return!!i&&(!!i.options.isEnabled&&!!D_(n.getState(),o))}function cS(e){var t=e.lockAPI,n=e.contextId,r=e.store,o=e.registry,i=e.draggableId,a=e.forceSensorStop,c=e.sourceEvent;if(!aS({lockAPI:t,store:r,registry:o,draggableId:i}))return null;var s=o.draggable.getById(i),l=function(e,t){var n="["+sq.contextId+'="'+e+'"]',r=CM(_M(document.querySelectorAll(n)),(function(e){return e.getAttribute(sq.id)===t}));return r&&gq(r)?r:null}(n,s.descriptor.id);if(!l)return null;if(c&&!s.options.canDragInteractiveElements&&Jq(l,c))return null;var u=t.claim(a||Qj),f="PRE_DRAG";function d(){return s.options.shouldRespectForcePress}function h(){return t.isActive(u)}var p=function(e,t){iS({expected:e,phase:f,isLockActive:h,shouldWarn:!0})&&r.dispatch(t())}.bind(null,"DRAGGING");function z(e){function n(){t.release(),f="COMPLETED"}function o(t,o){if(void 0===o&&(o={shouldBlockNextClick:!1}),e.cleanup(),o.shouldBlockNextClick){var i=Jj(window,[{eventName:"click",fn:oS,options:{once:!0,passive:!1,capture:!0}}]);setTimeout(i)}n(),r.dispatch(n_({reason:t}))}return"PRE_DRAG"!==f&&(n(),"PRE_DRAG"!==f&&rM(!1)),r.dispatch(BC(e.liftActionArgs)),f="DRAGGING",Fx({isActive:function(){return iS({expected:"DRAGGING",phase:f,isLockActive:h,shouldWarn:!1})},shouldRespectForcePress:d,drop:function(e){return o("DROP",e)},cancel:function(e){return o("CANCEL",e)}},e.actions)}return{isActive:function(){return iS({expected:"PRE_DRAG",phase:f,isLockActive:h,shouldWarn:!1})},shouldRespectForcePress:d,fluidLift:function(e){var t=Bj((function(e){p((function(){return ZC({client:e})}))}));return Fx({},z({liftActionArgs:{id:i,clientSelection:e,movementMode:"FLUID"},cleanup:function(){return t.cancel()},actions:{move:t}}),{move:t})},snapLift:function(){var e={moveUp:function(){return p(XC)},moveRight:function(){return p(QC)},moveDown:function(){return p(KC)},moveLeft:function(){return p(JC)}};return z({liftActionArgs:{id:i,clientSelection:eS(l),movementMode:"SNAP"},cleanup:Qj,actions:e})},abort:function(){iS({expected:"PRE_DRAG",phase:f,isLockActive:h,shouldWarn:!0})&&t.release()}}}var sS=[function(e){var t=Object(a.useRef)(Uq),n=Object(a.useRef)(Qj),r=Ej((function(){return{eventName:"mousedown",fn:function(t){if(!t.defaultPrevented&&t.button===Nq&&!(t.ctrlKey||t.metaKey||t.shiftKey||t.altKey)){var r=e.findClosestDraggableId(t);if(r){var o=e.tryGetLock(r,c,{sourceEvent:t});if(o){t.preventDefault();var i={x:t.clientX,y:t.clientY};n.current(),u(o,i)}}}}}}),[e]),o=Ej((function(){return{eventName:"webkitmouseforcewillbegin",fn:function(t){if(!t.defaultPrevented){var n=e.findClosestDraggableId(t);if(n){var r=e.findOptionsForDraggable(n);r&&(r.shouldRespectForcePress||e.canGetLock(n)&&t.preventDefault())}}}}}),[e]),i=Tj((function(){n.current=Jj(window,[o,r],{passive:!1,capture:!0})}),[o,r]),c=Tj((function(){"IDLE"!==t.current.type&&(t.current=Uq,n.current(),i())}),[i]),s=Tj((function(){var e=t.current;c(),"DRAGGING"===e.type&&e.actions.cancel({shouldBlockNextClick:!0}),"PENDING"===e.type&&e.actions.abort()}),[c]),l=Tj((function(){var e=Wq({cancel:s,completed:c,getPhase:function(){return t.current},setPhase:function(e){t.current=e}});n.current=Jj(window,e,{capture:!0,passive:!1})}),[s,c]),u=Tj((function(e,n){"IDLE"!==t.current.type&&rM(!1),t.current={type:"PENDING",point:n,actions:e},l()}),[l]);hq((function(){return i(),function(){n.current()}}),[i])},function(e){var t=Object(a.useRef)(Gq),n=Ej((function(){return{eventName:"keydown",fn:function(n){if(!n.defaultPrevented&&n.keyCode===Aq){var o=e.findClosestDraggableId(n);if(o){var i=e.tryGetLock(o,s,{sourceEvent:n});if(i){n.preventDefault();var a=!0,c=i.snapLift();t.current(),t.current=Jj(window,$q(c,s),{capture:!0,passive:!1})}}}function s(){a||rM(!1),a=!1,t.current(),r()}}}}),[e]),r=Tj((function(){t.current=Jj(window,[n],{passive:!1,capture:!0})}),[n]);hq((function(){return r(),function(){t.current()}}),[r])},function(e){var t=Object(a.useRef)(Zq),n=Object(a.useRef)(Qj),r=Tj((function(){return t.current}),[]),o=Tj((function(e){t.current=e}),[]),i=Ej((function(){return{eventName:"touchstart",fn:function(t){if(!t.defaultPrevented){var r=e.findClosestDraggableId(t);if(r){var o=e.tryGetLock(r,s,{sourceEvent:t});if(o){var i=t.touches[0],a={x:i.clientX,y:i.clientY};n.current(),d(o,a)}}}}}}),[e]),c=Tj((function(){n.current=Jj(window,[i],{capture:!0,passive:!1})}),[i]),s=Tj((function(){var e=t.current;"IDLE"!==e.type&&("PENDING"===e.type&&clearTimeout(e.longPressTimerId),o(Zq),n.current(),c())}),[c,o]),l=Tj((function(){var e=t.current;s(),"DRAGGING"===e.type&&e.actions.cancel({shouldBlockNextClick:!0}),"PENDING"===e.type&&e.actions.abort()}),[s]),u=Tj((function(){var e={capture:!0,passive:!1},t={cancel:l,completed:s,getPhase:r},o=Jj(window,function(e){var t=e.cancel,n=e.completed,r=e.getPhase;return[{eventName:"touchmove",options:{capture:!1},fn:function(e){var n=r();if("DRAGGING"===n.type){n.hasMoved=!0;var o=e.touches[0],i={x:o.clientX,y:o.clientY};e.preventDefault(),n.actions.move(i)}else t()}},{eventName:"touchend",fn:function(e){var o=r();"DRAGGING"===o.type?(e.preventDefault(),o.actions.drop({shouldBlockNextClick:!0}),n()):t()}},{eventName:"touchcancel",fn:function(e){"DRAGGING"===r().type?(e.preventDefault(),t()):t()}},{eventName:"touchforcechange",fn:function(e){var n=r();"IDLE"===n.type&&rM(!1);var o=e.touches[0];if(o&&o.force>=Kq){var i=n.actions.shouldRespectForcePress();if("PENDING"!==n.type)return i?n.hasMoved?void e.preventDefault():void t():void e.preventDefault();i&&t()}}},{eventName:Iq,fn:t}]}(t),e),i=Jj(window,function(e){var t=e.cancel,n=e.getPhase;return[{eventName:"orientationchange",fn:t},{eventName:"resize",fn:t},{eventName:"contextmenu",fn:function(e){e.preventDefault()}},{eventName:"keydown",fn:function(e){"DRAGGING"===n().type?(e.keyCode===Tq&&e.preventDefault(),t()):t()}},{eventName:Iq,fn:t}]}(t),e);n.current=function(){o(),i()}}),[l,r,s]),f=Tj((function(){var e=r();"PENDING"!==e.type&&rM(!1);var t=e.actions.fluidLift(e.point);o({type:"DRAGGING",actions:t,hasMoved:!1})}),[r,o]),d=Tj((function(e,t){"IDLE"!==r().type&&rM(!1);var n=setTimeout(f,Xq);o({type:"PENDING",point:t,actions:e,longPressTimerId:n}),u()}),[u,r,o,f]);hq((function(){return c(),function(){n.current();var e=r();"PENDING"===e.type&&(clearTimeout(e.longPressTimerId),o(Zq))}}),[r,c,o]),hq((function(){return Jj(window,[{eventName:"touchmove",fn:function(){},options:{capture:!1,passive:!1}}])}),[])}];function lS(e){var t=e.contextId,n=e.store,r=e.registry,o=e.customSensors,i=e.enableDefaultSensors,c=[].concat(i?sS:[],o||[]),s=Object(a.useState)((function(){return function(){var e=null;function t(){e||rM(!1),e=null}return{isClaimed:function(){return Boolean(e)},isActive:function(t){return t===e},claim:function(t){e&&rM(!1);var n={abandon:t};return e=n,n},release:t,tryAbandon:function(){e&&(e.abandon(),t())}}}()}))[0],l=Tj((function(e,t){e.isDragging&&!t.isDragging&&s.tryAbandon()}),[s]);hq((function(){var e=n.getState();return n.subscribe((function(){var t=n.getState();l(e,t),e=t}))}),[s,n,l]),hq((function(){return s.tryAbandon}),[s.tryAbandon]);var u=Tj((function(e){return aS({lockAPI:s,registry:r,store:n,draggableId:e})}),[s,r,n]),f=Tj((function(e,o,i){return cS({lockAPI:s,registry:r,contextId:t,store:n,draggableId:e,forceSensorStop:o,sourceEvent:i&&i.sourceEvent?i.sourceEvent:null})}),[t,s,r,n]),d=Tj((function(e){return function(e,t){var n=rS(e,t);return n?n.getAttribute(cq.draggableId):null}(t,e)}),[t]),h=Tj((function(e){var t=r.draggable.findById(e);return t?t.options:null}),[r.draggable]),p=Tj((function(){s.isClaimed()&&(s.tryAbandon(),"IDLE"!==n.getState().phase&&n.dispatch(e_()))}),[s,n]),z=Tj(s.isClaimed,[s]),v=Ej((function(){return{canGetLock:u,tryGetLock:f,findClosestDraggableId:d,findOptionsForDraggable:h,tryReleaseLock:p,isLockClaimed:z}}),[u,f,d,h,p,z]);qq();for(var g=0;go))return i.current=iO(iO({},i.current),{width:o,tabRight:f,containerRight:s}),f>=s&&!i.current.collapse?(i.current.collapse=!0,r(!0)):f+ds&&u(!0),l<=s&&u(!1),a>0&&c(!0),0===a&&c(!1)}}}),[r,n])]}(l,u,n,s),h=d[0],p=d[1],z=d[2];Object(a.useEffect)((function(){if(l.current){var e=l.current,t=Object(oO.a)(300,(function(){f(),z()}));return t(),e.addEventListener("scroll",z),window.addEventListener("resize",t),function(){e.removeEventListener("scroll",z),window.removeEventListener("resize",t)}}}),[n,s]);var v=Object(a.useCallback)((function(e){if(e){var t=u.current;n.length>=t.length&&(u.current=pO(pO([],t,!0),[e],!1)),n.length0||(o.disconnect(),jO.delete(e),delete kO[r],delete xO[r])}}((function(e){o&&o(e),u(e)}),e,s)}}),[t,n,r,o]);return Object(a.useEffect)((function(){return function(){var e;null===(e=c.current)||void 0===e||e.call(c),c.current=null}}),[]),[f,i,l]},qO=function(){return(qO=Object.assign||function(e){for(var t,n=1,r=arguments.length;n label {\n margin-bottom: 0;\n }\n"],["\n & input {\n background: transparent;\n }\n\n & > label {\n margin-bottom: 0;\n }\n"]))),zE=function(){return(zE=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0?BE(eT,--QE):0,XE--,10===JE&&(XE=1,ZE--),JE}function oT(){return JE=QE2||sT(JE)>3?"":" "}function hT(e,t){for(;--t&&oT()&&!(JE<48||JE>102||JE>57&&JE<65||JE>70&&JE<97););return cT(e,aT()+(t<6&&32==iT()&&32==oT()))}function pT(e,t){for(;oT()&&e+JE!==57&&(e+JE!==84||47!==iT()););return"/*"+cT(t,QE-1)+"*"+VE(47===e?e:oT())}function zT(e){for(;!sT(iT());)oT();return cT(e,QE)}function vT(e){return uT(function e(t,n,r,o,i,a,c,s,l){var u=0,f=0,d=c,h=0,p=0,z=0,v=1,g=1,m=1,y=0,b="",w=i,k=a,x=o,j=b;for(;g;)switch(z=y,y=oT()){case 40:if(108!=z&&58==j.charCodeAt(d-1)){-1!=FE(j+=NE(fT(y),"&","&\f"),"&\f")&&(m=-1);break}case 34:case 39:case 91:j+=fT(y);break;case 9:case 10:case 13:case 32:j+=dT(z);break;case 92:j+=hT(aT()-1,7);continue;case 47:switch(iT()){case 42:case 47:YE(mT(pT(oT(),aT()),n,r),l);break;default:j+="/"}break;case 123*v:s[u++]=WE(j)*m;case 125*v:case 59:case 0:switch(y){case 0:case 125:g=0;case 59+f:p>0&&WE(j)-d&&YE(p>32?yT(j+";",o,r,d-1):yT(NE(j," ","")+";",o,r,d-2),l);break;case 59:j+=";";default:if(YE(x=gT(j,n,r,u,f,i,s,b,w=[],k=[],d),a),123===y)if(0===f)e(j,n,x,x,w,a,d,s,k);else switch(h){case 100:case 109:case 115:e(t,x,x,o&&YE(gT(t,x,x,0,0,i,s,b,i,w=[],d),k),i,k,d,s,o?w:k);break;default:e(j,x,x,x,[""],k,0,s,k)}}u=f=p=0,v=m=1,b=j="",d=c;break;case 58:d=1+WE(j),p=z;default:if(v<1)if(123==y)--v;else if(125==y&&0==v++&&125==rT())continue;switch(j+=VE(y),y*v){case 38:m=f>0?1:(j+="\f",-1);break;case 44:s[u++]=(WE(j)-1)*m,m=1;break;case 64:45===iT()&&(j+=fT(oT())),h=iT(),f=d=WE(b=j+=zT(aT())),y++;break;case 45:45===z&&2==WE(j)&&(v=0)}}return a}("",null,null,null,[""],e=lT(e),0,[0],e))}function gT(e,t,n,r,o,i,a,c,s,l,u){for(var f=o-1,d=0===o?i:[""],h=GE(d),p=0,z=0,v=0;p0?d[g]+" "+m:NE(m,/&\f/g,d[g])))&&(s[v++]=y);return tT(e,t,n,0===o?AE:c,s,l,u)}function mT(e,t,n){return tT(e,t,n,TE,VE(JE),UE(e,2,-2),0)}function yT(e,t,n,r){return tT(e,t,n,LE,UE(e,0,r),UE(e,r+1,-1),r)}function bT(e,t){switch((((t<<2^BE(n=e,0))<<2^BE(n,1))<<2^BE(n,2))<<2^BE(n,3)){case 5103:return EE+"print-"+e+e;case 5737:case 4201:case 3177:case 3433:case 1641:case 4457:case 2921:case 5572:case 6356:case 5844:case 3191:case 6645:case 3005:case 6391:case 5879:case 5623:case 6135:case 4599:case 4855:case 4215:case 6389:case 5109:case 5365:case 5621:case 3829:return EE+e+e;case 5349:case 4246:case 4810:case 6968:case 2756:return EE+e+OE+e+SE+e+e;case 6828:case 4268:return EE+e+SE+e+e;case 6165:return EE+e+SE+"flex-"+e+e;case 5187:return EE+e+NE(e,/(\w+).+(:[^]+)/,EE+"box-$1$2"+SE+"flex-$1$2")+e;case 5443:return EE+e+SE+"flex-item-"+NE(e,/flex-|-self/,"")+e;case 4675:return EE+e+SE+"flex-line-pack"+NE(e,/align-content|flex-|-self/,"")+e;case 5548:return EE+e+SE+NE(e,"shrink","negative")+e;case 5292:return EE+e+SE+NE(e,"basis","preferred-size")+e;case 6060:return EE+"box-"+NE(e,"-grow","")+EE+e+SE+NE(e,"grow","positive")+e;case 4554:return EE+NE(e,/([^-])(transform)/g,"$1"+EE+"$2")+e;case 6187:return NE(NE(NE(e,/(zoom-|grab)/,EE+"$1"),/(image-set)/,EE+"$1"),e,"")+e;case 5495:case 3959:return NE(e,/(image-set\([^]*)/,EE+"$1$`$1");case 4968:return NE(NE(e,/(.+:)(flex-)?(.*)/,EE+"box-pack:$3"+SE+"flex-pack:$3"),/s.+-b[^;]+/,"justify")+EE+e+e;case 4095:case 3583:case 4068:case 2532:return NE(e,/(.+)-inline(.+)/,EE+"$1$2")+e;case 8116:case 7059:case 5753:case 5535:case 5445:case 5701:case 4933:case 4677:case 5533:case 5789:case 5021:case 4765:if(WE(e)-1-t>6)switch(BE(e,t+1)){case 109:if(45!==BE(e,t+4))break;case 102:return NE(e,/(.+:)(.+)-([^]+)/,"$1"+EE+"$2-$3$1"+OE+(108==BE(e,t+3)?"$3":"$2-$3"))+e;case 115:return~FE(e,"stretch")?bT(NE(e,"stretch","fill-available"),t)+e:e}break;case 4949:if(115!==BE(e,t+1))break;case 6444:switch(BE(e,WE(e)-3-(~FE(e,"!important")&&10))){case 107:return NE(e,":",":"+EE)+e;case 101:return NE(e,/(.+:)([^;!]+)(;|!.+)?/,"$1"+EE+(45===BE(e,14)?"inline-":"")+"box$3$1"+EE+"$2$3$1"+SE+"$2box$3")+e}break;case 5936:switch(BE(e,t+11)){case 114:return EE+e+SE+NE(e,/[svh]\w+-[tblr]{2}/,"tb")+e;case 108:return EE+e+SE+NE(e,/[svh]\w+-[tblr]{2}/,"tb-rl")+e;case 45:return EE+e+SE+NE(e,/[svh]\w+-[tblr]{2}/,"lr")+e}return EE+e+SE+e+e}var n;return e}function wT(e,t){for(var n="",r=GE(e),o=0;o-1&&!e.return)switch(e.type){case LE:e.return=bT(e.value,e.length);break;case DE:return wT([nT(e,{value:NE(e.value,"@","@"+EE)})],r);case AE:if(e.length)return $E(e.props,(function(t){switch((n=/(::plac\w+|:read-\w+)/.exec(n=t))?n[0]:n){case":read-only":case":read-write":return wT([nT(e,{props:[NE(t,/:(read-\w+)/,":"+OE+"$1")]})],r);case"::placeholder":return wT([nT(e,{props:[NE(t,/:(plac\w+)/,":"+EE+"input-$1")]}),nT(e,{props:[NE(t,/:(plac\w+)/,":"+OE+"$1")]}),nT(e,{props:[NE(t,/:(plac\w+)/,SE+"input-$1")]})],r)}var n;return""}))}}],OT=function(e){var t=e.key;if("css"===t){var n=document.querySelectorAll("style[data-emotion]:not([data-s])");Array.prototype.forEach.call(n,(function(e){-1!==e.getAttribute("data-emotion").indexOf(" ")&&(document.head.appendChild(e),e.setAttribute("data-s",""))}))}var r=e.stylisPlugins||ST;var o,i,a={},c=[];o=e.container||document.head,Array.prototype.forEach.call(document.querySelectorAll('style[data-emotion^="'+t+' "]'),(function(e){for(var t=e.getAttribute("data-emotion").split(" "),n=1;n=4;++r,o-=4)t=1540483477*(65535&(t=255&e.charCodeAt(r)|(255&e.charCodeAt(++r))<<8|(255&e.charCodeAt(++r))<<16|(255&e.charCodeAt(++r))<<24))+(59797*(t>>>16)<<16),n=1540483477*(65535&(t^=t>>>24))+(59797*(t>>>16)<<16)^1540483477*(65535&n)+(59797*(n>>>16)<<16);switch(o){case 3:n^=(255&e.charCodeAt(r+2))<<16;case 2:n^=(255&e.charCodeAt(r+1))<<8;case 1:n=1540483477*(65535&(n^=255&e.charCodeAt(r)))+(59797*(n>>>16)<<16)}return(((n=1540483477*(65535&(n^=n>>>13))+(59797*(n>>>16)<<16))^n>>>15)>>>0).toString(36)},HT=n(173),DT=/[A-Z]|^ms/g,PT=/_EMO_([^_]+?)_([^]*?)_EMO_/g,VT=function(e){return 45===e.charCodeAt(1)},RT=function(e){return null!=e&&"boolean"!==typeof e},IT=xT((function(e){return VT(e)?e:e.replace(DT,"-$&").toLowerCase()})),NT=function(e,t){switch(e){case"animation":case"animationName":if("string"===typeof t)return t.replace(PT,(function(e,t,n){return BT={name:t,styles:n,next:BT},t}))}return 1===HT.a[e]||VT(e)||"number"!==typeof t||0===t?t:t+"px"};function FT(e,t,n){if(null==n)return"";if(void 0!==n.__emotion_styles)return n;switch(typeof n){case"boolean":return"";case"object":if(1===n.anim)return BT={name:n.name,styles:n.styles,next:BT},n.name;if(void 0!==n.styles){var r=n.next;if(void 0!==r)for(;void 0!==r;)BT={name:r.name,styles:r.styles,next:BT},r=r.next;return n.styles+";"}return function(e,t,n){var r="";if(Array.isArray(n))for(var o=0;o=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function uA(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n-1}function AA(e){return TA(e)?window.pageYOffset:e.scrollTop}function LA(e,t){TA(e)?window.scrollTo(0,t):e.scrollTop=t}function HA(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:200,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:_A,o=AA(e),i=t-o,a=10,c=0;!function t(){var s=function(e,t,n,r){return n*((e=e/r-1)*e*e+1)+t}(c+=a,o,i,n);LA(e,s),c1?t-1:0),r=1;r=p)return{placement:"bottom",maxHeight:t};if(j>=p&&!a)return i&&HA(s,M,160),{placement:"bottom",maxHeight:t};if(!a&&j>=r||a&&k>=r)return i&&HA(s,M,160),{placement:"bottom",maxHeight:a?k-y:j-y};if("auto"===o||a){var _=t,q=a?w:x;return q>=r&&(_=Math.min(q-y-c.controlHeight,t)),{placement:"top",maxHeight:_}}if("bottom"===o)return i&&LA(s,M),{placement:"bottom",maxHeight:t};break;case"top":if(w>=p)return{placement:"top",maxHeight:t};if(x>=p&&!a)return i&&HA(s,C,160),{placement:"top",maxHeight:t};if(!a&&x>=r||a&&w>=r){var S=t;return(!a&&x>=r||a&&w>=r)&&(S=a?w-b:x-b),i&&HA(s,C,160),{placement:"top",maxHeight:S}}return{placement:"bottom",maxHeight:t};default:throw new Error('Invalid placement provided "'.concat(o,'".'))}return l}var WA=function(e){return"auto"===e?"bottom":e},GA=Object(a.createContext)({getPortalPlacement:null}),YA=function(e){mA(n,e);var t=MA(n);function n(){var e;pA(this,n);for(var r=arguments.length,o=new Array(r),i=0;i1?"s":""," ").concat(o.join(","),", selected.");case"select-option":return"option ".concat(r,i?" is disabled. Select another option.":", selected.");default:return""}},onFocus:function(e){var t=e.context,n=e.focused,r=e.options,o=e.label,i=void 0===o?"":o,a=e.selectValue,c=e.isDisabled,s=e.isSelected,l=function(e,t){return e&&e.length?"".concat(e.indexOf(t)+1," of ").concat(e.length):""};if("value"===t&&a)return"value ".concat(i," focused, ").concat(l(a,n),".");if("menu"===t){var u=c?" disabled":"",f="".concat(s?"selected":"focused").concat(u);return"option ".concat(i," ").concat(f,", ").concat(l(r,n),".")}return""},onFilter:function(e){var t=e.inputValue,n=e.resultsMessage;return"".concat(n).concat(t?" for search term "+t:"",".")}},_L=function(e){var t=e.ariaSelection,n=e.focusedOption,r=e.focusedValue,o=e.focusableOptions,i=e.isFocused,c=e.selectValue,s=e.selectProps,l=e.id,u=s.ariaLiveMessages,f=s.getOptionLabel,d=s.inputValue,h=s.isMulti,p=s.isOptionDisabled,z=s.isSearchable,v=s.menuIsOpen,g=s.options,m=s.screenReaderStatus,y=s.tabSelectsValue,b=s["aria-label"],w=s["aria-live"],k=Object(a.useMemo)((function(){return kA(kA({},CL),u||{})}),[u]),x=Object(a.useMemo)((function(){var e,n="";if(t&&k.onChange){var r=t.option,o=t.options,i=t.removedValue,a=t.removedValues,s=t.value,l=i||r||(e=s,Array.isArray(e)?null:e),u=l?f(l):"",d=o||a||void 0,h=d?d.map(f):[],z=kA({isDisabled:l&&p(l,c),label:u,labels:h},t);n=k.onChange(z)}return n}),[t,k,p,c,f]),j=Object(a.useMemo)((function(){var e="",t=n||r,o=!!(n&&c&&c.includes(n));if(t&&k.onFocus){var i={focused:t,label:f(t),isDisabled:p(t,c),isSelected:o,options:g,context:t===n?"menu":"value",selectValue:c};e=k.onFocus(i)}return e}),[n,r,f,p,k,g,c]),M=Object(a.useMemo)((function(){var e="";if(v&&g.length&&k.onFilter){var t=m({count:o.length});e=k.onFilter({inputValue:d,resultsMessage:t})}return e}),[o,d,v,k,g,m]),C=Object(a.useMemo)((function(){var e="";if(k.guidance){var t=r?"value":v?"menu":"input";e=k.guidance({"aria-label":b,context:t,isDisabled:n&&p(n,c),isMulti:h,isSearchable:z,tabSelectsValue:y})}return e}),[b,n,r,h,p,z,v,k,c,y]),_="".concat(j," ").concat(M," ").concat(C),q=rA(a.Fragment,null,rA("span",{id:"aria-selection"},x),rA("span",{id:"aria-context"},_)),S="initial-input-focus"===(null===t||void 0===t?void 0:t.action);return rA(a.Fragment,null,rA(ML,{id:l},S&&q),rA(ML,{"aria-live":w,"aria-atomic":"false","aria-relevant":"additions text"},i&&!S&&q))},qL=[{base:"A",letters:"A\u24b6\uff21\xc0\xc1\xc2\u1ea6\u1ea4\u1eaa\u1ea8\xc3\u0100\u0102\u1eb0\u1eae\u1eb4\u1eb2\u0226\u01e0\xc4\u01de\u1ea2\xc5\u01fa\u01cd\u0200\u0202\u1ea0\u1eac\u1eb6\u1e00\u0104\u023a\u2c6f"},{base:"AA",letters:"\ua732"},{base:"AE",letters:"\xc6\u01fc\u01e2"},{base:"AO",letters:"\ua734"},{base:"AU",letters:"\ua736"},{base:"AV",letters:"\ua738\ua73a"},{base:"AY",letters:"\ua73c"},{base:"B",letters:"B\u24b7\uff22\u1e02\u1e04\u1e06\u0243\u0182\u0181"},{base:"C",letters:"C\u24b8\uff23\u0106\u0108\u010a\u010c\xc7\u1e08\u0187\u023b\ua73e"},{base:"D",letters:"D\u24b9\uff24\u1e0a\u010e\u1e0c\u1e10\u1e12\u1e0e\u0110\u018b\u018a\u0189\ua779"},{base:"DZ",letters:"\u01f1\u01c4"},{base:"Dz",letters:"\u01f2\u01c5"},{base:"E",letters:"E\u24ba\uff25\xc8\xc9\xca\u1ec0\u1ebe\u1ec4\u1ec2\u1ebc\u0112\u1e14\u1e16\u0114\u0116\xcb\u1eba\u011a\u0204\u0206\u1eb8\u1ec6\u0228\u1e1c\u0118\u1e18\u1e1a\u0190\u018e"},{base:"F",letters:"F\u24bb\uff26\u1e1e\u0191\ua77b"},{base:"G",letters:"G\u24bc\uff27\u01f4\u011c\u1e20\u011e\u0120\u01e6\u0122\u01e4\u0193\ua7a0\ua77d\ua77e"},{base:"H",letters:"H\u24bd\uff28\u0124\u1e22\u1e26\u021e\u1e24\u1e28\u1e2a\u0126\u2c67\u2c75\ua78d"},{base:"I",letters:"I\u24be\uff29\xcc\xcd\xce\u0128\u012a\u012c\u0130\xcf\u1e2e\u1ec8\u01cf\u0208\u020a\u1eca\u012e\u1e2c\u0197"},{base:"J",letters:"J\u24bf\uff2a\u0134\u0248"},{base:"K",letters:"K\u24c0\uff2b\u1e30\u01e8\u1e32\u0136\u1e34\u0198\u2c69\ua740\ua742\ua744\ua7a2"},{base:"L",letters:"L\u24c1\uff2c\u013f\u0139\u013d\u1e36\u1e38\u013b\u1e3c\u1e3a\u0141\u023d\u2c62\u2c60\ua748\ua746\ua780"},{base:"LJ",letters:"\u01c7"},{base:"Lj",letters:"\u01c8"},{base:"M",letters:"M\u24c2\uff2d\u1e3e\u1e40\u1e42\u2c6e\u019c"},{base:"N",letters:"N\u24c3\uff2e\u01f8\u0143\xd1\u1e44\u0147\u1e46\u0145\u1e4a\u1e48\u0220\u019d\ua790\ua7a4"},{base:"NJ",letters:"\u01ca"},{base:"Nj",letters:"\u01cb"},{base:"O",letters:"O\u24c4\uff2f\xd2\xd3\xd4\u1ed2\u1ed0\u1ed6\u1ed4\xd5\u1e4c\u022c\u1e4e\u014c\u1e50\u1e52\u014e\u022e\u0230\xd6\u022a\u1ece\u0150\u01d1\u020c\u020e\u01a0\u1edc\u1eda\u1ee0\u1ede\u1ee2\u1ecc\u1ed8\u01ea\u01ec\xd8\u01fe\u0186\u019f\ua74a\ua74c"},{base:"OI",letters:"\u01a2"},{base:"OO",letters:"\ua74e"},{base:"OU",letters:"\u0222"},{base:"P",letters:"P\u24c5\uff30\u1e54\u1e56\u01a4\u2c63\ua750\ua752\ua754"},{base:"Q",letters:"Q\u24c6\uff31\ua756\ua758\u024a"},{base:"R",letters:"R\u24c7\uff32\u0154\u1e58\u0158\u0210\u0212\u1e5a\u1e5c\u0156\u1e5e\u024c\u2c64\ua75a\ua7a6\ua782"},{base:"S",letters:"S\u24c8\uff33\u1e9e\u015a\u1e64\u015c\u1e60\u0160\u1e66\u1e62\u1e68\u0218\u015e\u2c7e\ua7a8\ua784"},{base:"T",letters:"T\u24c9\uff34\u1e6a\u0164\u1e6c\u021a\u0162\u1e70\u1e6e\u0166\u01ac\u01ae\u023e\ua786"},{base:"TZ",letters:"\ua728"},{base:"U",letters:"U\u24ca\uff35\xd9\xda\xdb\u0168\u1e78\u016a\u1e7a\u016c\xdc\u01db\u01d7\u01d5\u01d9\u1ee6\u016e\u0170\u01d3\u0214\u0216\u01af\u1eea\u1ee8\u1eee\u1eec\u1ef0\u1ee4\u1e72\u0172\u1e76\u1e74\u0244"},{base:"V",letters:"V\u24cb\uff36\u1e7c\u1e7e\u01b2\ua75e\u0245"},{base:"VY",letters:"\ua760"},{base:"W",letters:"W\u24cc\uff37\u1e80\u1e82\u0174\u1e86\u1e84\u1e88\u2c72"},{base:"X",letters:"X\u24cd\uff38\u1e8a\u1e8c"},{base:"Y",letters:"Y\u24ce\uff39\u1ef2\xdd\u0176\u1ef8\u0232\u1e8e\u0178\u1ef6\u1ef4\u01b3\u024e\u1efe"},{base:"Z",letters:"Z\u24cf\uff3a\u0179\u1e90\u017b\u017d\u1e92\u1e94\u01b5\u0224\u2c7f\u2c6b\ua762"},{base:"a",letters:"a\u24d0\uff41\u1e9a\xe0\xe1\xe2\u1ea7\u1ea5\u1eab\u1ea9\xe3\u0101\u0103\u1eb1\u1eaf\u1eb5\u1eb3\u0227\u01e1\xe4\u01df\u1ea3\xe5\u01fb\u01ce\u0201\u0203\u1ea1\u1ead\u1eb7\u1e01\u0105\u2c65\u0250"},{base:"aa",letters:"\ua733"},{base:"ae",letters:"\xe6\u01fd\u01e3"},{base:"ao",letters:"\ua735"},{base:"au",letters:"\ua737"},{base:"av",letters:"\ua739\ua73b"},{base:"ay",letters:"\ua73d"},{base:"b",letters:"b\u24d1\uff42\u1e03\u1e05\u1e07\u0180\u0183\u0253"},{base:"c",letters:"c\u24d2\uff43\u0107\u0109\u010b\u010d\xe7\u1e09\u0188\u023c\ua73f\u2184"},{base:"d",letters:"d\u24d3\uff44\u1e0b\u010f\u1e0d\u1e11\u1e13\u1e0f\u0111\u018c\u0256\u0257\ua77a"},{base:"dz",letters:"\u01f3\u01c6"},{base:"e",letters:"e\u24d4\uff45\xe8\xe9\xea\u1ec1\u1ebf\u1ec5\u1ec3\u1ebd\u0113\u1e15\u1e17\u0115\u0117\xeb\u1ebb\u011b\u0205\u0207\u1eb9\u1ec7\u0229\u1e1d\u0119\u1e19\u1e1b\u0247\u025b\u01dd"},{base:"f",letters:"f\u24d5\uff46\u1e1f\u0192\ua77c"},{base:"g",letters:"g\u24d6\uff47\u01f5\u011d\u1e21\u011f\u0121\u01e7\u0123\u01e5\u0260\ua7a1\u1d79\ua77f"},{base:"h",letters:"h\u24d7\uff48\u0125\u1e23\u1e27\u021f\u1e25\u1e29\u1e2b\u1e96\u0127\u2c68\u2c76\u0265"},{base:"hv",letters:"\u0195"},{base:"i",letters:"i\u24d8\uff49\xec\xed\xee\u0129\u012b\u012d\xef\u1e2f\u1ec9\u01d0\u0209\u020b\u1ecb\u012f\u1e2d\u0268\u0131"},{base:"j",letters:"j\u24d9\uff4a\u0135\u01f0\u0249"},{base:"k",letters:"k\u24da\uff4b\u1e31\u01e9\u1e33\u0137\u1e35\u0199\u2c6a\ua741\ua743\ua745\ua7a3"},{base:"l",letters:"l\u24db\uff4c\u0140\u013a\u013e\u1e37\u1e39\u013c\u1e3d\u1e3b\u017f\u0142\u019a\u026b\u2c61\ua749\ua781\ua747"},{base:"lj",letters:"\u01c9"},{base:"m",letters:"m\u24dc\uff4d\u1e3f\u1e41\u1e43\u0271\u026f"},{base:"n",letters:"n\u24dd\uff4e\u01f9\u0144\xf1\u1e45\u0148\u1e47\u0146\u1e4b\u1e49\u019e\u0272\u0149\ua791\ua7a5"},{base:"nj",letters:"\u01cc"},{base:"o",letters:"o\u24de\uff4f\xf2\xf3\xf4\u1ed3\u1ed1\u1ed7\u1ed5\xf5\u1e4d\u022d\u1e4f\u014d\u1e51\u1e53\u014f\u022f\u0231\xf6\u022b\u1ecf\u0151\u01d2\u020d\u020f\u01a1\u1edd\u1edb\u1ee1\u1edf\u1ee3\u1ecd\u1ed9\u01eb\u01ed\xf8\u01ff\u0254\ua74b\ua74d\u0275"},{base:"oi",letters:"\u01a3"},{base:"ou",letters:"\u0223"},{base:"oo",letters:"\ua74f"},{base:"p",letters:"p\u24df\uff50\u1e55\u1e57\u01a5\u1d7d\ua751\ua753\ua755"},{base:"q",letters:"q\u24e0\uff51\u024b\ua757\ua759"},{base:"r",letters:"r\u24e1\uff52\u0155\u1e59\u0159\u0211\u0213\u1e5b\u1e5d\u0157\u1e5f\u024d\u027d\ua75b\ua7a7\ua783"},{base:"s",letters:"s\u24e2\uff53\xdf\u015b\u1e65\u015d\u1e61\u0161\u1e67\u1e63\u1e69\u0219\u015f\u023f\ua7a9\ua785\u1e9b"},{base:"t",letters:"t\u24e3\uff54\u1e6b\u1e97\u0165\u1e6d\u021b\u0163\u1e71\u1e6f\u0167\u01ad\u0288\u2c66\ua787"},{base:"tz",letters:"\ua729"},{base:"u",letters:"u\u24e4\uff55\xf9\xfa\xfb\u0169\u1e79\u016b\u1e7b\u016d\xfc\u01dc\u01d8\u01d6\u01da\u1ee7\u016f\u0171\u01d4\u0215\u0217\u01b0\u1eeb\u1ee9\u1eef\u1eed\u1ef1\u1ee5\u1e73\u0173\u1e77\u1e75\u0289"},{base:"v",letters:"v\u24e5\uff56\u1e7d\u1e7f\u028b\ua75f\u028c"},{base:"vy",letters:"\ua761"},{base:"w",letters:"w\u24e6\uff57\u1e81\u1e83\u0175\u1e87\u1e85\u1e98\u1e89\u2c73"},{base:"x",letters:"x\u24e7\uff58\u1e8b\u1e8d"},{base:"y",letters:"y\u24e8\uff59\u1ef3\xfd\u0177\u1ef9\u0233\u1e8f\xff\u1ef7\u1e99\u1ef5\u01b4\u024f\u1eff"},{base:"z",letters:"z\u24e9\uff5a\u017a\u1e91\u017c\u017e\u1e93\u1e95\u01b6\u0225\u0240\u2c6c\ua763"}],SL=new RegExp("["+qL.map((function(e){return e.letters})).join("")+"]","g"),OL={},EL=0;EL0,z=f-d-l,v=!1;z>t&&c.current&&(r&&r(e),c.current=!1),p&&s.current&&(i&&i(e),s.current=!1),p&&t>z?(n&&!c.current&&n(e),h.scrollTop=f,v=!0,c.current=!0):!p&&-t>l&&(o&&!s.current&&o(e),h.scrollTop=0,v=!0,s.current=!0),v&&IL(e)}}),[n,r,o,i]),d=Object(a.useCallback)((function(e){f(e,e.deltaY)}),[f]),h=Object(a.useCallback)((function(e){l.current=e.changedTouches[0].clientY}),[]),p=Object(a.useCallback)((function(e){var t=l.current-e.changedTouches[0].clientY;f(e,t)}),[f]),z=Object(a.useCallback)((function(e){if(e){var t=!!IA&&{passive:!1};e.addEventListener("wheel",d,t),e.addEventListener("touchstart",h,t),e.addEventListener("touchmove",p,t)}}),[p,h,d]),v=Object(a.useCallback)((function(e){e&&(e.removeEventListener("wheel",d,!1),e.removeEventListener("touchstart",h,!1),e.removeEventListener("touchmove",p,!1))}),[p,h,d]);return Object(a.useEffect)((function(){if(t){var e=u.current;return z(e),function(){v(e)}}}),[t,z,v]),function(e){u.current=e}}({isEnabled:void 0===r||r,onBottomArrive:e.onBottomArrive,onBottomLeave:e.onBottomLeave,onTopArrive:e.onTopArrive,onTopLeave:e.onTopLeave}),i=function(e){var t=e.isEnabled,n=e.accountForScrollbars,r=void 0===n||n,o=Object(a.useRef)({}),i=Object(a.useRef)(null),c=Object(a.useCallback)((function(e){if(YL){var t=document.body,n=t&&t.style;if(r&&NL.forEach((function(e){var t=n&&n[e];o.current[e]=t})),r&&$L<1){var i=parseInt(o.current.paddingRight,10)||0,a=document.body?document.body.clientWidth:0,c=window.innerWidth-a+i||0;Object.keys(FL).forEach((function(e){var t=FL[e];n&&(n[e]=t)})),n&&(n.paddingRight="".concat(c,"px"))}t&&GL()&&(t.addEventListener("touchmove",BL,ZL),e&&(e.addEventListener("touchstart",WL,ZL),e.addEventListener("touchmove",UL,ZL))),$L+=1}}),[r]),s=Object(a.useCallback)((function(e){if(YL){var t=document.body,n=t&&t.style;$L=Math.max($L-1,0),r&&$L<1&&NL.forEach((function(e){var t=o.current[e];n&&(n[e]=t)})),t&&GL()&&(t.removeEventListener("touchmove",BL,ZL),e&&(e.removeEventListener("touchstart",WL,ZL),e.removeEventListener("touchmove",UL,ZL)))}}),[r]);return Object(a.useEffect)((function(){if(t){var e=i.current;return c(e),function(){s(e)}}}),[t,c,s]),function(e){i.current=e}}({isEnabled:n});return rA(a.Fragment,null,n&&rA("div",{onClick:XL,css:KL}),t((function(e){o(e),i(e)})))}var JL={clearIndicator:uL,container:function(e){var t=e.isDisabled;return{label:"container",direction:e.isRtl?"rtl":void 0,pointerEvents:t?"none":void 0,position:"relative"}},control:function(e){var t=e.isDisabled,n=e.isFocused,r=e.theme,o=r.colors,i=r.borderRadius,a=r.spacing;return{label:"control",alignItems:"center",backgroundColor:t?o.neutral5:o.neutral0,borderColor:t?o.neutral10:n?o.primary:o.neutral20,borderRadius:i,borderStyle:"solid",borderWidth:1,boxShadow:n?"0 0 0 1px ".concat(o.primary):void 0,cursor:"default",display:"flex",flexWrap:"wrap",justifyContent:"space-between",minHeight:a.controlHeight,outline:"0 !important",position:"relative",transition:"all 100ms","&:hover":{borderColor:n?o.primary:o.neutral30}}},dropdownIndicator:lL,group:function(e){var t=e.theme.spacing;return{paddingBottom:2*t.baseUnit,paddingTop:2*t.baseUnit}},groupHeading:function(e){var t=e.theme.spacing;return{label:"group",color:"#999",cursor:"default",display:"block",fontSize:"75%",fontWeight:500,marginBottom:"0.25em",paddingLeft:3*t.baseUnit,paddingRight:3*t.baseUnit,textTransform:"uppercase"}},indicatorsContainer:function(){return{alignItems:"center",alignSelf:"stretch",display:"flex",flexShrink:0}},indicatorSeparator:function(e){var t=e.isDisabled,n=e.theme,r=n.spacing.baseUnit,o=n.colors;return{label:"indicatorSeparator",alignSelf:"stretch",backgroundColor:t?o.neutral10:o.neutral20,marginBottom:2*r,marginTop:2*r,width:1}},input:function(e){var t=e.isDisabled,n=e.value,r=e.theme,o=r.spacing,i=r.colors;return kA({margin:o.baseUnit/2,paddingBottom:o.baseUnit/2,paddingTop:o.baseUnit/2,visibility:t?"hidden":"visible",color:i.neutral80,transform:n?"translateZ(0)":""},gL)},loadingIndicator:function(e){var t=e.isFocused,n=e.size,r=e.theme,o=r.colors,i=r.spacing.baseUnit;return{label:"loadingIndicator",color:t?o.neutral60:o.neutral20,display:"flex",padding:2*i,transition:"color 150ms",alignSelf:"center",fontSize:n,lineHeight:1,marginRight:n,textAlign:"center",verticalAlign:"middle"}},loadingMessage:XA,menu:function(e){var t,n=e.placement,r=e.theme,o=r.borderRadius,i=r.spacing,a=r.colors;return yA(t={label:"menu"},function(e){return e?{bottom:"top",top:"bottom"}[e]:"bottom"}(n),"100%"),yA(t,"backgroundColor",a.neutral0),yA(t,"borderRadius",o),yA(t,"boxShadow","0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1)"),yA(t,"marginBottom",i.menuGutter),yA(t,"marginTop",i.menuGutter),yA(t,"position","absolute"),yA(t,"width","100%"),yA(t,"zIndex",1),t},menuList:function(e){var t=e.maxHeight,n=e.theme.spacing.baseUnit;return{maxHeight:t,overflowY:"auto",paddingBottom:n,paddingTop:n,position:"relative",WebkitOverflowScrolling:"touch"}},menuPortal:function(e){var t=e.rect,n=e.offset,r=e.position;return{left:t.left,position:r,top:n,width:t.width,zIndex:1}},multiValue:function(e){var t=e.theme,n=t.spacing,r=t.borderRadius;return{label:"multiValue",backgroundColor:t.colors.neutral10,borderRadius:r/2,display:"flex",margin:n.baseUnit/2,minWidth:0}},multiValueLabel:function(e){var t=e.theme,n=t.borderRadius,r=t.colors,o=e.cropWithEllipsis;return{borderRadius:n/2,color:r.neutral80,fontSize:"85%",overflow:"hidden",padding:3,paddingLeft:6,textOverflow:o||void 0===o?"ellipsis":void 0,whiteSpace:"nowrap"}},multiValueRemove:function(e){var t=e.theme,n=t.spacing,r=t.borderRadius,o=t.colors;return{alignItems:"center",borderRadius:r/2,backgroundColor:e.isFocused?o.dangerLight:void 0,display:"flex",paddingLeft:n.baseUnit,paddingRight:n.baseUnit,":hover":{backgroundColor:o.dangerLight,color:o.danger}}},noOptionsMessage:ZA,option:function(e){var t=e.isDisabled,n=e.isFocused,r=e.isSelected,o=e.theme,i=o.spacing,a=o.colors;return{label:"option",backgroundColor:r?a.primary:n?a.primary25:"transparent",color:t?a.neutral20:r?a.neutral0:"inherit",cursor:"default",display:"block",fontSize:"inherit",padding:"".concat(2*i.baseUnit,"px ").concat(3*i.baseUnit,"px"),width:"100%",userSelect:"none",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)",":active":{backgroundColor:t?void 0:r?a.primary:a.primary50}}},placeholder:function(e){var t=e.theme,n=t.spacing;return{label:"placeholder",color:t.colors.neutral50,gridArea:"1 / 1 / 2 / 3",marginLeft:n.baseUnit/2,marginRight:n.baseUnit/2}},singleValue:function(e){var t=e.isDisabled,n=e.theme,r=n.spacing,o=n.colors;return{label:"singleValue",color:t?o.neutral40:o.neutral80,gridArea:"1 / 1 / 2 / 3",marginLeft:r.baseUnit/2,marginRight:r.baseUnit/2,maxWidth:"100%",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},valueContainer:function(e){var t=e.theme.spacing,n=e.isMulti,r=e.hasValue,o=e.selectProps.controlShouldRenderValue;return{alignItems:"center",display:n&&r&&o?"flex":"grid",flex:1,flexWrap:"wrap",padding:"".concat(t.baseUnit/2,"px ").concat(2*t.baseUnit,"px"),WebkitOverflowScrolling:"touch",position:"relative",overflow:"hidden"}}};var eH,tH={borderRadius:4,colors:{primary:"#2684FF",primary75:"#4C9AFF",primary50:"#B2D4FF",primary25:"#DEEBFF",danger:"#DE350B",dangerLight:"#FFBDAD",neutral0:"hsl(0, 0%, 100%)",neutral5:"hsl(0, 0%, 95%)",neutral10:"hsl(0, 0%, 90%)",neutral20:"hsl(0, 0%, 80%)",neutral30:"hsl(0, 0%, 70%)",neutral40:"hsl(0, 0%, 60%)",neutral50:"hsl(0, 0%, 50%)",neutral60:"hsl(0, 0%, 40%)",neutral70:"hsl(0, 0%, 30%)",neutral80:"hsl(0, 0%, 20%)",neutral90:"hsl(0, 0%, 10%)"},spacing:{baseUnit:4,controlHeight:38,menuGutter:8}},nH={"aria-live":"polite",backspaceRemovesValue:!0,blurInputOnSelect:DA(),captureMenuScroll:!DA(),closeMenuOnSelect:!0,closeMenuOnScroll:!1,components:{},controlShouldRenderValue:!0,escapeClearsValue:!1,filterOption:function(e,t){if(e.data.__isNew__)return!0;var n=kA({ignoreCase:!0,ignoreAccents:!0,stringify:PL,trim:!0,matchFrom:"any"},eH),r=n.ignoreCase,o=n.ignoreAccents,i=n.stringify,a=n.trim,c=n.matchFrom,s=a?DL(t):t,l=a?DL(i(e)):i(e);return r&&(s=s.toLowerCase(),l=l.toLowerCase()),o&&(s=HL(s),l=LL(l)),"start"===c?l.substr(0,s.length)===s:l.indexOf(s)>-1},formatGroupLabel:function(e){return e.label},getOptionLabel:function(e){return e.label},getOptionValue:function(e){return e.value},isDisabled:!1,isLoading:!1,isMulti:!1,isRtl:!1,isSearchable:!0,isOptionDisabled:function(e){return!!e.isDisabled},loadingMessage:function(){return"Loading..."},maxMenuHeight:300,minMenuHeight:140,menuIsOpen:!1,menuPlacement:"bottom",menuPosition:"absolute",menuShouldBlockScroll:!1,menuShouldScrollIntoView:!function(){try{return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}catch(e){return!1}}(),noOptionsMessage:function(){return"No options"},openMenuOnFocus:!1,openMenuOnClick:!0,options:[],pageSize:5,placeholder:"Select...",screenReaderStatus:function(e){var t=e.count;return"".concat(t," result").concat(1!==t?"s":""," available")},styles:{},tabIndex:0,tabSelectsValue:!0};function rH(e,t,n,r){return{type:"option",data:t,isDisabled:lH(e,t,n),isSelected:uH(e,t,n),label:cH(e,t),value:sH(e,t),index:r}}function oH(e,t){return e.options.map((function(n,r){if("options"in n){var o=n.options.map((function(n,r){return rH(e,n,t,r)})).filter((function(t){return aH(e,t)}));return o.length>0?{type:"group",data:n,options:o,index:r}:void 0}var i=rH(e,n,t,r);return aH(e,i)?i:void 0})).filter(NA)}function iH(e){return e.reduce((function(e,t){return"group"===t.type?e.push.apply(e,xL(t.options.map((function(e){return e.data})))):e.push(t.data),e}),[])}function aH(e,t){var n=e.inputValue,r=void 0===n?"":n,o=t.data,i=t.isSelected,a=t.label,c=t.value;return(!dH(e)||!i)&&fH(e,{label:a,value:c,data:o},r)}var cH=function(e,t){return e.getOptionLabel(t)},sH=function(e,t){return e.getOptionValue(t)};function lH(e,t,n){return"function"===typeof e.isOptionDisabled&&e.isOptionDisabled(t,n)}function uH(e,t,n){if(n.indexOf(t)>-1)return!0;if("function"===typeof e.isOptionSelected)return e.isOptionSelected(t,n);var r=sH(e,t);return n.some((function(t){return sH(e,t)===r}))}function fH(e,t,n){return!e.filterOption||e.filterOption(t,n)}var dH=function(e){var t=e.hideSelectedOptions,n=e.isMulti;return void 0===t?n:t},hH=1,pH=function(e){mA(n,e);var t=MA(n);function n(e){var r;return pA(this,n),(r=t.call(this,e)).state={ariaSelection:null,focusedOption:null,focusedValue:null,inputIsHidden:!1,isFocused:!1,selectValue:[],clearFocusValueOnUpdate:!1,prevWasFocused:!1,inputIsHiddenAfterUpdate:void 0,prevProps:void 0},r.blockOptionHover=!1,r.isComposing=!1,r.commonProps=void 0,r.initialTouchX=0,r.initialTouchY=0,r.instancePrefix="",r.openAfterFocus=!1,r.scrollToFocusedOptionOnUpdate=!1,r.userIsDragging=void 0,r.controlRef=null,r.getControlRef=function(e){r.controlRef=e},r.focusedOptionRef=null,r.getFocusedOptionRef=function(e){r.focusedOptionRef=e},r.menuListRef=null,r.getMenuListRef=function(e){r.menuListRef=e},r.inputRef=null,r.getInputRef=function(e){r.inputRef=e},r.focus=r.focusInput,r.blur=r.blurInput,r.onChange=function(e,t){var n=r.props,o=n.onChange,i=n.name;t.name=i,r.ariaOnChange(e,t),o(e,t)},r.setValue=function(e,t,n){var o=r.props,i=o.closeMenuOnSelect,a=o.isMulti,c=o.inputValue;r.onInputChange("",{action:"set-value",prevInputValue:c}),i&&(r.setState({inputIsHiddenAfterUpdate:!a}),r.onMenuClose()),r.setState({clearFocusValueOnUpdate:!0}),r.onChange(e,{action:t,option:n})},r.selectOption=function(e){var t=r.props,n=t.blurInputOnSelect,o=t.isMulti,i=t.name,a=r.state.selectValue,c=o&&r.isOptionSelected(e,a),s=r.isOptionDisabled(e,a);if(c){var l=r.getOptionValue(e);r.setValue(a.filter((function(e){return r.getOptionValue(e)!==l})),"deselect-option",e)}else{if(s)return void r.ariaOnChange(e,{action:"select-option",option:e,name:i});o?r.setValue([].concat(xL(a),[e]),"select-option",e):r.setValue(e,"select-option")}n&&r.blurInput()},r.removeValue=function(e){var t=r.props.isMulti,n=r.state.selectValue,o=r.getOptionValue(e),i=n.filter((function(e){return r.getOptionValue(e)!==o})),a=FA(t,i,i[0]||null);r.onChange(a,{action:"remove-value",removedValue:e}),r.focusInput()},r.clearValue=function(){var e=r.state.selectValue;r.onChange(FA(r.props.isMulti,[],null),{action:"clear",removedValues:e})},r.popValue=function(){var e=r.props.isMulti,t=r.state.selectValue,n=t[t.length-1],o=t.slice(0,t.length-1),i=FA(e,o,o[0]||null);r.onChange(i,{action:"pop-value",removedValue:n})},r.getValue=function(){return r.state.selectValue},r.cx=function(){for(var e=arguments.length,t=new Array(e),n=0;n5||i>5}},r.onTouchEnd=function(e){r.userIsDragging||(r.controlRef&&!r.controlRef.contains(e.target)&&r.menuListRef&&!r.menuListRef.contains(e.target)&&r.blurInput(),r.initialTouchX=0,r.initialTouchY=0)},r.onControlTouchEnd=function(e){r.userIsDragging||r.onControlMouseDown(e)},r.onClearIndicatorTouchEnd=function(e){r.userIsDragging||r.onClearIndicatorMouseDown(e)},r.onDropdownIndicatorTouchEnd=function(e){r.userIsDragging||r.onDropdownIndicatorMouseDown(e)},r.handleInputChange=function(e){var t=r.props.inputValue,n=e.currentTarget.value;r.setState({inputIsHiddenAfterUpdate:!1}),r.onInputChange(n,{action:"input-change",prevInputValue:t}),r.props.menuIsOpen||r.onMenuOpen()},r.onInputFocus=function(e){r.props.onFocus&&r.props.onFocus(e),r.setState({inputIsHiddenAfterUpdate:!1,isFocused:!0}),(r.openAfterFocus||r.props.openMenuOnFocus)&&r.openMenu("first"),r.openAfterFocus=!1},r.onInputBlur=function(e){var t=r.props.inputValue;r.menuListRef&&r.menuListRef.contains(document.activeElement)?r.inputRef.focus():(r.props.onBlur&&r.props.onBlur(e),r.onInputChange("",{action:"input-blur",prevInputValue:t}),r.onMenuClose(),r.setState({focusedValue:null,isFocused:!1}))},r.onOptionHover=function(e){r.blockOptionHover||r.state.focusedOption===e||r.setState({focusedOption:e})},r.shouldHideSelectedOptions=function(){return dH(r.props)},r.onKeyDown=function(e){var t=r.props,n=t.isMulti,o=t.backspaceRemovesValue,i=t.escapeClearsValue,a=t.inputValue,c=t.isClearable,s=t.isDisabled,l=t.menuIsOpen,u=t.onKeyDown,f=t.tabSelectsValue,d=t.openMenuOnFocus,h=r.state,p=h.focusedOption,z=h.focusedValue,v=h.selectValue;if(!s&&("function"!==typeof u||(u(e),!e.defaultPrevented))){switch(r.blockOptionHover=!0,e.key){case"ArrowLeft":if(!n||a)return;r.focusValue("previous");break;case"ArrowRight":if(!n||a)return;r.focusValue("next");break;case"Delete":case"Backspace":if(a)return;if(z)r.removeValue(z);else{if(!o)return;n?r.popValue():c&&r.clearValue()}break;case"Tab":if(r.isComposing)return;if(e.shiftKey||!l||!f||!p||d&&r.isOptionSelected(p,v))return;r.selectOption(p);break;case"Enter":if(229===e.keyCode)break;if(l){if(!p)return;if(r.isComposing)return;r.selectOption(p);break}return;case"Escape":l?(r.setState({inputIsHiddenAfterUpdate:!1}),r.onInputChange("",{action:"menu-close",prevInputValue:a}),r.onMenuClose()):c&&i&&r.clearValue();break;case" ":if(a)return;if(!l){r.openMenu("first");break}if(!p)return;r.selectOption(p);break;case"ArrowUp":l?r.focusOption("up"):r.openMenu("last");break;case"ArrowDown":l?r.focusOption("down"):r.openMenu("first");break;case"PageUp":if(!l)return;r.focusOption("pageup");break;case"PageDown":if(!l)return;r.focusOption("pagedown");break;case"Home":if(!l)return;r.focusOption("first");break;case"End":if(!l)return;r.focusOption("last");break;default:return}e.preventDefault()}},r.instancePrefix="react-select-"+(r.props.instanceId||++hH),r.state.selectValue=OA(e.value),r}return vA(n,[{key:"componentDidMount",value:function(){this.startListeningComposition(),this.startListeningToTouch(),this.props.closeMenuOnScroll&&document&&document.addEventListener&&document.addEventListener("scroll",this.onScroll,!0),this.props.autoFocus&&this.focusInput()}},{key:"componentDidUpdate",value:function(e){var t=this.props,n=t.isDisabled,r=t.menuIsOpen,o=this.state.isFocused;(o&&!n&&e.isDisabled||o&&r&&!e.menuIsOpen)&&this.focusInput(),o&&n&&!e.isDisabled&&this.setState({isFocused:!1},this.onMenuClose),this.menuListRef&&this.focusedOptionRef&&this.scrollToFocusedOptionOnUpdate&&(!function(e,t){var n=e.getBoundingClientRect(),r=t.getBoundingClientRect(),o=t.offsetHeight/3;r.bottom+o>n.bottom?LA(e,Math.min(t.offsetTop+t.clientHeight-e.offsetHeight+o,e.scrollHeight)):r.top-o-1&&(a=c)}this.scrollToFocusedOptionOnUpdate=!(o&&this.menuListRef),this.setState({inputIsHiddenAfterUpdate:!1,focusedValue:null,focusedOption:i[a]},(function(){return t.onMenuOpen()}))}},{key:"focusValue",value:function(e){var t=this.state,n=t.selectValue,r=t.focusedValue;if(this.props.isMulti){this.setState({focusedOption:null});var o=n.indexOf(r);r||(o=-1);var i=n.length-1,a=-1;if(n.length){switch(e){case"previous":a=0===o?0:-1===o?i:o-1;break;case"next":o>-1&&o0&&void 0!==arguments[0]?arguments[0]:"first",t=this.props.pageSize,n=this.state.focusedOption,r=this.getFocusableOptions();if(r.length){var o=0,i=r.indexOf(n);n||(i=-1),"up"===e?o=i>0?i-1:r.length-1:"down"===e?o=(i+1)%r.length:"pageup"===e?(o=i-t)<0&&(o=0):"pagedown"===e?(o=i+t)>r.length-1&&(o=r.length-1):"last"===e&&(o=r.length-1),this.scrollToFocusedOptionOnUpdate=!0,this.setState({focusedOption:r[o],focusedValue:null})}}},{key:"getTheme",value:function(){return this.props.theme?"function"===typeof this.props.theme?this.props.theme(tH):kA(kA({},tH),this.props.theme):tH}},{key:"getCommonProps",value:function(){var e=this.clearValue,t=this.cx,n=this.getStyles,r=this.getValue,o=this.selectOption,i=this.setValue,a=this.props,c=a.isMulti,s=a.isRtl,l=a.options;return{clearValue:e,cx:t,getStyles:n,getValue:r,hasValue:this.hasValue(),isMulti:c,isRtl:s,options:l,selectOption:o,selectProps:a,setValue:i,theme:this.getTheme()}}},{key:"hasValue",value:function(){return this.state.selectValue.length>0}},{key:"hasOptions",value:function(){return!!this.getFocusableOptions().length}},{key:"isClearable",value:function(){var e=this.props,t=e.isClearable,n=e.isMulti;return void 0===t?n:t}},{key:"isOptionDisabled",value:function(e,t){return lH(this.props,e,t)}},{key:"isOptionSelected",value:function(e,t){return uH(this.props,e,t)}},{key:"filterOption",value:function(e,t){return fH(this.props,e,t)}},{key:"formatOptionLabel",value:function(e,t){if("function"===typeof this.props.formatOptionLabel){var n=this.props.inputValue,r=this.state.selectValue;return this.props.formatOptionLabel(e,{context:t,inputValue:n,selectValue:r})}return this.getOptionLabel(e)}},{key:"formatGroupLabel",value:function(e){return this.props.formatGroupLabel(e)}},{key:"startListeningComposition",value:function(){document&&document.addEventListener&&(document.addEventListener("compositionstart",this.onCompositionStart,!1),document.addEventListener("compositionend",this.onCompositionEnd,!1))}},{key:"stopListeningComposition",value:function(){document&&document.removeEventListener&&(document.removeEventListener("compositionstart",this.onCompositionStart),document.removeEventListener("compositionend",this.onCompositionEnd))}},{key:"startListeningToTouch",value:function(){document&&document.addEventListener&&(document.addEventListener("touchstart",this.onTouchStart,!1),document.addEventListener("touchmove",this.onTouchMove,!1),document.addEventListener("touchend",this.onTouchEnd,!1))}},{key:"stopListeningToTouch",value:function(){document&&document.removeEventListener&&(document.removeEventListener("touchstart",this.onTouchStart),document.removeEventListener("touchmove",this.onTouchMove),document.removeEventListener("touchend",this.onTouchEnd))}},{key:"renderInput",value:function(){var e=this.props,t=e.isDisabled,n=e.isSearchable,r=e.inputId,o=e.inputValue,i=e.tabIndex,c=e.form,s=e.menuIsOpen,l=this.getComponents().Input,u=this.state,f=u.inputIsHidden,d=u.ariaSelection,h=this.commonProps,p=r||this.getElementId("input"),z=kA(kA(kA({"aria-autocomplete":"list","aria-expanded":s,"aria-haspopup":!0,"aria-errormessage":this.props["aria-errormessage"],"aria-invalid":this.props["aria-invalid"],"aria-label":this.props["aria-label"],"aria-labelledby":this.props["aria-labelledby"],role:"combobox"},s&&{"aria-controls":this.getElementId("listbox"),"aria-owns":this.getElementId("listbox")}),!n&&{"aria-readonly":!0}),this.hasValue()?"initial-input-focus"===(null===d||void 0===d?void 0:d.action)&&{"aria-describedby":this.getElementId("live-region")}:{"aria-describedby":this.getElementId("placeholder")});return n?a.createElement(l,_E({},h,{autoCapitalize:"none",autoComplete:"off",autoCorrect:"off",id:p,innerRef:this.getInputRef,isDisabled:t,isHidden:f,onBlur:this.onInputBlur,onChange:this.handleInputChange,onFocus:this.onInputFocus,spellCheck:"false",tabIndex:i,form:c,type:"text",value:o},z)):a.createElement(RL,_E({id:p,innerRef:this.getInputRef,onBlur:this.onInputBlur,onChange:_A,onFocus:this.onInputFocus,disabled:t,tabIndex:i,inputMode:"none",form:c,value:""},z))}},{key:"renderPlaceholderOrValue",value:function(){var e=this,t=this.getComponents(),n=t.MultiValue,r=t.MultiValueContainer,o=t.MultiValueLabel,i=t.MultiValueRemove,c=t.SingleValue,s=t.Placeholder,l=this.commonProps,u=this.props,f=u.controlShouldRenderValue,d=u.isDisabled,h=u.isMulti,p=u.inputValue,z=u.placeholder,v=this.state,g=v.selectValue,m=v.focusedValue,y=v.isFocused;if(!this.hasValue()||!f)return p?null:a.createElement(s,_E({},l,{key:"placeholder",isDisabled:d,isFocused:y,innerProps:{id:this.getElementId("placeholder")}}),z);if(h)return g.map((function(t,c){var s=t===m,u="".concat(e.getOptionLabel(t),"-").concat(e.getOptionValue(t));return a.createElement(n,_E({},l,{components:{Container:r,Label:o,Remove:i},isFocused:s,isDisabled:d,key:u,index:c,removeProps:{onClick:function(){return e.removeValue(t)},onTouchEnd:function(){return e.removeValue(t)},onMouseDown:function(e){e.preventDefault()}},data:t}),e.formatOptionLabel(t,"value"))}));if(p)return null;var b=g[0];return(a.createElement(c,_E({},l,{data:b,isDisabled:d}),this.formatOptionLabel(b,"value")))}},{key:"renderClearIndicator",value:function(){var e=this.getComponents().ClearIndicator,t=this.commonProps,n=this.props,r=n.isDisabled,o=n.isLoading,i=this.state.isFocused;if(!this.isClearable()||!e||r||!this.hasValue()||o)return null;var c={onMouseDown:this.onClearIndicatorMouseDown,onTouchEnd:this.onClearIndicatorTouchEnd,"aria-hidden":"true"};return(a.createElement(e,_E({},t,{innerProps:c,isFocused:i})))}},{key:"renderLoadingIndicator",value:function(){var e=this.getComponents().LoadingIndicator,t=this.commonProps,n=this.props,r=n.isDisabled,o=n.isLoading,i=this.state.isFocused;if(!e||!o)return null;return a.createElement(e,_E({},t,{innerProps:{"aria-hidden":"true"},isDisabled:r,isFocused:i}))}},{key:"renderIndicatorSeparator",value:function(){var e=this.getComponents(),t=e.DropdownIndicator,n=e.IndicatorSeparator;if(!t||!n)return null;var r=this.commonProps,o=this.props.isDisabled,i=this.state.isFocused;return a.createElement(n,_E({},r,{isDisabled:o,isFocused:i}))}},{key:"renderDropdownIndicator",value:function(){var e=this.getComponents().DropdownIndicator;if(!e)return null;var t=this.commonProps,n=this.props.isDisabled,r=this.state.isFocused,o={onMouseDown:this.onDropdownIndicatorMouseDown,onTouchEnd:this.onDropdownIndicatorTouchEnd,"aria-hidden":"true"};return a.createElement(e,_E({},t,{innerProps:o,isDisabled:n,isFocused:r}))}},{key:"renderMenu",value:function(){var e=this,t=this.getComponents(),n=t.Group,r=t.GroupHeading,o=t.Menu,i=t.MenuList,c=t.MenuPortal,s=t.LoadingMessage,l=t.NoOptionsMessage,u=t.Option,f=this.commonProps,d=this.state.focusedOption,h=this.props,p=h.captureMenuScroll,z=h.inputValue,v=h.isLoading,g=h.loadingMessage,m=h.minMenuHeight,y=h.maxMenuHeight,b=h.menuIsOpen,w=h.menuPlacement,k=h.menuPosition,x=h.menuPortalTarget,j=h.menuShouldBlockScroll,M=h.menuShouldScrollIntoView,C=h.noOptionsMessage,_=h.onMenuScrollToTop,q=h.onMenuScrollToBottom;if(!b)return null;var S,O=function(t,n){var r=t.type,o=t.data,i=t.isDisabled,c=t.isSelected,s=t.label,l=t.value,h=d===o,p=i?void 0:function(){return e.onOptionHover(o)},z=i?void 0:function(){return e.selectOption(o)},v="".concat(e.getElementId("option"),"-").concat(n),g={id:v,onClick:z,onMouseMove:p,onMouseOver:p,tabIndex:-1};return a.createElement(u,_E({},f,{innerProps:g,data:o,isDisabled:i,isSelected:c,key:v,label:s,type:r,value:l,isFocused:h,innerRef:h?e.getFocusedOptionRef:void 0}),e.formatOptionLabel(t.data,"menu"))};if(this.hasOptions())S=this.getCategorizedOptions().map((function(t){if("group"===t.type){var o=t.data,i=t.options,c=t.index,s="".concat(e.getElementId("group"),"-").concat(c),l="".concat(s,"-heading");return a.createElement(n,_E({},f,{key:s,data:o,options:i,Heading:r,headingProps:{id:l,data:t.data},label:e.formatGroupLabel(t.data)}),t.options.map((function(e){return O(e,"".concat(c,"-").concat(e.index))})))}if("option"===t.type)return O(t,"".concat(t.index))}));else if(v){var E=g({inputValue:z});if(null===E)return null;S=a.createElement(s,f,E)}else{var T=C({inputValue:z});if(null===T)return null;S=a.createElement(l,f,T)}var A={minMenuHeight:m,maxMenuHeight:y,menuPlacement:w,menuPosition:k,menuShouldScrollIntoView:M},L=a.createElement(YA,_E({},f,A),(function(t){var n=t.ref,r=t.placerProps,c=r.placement,s=r.maxHeight;return(a.createElement(o,_E({},f,A,{innerRef:n,innerProps:{onMouseDown:e.onMenuMouseDown,onMouseMove:e.onMenuMouseMove,id:e.getElementId("listbox")},isLoading:v,placement:c}),a.createElement(QL,{captureEnabled:p,onTopArrive:_,onBottomArrive:q,lockEnabled:j},(function(t){return a.createElement(i,_E({},f,{innerRef:function(n){e.getMenuListRef(n),t(n)},isLoading:v,maxHeight:s,focusedOption:d}),S)}))))}));return x||"fixed"===k?a.createElement(c,_E({},f,{appendTo:x,controlElement:this.controlRef,menuPlacement:w,menuPosition:k}),L):L}},{key:"renderFormField",value:function(){var e=this,t=this.props,n=t.delimiter,r=t.isDisabled,o=t.isMulti,i=t.name,c=this.state.selectValue;if(i&&!r){if(o){if(n){var s=c.map((function(t){return e.getOptionValue(t)})).join(n);return(a.createElement("input",{name:i,type:"hidden",value:s}))}var l=c.length>0?c.map((function(t,n){return a.createElement("input",{key:"i-".concat(n),name:i,type:"hidden",value:e.getOptionValue(t)})})):a.createElement("input",{name:i,type:"hidden"});return(a.createElement("div",null,l))}var u=c[0]?this.getOptionValue(c[0]):"";return(a.createElement("input",{name:i,type:"hidden",value:u}))}}},{key:"renderLiveRegion",value:function(){var e=this.commonProps,t=this.state,n=t.ariaSelection,r=t.focusedOption,o=t.focusedValue,i=t.isFocused,c=t.selectValue,s=this.getFocusableOptions();return a.createElement(_L,_E({},e,{id:this.getElementId("live-region"),ariaSelection:n,focusedOption:r,focusedValue:o,isFocused:i,selectValue:c,focusableOptions:s}))}},{key:"render",value:function(){var e=this.getComponents(),t=e.Control,n=e.IndicatorsContainer,r=e.SelectContainer,o=e.ValueContainer,i=this.props,c=i.className,s=i.id,l=i.isDisabled,u=i.menuIsOpen,f=this.state.isFocused,d=this.commonProps=this.getCommonProps();return a.createElement(r,_E({},d,{className:c,innerProps:{id:s,onKeyDown:this.onKeyDown},isDisabled:l,isFocused:f}),this.renderLiveRegion(),a.createElement(t,_E({},d,{innerRef:this.getControlRef,innerProps:{onMouseDown:this.onControlMouseDown,onTouchEnd:this.onControlTouchEnd},isDisabled:l,isFocused:f,menuIsOpen:u}),a.createElement(o,_E({},d,{isDisabled:l}),this.renderPlaceholderOrValue(),this.renderInput()),a.createElement(n,_E({},d,{isDisabled:l}),this.renderClearIndicator(),this.renderLoadingIndicator(),this.renderIndicatorSeparator(),this.renderDropdownIndicator())),this.renderMenu(),this.renderFormField())}}],[{key:"getDerivedStateFromProps",value:function(e,t){var n=t.prevProps,r=t.clearFocusValueOnUpdate,o=t.inputIsHiddenAfterUpdate,i=t.ariaSelection,a=t.isFocused,c=t.prevWasFocused,s=e.options,l=e.value,u=e.menuIsOpen,f=e.inputValue,d=e.isMulti,h=OA(l),p={};if(n&&(l!==n.value||s!==n.options||u!==n.menuIsOpen||f!==n.inputValue)){var z=u?function(e,t){return iH(oH(e,t))}(e,h):[],v=r?function(e,t){var n=e.focusedValue,r=e.selectValue.indexOf(n);if(r>-1){if(t.indexOf(n)>-1)return n;if(r-1?n:t[0]}(t,z),focusedValue:v,clearFocusValueOnUpdate:!1}}var g=null!=o&&e!==n?{inputIsHidden:o,inputIsHiddenAfterUpdate:void 0}:{},m=i,y=a&&c;return a&&!y&&(m={value:FA(d,h,h[0]||null),options:h,action:"initial-input-focus"},y=!c),"initial-input-focus"===(null===i||void 0===i?void 0:i.action)&&(m=null),kA(kA(kA({},p),g),{},{prevProps:e,ariaSelection:m,prevWasFocused:y})}}]),n}(a.Component);pH.defaultProps=nH;n(420),n(424),n(426),n(430),n(431),n(432);var zH,vH=Object(a.forwardRef)((function(e,t){var n=function(e){var t=e.defaultInputValue,n=void 0===t?"":t,r=e.defaultMenuIsOpen,o=void 0!==r&&r,i=e.defaultValue,c=void 0===i?null:i,s=e.inputValue,l=e.menuIsOpen,u=e.onChange,f=e.onInputChange,d=e.onMenuClose,h=e.onMenuOpen,p=e.value,z=lA(e,kL),v=dA(Object(a.useState)(void 0!==s?s:n),2),g=v[0],m=v[1],y=dA(Object(a.useState)(void 0!==l?l:o),2),b=y[0],w=y[1],k=dA(Object(a.useState)(void 0!==p?p:c),2),x=k[0],j=k[1],M=Object(a.useCallback)((function(e,t){"function"===typeof u&&u(e,t),j(e)}),[u]),C=Object(a.useCallback)((function(e,t){var n;"function"===typeof f&&(n=f(e,t)),m(void 0!==n?n:e)}),[f]),_=Object(a.useCallback)((function(){"function"===typeof h&&h(),w(!0)}),[h]),q=Object(a.useCallback)((function(){"function"===typeof d&&d(),w(!1)}),[d]),S=void 0!==s?s:g,O=void 0!==l?l:b,E=void 0!==p?p:x;return kA(kA({},z),{},{inputValue:S,menuIsOpen:O,onChange:M,onInputChange:C,onMenuClose:q,onMenuOpen:_,value:E})}(e);return(a.createElement(pH,_E({ref:t},n)))})),gH=(a.Component,vH),mH=function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e},yH=function(){return(yH=Object.assign||function(e){for(var t,n=1,r=arguments.length;nt},eq:function(e,t){return e===t},lt:function(e,t){return e1?t-1:0),r=1;r0?" Args: "+n.join(", "):""))}var M=function(){function e(e){this.groupSizes=new Uint32Array(512),this.length=512,this.tag=e}var t=e.prototype;return t.indexOfGroup=function(e){for(var t=0,n=0;n=this.groupSizes.length){for(var n=this.groupSizes,r=n.length,o=r;e>=o;)(o<<=1)<0&&j(16,""+e);this.groupSizes=new Uint32Array(o),this.groupSizes.set(n),this.length=o;for(var i=r;i=this.length||0===this.groupSizes[e])return t;for(var n=this.groupSizes[e],r=this.indexOfGroup(e),o=r+n,i=r;i=q&&(q=t+1),C.set(e,t),_.set(t,e)},T="style["+w+'][data-styled-version="5.3.5"]',A=new RegExp("^"+w+'\\.g(\\d+)\\[id="([\\w\\d-]+)"\\].*?"([^"]*)'),L=function(e,t,n){for(var r,o=n.split(","),i=0,a=o.length;i=0;n--){var r=t[n];if(r&&1===r.nodeType&&r.hasAttribute(w))return r}}(n),i=void 0!==o?o.nextSibling:null;r.setAttribute(w,"active"),r.setAttribute("data-styled-version","5.3.5");var a=D();return a&&r.setAttribute("nonce",a),n.insertBefore(r,i),r},V=function(){function e(e){var t=this.element=P(e);t.appendChild(document.createTextNode("")),this.sheet=function(e){if(e.sheet)return e.sheet;for(var t=document.styleSheets,n=0,r=t.length;n=0){var n=document.createTextNode(t),r=this.nodes[e];return this.element.insertBefore(n,r||null),this.length++,!0}return!1},t.deleteRule=function(e){this.element.removeChild(this.nodes[e]),this.length--},t.getRule=function(e){return e0&&(l+=e+",")})),r+=""+c+s+'{content:"'+l+'"}/*!sc*/\n'}}}return r}(this)},e}(),U=/(a)(d)/gi,W=function(e){return String.fromCharCode(e+(e>25?39:97))};function G(e){var t,n="";for(t=Math.abs(e);t>52;t=t/52|0)n=W(t%52)+n;return(W(t%52)+n).replace(U,"$1-$2")}var Y=function(e,t){for(var n=t.length;n;)e=33*e^t.charCodeAt(--n);return e},$=function(e){return Y(5381,e)};function Z(e){for(var t=0;t>>0);if(!t.hasNameForId(r,a)){var c=n(i,"."+a,void 0,r);t.insertRules(r,a,c)}o.push(a),this.staticRulesId=a}else{for(var s=this.rules.length,l=Y(this.baseHash,n.hash),u="",f=0;f>>0);if(!t.hasNameForId(r,z)){var v=n(u,"."+z,void 0,r);t.insertRules(r,z,v)}o.push(z)}}return o.join(" ")},e}(),Q=/^\s*\/\/.*$/gm,J=[":","[",".","#"];function ee(e){var t,n,r,o,i=void 0===e?g:e,a=i.options,c=void 0===a?g:a,l=i.plugins,u=void 0===l?v:l,f=new s.a(c),d=[],h=function(e){function t(t){if(t)try{e(t+"}")}catch(e){}}return function(n,r,o,i,a,c,s,l,u,f){switch(n){case 1:if(0===u&&64===r.charCodeAt(0))return e(r+";"),"";break;case 2:if(0===l)return r+"/*|*/";break;case 3:switch(l){case 102:case 112:return e(o[0]+r),"";default:return r+(0===f?"/*|*/":"")}case-2:r.split("/*|*/}").forEach(t)}}}((function(e){d.push(e)})),p=function(e,r,i){return 0===r&&-1!==J.indexOf(i[n.length])||i.match(o)?e:"."+t};function z(e,i,a,c){void 0===c&&(c="&");var s=e.replace(Q,""),l=i&&a?a+" "+i+" { "+s+" }":s;return t=c,n=i,r=new RegExp("\\"+n+"\\b","g"),o=new RegExp("(\\"+n+"\\b){2,}"),f(a||!i?"":i,l)}return f.use([].concat(u,[function(e,t,o){2===e&&o.length&&o[0].lastIndexOf(n)>0&&(o[0]=o[0].replace(r,p))},h,function(e){if(-2===e){var t=d;return d=[],t}}])),z.hash=u.length?u.reduce((function(e,t){return t.name||j(15),Y(e,t.name)}),5381).toString():"",z}var te=i.a.createContext(),ne=(te.Consumer,i.a.createContext()),re=(ne.Consumer,new B),oe=ee();function ie(){return Object(o.useContext)(te)||re}function ae(){return Object(o.useContext)(ne)||oe}function ce(e){var t=Object(o.useState)(e.stylisPlugins),n=t[0],r=t[1],a=ie(),s=Object(o.useMemo)((function(){var t=a;return e.sheet?t=e.sheet:e.target&&(t=t.reconstructWithOptions({target:e.target},!1)),e.disableCSSOMInjection&&(t=t.reconstructWithOptions({useCSSOMInjection:!1})),t}),[e.disableCSSOMInjection,e.sheet,e.target]),l=Object(o.useMemo)((function(){return ee({options:{prefix:!e.disableVendorPrefixes},plugins:n})}),[e.disableVendorPrefixes,n]);return Object(o.useEffect)((function(){c()(n,e.stylisPlugins)||r(e.stylisPlugins)}),[e.stylisPlugins]),i.a.createElement(te.Provider,{value:s},i.a.createElement(ne.Provider,{value:l},e.children))}var se=function(){function e(e,t){var n=this;this.inject=function(e,t){void 0===t&&(t=oe);var r=n.name+t.hash;e.hasNameForId(n.id,r)||e.insertRules(n.id,r,t(n.rules,r,"@keyframes"))},this.toString=function(){return j(12,String(n.name))},this.name=e,this.id="sc-keyframes-"+e,this.rules=t}return e.prototype.getName=function(e){return void 0===e&&(e=oe),this.name+e.hash},e}(),le=/([A-Z])/,ue=/([A-Z])/g,fe=/^ms-/,de=function(e){return"-"+e.toLowerCase()};function he(e){return le.test(e)?e.replace(ue,de).replace(fe,"-ms-"):e}var pe=function(e){return null==e||!1===e||""===e};function ze(e,t,n,r){if(Array.isArray(e)){for(var o,i=[],a=0,c=e.length;a1?t-1:0),r=1;r?@[\\\]^`{|}~-]+/g,be=/(^-|-$)/g;function we(e){return e.replace(ye,"-").replace(be,"")}var ke=function(e){return G($(e)>>>0)};function xe(e){return"string"==typeof e&&!0}var je=function(e){return"function"==typeof e||"object"==typeof e&&null!==e&&!Array.isArray(e)},Me=function(e){return"__proto__"!==e&&"constructor"!==e&&"prototype"!==e};function Ce(e,t,n){var r=e[n];je(t)&&je(r)?_e(r,t):e[n]=t}function _e(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r=0||(o[n]=e[n]);return o}(t,["componentId"]),i=r&&r+"-"+(xe(e)?e:we(y(e)));return Ee(e,h({},o,{attrs:k,componentId:i}),n)},Object.defineProperty(j,"defaultProps",{get:function(){return this._foldedDefaultProps},set:function(t){this._foldedDefaultProps=r?_e({},e.defaultProps,t):t}}),j.toString=function(){return"."+j.styledComponentId},a&&d()(j,e,{attrs:!0,componentStyle:!0,displayName:!0,foldedComponentIds:!0,shouldForwardProp:!0,styledComponentId:!0,target:!0,withComponent:!0}),j}var Te=function(e){return function e(t,n,o){if(void 0===o&&(o=g),!Object(r.isValidElementType)(n))return j(1,String(n));var i=function(){return t(n,o,ge.apply(void 0,arguments))};return i.withConfig=function(r){return e(t,n,h({},o,{},r))},i.attrs=function(r){return e(t,n,h({},o,{attrs:Array.prototype.concat(o.attrs,r).filter(Boolean)}))},i}(Ee,e)};["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","dialog","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","marquee","menu","menuitem","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","picture","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","u","ul","var","video","wbr","circle","clipPath","defs","ellipse","foreignObject","g","image","line","linearGradient","marker","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","svg","text","textPath","tspan"].forEach((function(e){Te[e]=Te(e)}));!function(){function e(e,t){this.rules=e,this.componentId=t,this.isStatic=Z(e),B.registerId(this.componentId+1)}var t=e.prototype;t.createStyles=function(e,t,n,r){var o=r(ze(this.rules,t,n,r).join(""),""),i=this.componentId+e;n.insertRules(i,i,o)},t.removeStyles=function(e,t){t.clearRules(this.componentId+e)},t.renderStyles=function(e,t,n,r){e>2&&B.registerId(this.componentId+e),this.removeStyles(e,n),this.createStyles(e,t,n,r)}}();function Ae(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r"+t+""},this.getStyleTags=function(){return e.sealed?j(2):e._emitSheetCSS()},this.getStyleElement=function(){var t;if(e.sealed)return j(2);var n=((t={})[w]="",t["data-styled-version"]="5.3.5",t.dangerouslySetInnerHTML={__html:e.instance.toString()},t),r=D();return r&&(n.nonce=r),[i.a.createElement("style",h({},n,{key:"sc-0-0"}))]},this.seal=function(){e.sealed=!0},this.instance=new B({isServer:!0}),this.sealed=!1}var t=e.prototype;t.collectStyles=function(e){return this.sealed?j(2):i.a.createElement(ce,{sheet:this.instance},e)},t.interleaveWithNodeStream=function(e){return j(3)}}();t.d=Te}).call(this,n(93))},function(e,t,n){"use strict";var r=n(162);var o=n(163);function i(e,t){return Object(r.a)(e)||function(e,t){if(Symbol.iterator in Object(e)||"[object Arguments]"===Object.prototype.toString.call(e)){var n=[],r=!0,o=!1,i=void 0;try{for(var a,c=e[Symbol.iterator]();!(r=(a=c.next()).done)&&(n.push(a.value),!t||n.length!==t);r=!0);}catch(s){o=!0,i=s}finally{try{r||null==c.return||c.return()}finally{if(o)throw i}}return n}}(e,t)||Object(o.a)()}n.d(t,"a",(function(){return i}))},function(e,t,n){"use strict";function r(e,t){if(t.length1?"s":"")+" required, but only "+t.length+" present")}n.d(t,"a",(function(){return r}))},function(e,t,n){"use strict";n.d(t,"a",(function(){return i}));var r=n(14);function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0&&e.splice(n,1)}function p(e){var t=!1;return function(){t||(t=!0,e())}}var z=function(e){throw e},v=function(e){return{value:e,done:!0}};function g(e,t,n){void 0===t&&(t=z),void 0===n&&(n="iterator");var r={meta:{name:n},next:e,throw:t,return:v,isSagaIterator:!0};return"undefined"!==typeof Symbol&&(r[Symbol.iterator]=function(){return r}),r}function m(e,t){var n=t.sagaStack;console.error(e),console.error(n)}var y=function(e){return new Error("\n redux-saga: Error checking hooks detected an inconsistent state. This is likely a bug\n in redux-saga code and not yours. Thanks for reporting this in the project's github repo.\n Error: "+e+"\n")},b=function(e){return Array.apply(null,new Array(e))},w=function(e){return function(t){return e(Object.defineProperty(t,r.f,{value:!0}))}},k=function(e){return e===r.k},x=function(e){return e===r.j},j=function(e){return k(e)||x(e)};function M(e,t){var n=Object.keys(e),r=n.length;var o,a=0,c=Object(i.a)(e)?b(r):{},l={};return n.forEach((function(e){var n=function(n,i){o||(i||j(n)?(t.cancel(),t(n,i)):(c[e]=n,++a===r&&(o=!0,t(c))))};n.cancel=s,l[e]=n})),t.cancel=function(){o||(o=!0,n.forEach((function(e){return l[e].cancel()})))},l}function C(e){return{name:e.name||"anonymous",location:_(e)}}function _(e){return e[r.g]}var q="Channel's Buffer overflow!",S=1,O=3,E=4,T={isEmpty:c,put:s,take:s};function A(e,t){void 0===e&&(e=10);var n=new Array(e),r=0,o=0,i=0,a=function(t){n[o]=t,o=(o+1)%e,r++},c=function(){if(0!=r){var t=n[i];return n[i]=null,r--,i=(i+1)%e,t}},s=function(){for(var e=[];r;)e.push(c());return e};return{isEmpty:function(){return 0==r},put:function(c){var l;if(r1?t-1:0),r=1;r1?t-1:0),r=1;r1?t-1:0),r=1;r1?t-1:0),r=1;r2?n-2:0),i=2;i2?n-2:0),i=2;i1?t-1:0),r=1;r1&&void 0!==arguments[1]?arguments[1]:r,n=null,i=null;return function(){return o(t,n,arguments)||(i=e.apply(null,arguments)),n=arguments,i}}))},,function(e,t,n){"use strict";n.d(t,"a",(function(){return i}));var r=n(31),o=n(38);function i(e){return function t(n,i){switch(arguments.length){case 0:return t;case 1:return Object(o.a)(n)?t:Object(r.a)((function(t){return e(n,t)}));default:return Object(o.a)(n)&&Object(o.a)(i)?t:Object(o.a)(n)?Object(r.a)((function(t){return e(t,i)})):Object(o.a)(i)?Object(r.a)((function(t){return e(n,t)})):e(n,i)}}}},function(e,t,n){"use strict";function r(e,t){if(t.length1?"s":"")+" required, but only "+t.length+" present")}n.d(t,"a",(function(){return r}))},function(e,t,n){e.exports=n(341)},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"createAction",{enumerable:!0,get:function(){return o.default}}),Object.defineProperty(t,"createReducer",{enumerable:!0,get:function(){return i.default}}),Object.defineProperty(t,"assignAll",{enumerable:!0,get:function(){return a.default}}),Object.defineProperty(t,"bindAll",{enumerable:!0,get:function(){return c.default}}),Object.defineProperty(t,"batch",{enumerable:!0,get:function(){return s.default}}),Object.defineProperty(t,"disbatch",{enumerable:!0,get:function(){return l.default}}),Object.defineProperty(t,"loggers",{enumerable:!0,get:function(){return u.default}}),Object.defineProperty(t,"asError",{enumerable:!0,get:function(){return f.default}}),t.types=void 0;var r=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var r=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(e,n):{};r.get||r.set?Object.defineProperty(t,n,r):t[n]=e[n]}return t.default=e,t}(n(230)),o=d(n(231)),i=d(n(333)),a=d(n(334)),c=d(n(335)),s=d(n(151)),l=d(n(336)),u=d(n(337)),f=d(n(339));function d(e){return e&&e.__esModule?e:{default:e}}var h=r;t.types=h},function(e,t,n){"use strict";n.d(t,"a",(function(){return s})),n.d(t,"b",(function(){return h})),n.d(t,"c",(function(){return g})),n.d(t,"d",(function(){return a})),n.d(t,"e",(function(){return f})),n.d(t,"f",(function(){return v})),n.d(t,"g",(function(){return i})),n.d(t,"h",(function(){return l})),n.d(t,"i",(function(){return d})),n.d(t,"j",(function(){return u})),n.d(t,"k",(function(){return c})),n.d(t,"l",(function(){return p})),n.d(t,"m",(function(){return z})),n.d(t,"n",(function(){return o}));var r=n(33),o=function(e){return null===e||void 0===e},i=function(e){return null!==e&&void 0!==e},a=function(e){return"function"===typeof e},c=function(e){return"string"===typeof e},s=Array.isArray,l=function(e){return e&&!s(e)&&"object"===typeof e},u=function(e){return e&&a(e.then)},f=function(e){return e&&a(e.next)&&a(e.throw)},d=function e(t){return t&&(c(t)||z(t)||a(t)||s(t)&&t.every(e))},h=function(e){return e&&a(e.take)&&a(e.close)},p=function(e){return a(e)&&e.hasOwnProperty("toString")},z=function(e){return Boolean(e)&&"function"===typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype},v=function(e){return h(e)&&e[r.e]},g=function(e){return e&&e[r.c]}},function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));var r=n(20);function o(e){Object(r.a)(1,arguments);var t=Object.prototype.toString.call(e);return e instanceof Date||"object"===typeof e&&"[object Date]"===t?new Date(e.getTime()):"number"===typeof e||"[object Number]"===t?new Date(e):("string"!==typeof e&&"[object String]"!==t||"undefined"===typeof console||(console.warn("Starting with v2.0.0-beta.1 date-fns doesn't accept strings as date arguments. Please use `parseISO` to parse strings. See: https://git.io/fjule"),console.warn((new Error).stack)),new Date(NaN))}},,function(e,t,n){"use strict";function r(e){if(null===e||!0===e||!1===e)return NaN;var t=Number(e);return isNaN(t)?t:t<0?Math.ceil(t):Math.floor(t)}n.d(t,"a",(function(){return r}))},function(e,t,n){"use strict";var r=n(0),o=n.n(r),i=n(13),a=n.n(i),c=o.a.createContext(null);var s=function(e){e()},l=function(){return s},u=null,f={notify:function(){}};var d=function(){function e(e,t){this.store=e,this.parentSub=t,this.unsubscribe=null,this.listeners=f,this.handleChangeWrapper=this.handleChangeWrapper.bind(this)}var t=e.prototype;return t.addNestedSub=function(e){return this.trySubscribe(),this.listeners.subscribe(e)},t.notifyNestedSubs=function(){this.listeners.notify()},t.handleChangeWrapper=function(){this.onStateChange&&this.onStateChange()},t.isSubscribed=function(){return Boolean(this.unsubscribe)},t.trySubscribe=function(){this.unsubscribe||(this.unsubscribe=this.parentSub?this.parentSub.addNestedSub(this.handleChangeWrapper):this.store.subscribe(this.handleChangeWrapper),this.listeners=function(){var e=l(),t=[],n=[];return{clear:function(){n=u,t=u},notify:function(){var r=t=n;e((function(){for(var e=0;e. You may also pass a {context : MyContext} option to connect");var L=T;return function(t){var n=t.displayName||t.name||"Component",i=a(n),c=Object(z.a)({},A,{getDisplayName:a,methodName:l,renderCountProp:f,shouldHandleStateChanges:p,storeKey:y,displayName:i,wrappedComponentName:n,WrappedComponent:t}),s=A.pure;var u=s?r.useMemo:function(e){return e()};function h(n){var a=Object(r.useMemo)((function(){var e=n.forwardedRef,t=Object(v.a)(n,["forwardedRef"]);return[n.context,e,t]}),[n]),s=a[0],l=a[1],f=a[2],h=Object(r.useMemo)((function(){return s&&s.Consumer&&Object(w.isContextConsumer)(o.a.createElement(s.Consumer,null))?s:L}),[s,L]),g=Object(r.useContext)(h),m=Boolean(n.store)&&Boolean(n.store.getState)&&Boolean(n.store.dispatch),y=Boolean(g)&&Boolean(g.store);b()(m||y,'Could not find "store" in the context of "'+i+'". Either wrap the root component in a , or pass a custom React context provider to and the corresponding React context consumer to '+i+" in connect options.");var _=m?n.store:g.store,q=Object(r.useMemo)((function(){return function(t){return e(t.dispatch,c)}(_)}),[_]),S=Object(r.useMemo)((function(){if(!p)return j;var e=new d(_,m?null:g.subscription),t=e.notifyNestedSubs.bind(e);return[e,t]}),[_,m,g]),O=S[0],E=S[1],T=Object(r.useMemo)((function(){return m?g:Object(z.a)({},g,{subscription:O})}),[m,g,O]),A=Object(r.useReducer)(M,x,C),H=A[0][0],D=A[1];if(H&&H.error)throw H.error;var P=Object(r.useRef)(),V=Object(r.useRef)(f),R=Object(r.useRef)(),I=Object(r.useRef)(!1),N=u((function(){return R.current&&f===V.current?R.current:q(_.getState(),f)}),[_,H,f]);k((function(){V.current=f,P.current=N,I.current=!1,R.current&&(R.current=null,E())})),k((function(){if(p){var e=!1,t=null,n=function(){if(!e){var n,r,o=_.getState();try{n=q(o,V.current)}catch(i){r=i,t=i}r||(t=null),n===P.current?I.current||E():(P.current=n,R.current=n,I.current=!0,D({type:"STORE_UPDATED",payload:{error:r}}))}};O.onStateChange=n,O.trySubscribe(),n();return function(){if(e=!0,O.tryUnsubscribe(),O.onStateChange=null,t)throw t}}}),[_,O,q]);var F=Object(r.useMemo)((function(){return o.a.createElement(t,Object(z.a)({},N,{ref:l}))}),[l,t,N]);return Object(r.useMemo)((function(){return p?o.a.createElement(h.Provider,{value:T},F):F}),[h,F,T])}var g=s?o.a.memo(h):h;if(g.WrappedComponent=t,g.displayName=i,O){var _=o.a.forwardRef((function(e,t){return o.a.createElement(g,Object(z.a)({},e,{forwardedRef:t}))}));return _.displayName=i,_.WrappedComponent=t,m()(_,t)}return m()(g,t)}}var q=Object.prototype.hasOwnProperty;function S(e,t){return e===t?0!==e||0!==t||1/e===1/t:e!==e&&t!==t}function O(e,t){if(S(e,t))return!0;if("object"!==typeof e||null===e||"object"!==typeof t||null===t)return!1;var n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(var o=0;o=0;r--){var o=t[r](e);if(o)return o}return function(t,r){throw new Error("Invalid value of type "+typeof e+" for "+n+" argument when connecting component "+r.wrappedComponentName+".")}}function B(e,t){return e===t}!function(e){var t=void 0===e?{}:e,n=t.connectHOC,r=void 0===n?_:n,o=t.mapStateToPropsFactories,i=void 0===o?D:o,a=t.mapDispatchToPropsFactories,c=void 0===a?H:a,s=t.mergePropsFactories,l=void 0===s?V:s,u=t.selectorFactory,f=void 0===u?N:u}();function U(){var e=Object(r.useContext)(c);return b()(e,"could not find react-redux context value; please ensure the component is wrapped in a "),e}function W(e){void 0===e&&(e=c);var t=e===c?U:function(){return Object(r.useContext)(e)};return function(){return t().store}}var G=W();function Y(e){void 0===e&&(e=c);var t=e===c?G:W(e);return function(){return t().dispatch}}var $=Y(),Z=function(e,t){return e===t};function X(e){void 0===e&&(e=c);var t=e===c?U:function(){return Object(r.useContext)(e)};return function(e,n){void 0===n&&(n=Z),b()(e,"You must pass a selector to useSelectors");var o=t();return function(e,t,n,o){var i,a=Object(r.useReducer)((function(e){return e+1}),0)[1],c=Object(r.useMemo)((function(){return new d(n,o)}),[n,o]),s=Object(r.useRef)(),l=Object(r.useRef)(),u=Object(r.useRef)();try{i=e!==l.current||s.current?e(n.getState()):u.current}catch(h){var f="An error occurred while selecting the store state: "+h.message+".";throw s.current&&(f+="\nThe error may be correlated with this previous error:\n"+s.current.stack+"\n\nOriginal stack trace:"),new Error(f)}return k((function(){l.current=e,u.current=i,s.current=void 0})),k((function(){function e(){try{var e=l.current(n.getState());if(t(e,u.current))return;u.current=e}catch(h){s.current=h}a({})}return c.onStateChange=e,c.trySubscribe(),e(),function(){return c.tryUnsubscribe()}}),[n,c]),i}(e,n,o.store,o.subscription)}}var K,Q=X(),J=n(30);n.d(t,"a",(function(){return p})),n.d(t,"d",(function(){return $})),n.d(t,"b",(function(){return Y})),n.d(t,"e",(function(){return Q})),n.d(t,"c",(function(){return X})),n.d(t,"f",(function(){return G})),K=J.unstable_batchedUpdates,s=K},,function(e,t,n){"use strict";function r(){return(r=Object.assign||function(e){for(var t=1;t=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}}}},function(e,t,n){"use strict";n.d(t,"a",(function(){return o})),n.d(t,"b",(function(){return i})),n.d(t,"c",(function(){return a})),n.d(t,"d",(function(){return c})),n.d(t,"e",(function(){return s})),n.d(t,"f",(function(){return l})),n.d(t,"g",(function(){return p})),n.d(t,"h",(function(){return u})),n.d(t,"i",(function(){return f})),n.d(t,"j",(function(){return d})),n.d(t,"k",(function(){return h}));var r=function(e){return"@@redux-saga/"+e},o=r("CANCEL_PROMISE"),i=r("CHANNEL_END"),a=r("IO"),c=r("MATCH"),s=r("MULTICAST"),l=r("SAGA_ACTION"),u=r("SELF_CANCELLATION"),f=r("TASK"),d=r("TASK_CANCEL"),h=r("TERMINATE"),p=r("LOCATION")},function(e,t,n){var r;!function(){"use strict";var n={}.hasOwnProperty;function o(){for(var e=[],t=0;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}n.d(t,"a",(function(){return r}))},function(e,t,n){"use strict";function r(e){return null!=e&&"object"===typeof e&&!0===e["@@functional/placeholder"]}n.d(t,"a",(function(){return r}))},,function(e,t,n){"use strict";n.d(t,"c",(function(){return i})),n.d(t,"e",(function(){return a})),n.d(t,"d",(function(){return c})),n.d(t,"b",(function(){return r})),n.d(t,"a",(function(){return o}));var r,o,i={ANCHOR:"mdc-menu-surface--anchor",ANIMATING_CLOSED:"mdc-menu-surface--animating-closed",ANIMATING_OPEN:"mdc-menu-surface--animating-open",FIXED:"mdc-menu-surface--fixed",OPEN:"mdc-menu-surface--open",ROOT:"mdc-menu-surface"},a={CLOSED_EVENT:"MDCMenuSurface:closed",OPENED_EVENT:"MDCMenuSurface:opened",FOCUSABLE_ELEMENTS:["button:not(:disabled)",'[href]:not([aria-disabled="true"])',"input:not(:disabled)","select:not(:disabled)","textarea:not(:disabled)",'[tabindex]:not([tabindex="-1"]):not([aria-disabled="true"])'].join(", ")},c={TRANSITION_OPEN_DURATION:120,TRANSITION_CLOSE_DURATION:75,MARGIN_TO_EDGE:32,ANCHOR_TO_MENU_SURFACE_WIDTH_RATIO:.67};!function(e){e[e.BOTTOM=1]="BOTTOM",e[e.CENTER=2]="CENTER",e[e.RIGHT=4]="RIGHT",e[e.FLIP_RTL=8]="FLIP_RTL"}(r||(r={})),function(e){e[e.TOP_LEFT=0]="TOP_LEFT",e[e.TOP_RIGHT=4]="TOP_RIGHT",e[e.BOTTOM_LEFT=1]="BOTTOM_LEFT",e[e.BOTTOM_RIGHT=5]="BOTTOM_RIGHT",e[e.TOP_START=8]="TOP_START",e[e.TOP_END=12]="TOP_END",e[e.BOTTOM_START=9]="BOTTOM_START",e[e.BOTTOM_END=13]="BOTTOM_END"}(o||(o={}))},,,function(e,t,n){"use strict";var r=n(53);var o=n(106),i=n(134);var a=n(78),c=n(77);function s(e){return e?1===e.length?e[0]:function(t){return e.reduce((function(e,t){return t(e)}),t)}:c.a}var l=n(61);n.d(t,"a",(function(){return u}));var u=function(){function e(e){this._isScalar=!1,e&&(this._subscribe=e)}return e.prototype.lift=function(t){var n=new e;return n.source=this,n.operator=t,n},e.prototype.subscribe=function(e,t,n){var a=this.operator,c=function(e,t,n){if(e){if(e instanceof r.a)return e;if(e[o.a])return e[o.a]()}return e||t||n?new r.a(e,t,n):new r.a(i.a)}(e,t,n);if(a?c.add(a.call(c,this.source)):c.add(this.source||l.a.useDeprecatedSynchronousErrorHandling&&!c.syncErrorThrowable?this._subscribe(c):this._trySubscribe(c)),l.a.useDeprecatedSynchronousErrorHandling&&c.syncErrorThrowable&&(c.syncErrorThrowable=!1,c.syncErrorThrown))throw c.syncErrorValue;return c},e.prototype._trySubscribe=function(e){try{return this._subscribe(e)}catch(t){l.a.useDeprecatedSynchronousErrorHandling&&(e.syncErrorThrown=!0,e.syncErrorValue=t),!function(e){for(;e;){var t=e,n=t.closed,o=t.destination,i=t.isStopped;if(n||i)return!1;e=o&&o instanceof r.a?o:null}return!0}(e)?console.warn(t):e.error(t)}},e.prototype.forEach=function(e,t){var n=this;return new(t=f(t))((function(t,r){var o;o=n.subscribe((function(t){try{e(t)}catch(n){r(n),o&&o.unsubscribe()}}),r,t)}))},e.prototype._subscribe=function(e){var t=this.source;return t&&t.subscribe(e)},e.prototype[a.a]=function(){return this},e.prototype.pipe=function(){for(var e=[],t=0;t=0||(o[n]=e[n]);return o}n.d(t,"a",(function(){return r}))},function(e,t,n){"use strict";n.d(t,"a",(function(){return v})),n.d(t,"b",(function(){return f})),n.d(t,"c",(function(){return l})),n.d(t,"d",(function(){return z})),n.d(t,"e",(function(){return c}));var r=n(220),o=function(){return Math.random().toString(36).substring(7).split("").join(".")},i={INIT:"@@redux/INIT"+o(),REPLACE:"@@redux/REPLACE"+o(),PROBE_UNKNOWN_ACTION:function(){return"@@redux/PROBE_UNKNOWN_ACTION"+o()}};function a(e){if("object"!==typeof e||null===e)return!1;for(var t=e;null!==Object.getPrototypeOf(t);)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t}function c(e,t,n){var o;if("function"===typeof t&&"function"===typeof n||"function"===typeof n&&"function"===typeof arguments[3])throw new Error("It looks like you are passing several store enhancers to createStore(). This is not supported. Instead, compose them together to a single function.");if("function"===typeof t&&"undefined"===typeof n&&(n=t,t=void 0),"undefined"!==typeof n){if("function"!==typeof n)throw new Error("Expected the enhancer to be a function.");return n(c)(e,t)}if("function"!==typeof e)throw new Error("Expected the reducer to be a function.");var s=e,l=t,u=[],f=u,d=!1;function h(){f===u&&(f=u.slice())}function p(){if(d)throw new Error("You may not call store.getState() while the reducer is executing. The reducer has already received the state as an argument. Pass it down from the top reducer instead of reading it from the store.");return l}function z(e){if("function"!==typeof e)throw new Error("Expected the listener to be a function.");if(d)throw new Error("You may not call store.subscribe() while the reducer is executing. If you would like to be notified after the store has been updated, subscribe from a component and invoke store.getState() in the callback to access the latest state. See https://redux.js.org/api-reference/store#subscribe(listener) for more details.");var t=!0;return h(),f.push(e),function(){if(t){if(d)throw new Error("You may not unsubscribe from a store listener while the reducer is executing. See https://redux.js.org/api-reference/store#subscribe(listener) for more details.");t=!1,h();var n=f.indexOf(e);f.splice(n,1)}}}function v(e){if(!a(e))throw new Error("Actions must be plain objects. Use custom middleware for async actions.");if("undefined"===typeof e.type)throw new Error('Actions may not have an undefined "type" property. Have you misspelled a constant?');if(d)throw new Error("Reducers may not dispatch actions.");try{d=!0,l=s(l,e)}finally{d=!1}for(var t=u=f,n=0;n0;)a[i=r[o]]||(t[i]=e[i],a[i]=!0);e=Object.getPrototypeOf(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},kindOf:a,kindOfTest:c,endsWith:function(e,t,n){e=String(e),(void 0===n||n>e.length)&&(n=e.length),n-=t.length;var r=e.indexOf(t,n);return-1!==r&&r===n},toArray:function(e){if(!e)return null;var t=e.length;if(l(t))return null;for(var n=new Array(t);t-- >0;)n[t]=e[t];return n},isTypedArray:w,isFileList:v}},,,function(e,t,n){"use strict";n.d(t,"a",(function(){return u}));var r=n(32),o=n(107),i=n(134),a=n(54),c=n(106),s=n(61),l=n(90),u=function(e){function t(n,r,o){var a=e.call(this)||this;switch(a.syncErrorValue=null,a.syncErrorThrown=!1,a.syncErrorThrowable=!1,a.isStopped=!1,arguments.length){case 0:a.destination=i.a;break;case 1:if(!n){a.destination=i.a;break}if("object"===typeof n){n instanceof t?(a.syncErrorThrowable=n.syncErrorThrowable,a.destination=n,n.add(a)):(a.syncErrorThrowable=!0,a.destination=new f(a,n));break}default:a.syncErrorThrowable=!0,a.destination=new f(a,n,r,o)}return a}return r.b(t,e),t.prototype[c.a]=function(){return this},t.create=function(e,n,r){var o=new t(e,n,r);return o.syncErrorThrowable=!1,o},t.prototype.next=function(e){this.isStopped||this._next(e)},t.prototype.error=function(e){this.isStopped||(this.isStopped=!0,this._error(e))},t.prototype.complete=function(){this.isStopped||(this.isStopped=!0,this._complete())},t.prototype.unsubscribe=function(){this.closed||(this.isStopped=!0,e.prototype.unsubscribe.call(this))},t.prototype._next=function(e){this.destination.next(e)},t.prototype._error=function(e){this.destination.error(e),this.unsubscribe()},t.prototype._complete=function(){this.destination.complete(),this.unsubscribe()},t.prototype._unsubscribeAndRecycle=function(){var e=this._parentOrParents;return this._parentOrParents=null,this.unsubscribe(),this.closed=!1,this.isStopped=!1,this._parentOrParents=e,this},t}(a.a),f=function(e){function t(t,n,r,a){var c,s=e.call(this)||this;s._parentSubscriber=t;var l=s;return Object(o.a)(n)?c=n:n&&(c=n.next,r=n.error,a=n.complete,n!==i.a&&(l=Object.create(n),Object(o.a)(l.unsubscribe)&&s.add(l.unsubscribe.bind(l)),l.unsubscribe=s.unsubscribe.bind(s))),s._context=l,s._next=c,s._error=r,s._complete=a,s}return r.b(t,e),t.prototype.next=function(e){if(!this.isStopped&&this._next){var t=this._parentSubscriber;s.a.useDeprecatedSynchronousErrorHandling&&t.syncErrorThrowable?this.__tryOrSetError(t,this._next,e)&&this.unsubscribe():this.__tryOrUnsub(this._next,e)}},t.prototype.error=function(e){if(!this.isStopped){var t=this._parentSubscriber,n=s.a.useDeprecatedSynchronousErrorHandling;if(this._error)n&&t.syncErrorThrowable?(this.__tryOrSetError(t,this._error,e),this.unsubscribe()):(this.__tryOrUnsub(this._error,e),this.unsubscribe());else if(t.syncErrorThrowable)n?(t.syncErrorValue=e,t.syncErrorThrown=!0):Object(l.a)(e),this.unsubscribe();else{if(this.unsubscribe(),n)throw e;Object(l.a)(e)}}},t.prototype.complete=function(){var e=this;if(!this.isStopped){var t=this._parentSubscriber;if(this._complete){var n=function(){return e._complete.call(e._context)};s.a.useDeprecatedSynchronousErrorHandling&&t.syncErrorThrowable?(this.__tryOrSetError(t,n),this.unsubscribe()):(this.__tryOrUnsub(n),this.unsubscribe())}else this.unsubscribe()}},t.prototype.__tryOrUnsub=function(e,t){try{e.call(this._context,t)}catch(n){if(this.unsubscribe(),s.a.useDeprecatedSynchronousErrorHandling)throw n;Object(l.a)(n)}},t.prototype.__tryOrSetError=function(e,t,n){if(!s.a.useDeprecatedSynchronousErrorHandling)throw new Error("bad call");try{t.call(this._context,n)}catch(r){return s.a.useDeprecatedSynchronousErrorHandling?(e.syncErrorValue=r,e.syncErrorThrown=!0,!0):(Object(l.a)(r),!0)}return!1},t.prototype._unsubscribe=function(){var e=this._parentSubscriber;this._context=null,this._parentSubscriber=null,e.unsubscribe()},t}(u)},function(e,t,n){"use strict";var r=function(){return Array.isArray||function(e){return e&&"number"===typeof e.length}}(),o=n(168),i=n(107),a=function(){function e(e){return Error.call(this),this.message=e?e.length+" errors occurred during unsubscription:\n"+e.map((function(e,t){return t+1+") "+e.toString()})).join("\n "):"",this.name="UnsubscriptionError",this.errors=e,this}return e.prototype=Object.create(Error.prototype),e}();n.d(t,"a",(function(){return c}));var c=function(){function e(e){this.closed=!1,this._parentOrParents=null,this._subscriptions=null,e&&(this._unsubscribe=e)}var t;return e.prototype.unsubscribe=function(){var t;if(!this.closed){var n=this._parentOrParents,c=this._unsubscribe,l=this._subscriptions;if(this.closed=!0,this._parentOrParents=null,this._subscriptions=null,n instanceof e)n.remove(this);else if(null!==n)for(var u=0;u=0;)t=c[n],Object(o.a)(t,e)&&!l(r,t)&&(r[r.length]=t),n-=1;return r})):Object(r.a)((function(e){return Object(e)!==e?[]:Object.keys(e)}));t.a=u},function(e,t,n){"use strict";var r=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable;function a(e){if(null===e||void 0===e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(o){return!1}}()?Object.assign:function(e,t){for(var n,c,s=a(e),l=1;l8.64*1e15?NaN:function(e){var t=a(e);if(isNaN(t)||l(t,-0))return 0;if(isFinite(t))return t;var n=Math.floor(Math.abs(t));return t<0&&(n=-n),l(n,-0)?0:n}(e):NaN}function s(e){if(null==e)throw new TypeError("undefined/null cannot be converted to object");return Object(e)}function l(e,t){return Object.is?Object.is(e,t):e===t?0!==e||1/e===1/t:e!==e&&t!==t}function u(e){return new Array(e)}function f(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function d(e){return null===e?"Null":"undefined"===typeof e?"Undefined":"function"===typeof e||"object"===typeof e?"Object":"number"===typeof e?"Number":"boolean"===typeof e?"Boolean":"string"===typeof e?"String":"symbol"===typeof e?"Symbol":"bigint"===typeof e?"BigInt":void 0}n.r(t);var h=864e5;function p(e,t){return e-Math.floor(e/t)*t}function z(e){return Math.floor(e/h)}function v(e){return p(z(e)+4,7)}function g(e){return Date.UTC(e,0)/h}function m(e){return Date.UTC(e,0)}function y(e){return new Date(e).getUTCFullYear()}function b(e){return e%4!==0?365:e%100!==0?366:e%400!==0?365:366}function w(e){return z(e)-g(y(e))}function k(e){return 365===b(y(e))?0:1}function x(e){var t=w(e),n=k(e);if(t>=0&&t<31)return 0;if(t<59+n)return 1;if(t<90+n)return 2;if(t<120+n)return 3;if(t<151+n)return 4;if(t<181+n)return 5;if(t<212+n)return 6;if(t<243+n)return 7;if(t<273+n)return 8;if(t<304+n)return 9;if(t<334+n)return 10;if(t<365+n)return 11;throw new Error("Invalid time")}function j(e){var t=w(e),n=x(e),r=k(e);if(0===n)return t+1;if(1===n)return t-30;if(2===n)return t-58-r;if(3===n)return t-89-r;if(4===n)return t-119-r;if(5===n)return t-150-r;if(6===n)return t-180-r;if(7===n)return t-211-r;if(8===n)return t-242-r;if(9===n)return t-272-r;if(10===n)return t-303-r;if(11===n)return t-333-r;throw new Error("Invalid time")}var M=24,C=60,_=60,q=1e3,S=q*_,O=S*C;function E(e){return p(Math.floor(e/O),M)}function T(e){return p(Math.floor(e/S),C)}function A(e){return p(Math.floor(e/q),_)}function L(e,t,n){if("function"!==typeof e)return!1;if(null===n||void 0===n?void 0:n.boundTargetFunction)return t instanceof(null===n||void 0===n?void 0:n.boundTargetFunction);if("object"!==typeof t)return!1;var r=e.prototype;if("object"!==typeof r)throw new TypeError("OrdinaryHasInstance called on an object with an invalid prototype property.");return Object.prototype.isPrototypeOf.call(r,t)}function H(e){return p(e,q)}function D(e){return"undefined"===typeof e?Object.create(null):s(e)}function P(e,t,n,r){if(void 0!==e){if(e=Number(e),isNaN(e)||en)throw new RangeError(e+" is outside of range ["+t+", "+n+"]");return Math.floor(e)}return r}function V(e,t,n,r,o){return P(e[t],n,r,o)}function R(e,t,n,r,o){if("object"!==typeof e)throw new TypeError("Options must be an object");var a=e[t];if(void 0!==a){if("boolean"!==n&&"string"!==n)throw new TypeError("invalid type");if("boolean"===n&&(a=Boolean(a)),"string"===n&&(a=i(a)),void 0!==r&&!r.filter((function(e){return e==a})).length)throw new RangeError(a+" is not within "+r.join(", "));return a}return o}function I(e){if("undefined"===typeof e)return Object.create(null);if("object"===typeof e)return e;throw new TypeError("Options must be an object")}var N=["angle-degree","area-acre","area-hectare","concentr-percent","digital-bit","digital-byte","digital-gigabit","digital-gigabyte","digital-kilobit","digital-kilobyte","digital-megabit","digital-megabyte","digital-petabyte","digital-terabit","digital-terabyte","duration-day","duration-hour","duration-millisecond","duration-minute","duration-month","duration-second","duration-week","duration-year","length-centimeter","length-foot","length-inch","length-kilometer","length-meter","length-mile-scandinavian","length-mile","length-millimeter","length-yard","mass-gram","mass-kilogram","mass-ounce","mass-pound","mass-stone","temperature-celsius","temperature-fahrenheit","volume-fluid-ounce","volume-gallon","volume-liter","volume-milliliter"];function F(e){return e.slice(e.indexOf("-")+1)}var B=N.map(F);function U(e){return B.indexOf(e)>-1}function W(e,t){var n=t.tzData,r=t.uppercaseLinks,o=e.toUpperCase(),i=new Set,a=new Set;return Object.keys(n).map((function(e){return e.toUpperCase()})).forEach((function(e){return i.add(e)})),Object.keys(r).forEach((function(e){a.add(e.toUpperCase()),i.add(r[e].toUpperCase())})),i.has(o)||a.has(o)}var G=/[^A-Z]/;function Y(e){return 3===(e=e.replace(/([a-z])/g,(function(e,t){return t.toUpperCase()}))).length&&!G.test(e)}function $(e){if(U(e=e.replace(/([A-Z])/g,(function(e,t){return t.toLowerCase()}))))return!0;var t=e.split("-per-");if(2!==t.length)return!1;var n=t[0],r=t[1];return!(!U(n)||!U(r))}function Z(e){return Math.floor(Math.log(e)*Math.LOG10E)}function X(e,t){if("function"===typeof e.repeat)return e.repeat(t);for(var n=new Array(t),r=0;rh[h.length-1])return h[h.length-1].length-1;var p=h.indexOf(d);if(-1===p)return 0;var z=h[p];return"0"===u[z].other?0:z.length-u[z].other.match(/0+/)[0].length}}function ie(e,t,n){var r,o,i,a,c=n;if(0===e)r=X("0",c),o=0,i=0;else{var s=e.toString(),l=s.indexOf("e"),u=s.split("e"),f=u[0],d=u[1],h=f.replace(".","");if(l>=0&&h.length<=c)o=+d,r=h+X("0",c-h.length),i=e;else{var p=(o=Z(e))-c+1,z=Math.round(g(e,p));g(z,c-1)>=10&&(o+=1,z=Math.floor(z/10)),r=z.toString(),i=g(z,c-1-o)}}if(o>=c-1?(r+=X("0",o-c+1),a=o+1):o>=0?(r=r.slice(0,o+1)+"."+r.slice(o+1),a=o+1):(r="0."+X("0",-o-1)+r,a=1),r.indexOf(".")>=0&&n>t){for(var v=n-t;v>0&&"0"===r[r.length-1];)r=r.slice(0,-1),v--;"."===r[r.length-1]&&(r=r.slice(0,-1))}return{formattedString:r,roundedNumber:i,integerDigitsCount:a};function g(e,t){return t<0?e*Math.pow(10,-t):e/Math.pow(10,t)}}function ae(e,t,n){var r,o,i=n,a=Math.round(e*Math.pow(10,i)),c=a/Math.pow(10,i);if(a<1e21)r=a.toString();else{var s=(r=a.toString()).split("e"),l=s[0],u=s[1];r=l.replace(".",""),r+=X("0",Math.max(+u-r.length+1,0))}if(0!==i){var f=r.length;if(f<=i)r=X("0",i+1-f)+r,f=i+1;var d=r.slice(0,f-i),h=r.slice(f-i);r=d+"."+h,o=d.length}else o=r.length;for(var p=n-t;p>0&&"0"===r[r.length-1];)r=r.slice(0,-1),p--;return"."===r[r.length-1]&&(r=r.slice(0,-1)),{formattedString:r,roundedNumber:c,integerDigitsCount:o}}function ce(e,t){var n,r=t<0||l(t,-0);switch(r&&(t=-t),e.roundingType){case"significantDigits":n=ie(t,e.minimumSignificantDigits,e.maximumSignificantDigits);break;case"fractionDigits":n=ae(t,e.minimumFractionDigits,e.maximumFractionDigits);break;default:(n=ie(t,1,2)).integerDigitsCount>1&&(n=ae(t,0,0))}t=n.roundedNumber;var o=n.formattedString,i=n.integerDigitsCount,a=e.minimumIntegerDigits;i\^`\|~\xA2-\xA6\xA8\xA9\xAC\xAE-\xB1\xB4\xB8\xD7\xF7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0384\u0385\u03F6\u0482\u058D-\u058F\u0606-\u0608\u060B\u060E\u060F\u06DE\u06E9\u06FD\u06FE\u07F6\u07FE\u07FF\u09F2\u09F3\u09FA\u09FB\u0AF1\u0B70\u0BF3-\u0BFA\u0C7F\u0D4F\u0D79\u0E3F\u0F01-\u0F03\u0F13\u0F15-\u0F17\u0F1A-\u0F1F\u0F34\u0F36\u0F38\u0FBE-\u0FC5\u0FC7-\u0FCC\u0FCE\u0FCF\u0FD5-\u0FD8\u109E\u109F\u1390-\u1399\u166D\u17DB\u1940\u19DE-\u19FF\u1B61-\u1B6A\u1B74-\u1B7C\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2044\u2052\u207A-\u207C\u208A-\u208C\u20A0-\u20BF\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F\u218A\u218B\u2190-\u2307\u230C-\u2328\u232B-\u2426\u2440-\u244A\u249C-\u24E9\u2500-\u2767\u2794-\u27C4\u27C7-\u27E5\u27F0-\u2982\u2999-\u29D7\u29DC-\u29FB\u29FE-\u2B73\u2B76-\u2B95\u2B97-\u2BFF\u2CE5-\u2CEA\u2E50\u2E51\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3004\u3012\u3013\u3020\u3036\u3037\u303E\u303F\u309B\u309C\u3190\u3191\u3196-\u319F\u31C0-\u31E3\u3200-\u321E\u322A-\u3247\u3250\u3260-\u327F\u328A-\u32B0\u32C0-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA700-\uA716\uA720\uA721\uA789\uA78A\uA828-\uA82B\uA836-\uA839\uAA77-\uAA79\uAB5B\uAB6A\uAB6B\uFB29\uFBB2-\uFBC1\uFDFC\uFDFD\uFE62\uFE64-\uFE66\uFE69\uFF04\uFF0B\uFF1C-\uFF1E\uFF3E\uFF40\uFF5C\uFF5E\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFFC\uFFFD]|\uD800[\uDD37-\uDD3F\uDD79-\uDD89\uDD8C-\uDD8E\uDD90-\uDD9C\uDDA0\uDDD0-\uDDFC]|\uD802[\uDC77\uDC78\uDEC8]|\uD805\uDF3F|\uD807[\uDFD5-\uDFF1]|\uD81A[\uDF3C-\uDF3F\uDF45]|\uD82F\uDC9C|\uD834[\uDC00-\uDCF5\uDD00-\uDD26\uDD29-\uDD64\uDD6A-\uDD6C\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDDE8\uDE00-\uDE41\uDE45\uDF00-\uDF56]|\uD835[\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85\uDE86]|\uD838[\uDD4F\uDEFF]|\uD83B[\uDCAC\uDCB0\uDD2E\uDEF0\uDEF1]|\uD83C[\uDC00-\uDC2B\uDC30-\uDC93\uDCA0-\uDCAE\uDCB1-\uDCBF\uDCC1-\uDCCF\uDCD1-\uDCF5\uDD0D-\uDDAD\uDDE6-\uDE02\uDE10-\uDE3B\uDE40-\uDE48\uDE50\uDE51\uDE60-\uDE65\uDF00-\uDFFF]|\uD83D[\uDC00-\uDED7\uDEE0-\uDEEC\uDEF0-\uDEFC\uDF00-\uDF73\uDF80-\uDFD8\uDFE0-\uDFEB]|\uD83E[\uDC00-\uDC0B\uDC10-\uDC47\uDC50-\uDC59\uDC60-\uDC87\uDC90-\uDCAD\uDCB0\uDCB1\uDD00-\uDD78\uDD7A-\uDDCB\uDDCD-\uDE53\uDE60-\uDE6D\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6\uDF00-\uDF92\uDF94-\uDFCA]/,de=new RegExp("^"+fe.source),he=new RegExp(fe.source+"$"),pe=/[#0](?:[\.,][#0]+)*/g;function ze(e,t,n,r){var o,i,a=e.sign,c=e.exponent,s=e.magnitude,l=r.notation,u=r.style,f=r.numberingSystem,d=t.numbers.nu[0],h=null;if("compact"===l&&s&&(h=function(e,t,n,r,o,i,a){var c,s,l=e.roundedNumber,u=e.sign,f=e.magnitude,d=String(Math.pow(10,f)),h=n.numbers.nu[0];if("currency"===r&&"name"!==i){var p=(v=n.numbers.currency)[a]||v[h],z=null===(c=p.short)||void 0===c?void 0:c[d];if(!z)return null;s=me(t,l,z)}else{var v,g=((v=n.numbers.decimal)[a]||v[h])[o][d];if(!g)return null;s=me(t,l,g)}if("0"===s)return null;return s=ge(s,u).replace(/([^\s;\-\+\d\xa4]+)/g,"{c:$1}").replace(/0+/,"0")}(e,n,t,u,r.compactDisplay,r.currencyDisplay,f)),"currency"===u&&"name"!==r.currencyDisplay){var p=t.currencies[r.currency];if(p)switch(r.currencyDisplay){case"code":o=r.currency;break;case"symbol":o=p.symbol;break;default:o=p.narrow}else o=r.currency}if(h)i=h;else if("decimal"===u||"unit"===u||"currency"===u&&"name"===r.currencyDisplay)i=ge((t.numbers.decimal[f]||t.numbers.decimal[d]).standard,a);else if("currency"===u){i=ge((v=t.numbers.currency[f]||t.numbers.currency[d])[r.currencySign],a)}else{i=ge(t.numbers.percent[f]||t.numbers.percent[d],a)}var z=pe.exec(i)[0];if(i=i.replace(pe,"{0}").replace(/'(.)'/g,"$1"),"currency"===u&&"name"!==r.currencyDisplay){var v,g=(v=t.numbers.currency[f]||t.numbers.currency[d]).currencySpacing.afterInsertBetween;g&&!he.test(o)&&(i=i.replace("\xa4{0}","\xa4"+g+"{0}"));var m=v.currencySpacing.beforeInsertBetween;m&&!de.test(o)&&(i=i.replace("{0}\xa4","{0}"+m+"\xa4"))}for(var y=i.split(/({c:[^}]+}|\{0\}|[\xa4%\-\+])/g),b=[],w=t.numbers.symbols[f]||t.numbers.symbols[d],k=0,x=y;k0?(f=s.slice(0,h),d=s.slice(h+1)):f=s,i&&("compact"!==n||l>=1e4)){var p=e.group,z=[],v=a.split(".")[0].split(","),g=3,m=3;v.length>1&&(g=v[v.length-1].length),v.length>2&&(m=v[v.length-2].length);var y=f.length-g;if(y>0){for(z.push(f.slice(y,y+g)),y-=m;y>0;y-=m)z.push(f.slice(y,y+m));z.push(f.slice(0,y+m))}else z.push(f);for(;z.length>0;){var b=z.pop();c.push({type:"integer",value:b}),z.length>0&&c.push({type:"group",value:p})}}else c.push({type:"integer",value:f});if(void 0!==d&&c.push({type:"decimal",value:e.decimal},{type:"fraction",value:d}),("scientific"===n||"engineering"===n)&&isFinite(l)){c.push({type:"exponentSeparator",value:e.exponential}),r<0&&(c.push({type:"exponentMinusSign",value:e.minusSign}),r=-r);var w=ae(r,0,0);c.push({type:"exponentInteger",value:w.formattedString})}return c}function ge(e,t){e.indexOf(";")<0&&(e=e+";-"+e);var n=e.split(";"),r=n[0],o=n[1];switch(t){case 0:return r;case-1:return o;default:return o.indexOf("-")>=0?o.replace(/-/g,"+"):"+"+r}}function me(e,t,n){return n[e.select(t)]||n.other}function ye(e,t,n){var r,o,i,a=n.getInternalSlots,c=a(e),s=c.pl,u=c.dataLocaleData,f=c.numberingSystem,d=u.numbers.symbols[f]||u.numbers.symbols[u.numbers.nu[0]],h=0,p=0;if(isNaN(t))o=d.nan;else if(isFinite(t)){"percent"===c.style&&(t*=100),p=(r=se(e,t,{getInternalSlots:a}))[0],h=r[1];var z=ce(c,t=p<0?t*Math.pow(10,-p):t/Math.pow(10,p));o=z.formattedString,t=z.roundedNumber}else o=d.infinity;switch(c.signDisplay){case"never":i=0;break;case"auto":i=l(t,0)||t>0||isNaN(t)?0:-1;break;case"always":i=l(t,0)||t>0||isNaN(t)?1:-1;break;default:i=0===t||isNaN(t)?0:t>0?1:-1}return ze({roundedNumber:t,formattedString:o,exponent:p,magnitude:h,sign:i},c.dataLocaleData,s,c)}function be(e,t,n){for(var r=ye(e,t,n),o=u(0),i=0,a=r;i-1;)re((r=e.indexOf("}",n))>n,"Invalid pattern "+e),n>o&&t.push({type:"literal",value:e.substring(o,n)}),t.push({type:e.substring(n+1,r),value:void 0}),o=r+1,n=e.indexOf("{",o);return ou)return-1;null!==s&&void 0!==s||(s=0);var f,d=function(e){return e>=0&&ea){if(s>0&&d(f=h-1)&&c[f]a)return h;e=a,t=c,n=s,r=h+1,o=u,i=!0,d=h=p=f=void 0}}},t.dateParser=function(e){var t,n;if((-1==e.search("-")||-1!=e.search("T")||-1!=e.search("Z"))&&(n=z(e))&&!isNaN(n))return n;if(-1!=e.search("-")){for(t=e.replace("-","/","g");-1!=t.search("-");)t=t.replace("-","/");n=z(t)}else 8==e.length?(t=e.substr(0,4)+"/"+e.substr(4,2)+"/"+e.substr(6,2),n=z(t)):n=z(e);n&&!isNaN(n)||console.error("Couldn't parse "+e+" as a date");return n},t.dateStrToMillis=z,t.update=function(e,t){if("undefined"!=typeof t&&null!==t)for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e},t.updateDeep=function e(t,n){function r(e){return"object"===typeof Node?e instanceof Node:"object"===typeof e&&"number"===typeof e.nodeType&&"string"===typeof e.nodeName}if("undefined"!=typeof n&&null!==n)for(var o in n)n.hasOwnProperty(o)&&(null===n[o]?t[o]=null:v(n[o])?t[o]=n[o].slice():r(n[o])?t[o]=n[o]:"object"==typeof n[o]?("object"==typeof t[o]&&null!==t[o]||(t[o]={}),e(t[o],n[o])):t[o]=n[o]);return t},t.isArrayLike=v,t.isDateLike=function(e){if("object"!=typeof e||null===e||"function"!=typeof e.getTime)return!1;return!0},t.clone=function e(t){for(var n=[],r=0;r=t||m.call(window,(function(){var t=(new Date).getTime()-a;o=i;var l=(i=Math.floor(t/n))-o;i+l>c||i>=c?(e(c),r()):(0!==l&&e(i),s())}))}()},t.isPixelChangingOptionList=function(e,t){var n={};if(e)for(var r=1;r=r.Granularity.DECADAL)return""+i;if(t>=r.Granularity.MONTHLY)return q[a]+" "+i;if(0===3600*s+60*h+p+.001*z||t>=r.Granularity.DAILY)return l(c)+" "+q[a];if(tr.Granularity.MINUTELY?d(s,h,p,0):d(s,h,p,z)},t.dateValueFormatter=function(e,t){return h(e,t("labelsUTC"))};var r=function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}(n(209));t.LOG_SCALE=10;var o=Math.log(10);t.LN_TEN=o;var i=function(e){return Math.log(e)/o};t.log10=i;t.logRangeFraction=function(e,t,n){var r=i(e),o=r+n*(i(t)-r);return Math.pow(10,o)};t.DOTTED_LINE=[2,2];t.DASHED_LINE=[7,3];t.DOT_DASH_LINE=[7,2,2,2];t.HORIZONTAL=1;t.VERTICAL=2;t.getContext=function(e){return e.getContext("2d")};function a(e){return!e.pageX||e.pageX<0?0:e.pageX}function c(e){return!e.pageY||e.pageY<0?0:e.pageY}function s(e,t){var n=Math.min(Math.max(1,t||2),21);return Math.abs(e)<.001&&0!==e?e.toExponential(n-1):e.toPrecision(n)}function l(e){return e<10?"0"+e:""+e}t.addEvent=function(e,t,n){e.addEventListener(t,n,!1)};var u={getFullYear:function(e){return e.getFullYear()},getMonth:function(e){return e.getMonth()},getDate:function(e){return e.getDate()},getHours:function(e){return e.getHours()},getMinutes:function(e){return e.getMinutes()},getSeconds:function(e){return e.getSeconds()},getMilliseconds:function(e){return e.getMilliseconds()},getDay:function(e){return e.getDay()},makeDate:function(e,t,n,r,o,i,a){return new Date(e,t,n,r,o,i,a)}};t.DateAccessorsLocal=u;var f={getFullYear:function(e){return e.getUTCFullYear()},getMonth:function(e){return e.getUTCMonth()},getDate:function(e){return e.getUTCDate()},getHours:function(e){return e.getUTCHours()},getMinutes:function(e){return e.getUTCMinutes()},getSeconds:function(e){return e.getUTCSeconds()},getMilliseconds:function(e){return e.getUTCMilliseconds()},getDay:function(e){return e.getUTCDay()},makeDate:function(e,t,n,r,o,i,a){return new Date(Date.UTC(e,t,n,r,o,i,a))}};function d(e,t,n,r){var o=l(e)+":"+l(t);if(n&&(o+=":"+l(n),r)){var i=""+r;o+="."+("000"+i).substring(i.length)}return o}function h(e,t){var n=t?f:u,r=new Date(e),o=n.getFullYear(r),i=n.getMonth(r),a=n.getDate(r),c=n.getHours(r),s=n.getMinutes(r),h=n.getSeconds(r),p=n.getMilliseconds(r),z=""+o+"/"+l(i+1)+"/"+l(a);return 3600*c+60*s+h+.001*p&&(z+=" "+d(c,s,h,p)),z}function p(e,t){var n=Math.pow(10,t);return Math.round(e*n)/n}function z(e){return new Date(e).getTime()}function v(e){var t=typeof e;return("object"==t||"function"==t&&"function"==typeof e.item)&&null!==e&&"number"==typeof e.length&&3!==e.nodeType}function g(e,t,n,r){t=t||0,n=n||e.length,this.hasNext=!0,this.peek=null,this.start_=t,this.array_=e,this.predicate_=r,this.end_=Math.min(e.length,t+n),this.nextIdx_=t-1,this.next()}t.DateAccessorsUTC=f,g.prototype.next=function(){if(!this.hasNext)return null;for(var e=this.peek,t=this.nextIdx_+1,n=!1;t=Math.pow(10,i)||Math.abs(e)=0;z--,h/=l)if(d>=h){r=p(e/h,o)+u[z];break}if(c){var v=String(e.toExponential()).split("e-");2===v.length&&v[1]>=3&&v[1]<=24&&(r=v[1]%3>0?p(v[0]/w(10,v[1]%3),o):Number(v[0]).toFixed(2),r+=f[Math.floor(v[1]/3)-1])}}return r}var q=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]},function(e,t,n){"use strict";t.a=Array.isArray||function(e){return null!=e&&e.length>=0&&"[object Array]"===Object.prototype.toString.call(e)}},function(e,t,n){"use strict";function r(e){return"[object String]"===Object.prototype.toString.call(e)}n.d(t,"a",(function(){return r}))},function(e,t,n){"use strict";n.d(t,"a",(function(){return a}));var r=n(26),o=n(24),i=n(20);function a(e,t){Object(i.a)(1,arguments);var n=t||{},a=n.locale,c=a&&a.options&&a.options.weekStartsOn,s=null==c?0:Object(r.a)(c),l=null==n.weekStartsOn?s:Object(r.a)(n.weekStartsOn);if(!(l>=0&&l<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var u=Object(o.a)(e),f=u.getUTCDay(),d=(f=0&&l<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var u=Object(o.default)(e),f=u.getUTCDay(),d=(f0&&t.forEach((function(e){i.remove(e)})),i.appendTo(n.scrollbarXRail,e)),e.contains(n.scrollbarYRail)||((t=i.queryChildren(e,".ps-scrollbar-y-rail")).length>0&&t.forEach((function(e){i.remove(e)})),i.appendTo(n.scrollbarYRail,e)),!n.settings.suppressScrollX&&n.containerWidth+n.settings.scrollXMarginOffset=n.railXWidth-n.scrollbarXWidth&&(n.scrollbarXLeft=n.railXWidth-n.scrollbarXWidth),n.scrollbarYTop>=n.railYHeight-n.scrollbarYHeight&&(n.scrollbarYTop=n.railYHeight-n.scrollbarYHeight),function(e,t){var n={width:t.railXWidth};t.isRtl?n.left=t.negativeScrollAdjustment+e.scrollLeft+t.containerWidth-t.contentWidth:n.left=e.scrollLeft,t.isScrollbarXUsingBottom?n.bottom=t.scrollbarXBottom-e.scrollTop:n.top=t.scrollbarXTop+e.scrollTop,i.css(t.scrollbarXRail,n);var r={top:e.scrollTop,height:t.railYHeight};t.isScrollbarYUsingRight?t.isRtl?r.right=t.contentWidth-(t.negativeScrollAdjustment+e.scrollLeft)-t.scrollbarYRight-t.scrollbarYOuterWidth:r.right=t.scrollbarYRight-e.scrollLeft:t.isRtl?r.left=t.negativeScrollAdjustment+e.scrollLeft+2*t.containerWidth-t.contentWidth-t.scrollbarYLeft-t.scrollbarYOuterWidth:r.left=t.scrollbarYLeft+e.scrollLeft,i.css(t.scrollbarYRail,r),i.css(t.scrollbarX,{left:t.scrollbarXLeft,width:t.scrollbarXWidth-t.railBorderXWidth}),i.css(t.scrollbarY,{top:t.scrollbarYTop,height:t.scrollbarYHeight-t.railBorderYWidth})}(e,n),n.scrollbarXActive?o.add(e,"ps-active-x"):(o.remove(e,"ps-active-x"),n.scrollbarXWidth=0,n.scrollbarXLeft=0,c(e,"left",0)),n.scrollbarYActive?o.add(e,"ps-active-y"):(o.remove(e,"ps-active-y"),n.scrollbarYHeight=0,n.scrollbarYTop=0,c(e,"top",0))}},function(e,t,n){"use strict";e.exports=function(e,t,n,r,o,i,a,c){if(!e){var s;if(void 0===t)s=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var l=[n,r,o,i,a,c],u=0;(s=new Error(t.replace(/%s/g,(function(){return l[u++]})))).name="Invariant Violation"}throw s.framesToPop=1,s}}},function(e,t,n){"use strict";function r(e){setTimeout((function(){throw e}),0)}n.d(t,"a",(function(){return r}))},,,function(e,t){var n,r,o=e.exports={};function i(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function c(e){if(n===setTimeout)return setTimeout(e,0);if((n===i||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"===typeof setTimeout?setTimeout:i}catch(e){n=i}try{r="function"===typeof clearTimeout?clearTimeout:a}catch(e){r=a}}();var s,l=[],u=!1,f=-1;function d(){u&&s&&(u=!1,s.length?l=s.concat(l):f=-1,l.length&&h())}function h(){if(!u){var e=c(d);u=!0;for(var t=l.length;t;){for(s=l,l=[];++f1)for(var n=1;n=c.contentHeight-c.containerHeight&&((n=c.contentHeight-c.containerHeight)-e.scrollTop<=1?n=e.scrollTop:e.scrollTop=n,e.dispatchEvent(a("ps-y-reach-end"))),"left"===t&&n>=c.contentWidth-c.containerWidth&&((n=c.contentWidth-c.containerWidth)-e.scrollLeft<=1?n=e.scrollLeft:e.scrollLeft=n,e.dispatchEvent(a("ps-x-reach-end"))),r||(r=e.scrollTop),o||(o=e.scrollLeft),"top"===t&&nr&&e.dispatchEvent(a("ps-scroll-down")),"left"===t&&no&&e.dispatchEvent(a("ps-scroll-right")),"top"===t&&(e.scrollTop=r=n,e.dispatchEvent(a("ps-scroll-y"))),"left"===t&&(e.scrollLeft=o=n,e.dispatchEvent(a("ps-scroll-x")))}},function(e,t,n){"use strict";function r(){return"function"===typeof Symbol&&Symbol.iterator?Symbol.iterator:"@@iterator"}n.d(t,"a",(function(){return o}));var o=r()},function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));var r=n(32),o=function(e){function t(t,n,r){var o=e.call(this)||this;return o.parent=t,o.outerValue=n,o.outerIndex=r,o.index=0,o}return r.b(t,e),t.prototype._next=function(e){this.parent.notifyNext(this.outerValue,e,this.outerIndex,this.index++,this)},t.prototype._error=function(e){this.parent.notifyError(e,this),this.unsubscribe()},t.prototype._complete=function(){this.parent.notifyComplete(this),this.unsubscribe()},t}(n(53).a)},function(e,t,n){"use strict";n.d(t,"a",(function(){return r}));var r=function(){function e(e){void 0===e&&(e={}),this.adapter_=e}return Object.defineProperty(e,"cssClasses",{get:function(){return{}},enumerable:!0,configurable:!0}),Object.defineProperty(e,"strings",{get:function(){return{}},enumerable:!0,configurable:!0}),Object.defineProperty(e,"numbers",{get:function(){return{}},enumerable:!0,configurable:!0}),Object.defineProperty(e,"defaultAdapter",{get:function(){return{}},enumerable:!0,configurable:!0}),e.prototype.init=function(){},e.prototype.destroy=function(){},e}()},function(e,t,n){"use strict";var r=n(31),o=n(70),i=n(71),a=Object(r.a)((function(e){return!!Object(o.a)(e)||!!e&&("object"===typeof e&&(!Object(i.a)(e)&&(1===e.nodeType?!!e.length:0===e.length||e.length>0&&(e.hasOwnProperty(0)&&e.hasOwnProperty(e.length-1)))))})),c=function(){function e(e){this.f=e}return e.prototype["@@transducer/init"]=function(){throw new Error("init not implemented on XWrap")},e.prototype["@@transducer/result"]=function(e){return e},e.prototype["@@transducer/step"]=function(e,t){return this.f(e,t)},e}();var s=n(68),l=n(19),u=Object(l.a)((function(e,t){return Object(s.a)(e.length,(function(){return e.apply(t,arguments)}))}));function f(e,t,n){for(var r=n.next();!r.done;){if((t=e["@@transducer/step"](t,r.value))&&t["@@transducer/reduced"]){t=t["@@transducer/value"];break}r=n.next()}return e["@@transducer/result"](t)}function d(e,t,n,r){return e["@@transducer/result"](n[r](u(e["@@transducer/step"],e),t))}n.d(t,"a",(function(){return p}));var h="undefined"!==typeof Symbol?Symbol.iterator:"@@iterator";function p(e,t,n){if("function"===typeof e&&(e=function(e){return new c(e)}(e)),a(n))return function(e,t,n){for(var r=0,o=n.length;r>>0;for(t=0;t0)for(n=0;n=0?n?"+":"":"-")+Math.pow(10,Math.max(0,o)).toString().substr(1)+r}r.suppressDeprecationWarnings=!1,r.deprecationHandler=null,j=Object.keys?Object.keys:function(e){var t,n=[];for(t in e)a(e,t)&&n.push(t);return n};var E=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,T=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,A={},L={};function H(e,t,n,r){var o=r;"string"===typeof r&&(o=function(){return this[r]()}),e&&(L[e]=o),t&&(L[t[0]]=function(){return O(o.apply(this,arguments),t[1],t[2])}),n&&(L[n]=function(){return this.localeData().ordinal(o.apply(this,arguments),e)})}function D(e,t){return e.isValid()?(t=P(t,e.localeData()),A[t]=A[t]||function(e){var t,n,r,o=e.match(E);for(t=0,n=o.length;t=0&&T.test(e);)e=e.replace(T,r),T.lastIndex=0,n-=1;return e}var V={};function R(e,t){var n=e.toLowerCase();V[n]=V[n+"s"]=V[t]=e}function I(e){return"string"===typeof e?V[e]||V[e.toLowerCase()]:void 0}function N(e){var t,n,r={};for(n in e)a(e,n)&&(t=I(n))&&(r[t]=e[n]);return r}var F={};function B(e,t){F[e]=t}function U(e){return e%4===0&&e%100!==0||e%400===0}function W(e){return e<0?Math.ceil(e)||0:Math.floor(e)}function G(e){var t=+e,n=0;return 0!==t&&isFinite(t)&&(n=W(t)),n}function Y(e,t){return function(n){return null!=n?(Z(this,e,n),r.updateOffset(this,t),this):$(this,e)}}function $(e,t){return e.isValid()?e._d["get"+(e._isUTC?"UTC":"")+t]():NaN}function Z(e,t,n){e.isValid()&&!isNaN(n)&&("FullYear"===t&&U(e.year())&&1===e.month()&&29===e.date()?(n=G(n),e._d["set"+(e._isUTC?"UTC":"")+t](n,e.month(),Oe(n,e.month()))):e._d["set"+(e._isUTC?"UTC":"")+t](n))}var X,K=/\d/,Q=/\d\d/,J=/\d{3}/,ee=/\d{4}/,te=/[+-]?\d{6}/,ne=/\d\d?/,re=/\d\d\d\d?/,oe=/\d\d\d\d\d\d?/,ie=/\d{1,3}/,ae=/\d{1,4}/,ce=/[+-]?\d{1,6}/,se=/\d+/,le=/[+-]?\d+/,ue=/Z|[+-]\d\d:?\d\d/gi,fe=/Z|[+-]\d\d(?::?\d\d)?/gi,de=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;function he(e,t,n){X[e]=_(t)?t:function(e,r){return e&&n?n:t}}function pe(e,t){return a(X,e)?X[e](t._strict,t._locale):new RegExp(ze(e.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,(function(e,t,n,r,o){return t||n||r||o}))))}function ze(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}X={};var ve={};function ge(e,t){var n,r=t;for("string"===typeof e&&(e=[e]),l(t)&&(r=function(e,n){n[t]=G(e)}),n=0;n68?1900:2e3)};var Ne=Y("FullYear",!0);function Fe(e,t,n,r,o,i,a){var c;return e<100&&e>=0?(c=new Date(e+400,t,n,r,o,i,a),isFinite(c.getFullYear())&&c.setFullYear(e)):c=new Date(e,t,n,r,o,i,a),c}function Be(e){var t,n;return e<100&&e>=0?((n=Array.prototype.slice.call(arguments))[0]=e+400,t=new Date(Date.UTC.apply(null,n)),isFinite(t.getUTCFullYear())&&t.setUTCFullYear(e)):t=new Date(Date.UTC.apply(null,arguments)),t}function Ue(e,t,n){var r=7+t-n;return-(7+Be(e,0,r).getUTCDay()-t)%7+r-1}function We(e,t,n,r,o){var i,a,c=1+7*(t-1)+(7+n-r)%7+Ue(e,r,o);return c<=0?a=Ie(i=e-1)+c:c>Ie(e)?(i=e+1,a=c-Ie(e)):(i=e,a=c),{year:i,dayOfYear:a}}function Ge(e,t,n){var r,o,i=Ue(e.year(),t,n),a=Math.floor((e.dayOfYear()-i-1)/7)+1;return a<1?r=a+Ye(o=e.year()-1,t,n):a>Ye(e.year(),t,n)?(r=a-Ye(e.year(),t,n),o=e.year()+1):(o=e.year(),r=a),{week:r,year:o}}function Ye(e,t,n){var r=Ue(e,t,n),o=Ue(e+1,t,n);return(Ie(e)-r+o)/7}function $e(e,t){return e.slice(t,7).concat(e.slice(0,t))}H("w",["ww",2],"wo","week"),H("W",["WW",2],"Wo","isoWeek"),R("week","w"),R("isoWeek","W"),B("week",5),B("isoWeek",5),he("w",ne),he("ww",ne,Q),he("W",ne),he("WW",ne,Q),me(["w","ww","W","WW"],(function(e,t,n,r){t[r.substr(0,1)]=G(e)})),H("d",0,"do","day"),H("dd",0,0,(function(e){return this.localeData().weekdaysMin(this,e)})),H("ddd",0,0,(function(e){return this.localeData().weekdaysShort(this,e)})),H("dddd",0,0,(function(e){return this.localeData().weekdays(this,e)})),H("e",0,0,"weekday"),H("E",0,0,"isoWeekday"),R("day","d"),R("weekday","e"),R("isoWeekday","E"),B("day",11),B("weekday",11),B("isoWeekday",11),he("d",ne),he("e",ne),he("E",ne),he("dd",(function(e,t){return t.weekdaysMinRegex(e)})),he("ddd",(function(e,t){return t.weekdaysShortRegex(e)})),he("dddd",(function(e,t){return t.weekdaysRegex(e)})),me(["dd","ddd","dddd"],(function(e,t,n,r){var o=n._locale.weekdaysParse(e,r,n._strict);null!=o?t.d=o:p(n).invalidWeekday=e})),me(["d","e","E"],(function(e,t,n,r){t[r]=G(e)}));var Ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Xe="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Ke="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Qe=de,Je=de,et=de;function tt(e,t,n){var r,o,i,a=e.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],r=0;r<7;++r)i=h([2e3,1]).day(r),this._minWeekdaysParse[r]=this.weekdaysMin(i,"").toLocaleLowerCase(),this._shortWeekdaysParse[r]=this.weekdaysShort(i,"").toLocaleLowerCase(),this._weekdaysParse[r]=this.weekdays(i,"").toLocaleLowerCase();return n?"dddd"===t?-1!==(o=be.call(this._weekdaysParse,a))?o:null:"ddd"===t?-1!==(o=be.call(this._shortWeekdaysParse,a))?o:null:-1!==(o=be.call(this._minWeekdaysParse,a))?o:null:"dddd"===t?-1!==(o=be.call(this._weekdaysParse,a))?o:-1!==(o=be.call(this._shortWeekdaysParse,a))?o:-1!==(o=be.call(this._minWeekdaysParse,a))?o:null:"ddd"===t?-1!==(o=be.call(this._shortWeekdaysParse,a))?o:-1!==(o=be.call(this._weekdaysParse,a))?o:-1!==(o=be.call(this._minWeekdaysParse,a))?o:null:-1!==(o=be.call(this._minWeekdaysParse,a))?o:-1!==(o=be.call(this._weekdaysParse,a))?o:-1!==(o=be.call(this._shortWeekdaysParse,a))?o:null}function nt(){function e(e,t){return t.length-e.length}var t,n,r,o,i,a=[],c=[],s=[],l=[];for(t=0;t<7;t++)n=h([2e3,1]).day(t),r=ze(this.weekdaysMin(n,"")),o=ze(this.weekdaysShort(n,"")),i=ze(this.weekdays(n,"")),a.push(r),c.push(o),s.push(i),l.push(r),l.push(o),l.push(i);a.sort(e),c.sort(e),s.sort(e),l.sort(e),this._weekdaysRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+c.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function rt(){return this.hours()%12||12}function ot(e,t){H(e,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)}))}function it(e,t){return t._meridiemParse}H("H",["HH",2],0,"hour"),H("h",["hh",2],0,rt),H("k",["kk",2],0,(function(){return this.hours()||24})),H("hmm",0,0,(function(){return""+rt.apply(this)+O(this.minutes(),2)})),H("hmmss",0,0,(function(){return""+rt.apply(this)+O(this.minutes(),2)+O(this.seconds(),2)})),H("Hmm",0,0,(function(){return""+this.hours()+O(this.minutes(),2)})),H("Hmmss",0,0,(function(){return""+this.hours()+O(this.minutes(),2)+O(this.seconds(),2)})),ot("a",!0),ot("A",!1),R("hour","h"),B("hour",13),he("a",it),he("A",it),he("H",ne),he("h",ne),he("k",ne),he("HH",ne,Q),he("hh",ne,Q),he("kk",ne,Q),he("hmm",re),he("hmmss",oe),he("Hmm",re),he("Hmmss",oe),ge(["H","HH"],je),ge(["k","kk"],(function(e,t,n){var r=G(e);t[je]=24===r?0:r})),ge(["a","A"],(function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e})),ge(["h","hh"],(function(e,t,n){t[je]=G(e),p(n).bigHour=!0})),ge("hmm",(function(e,t,n){var r=e.length-2;t[je]=G(e.substr(0,r)),t[Me]=G(e.substr(r)),p(n).bigHour=!0})),ge("hmmss",(function(e,t,n){var r=e.length-4,o=e.length-2;t[je]=G(e.substr(0,r)),t[Me]=G(e.substr(r,2)),t[Ce]=G(e.substr(o)),p(n).bigHour=!0})),ge("Hmm",(function(e,t,n){var r=e.length-2;t[je]=G(e.substr(0,r)),t[Me]=G(e.substr(r))})),ge("Hmmss",(function(e,t,n){var r=e.length-4,o=e.length-2;t[je]=G(e.substr(0,r)),t[Me]=G(e.substr(r,2)),t[Ce]=G(e.substr(o))}));var at,ct=Y("Hours",!0),st={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ee,monthsShort:Te,week:{dow:0,doy:6},weekdays:Ze,weekdaysMin:Ke,weekdaysShort:Xe,meridiemParse:/[ap]\.?m?\.?/i},lt={},ut={};function ft(e,t){var n,r=Math.min(e.length,t.length);for(n=0;n0;){if(r=ht(o.slice(0,t).join("-")))return r;if(n&&n.length>=t&&ft(o,n)>=t-1)break;t--}i++}return at}(e)}function gt(e){var t,n=e._a;return n&&-2===p(e).overflow&&(t=n[ke]<0||n[ke]>11?ke:n[xe]<1||n[xe]>Oe(n[we],n[ke])?xe:n[je]<0||n[je]>24||24===n[je]&&(0!==n[Me]||0!==n[Ce]||0!==n[_e])?je:n[Me]<0||n[Me]>59?Me:n[Ce]<0||n[Ce]>59?Ce:n[_e]<0||n[_e]>999?_e:-1,p(e)._overflowDayOfYear&&(txe)&&(t=xe),p(e)._overflowWeeks&&-1===t&&(t=qe),p(e)._overflowWeekday&&-1===t&&(t=Se),p(e).overflow=t),e}var mt=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,yt=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,bt=/Z|[+-]\d\d(?::?\d\d)?/,wt=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/],["YYYYMM",/\d{6}/,!1],["YYYY",/\d{4}/,!1]],kt=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],xt=/^\/?Date\((-?\d+)/i,jt=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,Mt={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function Ct(e){var t,n,r,o,i,a,c=e._i,s=mt.exec(c)||yt.exec(c);if(s){for(p(e).iso=!0,t=0,n=wt.length;t7)&&(s=!0)):(i=e._locale._week.dow,a=e._locale._week.doy,l=Ge(Lt(),i,a),n=St(t.gg,e._a[we],l.year),r=St(t.w,l.week),null!=t.d?((o=t.d)<0||o>6)&&(s=!0):null!=t.e?(o=t.e+i,(t.e<0||t.e>6)&&(s=!0)):o=i),r<1||r>Ye(n,i,a)?p(e)._overflowWeeks=!0:null!=s?p(e)._overflowWeekday=!0:(c=We(n,r,o,i,a),e._a[we]=c.year,e._dayOfYear=c.dayOfYear)}(e),null!=e._dayOfYear&&(a=St(e._a[we],o[we]),(e._dayOfYear>Ie(a)||0===e._dayOfYear)&&(p(e)._overflowDayOfYear=!0),n=Be(a,0,e._dayOfYear),e._a[ke]=n.getUTCMonth(),e._a[xe]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=c[t]=o[t];for(;t<7;t++)e._a[t]=c[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[je]&&0===e._a[Me]&&0===e._a[Ce]&&0===e._a[_e]&&(e._nextDay=!0,e._a[je]=0),e._d=(e._useUTC?Be:Fe).apply(null,c),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[je]=24),e._w&&"undefined"!==typeof e._w.d&&e._w.d!==i&&(p(e).weekdayMismatch=!0)}}function Et(e){if(e._f!==r.ISO_8601)if(e._f!==r.RFC_2822){e._a=[],p(e).empty=!0;var t,n,o,i,a,c,s=""+e._i,l=s.length,u=0;for(o=P(e._f,e._locale).match(E)||[],t=0;t0&&p(e).unusedInput.push(a),s=s.slice(s.indexOf(n)+n.length),u+=n.length),L[i]?(n?p(e).empty=!1:p(e).unusedTokens.push(i),ye(i,n,e)):e._strict&&!n&&p(e).unusedTokens.push(i);p(e).charsLeftOver=l-u,s.length>0&&p(e).unusedInput.push(s),e._a[je]<=12&&!0===p(e).bigHour&&e._a[je]>0&&(p(e).bigHour=void 0),p(e).parsedDateParts=e._a.slice(0),p(e).meridiem=e._meridiem,e._a[je]=function(e,t,n){var r;return null==n?t:null!=e.meridiemHour?e.meridiemHour(t,n):null!=e.isPM?((r=e.isPM(n))&&t<12&&(t+=12),r||12!==t||(t=0),t):t}(e._locale,e._a[je],e._meridiem),null!==(c=p(e).era)&&(e._a[we]=e._locale.erasConvertYear(c,e._a[we])),Ot(e),gt(e)}else qt(e);else Ct(e)}function Tt(e){var t=e._i,n=e._f;return e._locale=e._locale||vt(e._l),null===t||void 0===n&&""===t?v({nullInput:!0}):("string"===typeof t&&(e._i=t=e._locale.preparse(t)),w(t)?new b(gt(t)):(u(t)?e._d=t:o(n)?function(e){var t,n,r,o,i,a,c=!1;if(0===e._f.length)return p(e).invalidFormat=!0,void(e._d=new Date(NaN));for(o=0;othis?this:e:v()}));function Pt(e,t){var n,r;if(1===t.length&&o(t[0])&&(t=t[0]),!t.length)return Lt();for(n=t[0],r=1;r=0?new Date(e+400,t,n)-dn:new Date(e,t,n).valueOf()}function zn(e,t,n){return e<100&&e>=0?Date.UTC(e+400,t,n)-dn:Date.UTC(e,t,n)}function vn(e,t){return t.erasAbbrRegex(e)}function gn(){var e,t,n=[],r=[],o=[],i=[],a=this.eras();for(e=0,t=a.length;e(i=Ye(e,r,o))&&(t=i),bn.call(this,e,t,n,r,o))}function bn(e,t,n,r,o){var i=We(e,t,n,r,o),a=Be(i.year,0,i.dayOfYear);return this.year(a.getUTCFullYear()),this.month(a.getUTCMonth()),this.date(a.getUTCDate()),this}H("N",0,0,"eraAbbr"),H("NN",0,0,"eraAbbr"),H("NNN",0,0,"eraAbbr"),H("NNNN",0,0,"eraName"),H("NNNNN",0,0,"eraNarrow"),H("y",["y",1],"yo","eraYear"),H("y",["yy",2],0,"eraYear"),H("y",["yyy",3],0,"eraYear"),H("y",["yyyy",4],0,"eraYear"),he("N",vn),he("NN",vn),he("NNN",vn),he("NNNN",(function(e,t){return t.erasNameRegex(e)})),he("NNNNN",(function(e,t){return t.erasNarrowRegex(e)})),ge(["N","NN","NNN","NNNN","NNNNN"],(function(e,t,n,r){var o=n._locale.erasParse(e,r,n._strict);o?p(n).era=o:p(n).invalidEra=e})),he("y",se),he("yy",se),he("yyy",se),he("yyyy",se),he("yo",(function(e,t){return t._eraYearOrdinalRegex||se})),ge(["y","yy","yyy","yyyy"],we),ge(["yo"],(function(e,t,n,r){var o;n._locale._eraYearOrdinalRegex&&(o=e.match(n._locale._eraYearOrdinalRegex)),n._locale.eraYearOrdinalParse?t[we]=n._locale.eraYearOrdinalParse(e,o):t[we]=parseInt(e,10)})),H(0,["gg",2],0,(function(){return this.weekYear()%100})),H(0,["GG",2],0,(function(){return this.isoWeekYear()%100})),mn("gggg","weekYear"),mn("ggggg","weekYear"),mn("GGGG","isoWeekYear"),mn("GGGGG","isoWeekYear"),R("weekYear","gg"),R("isoWeekYear","GG"),B("weekYear",1),B("isoWeekYear",1),he("G",le),he("g",le),he("GG",ne,Q),he("gg",ne,Q),he("GGGG",ae,ee),he("gggg",ae,ee),he("GGGGG",ce,te),he("ggggg",ce,te),me(["gggg","ggggg","GGGG","GGGGG"],(function(e,t,n,r){t[r.substr(0,2)]=G(e)})),me(["gg","GG"],(function(e,t,n,o){t[o]=r.parseTwoDigitYear(e)})),H("Q",0,"Qo","quarter"),R("quarter","Q"),B("quarter",7),he("Q",K),ge("Q",(function(e,t){t[ke]=3*(G(e)-1)})),H("D",["DD",2],"Do","date"),R("date","D"),B("date",9),he("D",ne),he("DD",ne,Q),he("Do",(function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient})),ge(["D","DD"],xe),ge("Do",(function(e,t){t[xe]=G(e.match(ne)[0])}));var wn=Y("Date",!0);H("DDD",["DDDD",3],"DDDo","dayOfYear"),R("dayOfYear","DDD"),B("dayOfYear",4),he("DDD",ie),he("DDDD",J),ge(["DDD","DDDD"],(function(e,t,n){n._dayOfYear=G(e)})),H("m",["mm",2],0,"minute"),R("minute","m"),B("minute",14),he("m",ne),he("mm",ne,Q),ge(["m","mm"],Me);var kn=Y("Minutes",!1);H("s",["ss",2],0,"second"),R("second","s"),B("second",15),he("s",ne),he("ss",ne,Q),ge(["s","ss"],Ce);var xn,jn,Mn=Y("Seconds",!1);for(H("S",0,0,(function(){return~~(this.millisecond()/100)})),H(0,["SS",2],0,(function(){return~~(this.millisecond()/10)})),H(0,["SSS",3],0,"millisecond"),H(0,["SSSS",4],0,(function(){return 10*this.millisecond()})),H(0,["SSSSS",5],0,(function(){return 100*this.millisecond()})),H(0,["SSSSSS",6],0,(function(){return 1e3*this.millisecond()})),H(0,["SSSSSSS",7],0,(function(){return 1e4*this.millisecond()})),H(0,["SSSSSSSS",8],0,(function(){return 1e5*this.millisecond()})),H(0,["SSSSSSSSS",9],0,(function(){return 1e6*this.millisecond()})),R("millisecond","ms"),B("millisecond",16),he("S",ie,K),he("SS",ie,Q),he("SSS",ie,J),xn="SSSS";xn.length<=9;xn+="S")he(xn,se);function Cn(e,t){t[_e]=G(1e3*("0."+e))}for(xn="S";xn.length<=9;xn+="S")ge(xn,Cn);jn=Y("Milliseconds",!1),H("z",0,0,"zoneAbbr"),H("zz",0,0,"zoneName");var _n=b.prototype;function qn(e){return e}_n.add=tn,_n.calendar=function(e,t){var n;1===arguments.length&&(arguments[0]?w(n=arguments[0])||u(n)||rn(n)||l(n)||function(e){var t=o(e),n=!1;return t&&(n=0===e.filter((function(t){return!l(t)&&rn(e)})).length),t&&n}(n)||function(e){var t,n,r=i(e)&&!c(e),o=!1,s=["years","year","y","months","month","M","days","day","d","dates","date","D","hours","hour","h","minutes","minute","m","seconds","second","s","milliseconds","millisecond","ms"];for(t=0;tn.valueOf():n.valueOf()9999?D(n,t?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):_(Date.prototype.toISOString)?t?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace("Z",D(n,"Z")):D(n,t?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},_n.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e,t,n,r="moment",o="";return this.isLocal()||(r=0===this.utcOffset()?"moment.utc":"moment.parseZone",o="Z"),e="["+r+'("]',t=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",n=o+'[")]',this.format(e+t+"-MM-DD[T]HH:mm:ss.SSS"+n)},"undefined"!==typeof Symbol&&null!=Symbol.for&&(_n[Symbol.for("nodejs.util.inspect.custom")]=function(){return"Moment<"+this.format()+">"}),_n.toJSON=function(){return this.isValid()?this.toISOString():null},_n.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},_n.unix=function(){return Math.floor(this.valueOf()/1e3)},_n.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},_n.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},_n.eraName=function(){var e,t,n,r=this.localeData().eras();for(e=0,t=r.length;ethis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},_n.isLocal=function(){return!!this.isValid()&&!this._isUTC},_n.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},_n.isUtc=Yt,_n.isUTC=Yt,_n.zoneAbbr=function(){return this._isUTC?"UTC":""},_n.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},_n.dates=x("dates accessor is deprecated. Use date instead.",wn),_n.months=x("months accessor is deprecated. Use month instead",Ve),_n.years=x("years accessor is deprecated. Use year instead",Ne),_n.zone=x("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",(function(e,t){return null!=e?("string"!==typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()})),_n.isDSTShifted=x("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",(function(){if(!s(this._isDSTShifted))return this._isDSTShifted;var e,t={};return y(t,this),(t=Tt(t))._a?(e=t._isUTC?h(t._a):Lt(t._a),this._isDSTShifted=this.isValid()&&function(e,t,n){var r,o=Math.min(e.length,t.length),i=Math.abs(e.length-t.length),a=0;for(r=0;r0):this._isDSTShifted=!1,this._isDSTShifted}));var Sn=S.prototype;function On(e,t,n,r){var o=vt(),i=h().set(r,t);return o[n](i,e)}function En(e,t,n){if(l(e)&&(t=e,e=void 0),e=e||"",null!=t)return On(e,t,n,"month");var r,o=[];for(r=0;r<12;r++)o[r]=On(e,r,n,"month");return o}function Tn(e,t,n,r){"boolean"===typeof e?(l(t)&&(n=t,t=void 0),t=t||""):(n=t=e,e=!1,l(t)&&(n=t,t=void 0),t=t||"");var o,i=vt(),a=e?i._week.dow:0,c=[];if(null!=n)return On(t,(n+a)%7,r,"day");for(o=0;o<7;o++)c[o]=On(t,(o+a)%7,r,"day");return c}Sn.calendar=function(e,t,n){var r=this._calendar[e]||this._calendar.sameElse;return _(r)?r.call(t,n):r},Sn.longDateFormat=function(e){var t=this._longDateFormat[e],n=this._longDateFormat[e.toUpperCase()];return t||!n?t:(this._longDateFormat[e]=n.match(E).map((function(e){return"MMMM"===e||"MM"===e||"DD"===e||"dddd"===e?e.slice(1):e})).join(""),this._longDateFormat[e])},Sn.invalidDate=function(){return this._invalidDate},Sn.ordinal=function(e){return this._ordinal.replace("%d",e)},Sn.preparse=qn,Sn.postformat=qn,Sn.relativeTime=function(e,t,n,r){var o=this._relativeTime[n];return _(o)?o(e,t,n,r):o.replace(/%d/i,e)},Sn.pastFuture=function(e,t){var n=this._relativeTime[e>0?"future":"past"];return _(n)?n(t):n.replace(/%s/i,t)},Sn.set=function(e){var t,n;for(n in e)a(e,n)&&(_(t=e[n])?this[n]=t:this["_"+n]=t);this._config=e,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},Sn.eras=function(e,t){var n,o,i,a=this._eras||vt("en")._eras;for(n=0,o=a.length;n=0)return s[r]},Sn.erasConvertYear=function(e,t){var n=e.since<=e.until?1:-1;return void 0===t?r(e.since).year():r(e.since).year()+(t-e.offset)*n},Sn.erasAbbrRegex=function(e){return a(this,"_erasAbbrRegex")||gn.call(this),e?this._erasAbbrRegex:this._erasRegex},Sn.erasNameRegex=function(e){return a(this,"_erasNameRegex")||gn.call(this),e?this._erasNameRegex:this._erasRegex},Sn.erasNarrowRegex=function(e){return a(this,"_erasNarrowRegex")||gn.call(this),e?this._erasNarrowRegex:this._erasRegex},Sn.months=function(e,t){return e?o(this._months)?this._months[e.month()]:this._months[(this._months.isFormat||Ae).test(t)?"format":"standalone"][e.month()]:o(this._months)?this._months:this._months.standalone},Sn.monthsShort=function(e,t){return e?o(this._monthsShort)?this._monthsShort[e.month()]:this._monthsShort[Ae.test(t)?"format":"standalone"][e.month()]:o(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},Sn.monthsParse=function(e,t,n){var r,o,i;if(this._monthsParseExact)return De.call(this,e,t,n);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),r=0;r<12;r++){if(o=h([2e3,r]),n&&!this._longMonthsParse[r]&&(this._longMonthsParse[r]=new RegExp("^"+this.months(o,"").replace(".","")+"$","i"),this._shortMonthsParse[r]=new RegExp("^"+this.monthsShort(o,"").replace(".","")+"$","i")),n||this._monthsParse[r]||(i="^"+this.months(o,"")+"|^"+this.monthsShort(o,""),this._monthsParse[r]=new RegExp(i.replace(".",""),"i")),n&&"MMMM"===t&&this._longMonthsParse[r].test(e))return r;if(n&&"MMM"===t&&this._shortMonthsParse[r].test(e))return r;if(!n&&this._monthsParse[r].test(e))return r}},Sn.monthsRegex=function(e){return this._monthsParseExact?(a(this,"_monthsRegex")||Re.call(this),e?this._monthsStrictRegex:this._monthsRegex):(a(this,"_monthsRegex")||(this._monthsRegex=He),this._monthsStrictRegex&&e?this._monthsStrictRegex:this._monthsRegex)},Sn.monthsShortRegex=function(e){return this._monthsParseExact?(a(this,"_monthsRegex")||Re.call(this),e?this._monthsShortStrictRegex:this._monthsShortRegex):(a(this,"_monthsShortRegex")||(this._monthsShortRegex=Le),this._monthsShortStrictRegex&&e?this._monthsShortStrictRegex:this._monthsShortRegex)},Sn.week=function(e){return Ge(e,this._week.dow,this._week.doy).week},Sn.firstDayOfYear=function(){return this._week.doy},Sn.firstDayOfWeek=function(){return this._week.dow},Sn.weekdays=function(e,t){var n=o(this._weekdays)?this._weekdays:this._weekdays[e&&!0!==e&&this._weekdays.isFormat.test(t)?"format":"standalone"];return!0===e?$e(n,this._week.dow):e?n[e.day()]:n},Sn.weekdaysMin=function(e){return!0===e?$e(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin},Sn.weekdaysShort=function(e){return!0===e?$e(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort},Sn.weekdaysParse=function(e,t,n){var r,o,i;if(this._weekdaysParseExact)return tt.call(this,e,t,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),r=0;r<7;r++){if(o=h([2e3,1]).day(r),n&&!this._fullWeekdaysParse[r]&&(this._fullWeekdaysParse[r]=new RegExp("^"+this.weekdays(o,"").replace(".","\\.?")+"$","i"),this._shortWeekdaysParse[r]=new RegExp("^"+this.weekdaysShort(o,"").replace(".","\\.?")+"$","i"),this._minWeekdaysParse[r]=new RegExp("^"+this.weekdaysMin(o,"").replace(".","\\.?")+"$","i")),this._weekdaysParse[r]||(i="^"+this.weekdays(o,"")+"|^"+this.weekdaysShort(o,"")+"|^"+this.weekdaysMin(o,""),this._weekdaysParse[r]=new RegExp(i.replace(".",""),"i")),n&&"dddd"===t&&this._fullWeekdaysParse[r].test(e))return r;if(n&&"ddd"===t&&this._shortWeekdaysParse[r].test(e))return r;if(n&&"dd"===t&&this._minWeekdaysParse[r].test(e))return r;if(!n&&this._weekdaysParse[r].test(e))return r}},Sn.weekdaysRegex=function(e){return this._weekdaysParseExact?(a(this,"_weekdaysRegex")||nt.call(this),e?this._weekdaysStrictRegex:this._weekdaysRegex):(a(this,"_weekdaysRegex")||(this._weekdaysRegex=Qe),this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex)},Sn.weekdaysShortRegex=function(e){return this._weekdaysParseExact?(a(this,"_weekdaysRegex")||nt.call(this),e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(a(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=Je),this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},Sn.weekdaysMinRegex=function(e){return this._weekdaysParseExact?(a(this,"_weekdaysRegex")||nt.call(this),e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(a(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=et),this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},Sn.isPM=function(e){return"p"===(e+"").toLowerCase().charAt(0)},Sn.meridiem=function(e,t,n){return e>11?n?"pm":"PM":n?"am":"AM"},pt("en",{eras:[{since:"0001-01-01",until:1/0,offset:1,name:"Anno Domini",narrow:"AD",abbr:"AD"},{since:"0000-12-31",until:-1/0,offset:1,name:"Before Christ",narrow:"BC",abbr:"BC"}],dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1===G(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")}}),r.lang=x("moment.lang is deprecated. Use moment.locale instead.",pt),r.langData=x("moment.langData is deprecated. Use moment.localeData instead.",vt);var An=Math.abs;function Ln(e,t,n,r){var o=Xt(t,n);return e._milliseconds+=r*o._milliseconds,e._days+=r*o._days,e._months+=r*o._months,e._bubble()}function Hn(e){return e<0?Math.floor(e):Math.ceil(e)}function Dn(e){return 4800*e/146097}function Pn(e){return 146097*e/4800}function Vn(e){return function(){return this.as(e)}}var Rn=Vn("ms"),In=Vn("s"),Nn=Vn("m"),Fn=Vn("h"),Bn=Vn("d"),Un=Vn("w"),Wn=Vn("M"),Gn=Vn("Q"),Yn=Vn("y");function $n(e){return function(){return this.isValid()?this._data[e]:NaN}}var Zn=$n("milliseconds"),Xn=$n("seconds"),Kn=$n("minutes"),Qn=$n("hours"),Jn=$n("days"),er=$n("months"),tr=$n("years"),nr=Math.round,rr={ss:44,s:45,m:45,h:22,d:26,w:null,M:11};function or(e,t,n,r,o){return o.relativeTime(t||1,!!n,e,r)}var ir=Math.abs;function ar(e){return(e>0)-(e<0)||+e}function cr(){if(!this.isValid())return this.localeData().invalidDate();var e,t,n,r,o,i,a,c,s=ir(this._milliseconds)/1e3,l=ir(this._days),u=ir(this._months),f=this.asSeconds();return f?(e=W(s/60),t=W(e/60),s%=60,e%=60,n=W(u/12),u%=12,r=s?s.toFixed(3).replace(/\.?0+$/,""):"",o=f<0?"-":"",i=ar(this._months)!==ar(f)?"-":"",a=ar(this._days)!==ar(f)?"-":"",c=ar(this._milliseconds)!==ar(f)?"-":"",o+"P"+(n?i+n+"Y":"")+(u?i+u+"M":"")+(l?a+l+"D":"")+(t||e||s?"T":"")+(t?c+t+"H":"")+(e?c+e+"M":"")+(s?c+r+"S":"")):"P0D"}var sr=Rt.prototype;return sr.isValid=function(){return this._isValid},sr.abs=function(){var e=this._data;return this._milliseconds=An(this._milliseconds),this._days=An(this._days),this._months=An(this._months),e.milliseconds=An(e.milliseconds),e.seconds=An(e.seconds),e.minutes=An(e.minutes),e.hours=An(e.hours),e.months=An(e.months),e.years=An(e.years),this},sr.add=function(e,t){return Ln(this,e,t,1)},sr.subtract=function(e,t){return Ln(this,e,t,-1)},sr.as=function(e){if(!this.isValid())return NaN;var t,n,r=this._milliseconds;if("month"===(e=I(e))||"quarter"===e||"year"===e)switch(t=this._days+r/864e5,n=this._months+Dn(t),e){case"month":return n;case"quarter":return n/3;case"year":return n/12}else switch(t=this._days+Math.round(Pn(this._months)),e){case"week":return t/7+r/6048e5;case"day":return t+r/864e5;case"hour":return 24*t+r/36e5;case"minute":return 1440*t+r/6e4;case"second":return 86400*t+r/1e3;case"millisecond":return Math.floor(864e5*t)+r;default:throw new Error("Unknown unit "+e)}},sr.asMilliseconds=Rn,sr.asSeconds=In,sr.asMinutes=Nn,sr.asHours=Fn,sr.asDays=Bn,sr.asWeeks=Un,sr.asMonths=Wn,sr.asQuarters=Gn,sr.asYears=Yn,sr.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*G(this._months/12):NaN},sr._bubble=function(){var e,t,n,r,o,i=this._milliseconds,a=this._days,c=this._months,s=this._data;return i>=0&&a>=0&&c>=0||i<=0&&a<=0&&c<=0||(i+=864e5*Hn(Pn(c)+a),a=0,c=0),s.milliseconds=i%1e3,e=W(i/1e3),s.seconds=e%60,t=W(e/60),s.minutes=t%60,n=W(t/60),s.hours=n%24,a+=W(n/24),o=W(Dn(a)),c+=o,a-=Hn(Pn(o)),r=W(c/12),c%=12,s.days=a,s.months=c,s.years=r,this},sr.clone=function(){return Xt(this)},sr.get=function(e){return e=I(e),this.isValid()?this[e+"s"]():NaN},sr.milliseconds=Zn,sr.seconds=Xn,sr.minutes=Kn,sr.hours=Qn,sr.days=Jn,sr.weeks=function(){return W(this.days()/7)},sr.months=er,sr.years=tr,sr.humanize=function(e,t){if(!this.isValid())return this.localeData().invalidDate();var n,r,o=!1,i=rr;return"object"===typeof e&&(t=e,e=!1),"boolean"===typeof e&&(o=e),"object"===typeof t&&(i=Object.assign({},rr,t),null!=t.s&&null==t.ss&&(i.ss=t.s-1)),n=this.localeData(),r=function(e,t,n,r){var o=Xt(e).abs(),i=nr(o.as("s")),a=nr(o.as("m")),c=nr(o.as("h")),s=nr(o.as("d")),l=nr(o.as("M")),u=nr(o.as("w")),f=nr(o.as("y")),d=i<=n.ss&&["s",i]||i0,d[4]=r,or.apply(null,d)}(this,!o,i,n),o&&(r=n.pastFuture(+this,r)),n.postformat(r)},sr.toISOString=cr,sr.toString=cr,sr.toJSON=cr,sr.locale=an,sr.localeData=sn,sr.toIsoString=x("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",cr),sr.lang=cn,H("X",0,0,"unix"),H("x",0,0,"valueOf"),he("x",le),he("X",/[+-]?\d+(\.\d{1,3})?/),ge("X",(function(e,t,n){n._d=new Date(1e3*parseFloat(e))})),ge("x",(function(e,t,n){n._d=new Date(G(e))})),r.version="2.29.1",t=Lt,r.fn=_n,r.min=function(){return Pt("isBefore",[].slice.call(arguments,0))},r.max=function(){return Pt("isAfter",[].slice.call(arguments,0))},r.now=function(){return Date.now?Date.now():+new Date},r.utc=h,r.unix=function(e){return Lt(1e3*e)},r.months=function(e,t){return En(e,t,"months")},r.isDate=u,r.locale=pt,r.invalid=v,r.duration=Xt,r.isMoment=w,r.weekdays=function(e,t,n){return Tn(e,t,n,"weekdays")},r.parseZone=function(){return Lt.apply(null,arguments).parseZone()},r.localeData=vt,r.isDuration=It,r.monthsShort=function(e,t){return En(e,t,"monthsShort")},r.weekdaysMin=function(e,t,n){return Tn(e,t,n,"weekdaysMin")},r.defineLocale=zt,r.updateLocale=function(e,t){if(null!=t){var n,r,o=st;null!=lt[e]&&null!=lt[e].parentLocale?lt[e].set(q(lt[e]._config,t)):(null!=(r=ht(e))&&(o=r._config),t=q(o,t),null==r&&(t.abbr=e),(n=new S(t)).parentLocale=lt[e],lt[e]=n),pt(e)}else null!=lt[e]&&(null!=lt[e].parentLocale?(lt[e]=lt[e].parentLocale,e===pt()&&pt(e)):null!=lt[e]&&delete lt[e]);return lt[e]},r.locales=function(){return j(lt)},r.weekdaysShort=function(e,t,n){return Tn(e,t,n,"weekdaysShort")},r.normalizeUnits=I,r.relativeTimeRounding=function(e){return void 0===e?nr:"function"===typeof e&&(nr=e,!0)},r.relativeTimeThreshold=function(e,t){return void 0!==rr[e]&&(void 0===t?rr[e]:(rr[e]=t,"s"===e&&(rr.ss=t-1),!0))},r.calendarFormat=function(e,t){var n=e.diff(t,"days",!0);return n<-6?"sameElse":n<-1?"lastWeek":n<0?"lastDay":n<1?"sameDay":n<2?"nextDay":n<7?"nextWeek":"sameElse"},r.prototype=_n,r.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},r}()}).call(this,n(491)(e))},,function(e,t,n){"use strict";var r={};r.e=function(e,t){var n=document.createElement(e);return n.className=t,n},r.appendTo=function(e,t){return t.appendChild(e),e},r.css=function(e,t,n){return"object"===typeof t?function(e,t){for(var n in t){var r=t[n];"number"===typeof r&&(r=r.toString()+"px"),e.style[n]=r}return e}(e,t):"undefined"===typeof n?function(e,t){return window.getComputedStyle(e)[t]}(e,t):function(e,t,n){return"number"===typeof n&&(n=n.toString()+"px"),e.style[t]=n,e}(e,t,n)},r.matches=function(e,t){return"undefined"!==typeof e.matches?e.matches(t):"undefined"!==typeof e.matchesSelector?e.matchesSelector(t):"undefined"!==typeof e.webkitMatchesSelector?e.webkitMatchesSelector(t):"undefined"!==typeof e.mozMatchesSelector?e.mozMatchesSelector(t):"undefined"!==typeof e.msMatchesSelector?e.msMatchesSelector(t):void 0},r.remove=function(e){"undefined"!==typeof e.remove?e.remove():e.parentNode&&e.parentNode.removeChild(e)},r.queryChildren=function(e,t){return Array.prototype.filter.call(e.childNodes,(function(e){return r.matches(e,t)}))},e.exports=r},function(e,t,n){"use strict";e.exports=n(331)},function(e,t,n){"use strict";function r(e){return"[object Object]"===Object.prototype.toString.call(e)}n.d(t,"a",(function(){return r}))},function(e,t,n){"use strict";n.d(t,"a",(function(){return r}));var r=function(){return"function"===typeof Symbol?Symbol("rxSubscriber"):"@@rxSubscriber_"+Math.random()}()},function(e,t,n){"use strict";function r(e){return"function"===typeof e}n.d(t,"a",(function(){return r}))},function(e,t,n){"use strict";function r(e){var t=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds(),e.getMilliseconds()));return t.setUTCFullYear(e.getFullYear()),e.getTime()-t.getTime()}n.d(t,"a",(function(){return r}))},function(e,t,n){"use strict";n.r(t),n.d(t,"default",(function(){return i}));var r=n(10),o=n(6);function i(e){Object(o.a)(1,arguments);var t=Object(r.default)(e);return t.setHours(0,0,0,0),t}},,function(e,t,n){"use strict";n.d(t,"createTable",(function(){return s})),n.d(t,"useTableInstance",(function(){return l}));var r=n(0),o=n(140);n.o(o,"getCoreRowModel")&&n.d(t,"getCoreRowModel",(function(){return o.getCoreRowModel})),n.o(o,"getFilteredRowModel")&&n.d(t,"getFilteredRowModel",(function(){return o.getFilteredRowModel})),n.o(o,"getPaginationRowModel")&&n.d(t,"getPaginationRowModel",(function(){return o.getPaginationRowModel})),n.o(o,"getSortedRowModel")&&n.d(t,"getSortedRowModel",(function(){return o.getSortedRowModel}));var i=function(){return(i=Object.assign||function(e){for(var t,n=1,r=arguments.length;n=0;c--)(o=e[c])&&(a=(i<3?o(a):i>3?o(t,n,a):o(t,n))||a);return i>3&&a&&Object.defineProperty(t,n,a),a}function s(e,t){return function(n,r){t(n,r,e)}}function l(e,t){if("object"===typeof Reflect&&"function"===typeof Reflect.metadata)return Reflect.metadata(e,t)}function u(e,t,n,r){return new(n||(n=Promise))((function(o,i){function a(e){try{s(r.next(e))}catch(t){i(t)}}function c(e){try{s(r.throw(e))}catch(t){i(t)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,c)}s((r=r.apply(e,t||[])).next())}))}function f(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"===typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(i){return function(c){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=e.length&&(e=void 0),{value:e&&e[r++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function z(e,t){var n="function"===typeof Symbol&&e[Symbol.iterator];if(!n)return e;var r,o,i=n.call(e),a=[];try{for(;(void 0===t||t-- >0)&&!(r=i.next()).done;)a.push(r.value)}catch(c){o={error:c}}finally{try{r&&!r.done&&(n=i.return)&&n.call(i)}finally{if(o)throw o.error}}return a}function v(){for(var e=[],t=0;t1||c(e,t)}))})}function c(e,t){try{(n=o[e](t)).value instanceof y?Promise.resolve(n.value.v).then(s,l):u(i[0][2],n)}catch(r){u(i[0][3],r)}var n}function s(e){c("next",e)}function l(e){c("throw",e)}function u(e,t){e(t),i.shift(),i.length&&c(i[0][0],i[0][1])}}function w(e){var t,n;return t={},r("next"),r("throw",(function(e){throw e})),r("return"),t[Symbol.iterator]=function(){return this},t;function r(r,o){t[r]=e[r]?function(t){return(n=!n)?{value:y(e[r](t)),done:"return"===r}:o?o(t):t}:o}}function k(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,n=e[Symbol.asyncIterator];return n?n.call(e):(e=p(e),t={},r("next"),r("throw"),r("return"),t[Symbol.asyncIterator]=function(){return this},t);function r(n){t[n]=e[n]&&function(t){return new Promise((function(r,o){(function(e,t,n,r){Promise.resolve(r).then((function(t){e({value:t,done:n})}),t)})(r,o,(t=e[n](t)).done,t.value)}))}}}function x(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e}var j=Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t};function M(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&d(t,e,n);return j(t,e),t}function C(e){return e&&e.__esModule?e:{default:e}}function _(e,t,n,r){if("a"===n&&!r)throw new TypeError("Private accessor was defined without a getter");if("function"===typeof t?e!==t||!r:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?r:"a"===n?r.call(e):r?r.value:t.get(e)}function q(e,t,n,r,o){if("m"===r)throw new TypeError("Private method is not writable");if("a"===r&&!o)throw new TypeError("Private accessor was defined without a setter");if("function"===typeof t?e!==t||!o:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===r?o.call(e,n):o?o.value=n:t.set(e,n),n}},,function(e,t,n){"use strict";var r=n(67),o=n(98),i=Object(r.a)(o.a);t.a=i},function(e,t,n){"use strict";var r=n(164),o=n(67),i=Object(o.a)(Object(r.a)("slice",(function(e,t,n){return Array.prototype.slice.call(n,e,t)})));t.a=i},function(e,t,n){e.exports=n(342)},function(e,t,n){"use strict";n.d(t,"a",(function(){return i}));var r=n(43),o=n(54);function i(e,t){return new r.a((function(n){var r=new o.a,i=0;return r.add(t.schedule((function(){i!==e.length?(n.next(e[i++]),n.closed||r.add(this.schedule())):n.complete()}))),r}))}},function(e,t,n){"use strict";n.d(t,"a",(function(){return a}));var r=n(96),o=n(191),i=n(43);function a(e,t,n,a,c){if(void 0===c&&(c=new r.a(e,n,a)),!c.closed)return t instanceof i.a?t.subscribe(c):Object(o.a)(t)(c)}},function(e,t,n){"use strict";n.d(t,"a",(function(){return o}));var r=n(32),o=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return r.b(t,e),t.prototype.notifyNext=function(e,t,n,r,o){this.destination.next(t)},t.prototype.notifyError=function(e,t){this.destination.error(e)},t.prototype.notifyComplete=function(e){this.destination.complete()},t}(n(53).a)},function(e,t,n){var r,o;window,e.exports=(r=n(0),o=n(30),function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=9)}([function(e,t,n){(function(e,r){var o;(function(){var i="Expected a function",a="__lodash_placeholder__",c=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]],s="[object Arguments]",l="[object Array]",u="[object Boolean]",f="[object Date]",d="[object Error]",h="[object Function]",p="[object GeneratorFunction]",z="[object Map]",v="[object Number]",g="[object Object]",m="[object RegExp]",y="[object Set]",b="[object String]",w="[object Symbol]",k="[object WeakMap]",x="[object ArrayBuffer]",j="[object DataView]",M="[object Float32Array]",C="[object Float64Array]",_="[object Int8Array]",q="[object Int16Array]",S="[object Int32Array]",O="[object Uint8Array]",E="[object Uint16Array]",T="[object Uint32Array]",A=/\b__p \+= '';/g,L=/\b(__p \+=) '' \+/g,H=/(__e\(.*?\)|\b__t\)) \+\n'';/g,D=/&(?:amp|lt|gt|quot|#39);/g,P=/[&<>"']/g,V=RegExp(D.source),R=RegExp(P.source),I=/<%-([\s\S]+?)%>/g,N=/<%([\s\S]+?)%>/g,F=/<%=([\s\S]+?)%>/g,B=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,U=/^\w*$/,W=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,G=/[\\^$.*+?()[\]{}|]/g,Y=RegExp(G.source),$=/^\s+|\s+$/g,Z=/^\s+/,X=/\s+$/,K=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,Q=/\{\n\/\* \[wrapped with (.+)\] \*/,J=/,? & /,ee=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,te=/\\(\\)?/g,ne=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,re=/\w*$/,oe=/^[-+]0x[0-9a-f]+$/i,ie=/^0b[01]+$/i,ae=/^\[object .+?Constructor\]$/,ce=/^0o[0-7]+$/i,se=/^(?:0|[1-9]\d*)$/,le=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,ue=/($^)/,fe=/['\n\r\u2028\u2029\\]/g,de="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",he="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",pe="["+he+"]",ze="["+de+"]",ve="\\d+",ge="[a-z\\xdf-\\xf6\\xf8-\\xff]",me="[^\\ud800-\\udfff"+he+ve+"\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde]",ye="\\ud83c[\\udffb-\\udfff]",be="[^\\ud800-\\udfff]",we="(?:\\ud83c[\\udde6-\\uddff]){2}",ke="[\\ud800-\\udbff][\\udc00-\\udfff]",xe="[A-Z\\xc0-\\xd6\\xd8-\\xde]",je="(?:"+ge+"|"+me+")",Me="(?:"+xe+"|"+me+")",Ce="(?:"+ze+"|"+ye+")?",_e="[\\ufe0e\\ufe0f]?"+Ce+"(?:\\u200d(?:"+[be,we,ke].join("|")+")[\\ufe0e\\ufe0f]?"+Ce+")*",qe="(?:"+["[\\u2700-\\u27bf]",we,ke].join("|")+")"+_e,Se="(?:"+[be+ze+"?",ze,we,ke,"[\\ud800-\\udfff]"].join("|")+")",Oe=RegExp("['\u2019]","g"),Ee=RegExp(ze,"g"),Te=RegExp(ye+"(?="+ye+")|"+Se+_e,"g"),Ae=RegExp([xe+"?"+ge+"+(?:['\u2019](?:d|ll|m|re|s|t|ve))?(?="+[pe,xe,"$"].join("|")+")",Me+"+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?(?="+[pe,xe+je,"$"].join("|")+")",xe+"?"+je+"+(?:['\u2019](?:d|ll|m|re|s|t|ve))?",xe+"+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?","\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",ve,qe].join("|"),"g"),Le=RegExp("[\\u200d\\ud800-\\udfff"+de+"\\ufe0e\\ufe0f]"),He=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,De=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],Pe=-1,Ve={};Ve[M]=Ve[C]=Ve[_]=Ve[q]=Ve[S]=Ve[O]=Ve["[object Uint8ClampedArray]"]=Ve[E]=Ve[T]=!0,Ve[s]=Ve[l]=Ve[x]=Ve[u]=Ve[j]=Ve[f]=Ve[d]=Ve[h]=Ve[z]=Ve[v]=Ve[g]=Ve[m]=Ve[y]=Ve[b]=Ve[k]=!1;var Re={};Re[s]=Re[l]=Re[x]=Re[j]=Re[u]=Re[f]=Re[M]=Re[C]=Re[_]=Re[q]=Re[S]=Re[z]=Re[v]=Re[g]=Re[m]=Re[y]=Re[b]=Re[w]=Re[O]=Re["[object Uint8ClampedArray]"]=Re[E]=Re[T]=!0,Re[d]=Re[h]=Re[k]=!1;var Ie={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Ne=parseFloat,Fe=parseInt,Be="object"==typeof e&&e&&e.Object===Object&&e,Ue="object"==typeof self&&self&&self.Object===Object&&self,We=Be||Ue||Function("return this")(),Ge=t&&!t.nodeType&&t,Ye=Ge&&"object"==typeof r&&r&&!r.nodeType&&r,$e=Ye&&Ye.exports===Ge,Ze=$e&&Be.process,Xe=function(){try{return Ye&&Ye.require&&Ye.require("util").types||Ze&&Ze.binding&&Ze.binding("util")}catch(e){}}(),Ke=Xe&&Xe.isArrayBuffer,Qe=Xe&&Xe.isDate,Je=Xe&&Xe.isMap,et=Xe&&Xe.isRegExp,tt=Xe&&Xe.isSet,nt=Xe&&Xe.isTypedArray;function rt(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}function ot(e,t,n,r){for(var o=-1,i=null==e?0:e.length;++o-1}function ut(e,t,n){for(var r=-1,o=null==e?0:e.length;++r-1;);return n}function Tt(e,t){for(var n=e.length;n--&&yt(t,e[n],0)>-1;);return n}function At(e,t){for(var n=e.length,r=0;n--;)e[n]===t&&++r;return r}var Lt=jt({"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"}),Ht=jt({"&":"&","<":"<",">":">",'"':""","'":"'"});function Dt(e){return"\\"+Ie[e]}function Pt(e){return Le.test(e)}function Vt(e){var t=-1,n=Array(e.size);return e.forEach((function(e,r){n[++t]=[r,e]})),n}function Rt(e,t){return function(n){return e(t(n))}}function It(e,t){for(var n=-1,r=e.length,o=0,i=[];++n",""":'"',"'":"'"}),Wt=function e(t){var n,r=(t=null==t?We:Wt.defaults(We.Object(),t,Wt.pick(We,De))).Array,o=t.Date,de=t.Error,he=t.Function,pe=t.Math,ze=t.Object,ve=t.RegExp,ge=t.String,me=t.TypeError,ye=r.prototype,be=he.prototype,we=ze.prototype,ke=t["__core-js_shared__"],xe=be.toString,je=we.hasOwnProperty,Me=0,Ce=(n=/[^.]+$/.exec(ke&&ke.keys&&ke.keys.IE_PROTO||""))?"Symbol(src)_1."+n:"",_e=we.toString,qe=xe.call(ze),Se=We._,Te=ve("^"+xe.call(je).replace(G,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),Le=$e?t.Buffer:void 0,Ie=t.Symbol,Be=t.Uint8Array,Ue=Le?Le.allocUnsafe:void 0,Ge=Rt(ze.getPrototypeOf,ze),Ye=ze.create,Ze=we.propertyIsEnumerable,Xe=ye.splice,vt=Ie?Ie.isConcatSpreadable:void 0,jt=Ie?Ie.iterator:void 0,Gt=Ie?Ie.toStringTag:void 0,Yt=function(){try{var e=Qo(ze,"defineProperty");return e({},"",{}),e}catch(e){}}(),$t=t.clearTimeout!==We.clearTimeout&&t.clearTimeout,Zt=o&&o.now!==We.Date.now&&o.now,Xt=t.setTimeout!==We.setTimeout&&t.setTimeout,Kt=pe.ceil,Qt=pe.floor,Jt=ze.getOwnPropertySymbols,en=Le?Le.isBuffer:void 0,tn=t.isFinite,nn=ye.join,rn=Rt(ze.keys,ze),on=pe.max,an=pe.min,cn=o.now,sn=t.parseInt,ln=pe.random,un=ye.reverse,fn=Qo(t,"DataView"),dn=Qo(t,"Map"),hn=Qo(t,"Promise"),pn=Qo(t,"Set"),zn=Qo(t,"WeakMap"),vn=Qo(ze,"create"),gn=zn&&new zn,mn={},yn=Mi(fn),bn=Mi(dn),wn=Mi(hn),kn=Mi(pn),xn=Mi(zn),jn=Ie?Ie.prototype:void 0,Mn=jn?jn.valueOf:void 0,Cn=jn?jn.toString:void 0;function _n(e){if(Fa(e)&&!Ea(e)&&!(e instanceof En)){if(e instanceof On)return e;if(je.call(e,"__wrapped__"))return Ci(e)}return new On(e)}var qn=function(){function e(){}return function(t){if(!Na(t))return{};if(Ye)return Ye(t);e.prototype=t;var n=new e;return e.prototype=void 0,n}}();function Sn(){}function On(e,t){this.__wrapped__=e,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=void 0}function En(e){this.__wrapped__=e,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}function Tn(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t=t?e:t)),e}function Zn(e,t,n,r,o,i){var a,c=1&t,l=2&t,d=4&t;if(n&&(a=o?n(e,r,o,i):n(e)),void 0!==a)return a;if(!Na(e))return e;var k=Ea(e);if(k){if(a=function(e){var t=e.length,n=new e.constructor(t);return t&&"string"==typeof e[0]&&je.call(e,"index")&&(n.index=e.index,n.input=e.input),n}(e),!c)return vo(e,a)}else{var A=ti(e),L=A==h||A==p;if(Ha(e))return lo(e,c);if(A==g||A==s||L&&!o){if(a=l||L?{}:ri(e),!c)return l?function(e,t){return go(e,ei(e),t)}(e,function(e,t){return e&&go(t,yc(t),e)}(a,e)):function(e,t){return go(e,Jo(e),t)}(e,Wn(a,e))}else{if(!Re[A])return o?e:{};a=function(e,t,n){var r,o=e.constructor;switch(t){case x:return uo(e);case u:case f:return new o(+e);case j:return function(e,t){var n=t?uo(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.byteLength)}(e,n);case M:case C:case _:case q:case S:case O:case"[object Uint8ClampedArray]":case E:case T:return fo(e,n);case z:return new o;case v:case b:return new o(e);case m:return function(e){var t=new e.constructor(e.source,re.exec(e));return t.lastIndex=e.lastIndex,t}(e);case y:return new o;case w:return r=e,Mn?ze(Mn.call(r)):{}}}(e,A,c)}}i||(i=new Dn);var H=i.get(e);if(H)return H;i.set(e,a),Ya(e)?e.forEach((function(r){a.add(Zn(r,t,n,r,e,i))})):Ba(e)&&e.forEach((function(r,o){a.set(o,Zn(r,t,n,o,e,i))}));var D=k?void 0:(d?l?Wo:Uo:l?yc:mc)(e);return it(D||e,(function(r,o){D&&(r=e[o=r]),Fn(a,o,Zn(r,t,n,o,e,i))})),a}function Xn(e,t,n){var r=n.length;if(null==e)return!r;for(e=ze(e);r--;){var o=n[r],i=t[o],a=e[o];if(void 0===a&&!(o in e)||!i(a))return!1}return!0}function Kn(e,t,n){if("function"!=typeof e)throw new me(i);return mi((function(){e.apply(void 0,n)}),t)}function Qn(e,t,n,r){var o=-1,i=lt,a=!0,c=e.length,s=[],l=t.length;if(!c)return s;n&&(t=ft(t,qt(n))),r?(i=ut,a=!1):t.length>=200&&(i=Ot,a=!1,t=new Hn(t));e:for(;++o-1},An.prototype.set=function(e,t){var n=this.__data__,r=Bn(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this},Ln.prototype.clear=function(){this.size=0,this.__data__={hash:new Tn,map:new(dn||An),string:new Tn}},Ln.prototype.delete=function(e){var t=Xo(this,e).delete(e);return this.size-=t?1:0,t},Ln.prototype.get=function(e){return Xo(this,e).get(e)},Ln.prototype.has=function(e){return Xo(this,e).has(e)},Ln.prototype.set=function(e,t){var n=Xo(this,e),r=n.size;return n.set(e,t),this.size+=n.size==r?0:1,this},Hn.prototype.add=Hn.prototype.push=function(e){return this.__data__.set(e,"__lodash_hash_undefined__"),this},Hn.prototype.has=function(e){return this.__data__.has(e)},Dn.prototype.clear=function(){this.__data__=new An,this.size=0},Dn.prototype.delete=function(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n},Dn.prototype.get=function(e){return this.__data__.get(e)},Dn.prototype.has=function(e){return this.__data__.has(e)},Dn.prototype.set=function(e,t){var n=this.__data__;if(n instanceof An){var r=n.__data__;if(!dn||r.length<199)return r.push([e,t]),this.size=++n.size,this;n=this.__data__=new Ln(r)}return n.set(e,t),this.size=n.size,this};var Jn=bo(cr),er=bo(sr,!0);function tr(e,t){var n=!0;return Jn(e,(function(e,r,o){return n=!!t(e,r,o)})),n}function nr(e,t,n){for(var r=-1,o=e.length;++r0&&n(c)?t>1?or(c,t-1,n,r,o):dt(o,c):r||(o[o.length]=c)}return o}var ir=wo(),ar=wo(!0);function cr(e,t){return e&&ir(e,t,mc)}function sr(e,t){return e&&ar(e,t,mc)}function lr(e,t){return st(t,(function(t){return Va(e[t])}))}function ur(e,t){for(var n=0,r=(t=io(t,e)).length;null!=e&&nt}function pr(e,t){return null!=e&&je.call(e,t)}function zr(e,t){return null!=e&&t in ze(e)}function vr(e,t,n){for(var o=n?ut:lt,i=e[0].length,a=e.length,c=a,s=r(a),l=1/0,u=[];c--;){var f=e[c];c&&t&&(f=ft(f,qt(t))),l=an(f.length,l),s[c]=!n&&(t||i>=120&&f.length>=120)?new Hn(c&&f):void 0}f=e[0];var d=-1,h=s[0];e:for(;++d=c?s:s*("desc"==n[r]?-1:1)}return e.index-t.index}(e,t,n)}))}function Er(e,t,n){for(var r=-1,o=t.length,i={};++r-1;)c!==e&&Xe.call(c,s,1),Xe.call(e,s,1);return e}function Ar(e,t){for(var n=e?t.length:0,r=n-1;n--;){var o=t[n];if(n==r||o!==i){var i=o;ii(o)?Xe.call(e,o,1):Kr(e,o)}}return e}function Lr(e,t){return e+Qt(ln()*(t-e+1))}function Hr(e,t){var n="";if(!e||t<1||t>9007199254740991)return n;do{t%2&&(n+=e),(t=Qt(t/2))&&(e+=e)}while(t);return n}function Dr(e,t){return yi(hi(e,t,Uc),e+"")}function Pr(e){return Vn(_c(e))}function Vr(e,t){var n=_c(e);return ki(n,$n(t,0,n.length))}function Rr(e,t,n,r){if(!Na(e))return e;for(var o=-1,i=(t=io(t,e)).length,a=i-1,c=e;null!=c&&++oi?0:i+t),(n=n>i?i:n)<0&&(n+=i),i=t>n?0:n-t>>>0,t>>>=0;for(var a=r(i);++o>>1,a=e[i];null!==a&&!Za(a)&&(n?a<=t:a=200){var l=t?null:Do(e);if(l)return Nt(l);a=!1,o=Ot,s=new Hn}else s=t?[]:c;e:for(;++r=r?e:Br(e,t,n)}var so=$t||function(e){return We.clearTimeout(e)};function lo(e,t){if(t)return e.slice();var n=e.length,r=Ue?Ue(n):new e.constructor(n);return e.copy(r),r}function uo(e){var t=new e.constructor(e.byteLength);return new Be(t).set(new Be(e)),t}function fo(e,t){var n=t?uo(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.length)}function ho(e,t){if(e!==t){var n=void 0!==e,r=null===e,o=e==e,i=Za(e),a=void 0!==t,c=null===t,s=t==t,l=Za(t);if(!c&&!l&&!i&&e>t||i&&a&&s&&!c&&!l||r&&a&&s||!n&&s||!o)return 1;if(!r&&!i&&!l&&e1?n[o-1]:void 0,a=o>2?n[2]:void 0;for(i=e.length>3&&"function"==typeof i?(o--,i):void 0,a&&ai(n[0],n[1],a)&&(i=o<3?void 0:i,o=1),t=ze(t);++r-1?o[i?t[a]:a]:void 0}}function Co(e){return Bo((function(t){var n=t.length,r=n,o=On.prototype.thru;for(e&&t.reverse();r--;){var a=t[r];if("function"!=typeof a)throw new me(i);if(o&&!c&&"wrapper"==Yo(a))var c=new On([],!0)}for(r=c?r:n;++r1&&y.reverse(),f&&lc))return!1;var l=i.get(e),u=i.get(t);if(l&&u)return l==t&&u==e;var f=-1,d=!0,h=2&n?new Hn:void 0;for(i.set(e,t),i.set(t,e);++f-1&&e%1==0&&e1?"& ":"")+t[r],t=t.join(n>2?", ":" "),e.replace(K,"{\n/* [wrapped with "+t+"] */\n")}(r,function(e,t){return it(c,(function(n){var r="_."+n[0];t&n[1]&&!lt(e,r)&&e.push(r)})),e.sort()}(function(e){var t=e.match(Q);return t?t[1].split(J):[]}(r),n)))}function wi(e){var t=0,n=0;return function(){var r=cn(),o=16-(r-n);if(n=r,o>0){if(++t>=800)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}function ki(e,t){var n=-1,r=e.length,o=r-1;for(t=void 0===t?r:t;++n1?e[t-1]:void 0;return n="function"==typeof n?(e.pop(),n):void 0,Gi(e,n)}));function Ji(e){var t=_n(e);return t.__chain__=!0,t}function ea(e,t){return t(e)}var ta=Bo((function(e){var t=e.length,n=t?e[0]:0,r=this.__wrapped__,o=function(t){return Yn(t,e)};return!(t>1||this.__actions__.length)&&r instanceof En&&ii(n)?((r=r.slice(n,+n+(t?1:0))).__actions__.push({func:ea,args:[o],thisArg:void 0}),new On(r,this.__chain__).thru((function(e){return t&&!e.length&&e.push(void 0),e}))):this.thru(o)})),na=mo((function(e,t,n){je.call(e,n)?++e[n]:Gn(e,n,1)})),ra=Mo(Oi),oa=Mo(Ei);function ia(e,t){return(Ea(e)?it:Jn)(e,Zo(t,3))}function aa(e,t){return(Ea(e)?at:er)(e,Zo(t,3))}var ca=mo((function(e,t,n){je.call(e,n)?e[n].push(t):Gn(e,n,[t])})),sa=Dr((function(e,t,n){var o=-1,i="function"==typeof t,a=Aa(e)?r(e.length):[];return Jn(e,(function(e){a[++o]=i?rt(t,e,n):gr(e,t,n)})),a})),la=mo((function(e,t,n){Gn(e,n,t)}));function ua(e,t){return(Ea(e)?ft:Mr)(e,Zo(t,3))}var fa=mo((function(e,t,n){e[n?0:1].push(t)}),(function(){return[[],[]]})),da=Dr((function(e,t){if(null==e)return[];var n=t.length;return n>1&&ai(e,t[0],t[1])?t=[]:n>2&&ai(t[0],t[1],t[2])&&(t=[t[0]]),Or(e,or(t,1),[])})),ha=Zt||function(){return We.Date.now()};function pa(e,t,n){return t=n?void 0:t,Vo(e,128,void 0,void 0,void 0,void 0,t=e&&null==t?e.length:t)}function za(e,t){var n;if("function"!=typeof t)throw new me(i);return e=tc(e),function(){return--e>0&&(n=t.apply(this,arguments)),e<=1&&(t=void 0),n}}var va=Dr((function(e,t,n){var r=1;if(n.length){var o=It(n,$o(va));r|=32}return Vo(e,r,t,n,o)})),ga=Dr((function(e,t,n){var r=3;if(n.length){var o=It(n,$o(ga));r|=32}return Vo(t,r,e,n,o)}));function ma(e,t,n){var r,o,a,c,s,l,u=0,f=!1,d=!1,h=!0;if("function"!=typeof e)throw new me(i);function p(t){var n=r,i=o;return r=o=void 0,u=t,c=e.apply(i,n)}function z(e){var n=e-l;return void 0===l||n>=t||n<0||d&&e-u>=a}function v(){var e=ha();if(z(e))return g(e);s=mi(v,function(e){var n=t-(e-l);return d?an(n,a-(e-u)):n}(e))}function g(e){return s=void 0,h&&r?p(e):(r=o=void 0,c)}function m(){var e=ha(),n=z(e);if(r=arguments,o=this,l=e,n){if(void 0===s)return function(e){return u=e,s=mi(v,t),f?p(e):c}(l);if(d)return so(s),s=mi(v,t),p(l)}return void 0===s&&(s=mi(v,t)),c}return t=rc(t)||0,Na(n)&&(f=!!n.leading,a=(d="maxWait"in n)?on(rc(n.maxWait)||0,t):a,h="trailing"in n?!!n.trailing:h),m.cancel=function(){void 0!==s&&so(s),u=0,r=l=o=s=void 0},m.flush=function(){return void 0===s?c:g(ha())},m}var ya=Dr((function(e,t){return Kn(e,1,t)})),ba=Dr((function(e,t,n){return Kn(e,rc(t)||0,n)}));function wa(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw new me(i);var n=function n(){var r=arguments,o=t?t.apply(this,r):r[0],i=n.cache;if(i.has(o))return i.get(o);var a=e.apply(this,r);return n.cache=i.set(o,a)||i,a};return n.cache=new(wa.Cache||Ln),n}function ka(e){if("function"!=typeof e)throw new me(i);return function(){var t=arguments;switch(t.length){case 0:return!e.call(this);case 1:return!e.call(this,t[0]);case 2:return!e.call(this,t[0],t[1]);case 3:return!e.call(this,t[0],t[1],t[2])}return!e.apply(this,t)}}wa.Cache=Ln;var xa=ao((function(e,t){var n=(t=1==t.length&&Ea(t[0])?ft(t[0],qt(Zo())):ft(or(t,1),qt(Zo()))).length;return Dr((function(r){for(var o=-1,i=an(r.length,n);++o=t})),Oa=mr(function(){return arguments}())?mr:function(e){return Fa(e)&&je.call(e,"callee")&&!Ze.call(e,"callee")},Ea=r.isArray,Ta=Ke?qt(Ke):function(e){return Fa(e)&&dr(e)==x};function Aa(e){return null!=e&&Ia(e.length)&&!Va(e)}function La(e){return Fa(e)&&Aa(e)}var Ha=en||rs,Da=Qe?qt(Qe):function(e){return Fa(e)&&dr(e)==f};function Pa(e){if(!Fa(e))return!1;var t=dr(e);return t==d||"[object DOMException]"==t||"string"==typeof e.message&&"string"==typeof e.name&&!Wa(e)}function Va(e){if(!Na(e))return!1;var t=dr(e);return t==h||t==p||"[object AsyncFunction]"==t||"[object Proxy]"==t}function Ra(e){return"number"==typeof e&&e==tc(e)}function Ia(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=9007199254740991}function Na(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}function Fa(e){return null!=e&&"object"==typeof e}var Ba=Je?qt(Je):function(e){return Fa(e)&&ti(e)==z};function Ua(e){return"number"==typeof e||Fa(e)&&dr(e)==v}function Wa(e){if(!Fa(e)||dr(e)!=g)return!1;var t=Ge(e);if(null===t)return!0;var n=je.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&xe.call(n)==qe}var Ga=et?qt(et):function(e){return Fa(e)&&dr(e)==m},Ya=tt?qt(tt):function(e){return Fa(e)&&ti(e)==y};function $a(e){return"string"==typeof e||!Ea(e)&&Fa(e)&&dr(e)==b}function Za(e){return"symbol"==typeof e||Fa(e)&&dr(e)==w}var Xa=nt?qt(nt):function(e){return Fa(e)&&Ia(e.length)&&!!Ve[dr(e)]},Ka=Ao(jr),Qa=Ao((function(e,t){return e<=t}));function Ja(e){if(!e)return[];if(Aa(e))return $a(e)?Bt(e):vo(e);if(jt&&e[jt])return function(e){for(var t,n=[];!(t=e.next()).done;)n.push(t.value);return n}(e[jt]());var t=ti(e);return(t==z?Vt:t==y?Nt:_c)(e)}function ec(e){return e?(e=rc(e))===1/0||e===-1/0?17976931348623157e292*(e<0?-1:1):e==e?e:0:0===e?e:0}function tc(e){var t=ec(e),n=t%1;return t==t?n?t-n:t:0}function nc(e){return e?$n(tc(e),0,4294967295):0}function rc(e){if("number"==typeof e)return e;if(Za(e))return NaN;if(Na(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=Na(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace($,"");var n=ie.test(e);return n||ce.test(e)?Fe(e.slice(2),n?2:8):oe.test(e)?NaN:+e}function oc(e){return go(e,yc(e))}function ic(e){return null==e?"":Zr(e)}var ac=yo((function(e,t){if(ui(t)||Aa(t))go(t,mc(t),e);else for(var n in t)je.call(t,n)&&Fn(e,n,t[n])})),cc=yo((function(e,t){go(t,yc(t),e)})),sc=yo((function(e,t,n,r){go(t,yc(t),e,r)})),lc=yo((function(e,t,n,r){go(t,mc(t),e,r)})),uc=Bo(Yn),fc=Dr((function(e,t){e=ze(e);var n=-1,r=t.length,o=r>2?t[2]:void 0;for(o&&ai(t[0],t[1],o)&&(r=1);++n1),t})),go(e,Wo(e),n),r&&(n=Zn(n,7,No));for(var o=t.length;o--;)Kr(n,t[o]);return n})),xc=Bo((function(e,t){return null==e?{}:function(e,t){return Er(e,t,(function(t,n){return pc(e,n)}))}(e,t)}));function jc(e,t){if(null==e)return{};var n=ft(Wo(e),(function(e){return[e]}));return t=Zo(t),Er(e,n,(function(e,n){return t(e,n[0])}))}var Mc=Po(mc),Cc=Po(yc);function _c(e){return null==e?[]:St(e,mc(e))}var qc=xo((function(e,t,n){return t=t.toLowerCase(),e+(n?Sc(t):t)}));function Sc(e){return Pc(ic(e).toLowerCase())}function Oc(e){return(e=ic(e))&&e.replace(le,Lt).replace(Ee,"")}var Ec=xo((function(e,t,n){return e+(n?"-":"")+t.toLowerCase()})),Tc=xo((function(e,t,n){return e+(n?" ":"")+t.toLowerCase()})),Ac=ko("toLowerCase"),Lc=xo((function(e,t,n){return e+(n?"_":"")+t.toLowerCase()})),Hc=xo((function(e,t,n){return e+(n?" ":"")+Pc(t)})),Dc=xo((function(e,t,n){return e+(n?" ":"")+t.toUpperCase()})),Pc=ko("toUpperCase");function Vc(e,t,n){return e=ic(e),void 0===(t=n?void 0:t)?function(e){return He.test(e)}(e)?function(e){return e.match(Ae)||[]}(e):function(e){return e.match(ee)||[]}(e):e.match(t)||[]}var Rc=Dr((function(e,t){try{return rt(e,void 0,t)}catch(e){return Pa(e)?e:new de(e)}})),Ic=Bo((function(e,t){return it(t,(function(t){t=ji(t),Gn(e,t,va(e[t],e))})),e}));function Nc(e){return function(){return e}}var Fc=Co(),Bc=Co(!0);function Uc(e){return e}function Wc(e){return kr("function"==typeof e?e:Zn(e,1))}var Gc=Dr((function(e,t){return function(n){return gr(n,e,t)}})),Yc=Dr((function(e,t){return function(n){return gr(e,n,t)}}));function $c(e,t,n){var r=mc(t),o=lr(t,r);null!=n||Na(t)&&(o.length||!r.length)||(n=t,t=e,e=this,o=lr(t,mc(t)));var i=!(Na(n)&&"chain"in n&&!n.chain),a=Va(e);return it(o,(function(n){var r=t[n];e[n]=r,a&&(e.prototype[n]=function(){var t=this.__chain__;if(i||t){var n=e(this.__wrapped__),o=n.__actions__=vo(this.__actions__);return o.push({func:r,args:arguments,thisArg:e}),n.__chain__=t,n}return r.apply(e,dt([this.value()],arguments))})})),e}function Zc(){}var Xc=Oo(ft),Kc=Oo(ct),Qc=Oo(zt);function Jc(e){return ci(e)?xt(ji(e)):function(e){return function(t){return ur(t,e)}}(e)}var es=To(),ts=To(!0);function ns(){return[]}function rs(){return!1}var os,is=So((function(e,t){return e+t}),0),as=Ho("ceil"),cs=So((function(e,t){return e/t}),1),ss=Ho("floor"),ls=So((function(e,t){return e*t}),1),us=Ho("round"),fs=So((function(e,t){return e-t}),0);return _n.after=function(e,t){if("function"!=typeof t)throw new me(i);return e=tc(e),function(){if(--e<1)return t.apply(this,arguments)}},_n.ary=pa,_n.assign=ac,_n.assignIn=cc,_n.assignInWith=sc,_n.assignWith=lc,_n.at=uc,_n.before=za,_n.bind=va,_n.bindAll=Ic,_n.bindKey=ga,_n.castArray=function(){if(!arguments.length)return[];var e=arguments[0];return Ea(e)?e:[e]},_n.chain=Ji,_n.chunk=function(e,t,n){t=(n?ai(e,t,n):void 0===t)?1:on(tc(t),0);var o=null==e?0:e.length;if(!o||t<1)return[];for(var i=0,a=0,c=r(Kt(o/t));io?0:o+n),(r=void 0===r||r>o?o:tc(r))<0&&(r+=o),r=n>r?0:nc(r);n>>0)?(e=ic(e))&&("string"==typeof t||null!=t&&!Ga(t))&&!(t=Zr(t))&&Pt(e)?co(Bt(e),0,n):e.split(t,n):[]},_n.spread=function(e,t){if("function"!=typeof e)throw new me(i);return t=null==t?0:on(tc(t),0),Dr((function(n){var r=n[t],o=co(n,0,t);return r&&dt(o,r),rt(e,this,o)}))},_n.tail=function(e){var t=null==e?0:e.length;return t?Br(e,1,t):[]},_n.take=function(e,t,n){return e&&e.length?Br(e,0,(t=n||void 0===t?1:tc(t))<0?0:t):[]},_n.takeRight=function(e,t,n){var r=null==e?0:e.length;return r?Br(e,(t=r-(t=n||void 0===t?1:tc(t)))<0?0:t,r):[]},_n.takeRightWhile=function(e,t){return e&&e.length?Jr(e,Zo(t,3),!1,!0):[]},_n.takeWhile=function(e,t){return e&&e.length?Jr(e,Zo(t,3)):[]},_n.tap=function(e,t){return t(e),e},_n.throttle=function(e,t,n){var r=!0,o=!0;if("function"!=typeof e)throw new me(i);return Na(n)&&(r="leading"in n?!!n.leading:r,o="trailing"in n?!!n.trailing:o),ma(e,t,{leading:r,maxWait:t,trailing:o})},_n.thru=ea,_n.toArray=Ja,_n.toPairs=Mc,_n.toPairsIn=Cc,_n.toPath=function(e){return Ea(e)?ft(e,ji):Za(e)?[e]:vo(xi(ic(e)))},_n.toPlainObject=oc,_n.transform=function(e,t,n){var r=Ea(e),o=r||Ha(e)||Xa(e);if(t=Zo(t,4),null==n){var i=e&&e.constructor;n=o?r?new i:[]:Na(e)&&Va(i)?qn(Ge(e)):{}}return(o?it:cr)(e,(function(e,r,o){return t(n,e,r,o)})),n},_n.unary=function(e){return pa(e,1)},_n.union=Fi,_n.unionBy=Bi,_n.unionWith=Ui,_n.uniq=function(e){return e&&e.length?Xr(e):[]},_n.uniqBy=function(e,t){return e&&e.length?Xr(e,Zo(t,2)):[]},_n.uniqWith=function(e,t){return t="function"==typeof t?t:void 0,e&&e.length?Xr(e,void 0,t):[]},_n.unset=function(e,t){return null==e||Kr(e,t)},_n.unzip=Wi,_n.unzipWith=Gi,_n.update=function(e,t,n){return null==e?e:Qr(e,t,oo(n))},_n.updateWith=function(e,t,n,r){return r="function"==typeof r?r:void 0,null==e?e:Qr(e,t,oo(n),r)},_n.values=_c,_n.valuesIn=function(e){return null==e?[]:St(e,yc(e))},_n.without=Yi,_n.words=Vc,_n.wrap=function(e,t){return ja(oo(t),e)},_n.xor=$i,_n.xorBy=Zi,_n.xorWith=Xi,_n.zip=Ki,_n.zipObject=function(e,t){return no(e||[],t||[],Fn)},_n.zipObjectDeep=function(e,t){return no(e||[],t||[],Rr)},_n.zipWith=Qi,_n.entries=Mc,_n.entriesIn=Cc,_n.extend=cc,_n.extendWith=sc,$c(_n,_n),_n.add=is,_n.attempt=Rc,_n.camelCase=qc,_n.capitalize=Sc,_n.ceil=as,_n.clamp=function(e,t,n){return void 0===n&&(n=t,t=void 0),void 0!==n&&(n=(n=rc(n))==n?n:0),void 0!==t&&(t=(t=rc(t))==t?t:0),$n(rc(e),t,n)},_n.clone=function(e){return Zn(e,4)},_n.cloneDeep=function(e){return Zn(e,5)},_n.cloneDeepWith=function(e,t){return Zn(e,5,t="function"==typeof t?t:void 0)},_n.cloneWith=function(e,t){return Zn(e,4,t="function"==typeof t?t:void 0)},_n.conformsTo=function(e,t){return null==t||Xn(e,t,mc(t))},_n.deburr=Oc,_n.defaultTo=function(e,t){return null==e||e!=e?t:e},_n.divide=cs,_n.endsWith=function(e,t,n){e=ic(e),t=Zr(t);var r=e.length,o=n=void 0===n?r:$n(tc(n),0,r);return(n-=t.length)>=0&&e.slice(n,o)==t},_n.eq=_a,_n.escape=function(e){return(e=ic(e))&&R.test(e)?e.replace(P,Ht):e},_n.escapeRegExp=function(e){return(e=ic(e))&&Y.test(e)?e.replace(G,"\\$&"):e},_n.every=function(e,t,n){var r=Ea(e)?ct:tr;return n&&ai(e,t,n)&&(t=void 0),r(e,Zo(t,3))},_n.find=ra,_n.findIndex=Oi,_n.findKey=function(e,t){return gt(e,Zo(t,3),cr)},_n.findLast=oa,_n.findLastIndex=Ei,_n.findLastKey=function(e,t){return gt(e,Zo(t,3),sr)},_n.floor=ss,_n.forEach=ia,_n.forEachRight=aa,_n.forIn=function(e,t){return null==e?e:ir(e,Zo(t,3),yc)},_n.forInRight=function(e,t){return null==e?e:ar(e,Zo(t,3),yc)},_n.forOwn=function(e,t){return e&&cr(e,Zo(t,3))},_n.forOwnRight=function(e,t){return e&&sr(e,Zo(t,3))},_n.get=hc,_n.gt=qa,_n.gte=Sa,_n.has=function(e,t){return null!=e&&ni(e,t,pr)},_n.hasIn=pc,_n.head=Ai,_n.identity=Uc,_n.includes=function(e,t,n,r){e=Aa(e)?e:_c(e),n=n&&!r?tc(n):0;var o=e.length;return n<0&&(n=on(o+n,0)),$a(e)?n<=o&&e.indexOf(t,n)>-1:!!o&&yt(e,t,n)>-1},_n.indexOf=function(e,t,n){var r=null==e?0:e.length;if(!r)return-1;var o=null==n?0:tc(n);return o<0&&(o=on(r+o,0)),yt(e,t,o)},_n.inRange=function(e,t,n){return t=ec(t),void 0===n?(n=t,t=0):n=ec(n),function(e,t,n){return e>=an(t,n)&&e=-9007199254740991&&e<=9007199254740991},_n.isSet=Ya,_n.isString=$a,_n.isSymbol=Za,_n.isTypedArray=Xa,_n.isUndefined=function(e){return void 0===e},_n.isWeakMap=function(e){return Fa(e)&&ti(e)==k},_n.isWeakSet=function(e){return Fa(e)&&"[object WeakSet]"==dr(e)},_n.join=function(e,t){return null==e?"":nn.call(e,t)},_n.kebabCase=Ec,_n.last=Pi,_n.lastIndexOf=function(e,t,n){var r=null==e?0:e.length;if(!r)return-1;var o=r;return void 0!==n&&(o=(o=tc(n))<0?on(r+o,0):an(o,r-1)),t==t?function(e,t,n){for(var r=n+1;r--;)if(e[r]===t)return r;return r}(e,t,o):mt(e,wt,o,!0)},_n.lowerCase=Tc,_n.lowerFirst=Ac,_n.lt=Ka,_n.lte=Qa,_n.max=function(e){return e&&e.length?nr(e,Uc,hr):void 0},_n.maxBy=function(e,t){return e&&e.length?nr(e,Zo(t,2),hr):void 0},_n.mean=function(e){return kt(e,Uc)},_n.meanBy=function(e,t){return kt(e,Zo(t,2))},_n.min=function(e){return e&&e.length?nr(e,Uc,jr):void 0},_n.minBy=function(e,t){return e&&e.length?nr(e,Zo(t,2),jr):void 0},_n.stubArray=ns,_n.stubFalse=rs,_n.stubObject=function(){return{}},_n.stubString=function(){return""},_n.stubTrue=function(){return!0},_n.multiply=ls,_n.nth=function(e,t){return e&&e.length?Sr(e,tc(t)):void 0},_n.noConflict=function(){return We._===this&&(We._=Se),this},_n.noop=Zc,_n.now=ha,_n.pad=function(e,t,n){e=ic(e);var r=(t=tc(t))?Ft(e):0;if(!t||r>=t)return e;var o=(t-r)/2;return Eo(Qt(o),n)+e+Eo(Kt(o),n)},_n.padEnd=function(e,t,n){e=ic(e);var r=(t=tc(t))?Ft(e):0;return t&&rt){var r=e;e=t,t=r}if(n||e%1||t%1){var o=ln();return an(e+o*(t-e+Ne("1e-"+((o+"").length-1))),t)}return Lr(e,t)},_n.reduce=function(e,t,n){var r=Ea(e)?ht:Mt,o=arguments.length<3;return r(e,Zo(t,4),n,o,Jn)},_n.reduceRight=function(e,t,n){var r=Ea(e)?pt:Mt,o=arguments.length<3;return r(e,Zo(t,4),n,o,er)},_n.repeat=function(e,t,n){return t=(n?ai(e,t,n):void 0===t)?1:tc(t),Hr(ic(e),t)},_n.replace=function(){var e=arguments,t=ic(e[0]);return e.length<3?t:t.replace(e[1],e[2])},_n.result=function(e,t,n){var r=-1,o=(t=io(t,e)).length;for(o||(o=1,e=void 0);++r9007199254740991)return[];var n=4294967295,r=an(e,4294967295);e-=4294967295;for(var o=_t(r,t=Zo(t));++n=i)return e;var c=n-Ft(r);if(c<1)return r;var s=a?co(a,0,c).join(""):e.slice(0,c);if(void 0===o)return s+r;if(a&&(c+=s.length-c),Ga(o)){if(e.slice(c).search(o)){var l,u=s;for(o.global||(o=ve(o.source,ic(re.exec(o))+"g")),o.lastIndex=0;l=o.exec(u);)var f=l.index;s=s.slice(0,void 0===f?c:f)}}else if(e.indexOf(Zr(o),c)!=c){var d=s.lastIndexOf(o);d>-1&&(s=s.slice(0,d))}return s+r},_n.unescape=function(e){return(e=ic(e))&&V.test(e)?e.replace(D,Ut):e},_n.uniqueId=function(e){var t=++Me;return ic(e)+t},_n.upperCase=Dc,_n.upperFirst=Pc,_n.each=ia,_n.eachRight=aa,_n.first=Ai,$c(_n,(os={},cr(_n,(function(e,t){je.call(_n.prototype,t)||(os[t]=e)})),os),{chain:!1}),_n.VERSION="4.17.20",it(["bind","bindKey","curry","curryRight","partial","partialRight"],(function(e){_n[e].placeholder=_n})),it(["drop","take"],(function(e,t){En.prototype[e]=function(n){n=void 0===n?1:on(tc(n),0);var r=this.__filtered__&&!t?new En(this):this.clone();return r.__filtered__?r.__takeCount__=an(n,r.__takeCount__):r.__views__.push({size:an(n,4294967295),type:e+(r.__dir__<0?"Right":"")}),r},En.prototype[e+"Right"]=function(t){return this.reverse()[e](t).reverse()}})),it(["filter","map","takeWhile"],(function(e,t){var n=t+1,r=1==n||3==n;En.prototype[e]=function(e){var t=this.clone();return t.__iteratees__.push({iteratee:Zo(e,3),type:n}),t.__filtered__=t.__filtered__||r,t}})),it(["head","last"],(function(e,t){var n="take"+(t?"Right":"");En.prototype[e]=function(){return this[n](1).value()[0]}})),it(["initial","tail"],(function(e,t){var n="drop"+(t?"":"Right");En.prototype[e]=function(){return this.__filtered__?new En(this):this[n](1)}})),En.prototype.compact=function(){return this.filter(Uc)},En.prototype.find=function(e){return this.filter(e).head()},En.prototype.findLast=function(e){return this.reverse().find(e)},En.prototype.invokeMap=Dr((function(e,t){return"function"==typeof e?new En(this):this.map((function(n){return gr(n,e,t)}))})),En.prototype.reject=function(e){return this.filter(ka(Zo(e)))},En.prototype.slice=function(e,t){e=tc(e);var n=this;return n.__filtered__&&(e>0||t<0)?new En(n):(e<0?n=n.takeRight(-e):e&&(n=n.drop(e)),void 0!==t&&(n=(t=tc(t))<0?n.dropRight(-t):n.take(t-e)),n)},En.prototype.takeRightWhile=function(e){return this.reverse().takeWhile(e).reverse()},En.prototype.toArray=function(){return this.take(4294967295)},cr(En.prototype,(function(e,t){var n=/^(?:filter|find|map|reject)|While$/.test(t),r=/^(?:head|last)$/.test(t),o=_n[r?"take"+("last"==t?"Right":""):t],i=r||/^find/.test(t);o&&(_n.prototype[t]=function(){var t=this.__wrapped__,a=r?[1]:arguments,c=t instanceof En,s=a[0],l=c||Ea(t),u=function(e){var t=o.apply(_n,dt([e],a));return r&&f?t[0]:t};l&&n&&"function"==typeof s&&1!=s.length&&(c=l=!1);var f=this.__chain__,d=!!this.__actions__.length,h=i&&!f,p=c&&!d;if(!i&&l){t=p?t:new En(this);var z=e.apply(t,a);return z.__actions__.push({func:ea,args:[u],thisArg:void 0}),new On(z,f)}return h&&p?e.apply(this,a):(z=this.thru(u),h?r?z.value()[0]:z.value():z)})})),it(["pop","push","shift","sort","splice","unshift"],(function(e){var t=ye[e],n=/^(?:push|sort|unshift)$/.test(e)?"tap":"thru",r=/^(?:pop|shift)$/.test(e);_n.prototype[e]=function(){var e=arguments;if(r&&!this.__chain__){var o=this.value();return t.apply(Ea(o)?o:[],e)}return this[n]((function(n){return t.apply(Ea(n)?n:[],e)}))}})),cr(En.prototype,(function(e,t){var n=_n[t];if(n){var r=n.name+"";je.call(mn,r)||(mn[r]=[]),mn[r].push({name:t,func:n})}})),mn[_o(void 0,2).name]=[{name:"wrapper",func:void 0}],En.prototype.clone=function(){var e=new En(this.__wrapped__);return e.__actions__=vo(this.__actions__),e.__dir__=this.__dir__,e.__filtered__=this.__filtered__,e.__iteratees__=vo(this.__iteratees__),e.__takeCount__=this.__takeCount__,e.__views__=vo(this.__views__),e},En.prototype.reverse=function(){if(this.__filtered__){var e=new En(this);e.__dir__=-1,e.__filtered__=!0}else(e=this.clone()).__dir__*=-1;return e},En.prototype.value=function(){var e=this.__wrapped__.value(),t=this.__dir__,n=Ea(e),r=t<0,o=n?e.length:0,i=function(e,t,n){for(var r=-1,o=n.length;++r=this.__values__.length;return{done:e,value:e?void 0:this.__values__[this.__index__++]}},_n.prototype.plant=function(e){for(var t,n=this;n instanceof Sn;){var r=Ci(n);r.__index__=0,r.__values__=void 0,t?o.__wrapped__=r:t=r;var o=r;n=n.__wrapped__}return o.__wrapped__=e,t},_n.prototype.reverse=function(){var e=this.__wrapped__;if(e instanceof En){var t=e;return this.__actions__.length&&(t=new En(this)),(t=t.reverse()).__actions__.push({func:ea,args:[Ni],thisArg:void 0}),new On(t,this.__chain__)}return this.thru(Ni)},_n.prototype.toJSON=_n.prototype.valueOf=_n.prototype.value=function(){return eo(this.__wrapped__,this.__actions__)},_n.prototype.first=_n.prototype.head,jt&&(_n.prototype[jt]=function(){return this}),_n}();We._=Wt,void 0===(o=function(){return Wt}.call(t,n,t,r))||(r.exports=o)}).call(this)}).call(this,n(7),n(21)(e))},function(e,t){e.exports=r},function(e,t,n){e.exports=function(){"use strict";var e=navigator.userAgent,t=navigator.platform,n=/gecko\/\d/i.test(e),r=/MSIE \d/.test(e),o=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(e),i=/Edge\/(\d+)/.exec(e),a=r||o||i,c=a&&(r?document.documentMode||6:+(i||o)[1]),s=!i&&/WebKit\//.test(e),l=s&&/Qt\/\d+\.\d+/.test(e),u=!i&&/Chrome\//.test(e),f=/Opera\//.test(e),d=/Apple Computer/.test(navigator.vendor),h=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(e),p=/PhantomJS/.test(e),z=!i&&/AppleWebKit/.test(e)&&/Mobile\/\w+/.test(e),v=/Android/.test(e),g=z||v||/webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(e),m=z||/Mac/.test(t),y=/\bCrOS\b/.test(e),b=/win/i.test(t),w=f&&e.match(/Version\/(\d*\.\d*)/);w&&(w=Number(w[1])),w&&w>=15&&(f=!1,s=!0);var k=m&&(l||f&&(null==w||w<12.11)),x=n||a&&c>=9;function j(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}var M,C=function(e,t){var n=e.className,r=j(t).exec(n);if(r){var o=n.slice(r.index+r[0].length);e.className=n.slice(0,r.index)+(o?r[1]+o:"")}};function _(e){for(var t=e.childNodes.length;t>0;--t)e.removeChild(e.firstChild);return e}function q(e,t){return _(e).appendChild(t)}function S(e,t,n,r){var o=document.createElement(e);if(n&&(o.className=n),r&&(o.style.cssText=r),"string"==typeof t)o.appendChild(document.createTextNode(t));else if(t)for(var i=0;i=t)return a+(t-i);a+=c-i,a+=n-a%n,i=c+1}}z?H=function(e){e.selectionStart=0,e.selectionEnd=e.value.length}:a&&(H=function(e){try{e.select()}catch(e){}});var R=function(){this.id=null,this.f=null,this.time=0,this.handler=D(this.onTimeout,this)};function I(e,t){for(var n=0;n=t)return r+Math.min(a,t-o);if(o+=i-r,r=i+1,(o+=n-o%n)>=t)return r}}var G=[""];function Y(e){for(;G.length<=e;)G.push($(G)+" ");return G[e]}function $(e){return e[e.length-1]}function Z(e,t){for(var n=[],r=0;r"\x80"&&(e.toUpperCase()!=e.toLowerCase()||Q.test(e))}function ee(e,t){return t?!!(t.source.indexOf("\\w")>-1&&J(e))||t.test(e):J(e)}function te(e){for(var t in e)if(e.hasOwnProperty(t)&&e[t])return!1;return!0}var ne=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;function re(e){return e.charCodeAt(0)>=768&&ne.test(e)}function oe(e,t,n){for(;(n<0?t>0:tn?-1:1;;){if(t==n)return t;var o=(t+n)/2,i=r<0?Math.ceil(o):Math.floor(o);if(i==t)return e(i)?t:n;e(i)?n=i:t=i+r}}var ae=null;function ce(e,t,n){var r;ae=null;for(var o=0;ot)return o;i.to==t&&(i.from!=i.to&&"before"==n?r=o:ae=o),i.from==t&&(i.from!=i.to&&"before"!=n?r=o:ae=o)}return null!=r?r:ae}var se=function(){var e=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,t=/[stwN]/,n=/[LRr]/,r=/[Lb1n]/,o=/[1n]/;function i(e,t,n){this.level=e,this.from=t,this.to=n}return function(a,c){var s="ltr"==c?"L":"R";if(0==a.length||"ltr"==c&&!e.test(a))return!1;for(var l,u=a.length,f=[],d=0;d-1&&(r[t]=o.slice(0,i).concat(o.slice(i+1)))}}}function pe(e,t){var n=de(e,t);if(n.length)for(var r=Array.prototype.slice.call(arguments,2),o=0;o0}function me(e){e.prototype.on=function(e,t){fe(this,e,t)},e.prototype.off=function(e,t){he(this,e,t)}}function ye(e){e.preventDefault?e.preventDefault():e.returnValue=!1}function be(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0}function we(e){return null!=e.defaultPrevented?e.defaultPrevented:0==e.returnValue}function ke(e){ye(e),be(e)}function xe(e){return e.target||e.srcElement}function je(e){var t=e.which;return null==t&&(1&e.button?t=1:2&e.button?t=3:4&e.button&&(t=2)),m&&e.ctrlKey&&1==t&&(t=3),t}var Me,Ce,_e=function(){if(a&&c<9)return!1;var e=S("div");return"draggable"in e||"dragDrop"in e}();function qe(e){if(null==Me){var t=S("span","\u200b");q(e,S("span",[t,document.createTextNode("x")])),0!=e.firstChild.offsetHeight&&(Me=t.offsetWidth<=1&&t.offsetHeight>2&&!(a&&c<8))}var n=Me?S("span","\u200b"):S("span","\xa0",null,"display: inline-block; width: 1px; margin-right: -1px");return n.setAttribute("cm-text",""),n}function Se(e){if(null!=Ce)return Ce;var t=q(e,document.createTextNode("A\u062eA")),n=M(t,0,1).getBoundingClientRect(),r=M(t,1,2).getBoundingClientRect();return _(e),!(!n||n.left==n.right)&&(Ce=r.right-n.right<3)}var Oe,Ee=3!="\n\nb".split(/\n/).length?function(e){for(var t=0,n=[],r=e.length;t<=r;){var o=e.indexOf("\n",t);-1==o&&(o=e.length);var i=e.slice(t,"\r"==e.charAt(o-1)?o-1:o),a=i.indexOf("\r");-1!=a?(n.push(i.slice(0,a)),t+=a+1):(n.push(i),t=o+1)}return n}:function(e){return e.split(/\r\n?|\n/)},Te=window.getSelection?function(e){try{return e.selectionStart!=e.selectionEnd}catch(e){return!1}}:function(e){var t;try{t=e.ownerDocument.selection.createRange()}catch(e){}return!(!t||t.parentElement()!=e)&&0!=t.compareEndPoints("StartToEnd",t)},Ae="oncopy"in(Oe=S("div"))||(Oe.setAttribute("oncopy","return;"),"function"==typeof Oe.oncopy),Le=null,He={},De={};function Pe(e,t){arguments.length>2&&(t.dependencies=Array.prototype.slice.call(arguments,2)),He[e]=t}function Ve(e){if("string"==typeof e&&De.hasOwnProperty(e))e=De[e];else if(e&&"string"==typeof e.name&&De.hasOwnProperty(e.name)){var t=De[e.name];"string"==typeof t&&(t={name:t}),(e=K(t,e)).name=t.name}else{if("string"==typeof e&&/^[\w\-]+\/[\w\-]+\+xml$/.test(e))return Ve("application/xml");if("string"==typeof e&&/^[\w\-]+\/[\w\-]+\+json$/.test(e))return Ve("application/json")}return"string"==typeof e?{name:e}:e||{name:"null"}}function Re(e,t){t=Ve(t);var n=He[t.name];if(!n)return Re(e,"text/plain");var r=n(e,t);if(Ie.hasOwnProperty(t.name)){var o=Ie[t.name];for(var i in o)o.hasOwnProperty(i)&&(r.hasOwnProperty(i)&&(r["_"+i]=r[i]),r[i]=o[i])}if(r.name=t.name,t.helperType&&(r.helperType=t.helperType),t.modeProps)for(var a in t.modeProps)r[a]=t.modeProps[a];return r}var Ie={};function Ne(e,t){P(t,Ie.hasOwnProperty(e)?Ie[e]:Ie[e]={})}function Fe(e,t){if(!0===t)return t;if(e.copyState)return e.copyState(t);var n={};for(var r in t){var o=t[r];o instanceof Array&&(o=o.concat([])),n[r]=o}return n}function Be(e,t){for(var n;e.innerMode&&(n=e.innerMode(t))&&n.mode!=e;)t=n.state,e=n.mode;return n||{mode:e,state:t}}function Ue(e,t,n){return!e.startState||e.startState(t,n)}var We=function(e,t,n){this.pos=this.start=0,this.string=e,this.tabSize=t||8,this.lastColumnPos=this.lastColumnValue=0,this.lineStart=0,this.lineOracle=n};function Ge(e,t){if((t-=e.first)<0||t>=e.size)throw new Error("There is no line "+(t+e.first)+" in the document.");for(var n=e;!n.lines;)for(var r=0;;++r){var o=n.children[r],i=o.chunkSize();if(t=e.first&&tn?et(n,Ge(e,n).text.length):function(e,t){var n=e.ch;return null==n||n>t?et(e.line,t):n<0?et(e.line,0):e}(t,Ge(e,t.line).text.length)}function st(e,t){for(var n=[],r=0;r=this.string.length},We.prototype.sol=function(){return this.pos==this.lineStart},We.prototype.peek=function(){return this.string.charAt(this.pos)||void 0},We.prototype.next=function(){if(this.post},We.prototype.eatSpace=function(){for(var e=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>e},We.prototype.skipToEnd=function(){this.pos=this.string.length},We.prototype.skipTo=function(e){var t=this.string.indexOf(e,this.pos);if(t>-1)return this.pos=t,!0},We.prototype.backUp=function(e){this.pos-=e},We.prototype.column=function(){return this.lastColumnPos0?null:(r&&!1!==t&&(this.pos+=r[0].length),r)}var o=function(e){return n?e.toLowerCase():e};if(o(this.string.substr(this.pos,e.length))==o(e))return!1!==t&&(this.pos+=e.length),!0},We.prototype.current=function(){return this.string.slice(this.start,this.pos)},We.prototype.hideFirstChars=function(e,t){this.lineStart+=e;try{return t()}finally{this.lineStart-=e}},We.prototype.lookAhead=function(e){var t=this.lineOracle;return t&&t.lookAhead(e)},We.prototype.baseToken=function(){var e=this.lineOracle;return e&&e.baseToken(this.pos)};var lt=function(e,t){this.state=e,this.lookAhead=t},ut=function(e,t,n,r){this.state=t,this.doc=e,this.line=n,this.maxLookAhead=r||0,this.baseTokens=null,this.baseTokenPos=1};function ft(e,t,n,r){var o=[e.state.modeGen],i={};bt(e,t.text,e.doc.mode,n,(function(e,t){return o.push(e,t)}),i,r);for(var a=n.state,c=function(r){n.baseTokens=o;var c=e.state.overlays[r],s=1,l=0;n.state=!0,bt(e,t.text,c.mode,n,(function(e,t){for(var n=s;le&&o.splice(s,1,e,o[s+1],r),s+=2,l=Math.min(e,r)}if(t)if(c.opaque)o.splice(n,s-n,e,"overlay "+t),s=n+2;else for(;ne.options.maxHighlightLength&&Fe(e.doc.mode,r.state),i=ft(e,t,r);o&&(r.state=o),t.stateAfter=r.save(!o),t.styles=i.styles,i.classes?t.styleClasses=i.classes:t.styleClasses&&(t.styleClasses=null),n===e.doc.highlightFrontier&&(e.doc.modeFrontier=Math.max(e.doc.modeFrontier,++e.doc.highlightFrontier))}return t.styles}function ht(e,t,n){var r=e.doc,o=e.display;if(!r.mode.startState)return new ut(r,!0,t);var i=function(e,t,n){for(var r,o,i=e.doc,a=n?-1:t-(e.doc.mode.innerMode?1e3:100),c=t;c>a;--c){if(c<=i.first)return i.first;var s=Ge(i,c-1),l=s.stateAfter;if(l&&(!n||c+(l instanceof lt?l.lookAhead:0)<=i.modeFrontier))return c;var u=V(s.text,null,e.options.tabSize);(null==o||r>u)&&(o=c-1,r=u)}return o}(e,t,n),a=i>r.first&&Ge(r,i-1).stateAfter,c=a?ut.fromSaved(r,a,i):new ut(r,Ue(r.mode),i);return r.iter(i,t,(function(n){pt(e,n.text,c);var r=c.line;n.stateAfter=r==t-1||r%5==0||r>=o.viewFrom&&rt.start)return i}throw new Error("Mode "+e.name+" failed to advance stream.")}ut.prototype.lookAhead=function(e){var t=this.doc.getLine(this.line+e);return null!=t&&e>this.maxLookAhead&&(this.maxLookAhead=e),t},ut.prototype.baseToken=function(e){if(!this.baseTokens)return null;for(;this.baseTokens[this.baseTokenPos]<=e;)this.baseTokenPos+=2;var t=this.baseTokens[this.baseTokenPos+1];return{type:t&&t.replace(/( |^)overlay .*/,""),size:this.baseTokens[this.baseTokenPos]-e}},ut.prototype.nextLine=function(){this.line++,this.maxLookAhead>0&&this.maxLookAhead--},ut.fromSaved=function(e,t,n){return t instanceof lt?new ut(e,Fe(e.mode,t.state),n,t.lookAhead):new ut(e,Fe(e.mode,t),n)},ut.prototype.save=function(e){var t=!1!==e?Fe(this.doc.mode,this.state):this.state;return this.maxLookAhead>0?new lt(t,this.maxLookAhead):t};var gt=function(e,t,n){this.start=e.start,this.end=e.pos,this.string=e.current(),this.type=t||null,this.state=n};function mt(e,t,n,r){var o,i,a=e.doc,c=a.mode,s=Ge(a,(t=ct(a,t)).line),l=ht(e,t.line,n),u=new We(s.text,e.options.tabSize,l);for(r&&(i=[]);(r||u.pose.options.maxHighlightLength?(c=!1,a&&pt(e,t,r,f.pos),f.pos=t.length,s=null):s=yt(vt(n,f,r.state,d),i),d){var h=d[0].name;h&&(s="m-"+(s?h+" "+s:h))}if(!c||u!=s){for(;l=t:i.to>t);(r||(r=[])).push(new xt(a,i.from,c?null:i.to))}}return r}(n,o,a),s=function(e,t,n){var r;if(e)for(var o=0;o=t:i.to>t)||i.from==t&&"bookmark"==a.type&&(!n||i.marker.insertLeft)){var c=null==i.from||(a.inclusiveLeft?i.from<=t:i.from0&&c)for(var y=0;yt)&&(!n||Tt(n,i.marker)<0)&&(n=i.marker)}return n}function Pt(e,t,n,r,o){var i=Ge(e,t),a=kt&&i.markedSpans;if(a)for(var c=0;c=0&&f<=0||u<=0&&f>=0)&&(u<=0&&(s.marker.inclusiveRight&&o.inclusiveLeft?tt(l.to,n)>=0:tt(l.to,n)>0)||u>=0&&(s.marker.inclusiveRight&&o.inclusiveLeft?tt(l.from,r)<=0:tt(l.from,r)<0)))return!0}}}function Vt(e){for(var t;t=Lt(e);)e=t.find(-1,!0).line;return e}function Rt(e,t){var n=Ge(e,t),r=Vt(n);return n==r?t:Xe(r)}function It(e,t){if(t>e.lastLine())return t;var n,r=Ge(e,t);if(!Nt(e,r))return t;for(;n=Ht(r);)r=n.find(1,!0).line;return Xe(r)+1}function Nt(e,t){var n=kt&&t.markedSpans;if(n)for(var r=void 0,o=0;ot.maxLineLength&&(t.maxLineLength=n,t.maxLine=e)}))}var Gt=function(e,t,n){this.text=e,St(this,t),this.height=n?n(this):1};function Yt(e){e.parent=null,qt(e)}Gt.prototype.lineNo=function(){return Xe(this)},me(Gt);var $t={},Zt={};function Xt(e,t){if(!e||/^\s*$/.test(e))return null;var n=t.addModeClass?Zt:$t;return n[e]||(n[e]=e.replace(/\S+/g,"cm-$&"))}function Kt(e,t){var n=O("span",null,null,s?"padding-right: .1px":null),r={pre:O("pre",[n],"CodeMirror-line"),content:n,col:0,pos:0,cm:e,trailingSpace:!1,splitSpaces:e.getOption("lineWrapping")};t.measure={};for(var o=0;o<=(t.rest?t.rest.length:0);o++){var i=o?t.rest[o-1]:t.line,a=void 0;r.pos=0,r.addToken=Jt,Se(e.display.measure)&&(a=le(i,e.doc.direction))&&(r.addToken=en(r.addToken,a)),r.map=[],nn(i,r,dt(e,i,t!=e.display.externalMeasured&&Xe(i))),i.styleClasses&&(i.styleClasses.bgClass&&(r.bgClass=L(i.styleClasses.bgClass,r.bgClass||"")),i.styleClasses.textClass&&(r.textClass=L(i.styleClasses.textClass,r.textClass||""))),0==r.map.length&&r.map.push(0,0,r.content.appendChild(qe(e.display.measure))),0==o?(t.measure.map=r.map,t.measure.cache={}):((t.measure.maps||(t.measure.maps=[])).push(r.map),(t.measure.caches||(t.measure.caches=[])).push({}))}if(s){var c=r.content.lastChild;(/\bcm-tab\b/.test(c.className)||c.querySelector&&c.querySelector(".cm-tab"))&&(r.content.className="cm-tab-wrap-hack")}return pe(e,"renderLine",e,t.line,r.pre),r.pre.className&&(r.textClass=L(r.pre.className,r.textClass||"")),r}function Qt(e){var t=S("span","\u2022","cm-invalidchar");return t.title="\\u"+e.charCodeAt(0).toString(16),t.setAttribute("aria-label",t.title),t}function Jt(e,t,n,r,o,i,s){if(t){var l,u=e.splitSpaces?function(e,t){if(e.length>1&&!/ /.test(e))return e;for(var n=t,r="",o=0;ol&&f.from<=l);d++);if(f.to>=u)return e(n,r,o,i,a,c,s);e(n,r.slice(0,f.to-l),o,i,null,c,s),i=null,r=r.slice(f.to-l),l=f.to}}}function tn(e,t,n,r){var o=!r&&n.widgetNode;o&&e.map.push(e.pos,e.pos+t,o),!r&&e.cm.display.input.needsContentAttribute&&(o||(o=e.content.appendChild(document.createElement("span"))),o.setAttribute("cm-marker",n.id)),o&&(e.cm.display.input.setUneditable(o),e.content.appendChild(o)),e.pos+=t,e.trailingSpace=!1}function nn(e,t,n){var r=e.markedSpans,o=e.text,i=0;if(r)for(var a,c,s,l,u,f,d,h=o.length,p=0,z=1,v="",g=0;;){if(g==p){s=l=u=c="",d=null,f=null,g=1/0;for(var m=[],y=void 0,b=0;bp||k.collapsed&&w.to==p&&w.from==p)){if(null!=w.to&&w.to!=p&&g>w.to&&(g=w.to,l=""),k.className&&(s+=" "+k.className),k.css&&(c=(c?c+";":"")+k.css),k.startStyle&&w.from==p&&(u+=" "+k.startStyle),k.endStyle&&w.to==g&&(y||(y=[])).push(k.endStyle,w.to),k.title&&((d||(d={})).title=k.title),k.attributes)for(var x in k.attributes)(d||(d={}))[x]=k.attributes[x];k.collapsed&&(!f||Tt(f.marker,k)<0)&&(f=w)}else w.from>p&&g>w.from&&(g=w.from)}if(y)for(var j=0;j=h)break;for(var C=Math.min(h,g);;){if(v){var _=p+v.length;if(!f){var q=_>C?v.slice(0,C-p):v;t.addToken(t,q,a?a+s:s,u,p+q.length==g?l:"",c,d)}if(_>=C){v=v.slice(C-p),p=C;break}p=_,u=""}v=o.slice(i,i=n[z++]),a=Xt(n[z++],t.cm.options)}}else for(var S=1;Sn)return{map:e.measure.maps[o],cache:e.measure.caches[o],before:!0}}function On(e,t,n,r){return An(e,Tn(e,t),n,r)}function En(e,t){if(t>=e.display.viewFrom&&t=n.lineN&&t2&&i.push((s.bottom+l.top)/2-n.top)}}i.push(n.bottom-n.top)}}(e,t.view,t.rect),t.hasHeights=!0),(i=function(e,t,n,r){var o,i=Dn(t.map,n,r),s=i.node,l=i.start,u=i.end,f=i.collapse;if(3==s.nodeType){for(var d=0;d<4;d++){for(;l&&re(t.line.text.charAt(i.coverStart+l));)--l;for(;i.coverStart+u1}(e))return t;var n=screen.logicalXDPI/screen.deviceXDPI,r=screen.logicalYDPI/screen.deviceYDPI;return{left:t.left*n,right:t.right*n,top:t.top*r,bottom:t.bottom*r}}(e.display.measure,o))}else{var h;l>0&&(f=r="right"),o=e.options.lineWrapping&&(h=s.getClientRects()).length>1?h["right"==r?h.length-1:0]:s.getBoundingClientRect()}if(a&&c<9&&!l&&(!o||!o.left&&!o.right)){var p=s.parentNode.getClientRects()[0];o=p?{left:p.left,right:p.left+or(e.display),top:p.top,bottom:p.bottom}:Hn}for(var z=o.top-t.rect.top,v=o.bottom-t.rect.top,g=(z+v)/2,m=t.view.measure.heights,y=0;yt)&&(o=(i=s-c)-1,t>=s&&(a="right")),null!=o){if(r=e[l+2],c==s&&n==(r.insertLeft?"left":"right")&&(a=n),"left"==n&&0==o)for(;l&&e[l-2]==e[l-3]&&e[l-1].insertLeft;)r=e[2+(l-=3)],a="left";if("right"==n&&o==s-c)for(;l=0&&(n=e[o]).left==n.right;o--);return n}function Vn(e){if(e.measure&&(e.measure.cache={},e.measure.heights=null,e.rest))for(var t=0;t=r.text.length?(s=r.text.length,l="before"):s<=0&&(s=0,l="after"),!c)return a("before"==l?s-1:s,"before"==l);function u(e,t,n){return a(n?e-1:e,1==c[t].level!=n)}var f=ce(c,s,l),d=ae,h=u(s,f,"before"==l);return null!=d&&(h.other=u(s,d,"before"!=l)),h}function $n(e,t){var n=0;t=ct(e.doc,t),e.options.lineWrapping||(n=or(e.display)*t.ch);var r=Ge(e.doc,t.line),o=Bt(r)+xn(e.display);return{left:n,right:n,top:o,bottom:o+r.height}}function Zn(e,t,n,r,o){var i=et(e,t,n);return i.xRel=o,r&&(i.outside=r),i}function Xn(e,t,n){var r=e.doc;if((n+=e.display.viewOffset)<0)return Zn(r.first,0,null,-1,-1);var o=Ke(r,n),i=r.first+r.size-1;if(o>i)return Zn(r.first+r.size-1,Ge(r,i).text.length,null,1,1);t<0&&(t=0);for(var a=Ge(r,o);;){var c=er(e,a,o,t,n),s=Dt(a,c.ch+(c.xRel>0||c.outside>0?1:0));if(!s)return c;var l=s.find(1);if(l.line==o)return l;a=Ge(r,o=l.line)}}function Kn(e,t,n,r){r-=Bn(t);var o=t.text.length,i=ie((function(t){return An(e,n,t-1).bottom<=r}),o,0);return{begin:i,end:o=ie((function(t){return An(e,n,t).top>r}),i,o)}}function Qn(e,t,n,r){return n||(n=Tn(e,t)),Kn(e,t,n,Un(e,t,An(e,n,r),"line").top)}function Jn(e,t,n,r){return!(e.bottom<=n)&&(e.top>n||(r?e.left:e.right)>t)}function er(e,t,n,r,o){o-=Bt(t);var i=Tn(e,t),a=Bn(t),c=0,s=t.text.length,l=!0,u=le(t,e.doc.direction);if(u){var f=(e.options.lineWrapping?nr:tr)(e,t,n,i,u,r,o);c=(l=1!=f.level)?f.from:f.to-1,s=l?f.to:f.from-1}var d,h,p=null,z=null,v=ie((function(t){var n=An(e,i,t);return n.top+=a,n.bottom+=a,!!Jn(n,r,o,!1)&&(n.top<=o&&n.left<=r&&(p=t,z=n),!0)}),c,s),g=!1;if(z){var m=r-z.left=b.bottom?1:0}return Zn(n,v=oe(t.text,v,1),h,g,r-d)}function tr(e,t,n,r,o,i,a){var c=ie((function(c){var s=o[c],l=1!=s.level;return Jn(Yn(e,et(n,l?s.to:s.from,l?"before":"after"),"line",t,r),i,a,!0)}),0,o.length-1),s=o[c];if(c>0){var l=1!=s.level,u=Yn(e,et(n,l?s.from:s.to,l?"after":"before"),"line",t,r);Jn(u,i,a,!0)&&u.top>a&&(s=o[c-1])}return s}function nr(e,t,n,r,o,i,a){var c=Kn(e,t,r,a),s=c.begin,l=c.end;/\s/.test(t.text.charAt(l-1))&&l--;for(var u=null,f=null,d=0;d=l||h.to<=s)){var p=An(e,r,1!=h.level?Math.min(l,h.to)-1:Math.max(s,h.from)).right,z=pz)&&(u=h,f=z)}}return u||(u=o[o.length-1]),u.froml&&(u={from:u.from,to:l,level:u.level}),u}function rr(e){if(null!=e.cachedTextHeight)return e.cachedTextHeight;if(null==Ln){Ln=S("pre",null,"CodeMirror-line-like");for(var t=0;t<49;++t)Ln.appendChild(document.createTextNode("x")),Ln.appendChild(S("br"));Ln.appendChild(document.createTextNode("x"))}q(e.measure,Ln);var n=Ln.offsetHeight/50;return n>3&&(e.cachedTextHeight=n),_(e.measure),n||1}function or(e){if(null!=e.cachedCharWidth)return e.cachedCharWidth;var t=S("span","xxxxxxxxxx"),n=S("pre",[t],"CodeMirror-line-like");q(e.measure,n);var r=t.getBoundingClientRect(),o=(r.right-r.left)/10;return o>2&&(e.cachedCharWidth=o),o||10}function ir(e){for(var t=e.display,n={},r={},o=t.gutters.clientLeft,i=t.gutters.firstChild,a=0;i;i=i.nextSibling,++a){var c=e.display.gutterSpecs[a].className;n[c]=i.offsetLeft+i.clientLeft+o,r[c]=i.clientWidth}return{fixedPos:ar(t),gutterTotalWidth:t.gutters.offsetWidth,gutterLeft:n,gutterWidth:r,wrapperWidth:t.wrapper.clientWidth}}function ar(e){return e.scroller.getBoundingClientRect().left-e.sizer.getBoundingClientRect().left}function cr(e){var t=rr(e.display),n=e.options.lineWrapping,r=n&&Math.max(5,e.display.scroller.clientWidth/or(e.display)-3);return function(o){if(Nt(e.doc,o))return 0;var i=0;if(o.widgets)for(var a=0;a0&&(s=Ge(e.doc,l.line).text).length==l.ch){var u=V(s,s.length,e.options.tabSize)-s.length;l=et(l.line,Math.max(0,Math.round((i-Mn(e.display).left)/or(e.display))-u))}return l}function ur(e,t){if(t>=e.display.viewTo)return null;if((t-=e.display.viewFrom)<0)return null;for(var n=e.display.view,r=0;rt)&&(o.updateLineNumbers=t),e.curOp.viewChanged=!0,t>=o.viewTo)kt&&Rt(e.doc,t)o.viewFrom?hr(e):(o.viewFrom+=r,o.viewTo+=r);else if(t<=o.viewFrom&&n>=o.viewTo)hr(e);else if(t<=o.viewFrom){var i=pr(e,n,n+r,1);i?(o.view=o.view.slice(i.index),o.viewFrom=i.lineN,o.viewTo+=r):hr(e)}else if(n>=o.viewTo){var a=pr(e,t,t,-1);a?(o.view=o.view.slice(0,a.index),o.viewTo=a.lineN):hr(e)}else{var c=pr(e,t,t,-1),s=pr(e,n,n+r,1);c&&s?(o.view=o.view.slice(0,c.index).concat(on(e,c.lineN,s.lineN)).concat(o.view.slice(s.index)),o.viewTo+=r):hr(e)}var l=o.externalMeasured;l&&(n=o.lineN&&t=r.viewTo)){var i=r.view[ur(e,t)];if(null!=i.node){var a=i.changes||(i.changes=[]);-1==I(a,n)&&a.push(n)}}}function hr(e){e.display.viewFrom=e.display.viewTo=e.doc.first,e.display.view=[],e.display.viewOffset=0}function pr(e,t,n,r){var o,i=ur(e,t),a=e.display.view;if(!kt||n==e.doc.first+e.doc.size)return{index:i,lineN:n};for(var c=e.display.viewFrom,s=0;s0){if(i==a.length-1)return null;o=c+a[i].size-t,i++}else o=c-t;t+=o,n+=o}for(;Rt(e.doc,n)!=n;){if(i==(r<0?0:a.length-1))return null;n+=r*a[i-(r<0?1:0)].size,i+=r}return{index:i,lineN:n}}function zr(e){for(var t=e.display.view,n=0,r=0;r=e.display.viewTo||c.to().linet||t==n&&a.to==t)&&(r(Math.max(a.from,t),Math.min(a.to,n),1==a.level?"rtl":"ltr",i),o=!0)}o||r(t,n,"ltr")}(z,n||0,null==r?d:r,(function(e,t,o,f){var v="ltr"==o,g=h(e,v?"left":"right"),m=h(t-1,v?"right":"left"),y=null==n&&0==e,b=null==r&&t==d,w=0==f,k=!z||f==z.length-1;if(m.top-g.top<=3){var x=(l?b:y)&&k,j=(l?y:b)&&w?c:(v?g:m).left,M=x?s:(v?m:g).right;u(j,g.top,M-j,g.bottom)}else{var C,_,q,S;v?(C=l&&y&&w?c:g.left,_=l?s:p(e,o,"before"),q=l?c:p(t,o,"after"),S=l&&b&&k?s:m.right):(C=l?p(e,o,"before"):c,_=!l&&y&&w?s:g.right,q=!l&&b&&k?c:m.left,S=l?p(t,o,"after"):s),u(C,g.top,_-C,g.bottom),g.bottom0?t.blinker=setInterval((function(){return t.cursorDiv.style.visibility=(n=!n)?"":"hidden"}),e.options.cursorBlinkRate):e.options.cursorBlinkRate<0&&(t.cursorDiv.style.visibility="hidden")}}function kr(e){e.state.focused||(e.display.input.focus(),jr(e))}function xr(e){e.state.delayingBlurEvent=!0,setTimeout((function(){e.state.delayingBlurEvent&&(e.state.delayingBlurEvent=!1,Mr(e))}),100)}function jr(e,t){e.state.delayingBlurEvent&&(e.state.delayingBlurEvent=!1),"nocursor"!=e.options.readOnly&&(e.state.focused||(pe(e,"focus",e,t),e.state.focused=!0,A(e.display.wrapper,"CodeMirror-focused"),e.curOp||e.display.selForContextMenu==e.doc.sel||(e.display.input.reset(),s&&setTimeout((function(){return e.display.input.reset(!0)}),20)),e.display.input.receivedFocus()),wr(e))}function Mr(e,t){e.state.delayingBlurEvent||(e.state.focused&&(pe(e,"blur",e,t),e.state.focused=!1,C(e.display.wrapper,"CodeMirror-focused")),clearInterval(e.display.blinker),setTimeout((function(){e.state.focused||(e.display.shift=!1)}),150))}function Cr(e){for(var t=e.display,n=t.lineDiv.offsetTop,r=0;r.005||d<-.005)&&(Ze(o.line,s),_r(o.line),o.rest))for(var h=0;he.display.sizerWidth){var p=Math.ceil(l/or(e.display));p>e.display.maxLineLength&&(e.display.maxLineLength=p,e.display.maxLine=o.line,e.display.maxLineChanged=!0)}}}}function _r(e){if(e.widgets)for(var t=0;t=a&&(i=Ke(t,Bt(Ge(t,s))-e.wrapper.clientHeight),a=s)}return{from:i,to:Math.max(a,i+1)}}function Sr(e,t){var n=e.display,r=rr(e.display);t.top<0&&(t.top=0);var o=e.curOp&&null!=e.curOp.scrollTop?e.curOp.scrollTop:n.scroller.scrollTop,i=qn(e),a={};t.bottom-t.top>i&&(t.bottom=t.top+i);var c=e.doc.height+jn(n),s=t.topc-r;if(t.topo+i){var u=Math.min(t.top,(l?c:t.bottom)-i);u!=o&&(a.scrollTop=u)}var f=e.curOp&&null!=e.curOp.scrollLeft?e.curOp.scrollLeft:n.scroller.scrollLeft,d=_n(e)-(e.options.fixedGutter?n.gutters.offsetWidth:0),h=t.right-t.left>d;return h&&(t.right=t.left+d),t.left<10?a.scrollLeft=0:t.leftd+f-3&&(a.scrollLeft=t.right+(h?0:10)-d),a}function Or(e,t){null!=t&&(Ar(e),e.curOp.scrollTop=(null==e.curOp.scrollTop?e.doc.scrollTop:e.curOp.scrollTop)+t)}function Er(e){Ar(e);var t=e.getCursor();e.curOp.scrollToPos={from:t,to:t,margin:e.options.cursorScrollMargin}}function Tr(e,t,n){null==t&&null==n||Ar(e),null!=t&&(e.curOp.scrollLeft=t),null!=n&&(e.curOp.scrollTop=n)}function Ar(e){var t=e.curOp.scrollToPos;t&&(e.curOp.scrollToPos=null,Lr(e,$n(e,t.from),$n(e,t.to),t.margin))}function Lr(e,t,n,r){var o=Sr(e,{left:Math.min(t.left,n.left),top:Math.min(t.top,n.top)-r,right:Math.max(t.right,n.right),bottom:Math.max(t.bottom,n.bottom)+r});Tr(e,o.scrollLeft,o.scrollTop)}function Hr(e,t){Math.abs(e.doc.scrollTop-t)<2||(n||so(e,{top:t}),Dr(e,t,!0),n&&so(e),ro(e,100))}function Dr(e,t,n){t=Math.max(0,Math.min(e.display.scroller.scrollHeight-e.display.scroller.clientHeight,t)),(e.display.scroller.scrollTop!=t||n)&&(e.doc.scrollTop=t,e.display.scrollbars.setScrollTop(t),e.display.scroller.scrollTop!=t&&(e.display.scroller.scrollTop=t))}function Pr(e,t,n,r){t=Math.max(0,Math.min(t,e.display.scroller.scrollWidth-e.display.scroller.clientWidth)),(n?t==e.doc.scrollLeft:Math.abs(e.doc.scrollLeft-t)<2)&&!r||(e.doc.scrollLeft=t,fo(e),e.display.scroller.scrollLeft!=t&&(e.display.scroller.scrollLeft=t),e.display.scrollbars.setScrollLeft(t))}function Vr(e){var t=e.display,n=t.gutters.offsetWidth,r=Math.round(e.doc.height+jn(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?n:0,docHeight:r,scrollHeight:r+Cn(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:n}}var Rr=function(e,t,n){this.cm=n;var r=this.vert=S("div",[S("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar"),o=this.horiz=S("div",[S("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");r.tabIndex=o.tabIndex=-1,e(r),e(o),fe(r,"scroll",(function(){r.clientHeight&&t(r.scrollTop,"vertical")})),fe(o,"scroll",(function(){o.clientWidth&&t(o.scrollLeft,"horizontal")})),this.checkedZeroWidth=!1,a&&c<8&&(this.horiz.style.minHeight=this.vert.style.minWidth="18px")};Rr.prototype.update=function(e){var t=e.scrollWidth>e.clientWidth+1,n=e.scrollHeight>e.clientHeight+1,r=e.nativeBarWidth;if(n){this.vert.style.display="block",this.vert.style.bottom=t?r+"px":"0";var o=e.viewHeight-(t?r:0);this.vert.firstChild.style.height=Math.max(0,e.scrollHeight-e.clientHeight+o)+"px"}else this.vert.style.display="",this.vert.firstChild.style.height="0";if(t){this.horiz.style.display="block",this.horiz.style.right=n?r+"px":"0",this.horiz.style.left=e.barLeft+"px";var i=e.viewWidth-e.barLeft-(n?r:0);this.horiz.firstChild.style.width=Math.max(0,e.scrollWidth-e.clientWidth+i)+"px"}else this.horiz.style.display="",this.horiz.firstChild.style.width="0";return!this.checkedZeroWidth&&e.clientHeight>0&&(0==r&&this.zeroWidthHack(),this.checkedZeroWidth=!0),{right:n?r:0,bottom:t?r:0}},Rr.prototype.setScrollLeft=function(e){this.horiz.scrollLeft!=e&&(this.horiz.scrollLeft=e),this.disableHoriz&&this.enableZeroWidthBar(this.horiz,this.disableHoriz,"horiz")},Rr.prototype.setScrollTop=function(e){this.vert.scrollTop!=e&&(this.vert.scrollTop=e),this.disableVert&&this.enableZeroWidthBar(this.vert,this.disableVert,"vert")},Rr.prototype.zeroWidthHack=function(){var e=m&&!h?"12px":"18px";this.horiz.style.height=this.vert.style.width=e,this.horiz.style.pointerEvents=this.vert.style.pointerEvents="none",this.disableHoriz=new R,this.disableVert=new R},Rr.prototype.enableZeroWidthBar=function(e,t,n){e.style.pointerEvents="auto",t.set(1e3,(function r(){var o=e.getBoundingClientRect();("vert"==n?document.elementFromPoint(o.right-1,(o.top+o.bottom)/2):document.elementFromPoint((o.right+o.left)/2,o.bottom-1))!=e?e.style.pointerEvents="none":t.set(1e3,r)}))},Rr.prototype.clear=function(){var e=this.horiz.parentNode;e.removeChild(this.horiz),e.removeChild(this.vert)};var Ir=function(){};function Nr(e,t){t||(t=Vr(e));var n=e.display.barWidth,r=e.display.barHeight;Fr(e,t);for(var o=0;o<4&&n!=e.display.barWidth||r!=e.display.barHeight;o++)n!=e.display.barWidth&&e.options.lineWrapping&&Cr(e),Fr(e,Vr(e)),n=e.display.barWidth,r=e.display.barHeight}function Fr(e,t){var n=e.display,r=n.scrollbars.update(t);n.sizer.style.paddingRight=(n.barWidth=r.right)+"px",n.sizer.style.paddingBottom=(n.barHeight=r.bottom)+"px",n.heightForcer.style.borderBottom=r.bottom+"px solid transparent",r.right&&r.bottom?(n.scrollbarFiller.style.display="block",n.scrollbarFiller.style.height=r.bottom+"px",n.scrollbarFiller.style.width=r.right+"px"):n.scrollbarFiller.style.display="",r.bottom&&e.options.coverGutterNextToScrollbar&&e.options.fixedGutter?(n.gutterFiller.style.display="block",n.gutterFiller.style.height=r.bottom+"px",n.gutterFiller.style.width=t.gutterWidth+"px"):n.gutterFiller.style.display=""}Ir.prototype.update=function(){return{bottom:0,right:0}},Ir.prototype.setScrollLeft=function(){},Ir.prototype.setScrollTop=function(){},Ir.prototype.clear=function(){};var Br={native:Rr,null:Ir};function Ur(e){e.display.scrollbars&&(e.display.scrollbars.clear(),e.display.scrollbars.addClass&&C(e.display.wrapper,e.display.scrollbars.addClass)),e.display.scrollbars=new Br[e.options.scrollbarStyle]((function(t){e.display.wrapper.insertBefore(t,e.display.scrollbarFiller),fe(t,"mousedown",(function(){e.state.focused&&setTimeout((function(){return e.display.input.focus()}),0)})),t.setAttribute("cm-not-content","true")}),(function(t,n){"horizontal"==n?Pr(e,t):Hr(e,t)}),e),e.display.scrollbars.addClass&&A(e.display.wrapper,e.display.scrollbars.addClass)}var Wr=0;function Gr(e){var t;e.curOp={cm:e,viewChanged:!1,startHeight:e.doc.height,forceUpdate:!1,updateInput:0,typing:!1,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:!1,updateMaxLine:!1,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:!1,id:++Wr},t=e.curOp,an?an.ops.push(t):t.ownsGroup=an={ops:[t],delayedCallbacks:[]}}function Yr(e){var t=e.curOp;t&&function(e,t){var n=e.ownsGroup;if(n)try{!function(e){var t=e.delayedCallbacks,n=0;do{for(;n=n.viewTo)||n.maxLineChanged&&t.options.lineWrapping,e.update=e.mustUpdate&&new io(t,e.mustUpdate&&{top:e.scrollTop,ensure:e.scrollToPos},e.forceUpdate)}function Zr(e){e.updatedDisplay=e.mustUpdate&&ao(e.cm,e.update)}function Xr(e){var t=e.cm,n=t.display;e.updatedDisplay&&Cr(t),e.barMeasure=Vr(t),n.maxLineChanged&&!t.options.lineWrapping&&(e.adjustWidthTo=On(t,n.maxLine,n.maxLine.text.length).left+3,t.display.sizerWidth=e.adjustWidthTo,e.barMeasure.scrollWidth=Math.max(n.scroller.clientWidth,n.sizer.offsetLeft+e.adjustWidthTo+Cn(t)+t.display.barWidth),e.maxScrollLeft=Math.max(0,n.sizer.offsetLeft+e.adjustWidthTo-_n(t))),(e.updatedDisplay||e.selectionChanged)&&(e.preparedSelection=n.input.prepareSelection())}function Kr(e){var t=e.cm;null!=e.adjustWidthTo&&(t.display.sizer.style.minWidth=e.adjustWidthTo+"px",e.maxScrollLeft(window.innerHeight||document.documentElement.clientHeight)&&(o=!1),null!=o&&!p){var i=S("div","\u200b",null,"position: absolute;\n top: "+(t.top-n.viewOffset-xn(e.display))+"px;\n height: "+(t.bottom-t.top+Cn(e)+n.barHeight)+"px;\n left: "+t.left+"px; width: "+Math.max(2,t.right-t.left)+"px;");e.display.lineSpace.appendChild(i),i.scrollIntoView(o),e.display.lineSpace.removeChild(i)}}}(t,function(e,t,n,r){var o;null==r&&(r=0),e.options.lineWrapping||t!=n||(n="before"==(t=t.ch?et(t.line,"before"==t.sticky?t.ch-1:t.ch,"after"):t).sticky?et(t.line,t.ch+1,"before"):t);for(var i=0;i<5;i++){var a=!1,c=Yn(e,t),s=n&&n!=t?Yn(e,n):c,l=Sr(e,o={left:Math.min(c.left,s.left),top:Math.min(c.top,s.top)-r,right:Math.max(c.left,s.left),bottom:Math.max(c.bottom,s.bottom)+r}),u=e.doc.scrollTop,f=e.doc.scrollLeft;if(null!=l.scrollTop&&(Hr(e,l.scrollTop),Math.abs(e.doc.scrollTop-u)>1&&(a=!0)),null!=l.scrollLeft&&(Pr(e,l.scrollLeft),Math.abs(e.doc.scrollLeft-f)>1&&(a=!0)),!a)break}return o}(t,ct(r,e.scrollToPos.from),ct(r,e.scrollToPos.to),e.scrollToPos.margin));var o=e.maybeHiddenMarkers,i=e.maybeUnhiddenMarkers;if(o)for(var a=0;a=e.display.viewTo)){var n=+new Date+e.options.workTime,r=ht(e,t.highlightFrontier),o=[];t.iter(r.line,Math.min(t.first+t.size,e.display.viewTo+500),(function(i){if(r.line>=e.display.viewFrom){var a=i.styles,c=i.text.length>e.options.maxHighlightLength?Fe(t.mode,r.state):null,s=ft(e,i,r,!0);c&&(r.state=c),i.styles=s.styles;var l=i.styleClasses,u=s.classes;u?i.styleClasses=u:l&&(i.styleClasses=null);for(var f=!a||a.length!=i.styles.length||l!=u&&(!l||!u||l.bgClass!=u.bgClass||l.textClass!=u.textClass),d=0;!f&&dn)return ro(e,e.options.workDelay),!0})),t.highlightFrontier=r.line,t.modeFrontier=Math.max(t.modeFrontier,r.line),o.length&&Jr(e,(function(){for(var t=0;t=n.viewFrom&&t.visible.to<=n.viewTo&&(null==n.updateLineNumbers||n.updateLineNumbers>=n.viewTo)&&n.renderedView==n.view&&0==zr(e))return!1;ho(e)&&(hr(e),t.dims=ir(e));var o=r.first+r.size,i=Math.max(t.visible.from-e.options.viewportMargin,r.first),a=Math.min(o,t.visible.to+e.options.viewportMargin);n.viewFroma&&n.viewTo-a<20&&(a=Math.min(o,n.viewTo)),kt&&(i=Rt(e.doc,i),a=It(e.doc,a));var c=i!=n.viewFrom||a!=n.viewTo||n.lastWrapHeight!=t.wrapperHeight||n.lastWrapWidth!=t.wrapperWidth;!function(e,t,n){var r=e.display;0==r.view.length||t>=r.viewTo||n<=r.viewFrom?(r.view=on(e,t,n),r.viewFrom=t):(r.viewFrom>t?r.view=on(e,t,r.viewFrom).concat(r.view):r.viewFromn&&(r.view=r.view.slice(0,ur(e,n)))),r.viewTo=n}(e,i,a),n.viewOffset=Bt(Ge(e.doc,n.viewFrom)),e.display.mover.style.top=n.viewOffset+"px";var l=zr(e);if(!c&&0==l&&!t.force&&n.renderedView==n.view&&(null==n.updateLineNumbers||n.updateLineNumbers>=n.viewTo))return!1;var u=function(e){if(e.hasFocus())return null;var t=T();if(!t||!E(e.display.lineDiv,t))return null;var n={activeElt:t};if(window.getSelection){var r=window.getSelection();r.anchorNode&&r.extend&&E(e.display.lineDiv,r.anchorNode)&&(n.anchorNode=r.anchorNode,n.anchorOffset=r.anchorOffset,n.focusNode=r.focusNode,n.focusOffset=r.focusOffset)}return n}(e);return l>4&&(n.lineDiv.style.display="none"),function(e,t,n){var r=e.display,o=e.options.lineNumbers,i=r.lineDiv,a=i.firstChild;function c(t){var n=t.nextSibling;return s&&m&&e.display.currentWheelTarget==t?t.style.display="none":t.parentNode.removeChild(t),n}for(var l=r.view,u=r.viewFrom,f=0;f-1&&(h=!1),un(e,d,u,n)),h&&(_(d.lineNumber),d.lineNumber.appendChild(document.createTextNode(Je(e.options,u)))),a=d.node.nextSibling}else{var p=gn(e,d,u,n);i.insertBefore(p,a)}u+=d.size}for(;a;)a=c(a)}(e,n.updateLineNumbers,t.dims),l>4&&(n.lineDiv.style.display=""),n.renderedView=n.view,function(e){if(e&&e.activeElt&&e.activeElt!=T()&&(e.activeElt.focus(),!/^(INPUT|TEXTAREA)$/.test(e.activeElt.nodeName)&&e.anchorNode&&E(document.body,e.anchorNode)&&E(document.body,e.focusNode))){var t=window.getSelection(),n=document.createRange();n.setEnd(e.anchorNode,e.anchorOffset),n.collapse(!1),t.removeAllRanges(),t.addRange(n),t.extend(e.focusNode,e.focusOffset)}}(u),_(n.cursorDiv),_(n.selectionDiv),n.gutters.style.height=n.sizer.style.minHeight=0,c&&(n.lastWrapHeight=t.wrapperHeight,n.lastWrapWidth=t.wrapperWidth,ro(e,400)),n.updateLineNumbers=null,!0}function co(e,t){for(var n=t.viewport,r=!0;;r=!1){if(r&&e.options.lineWrapping&&t.oldDisplayWidth!=_n(e))r&&(t.visible=qr(e.display,e.doc,n));else if(n&&null!=n.top&&(n={top:Math.min(e.doc.height+jn(e.display)-qn(e),n.top)}),t.visible=qr(e.display,e.doc,n),t.visible.from>=e.display.viewFrom&&t.visible.to<=e.display.viewTo)break;if(!ao(e,t))break;Cr(e);var o=Vr(e);vr(e),Nr(e,o),uo(e,o),t.force=!1}t.signal(e,"update",e),e.display.viewFrom==e.display.reportedViewFrom&&e.display.viewTo==e.display.reportedViewTo||(t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo),e.display.reportedViewFrom=e.display.viewFrom,e.display.reportedViewTo=e.display.viewTo)}function so(e,t){var n=new io(e,t);if(ao(e,n)){Cr(e),co(e,n);var r=Vr(e);vr(e),Nr(e,r),uo(e,r),n.finish()}}function lo(e){var t=e.gutters.offsetWidth;e.sizer.style.marginLeft=t+"px"}function uo(e,t){e.display.sizer.style.minHeight=t.docHeight+"px",e.display.heightForcer.style.top=t.docHeight+"px",e.display.gutters.style.height=t.docHeight+e.display.barHeight+Cn(e)+"px"}function fo(e){var t=e.display,n=t.view;if(t.alignWidgets||t.gutters.firstChild&&e.options.fixedGutter){for(var r=ar(t)-t.scroller.scrollLeft+e.doc.scrollLeft,o=t.gutters.offsetWidth,i=r+"px",a=0;ac.clientWidth,u=c.scrollHeight>c.clientHeight;if(o&&l||i&&u){if(i&&m&&s)e:for(var d=t.target,h=a.view;d!=c;d=d.parentNode)for(var p=0;p=0&&tt(e,r.to())<=0)return n}return-1};var jo=function(e,t){this.anchor=e,this.head=t};function Mo(e,t,n){var r=e&&e.options.selectionsMayTouch,o=t[n];t.sort((function(e,t){return tt(e.from(),t.from())})),n=I(t,o);for(var i=1;i0:s>=0){var l=it(c.from(),a.from()),u=ot(c.to(),a.to()),f=c.empty()?a.from()==a.head:c.from()==c.head;i<=n&&--n,t.splice(--i,2,new jo(f?u:l,f?l:u))}}return new xo(t,n)}function Co(e,t){return new xo([new jo(e,t||e)],0)}function _o(e){return e.text?et(e.from.line+e.text.length-1,$(e.text).length+(1==e.text.length?e.from.ch:0)):e.to}function qo(e,t){if(tt(e,t.from)<0)return e;if(tt(e,t.to)<=0)return _o(t);var n=e.line+t.text.length-(t.to.line-t.from.line)-1,r=e.ch;return e.line==t.to.line&&(r+=_o(t).ch-t.to.ch),et(n,r)}function So(e,t){for(var n=[],r=0;r1&&e.remove(c.line+1,p-1),e.insert(c.line+1,g)}sn(e,"change",e,t)}function Ho(e,t,n){!function e(r,o,i){if(r.linked)for(var a=0;ac-(e.cm?e.cm.options.historyEventDelay:500)||"*"==t.origin.charAt(0)))&&(i=function(e,t){return t?(Io(e.done),$(e.done)):e.done.length&&!$(e.done).ranges?$(e.done):e.done.length>1&&!e.done[e.done.length-2].ranges?(e.done.pop(),$(e.done)):void 0}(o,o.lastOp==r)))a=$(i.changes),0==tt(t.from,t.to)&&0==tt(t.from,a.to)?a.to=_o(t):i.changes.push(Ro(e,t));else{var s=$(o.done);for(s&&s.ranges||Fo(e.sel,o.done),i={changes:[Ro(e,t)],generation:o.generation},o.done.push(i);o.done.length>o.undoDepth;)o.done.shift(),o.done[0].ranges||o.done.shift()}o.done.push(n),o.generation=++o.maxGeneration,o.lastModTime=o.lastSelTime=c,o.lastOp=o.lastSelOp=r,o.lastOrigin=o.lastSelOrigin=t.origin,a||pe(e,"historyAdded")}function Fo(e,t){var n=$(t);n&&n.ranges&&n.equals(e)||t.push(e)}function Bo(e,t,n,r){var o=t["spans_"+e.id],i=0;e.iter(Math.max(e.first,n),Math.min(e.first+e.size,r),(function(n){n.markedSpans&&((o||(o=t["spans_"+e.id]={}))[i]=n.markedSpans),++i}))}function Uo(e){if(!e)return null;for(var t,n=0;n-1&&($(c)[f]=l[f],delete l[f])}}}return r}function Yo(e,t,n,r){if(r){var o=e.anchor;if(n){var i=tt(t,o)<0;i!=tt(n,o)<0?(o=t,t=n):i!=tt(t,n)<0&&(t=n)}return new jo(o,t)}return new jo(n||t,t)}function $o(e,t,n,r,o){null==o&&(o=e.cm&&(e.cm.display.shift||e.extend)),Jo(e,new xo([Yo(e.sel.primary(),t,n,o)],0),r)}function Zo(e,t,n){for(var r=[],o=e.cm&&(e.cm.display.shift||e.extend),i=0;i=t.ch:c.to>t.ch))){if(o&&(pe(s,"beforeCursorEnter"),s.explicitlyCleared)){if(i.markedSpans){--a;continue}break}if(!s.atomic)continue;if(n){var f=s.find(r<0?1:-1),d=void 0;if((r<0?u:l)&&(f=ai(e,f,-r,f&&f.line==t.line?i:null)),f&&f.line==t.line&&(d=tt(f,n))&&(r<0?d<0:d>0))return oi(e,f,t,r,o)}var h=s.find(r<0?-1:1);return(r<0?l:u)&&(h=ai(e,h,r,h.line==t.line?i:null)),h?oi(e,h,t,r,o):null}}return t}function ii(e,t,n,r,o){var i=r||1;return oi(e,t,n,i,o)||!o&&oi(e,t,n,i,!0)||oi(e,t,n,-i,o)||!o&&oi(e,t,n,-i,!0)||(e.cantEdit=!0,et(e.first,0))}function ai(e,t,n,r){return n<0&&0==t.ch?t.line>e.first?ct(e,et(t.line-1)):null:n>0&&t.ch==(r||Ge(e,t.line)).text.length?t.line0)){var u=[s,1],f=tt(l.from,c.from),d=tt(l.to,c.to);(f<0||!a.inclusiveLeft&&!f)&&u.push({from:l.from,to:c.from}),(d>0||!a.inclusiveRight&&!d)&&u.push({from:c.to,to:l.to}),o.splice.apply(o,u),s+=u.length-3}}return o}(e,t.from,t.to);if(r)for(var o=r.length-1;o>=0;--o)ui(e,{from:r[o].from,to:r[o].to,text:o?[""]:t.text,origin:t.origin});else ui(e,t)}}function ui(e,t){if(1!=t.text.length||""!=t.text[0]||0!=tt(t.from,t.to)){var n=So(e,t);No(e,t,n,e.cm?e.cm.curOp.id:NaN),hi(e,t,n,Ct(e,t));var r=[];Ho(e,(function(e,n){n||-1!=I(r,e.history)||(gi(e.history,t),r.push(e.history)),hi(e,t,null,Ct(e,t))}))}}function fi(e,t,n){var r=e.cm&&e.cm.state.suppressEdits;if(!r||n){for(var o,i=e.history,a=e.sel,c="undo"==t?i.done:i.undone,s="undo"==t?i.undone:i.done,l=0;l=0;--h){var p=d(h);if(p)return p.v}}}}function di(e,t){if(0!=t&&(e.first+=t,e.sel=new xo(Z(e.sel.ranges,(function(e){return new jo(et(e.anchor.line+t,e.anchor.ch),et(e.head.line+t,e.head.ch))})),e.sel.primIndex),e.cm)){fr(e.cm,e.first,e.first-t,t);for(var n=e.cm.display,r=n.viewFrom;re.lastLine())){if(t.from.linei&&(t={from:t.from,to:et(i,Ge(e,i).text.length),text:[t.text[0]],origin:t.origin}),t.removed=Ye(e,t.from,t.to),n||(n=So(e,t)),e.cm?function(e,t,n){var r=e.doc,o=e.display,i=t.from,a=t.to,c=!1,s=i.line;e.options.lineWrapping||(s=Xe(Vt(Ge(r,i.line))),r.iter(s,a.line+1,(function(e){if(e==o.maxLine)return c=!0,!0}))),r.sel.contains(t.from,t.to)>-1&&ve(e),Lo(r,t,n,cr(e)),e.options.lineWrapping||(r.iter(s,i.line+t.text.length,(function(e){var t=Ut(e);t>o.maxLineLength&&(o.maxLine=e,o.maxLineLength=t,o.maxLineChanged=!0,c=!1)})),c&&(e.curOp.updateMaxLine=!0)),function(e,t){if(e.modeFrontier=Math.min(e.modeFrontier,t),!(e.highlightFrontiern;r--){var o=Ge(e,r).stateAfter;if(o&&(!(o instanceof lt)||r+o.lookAhead1||!(this.children[0]instanceof yi))){var c=[];this.collapse(c),this.children=[new yi(c)],this.children[0].parent=this}},collapse:function(e){for(var t=0;t50){for(var a=o.lines.length%25+25,c=a;c10);e.parent.maybeSpill()}},iterN:function(e,t,n){for(var r=0;r0||0==a&&!1!==i.clearWhenEmpty)return i;if(i.replacedWith&&(i.collapsed=!0,i.widgetNode=O("span",[i.replacedWith],"CodeMirror-widget"),r.handleMouseEvents||i.widgetNode.setAttribute("cm-ignore-events","true"),r.insertLeft&&(i.widgetNode.insertLeft=!0)),i.collapsed){if(Pt(e,t.line,t,n,i)||t.line!=n.line&&Pt(e,n.line,t,n,i))throw new Error("Inserting collapsed marker partially overlapping an existing one");kt=!0}i.addToHistory&&No(e,{from:t,to:n,origin:"markText"},e.sel,NaN);var c,s=t.line,l=e.cm;if(e.iter(s,n.line+1,(function(e){l&&i.collapsed&&!l.options.lineWrapping&&Vt(e)==l.display.maxLine&&(c=!0),i.collapsed&&s!=t.line&&Ze(e,0),function(e,t){e.markedSpans=e.markedSpans?e.markedSpans.concat([t]):[t],t.marker.attachLine(e)}(e,new xt(i,s==t.line?t.ch:null,s==n.line?n.ch:null)),++s})),i.collapsed&&e.iter(t.line,n.line+1,(function(t){Nt(e,t)&&Ze(t,0)})),i.clearOnEnter&&fe(i,"beforeCursorEnter",(function(){return i.clear()})),i.readOnly&&(wt=!0,(e.history.done.length||e.history.undone.length)&&e.clearHistory()),i.collapsed&&(i.id=++xi,i.atomic=!0),l){if(c&&(l.curOp.updateMaxLine=!0),i.collapsed)fr(l,t.line,n.line+1);else if(i.className||i.startStyle||i.endStyle||i.css||i.attributes||i.title)for(var u=t.line;u<=n.line;u++)dr(l,u,"text");i.atomic&&ni(l.doc),sn(l,"markerAdded",l,i)}return i}ji.prototype.clear=function(){if(!this.explicitlyCleared){var e=this.doc.cm,t=e&&!e.curOp;if(t&&Gr(e),ge(this,"clear")){var n=this.find();n&&sn(this,"clear",n.from,n.to)}for(var r=null,o=null,i=0;ie.display.maxLineLength&&(e.display.maxLine=l,e.display.maxLineLength=u,e.display.maxLineChanged=!0)}null!=r&&e&&this.collapsed&&fr(e,r,o+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,e&&ni(e.doc)),e&&sn(e,"markerCleared",e,this,r,o),t&&Yr(e),this.parent&&this.parent.clear()}},ji.prototype.find=function(e,t){var n,r;null==e&&"bookmark"==this.type&&(e=1);for(var o=0;o=0;s--)li(this,r[s]);c?Qo(this,c):this.cm&&Er(this.cm)})),undo:no((function(){fi(this,"undo")})),redo:no((function(){fi(this,"redo")})),undoSelection:no((function(){fi(this,"undo",!0)})),redoSelection:no((function(){fi(this,"redo",!0)})),setExtending:function(e){this.extend=e},getExtending:function(){return this.extend},historySize:function(){for(var e=this.history,t=0,n=0,r=0;r=e.ch)&&t.push(o.marker.parent||o.marker)}return t},findMarks:function(e,t,n){e=ct(this,e),t=ct(this,t);var r=[],o=e.line;return this.iter(e.line,t.line+1,(function(i){var a=i.markedSpans;if(a)for(var c=0;c=s.to||null==s.from&&o!=e.line||null!=s.from&&o==t.line&&s.from>=t.ch||n&&!n(s.marker)||r.push(s.marker.parent||s.marker)}++o})),r},getAllMarks:function(){var e=[];return this.iter((function(t){var n=t.markedSpans;if(n)for(var r=0;re)return t=e,!0;e-=i,++n})),ct(this,et(n,t))},indexFromPos:function(e){var t=(e=ct(this,e)).ch;if(e.linet&&(t=e.from),null!=e.to&&e.to-1)return t.state.draggingText(e),void setTimeout((function(){return t.display.input.focus()}),20);try{var f=e.dataTransfer.getData("Text");if(f){var d;if(t.state.draggingText&&!t.state.draggingText.copy&&(d=t.listSelections()),ei(t.doc,Co(n,n)),d)for(var h=0;h=0;t--)pi(e.doc,"",r[t].from,r[t].to,"+delete");Er(e)}))}function Xi(e,t,n){var r=oe(e.text,t+n,n);return r<0||r>e.text.length?null:r}function Ki(e,t,n){var r=Xi(e,t.ch,n);return null==r?null:new et(t.line,r,n<0?"after":"before")}function Qi(e,t,n,r,o){if(e){"rtl"==t.doc.direction&&(o=-o);var i=le(n,t.doc.direction);if(i){var a,c=o<0?$(i):i[0],s=o<0==(1==c.level)?"after":"before";if(c.level>0||"rtl"==t.doc.direction){var l=Tn(t,n);a=o<0?n.text.length-1:0;var u=An(t,l,a).top;a=ie((function(e){return An(t,l,e).top==u}),o<0==(1==c.level)?c.from:c.to-1,a),"before"==s&&(a=Xi(n,a,1))}else a=o<0?c.to:c.from;return new et(r,a,s)}}return new et(r,o<0?n.text.length:0,o<0?"before":"after")}Ni.basic={Left:"goCharLeft",Right:"goCharRight",Up:"goLineUp",Down:"goLineDown",End:"goLineEnd",Home:"goLineStartSmart",PageUp:"goPageUp",PageDown:"goPageDown",Delete:"delCharAfter",Backspace:"delCharBefore","Shift-Backspace":"delCharBefore",Tab:"defaultTab","Shift-Tab":"indentAuto",Enter:"newlineAndIndent",Insert:"toggleOverwrite",Esc:"singleSelection"},Ni.pcDefault={"Ctrl-A":"selectAll","Ctrl-D":"deleteLine","Ctrl-Z":"undo","Shift-Ctrl-Z":"redo","Ctrl-Y":"redo","Ctrl-Home":"goDocStart","Ctrl-End":"goDocEnd","Ctrl-Up":"goLineUp","Ctrl-Down":"goLineDown","Ctrl-Left":"goGroupLeft","Ctrl-Right":"goGroupRight","Alt-Left":"goLineStart","Alt-Right":"goLineEnd","Ctrl-Backspace":"delGroupBefore","Ctrl-Delete":"delGroupAfter","Ctrl-S":"save","Ctrl-F":"find","Ctrl-G":"findNext","Shift-Ctrl-G":"findPrev","Shift-Ctrl-F":"replace","Shift-Ctrl-R":"replaceAll","Ctrl-[":"indentLess","Ctrl-]":"indentMore","Ctrl-U":"undoSelection","Shift-Ctrl-U":"redoSelection","Alt-U":"redoSelection",fallthrough:"basic"},Ni.emacsy={"Ctrl-F":"goCharRight","Ctrl-B":"goCharLeft","Ctrl-P":"goLineUp","Ctrl-N":"goLineDown","Alt-F":"goWordRight","Alt-B":"goWordLeft","Ctrl-A":"goLineStart","Ctrl-E":"goLineEnd","Ctrl-V":"goPageDown","Shift-Ctrl-V":"goPageUp","Ctrl-D":"delCharAfter","Ctrl-H":"delCharBefore","Alt-D":"delWordAfter","Alt-Backspace":"delWordBefore","Ctrl-K":"killLine","Ctrl-T":"transposeChars","Ctrl-O":"openLine"},Ni.macDefault={"Cmd-A":"selectAll","Cmd-D":"deleteLine","Cmd-Z":"undo","Shift-Cmd-Z":"redo","Cmd-Y":"redo","Cmd-Home":"goDocStart","Cmd-Up":"goDocStart","Cmd-End":"goDocEnd","Cmd-Down":"goDocEnd","Alt-Left":"goGroupLeft","Alt-Right":"goGroupRight","Cmd-Left":"goLineLeft","Cmd-Right":"goLineRight","Alt-Backspace":"delGroupBefore","Ctrl-Alt-Backspace":"delGroupAfter","Alt-Delete":"delGroupAfter","Cmd-S":"save","Cmd-F":"find","Cmd-G":"findNext","Shift-Cmd-G":"findPrev","Cmd-Alt-F":"replace","Shift-Cmd-Alt-F":"replaceAll","Cmd-[":"indentLess","Cmd-]":"indentMore","Cmd-Backspace":"delWrappedLineLeft","Cmd-Delete":"delWrappedLineRight","Cmd-U":"undoSelection","Shift-Cmd-U":"redoSelection","Ctrl-Up":"goDocStart","Ctrl-Down":"goDocEnd",fallthrough:["basic","emacsy"]},Ni.default=m?Ni.macDefault:Ni.pcDefault;var Ji={selectAll:ci,singleSelection:function(e){return e.setSelection(e.getCursor("anchor"),e.getCursor("head"),F)},killLine:function(e){return Zi(e,(function(t){if(t.empty()){var n=Ge(e.doc,t.head.line).text.length;return t.head.ch==n&&t.head.line0)o=new et(o.line,o.ch+1),e.replaceRange(i.charAt(o.ch-1)+i.charAt(o.ch-2),et(o.line,o.ch-2),o,"+transpose");else if(o.line>e.doc.first){var a=Ge(e.doc,o.line-1).text;a&&(o=new et(o.line,1),e.replaceRange(i.charAt(0)+e.doc.lineSeparator()+a.charAt(a.length-1),et(o.line-1,a.length-1),o,"+transpose"))}n.push(new jo(o,o))}e.setSelections(n)}))},newlineAndIndent:function(e){return Jr(e,(function(){for(var t=e.listSelections(),n=t.length-1;n>=0;n--)e.replaceRange(e.doc.lineSeparator(),t[n].anchor,t[n].head,"+input");t=e.listSelections();for(var r=0;r-1&&(tt((o=l.ranges[o]).from(),t)<0||t.xRel>0)&&(tt(o.to(),t)>0||t.xRel<0)?function(e,t,n,r){var o=e.display,i=!1,l=eo(e,(function(t){s&&(o.scroller.draggable=!1),e.state.draggingText=!1,he(o.wrapper.ownerDocument,"mouseup",l),he(o.wrapper.ownerDocument,"mousemove",u),he(o.scroller,"dragstart",f),he(o.scroller,"drop",l),i||(ye(t),r.addNew||$o(e.doc,n,null,null,r.extend),s&&!d||a&&9==c?setTimeout((function(){o.wrapper.ownerDocument.body.focus({preventScroll:!0}),o.input.focus()}),20):o.input.focus())})),u=function(e){i=i||Math.abs(t.clientX-e.clientX)+Math.abs(t.clientY-e.clientY)>=10},f=function(){return i=!0};s&&(o.scroller.draggable=!0),e.state.draggingText=l,l.copy=!r.moveOnDrag,o.scroller.dragDrop&&o.scroller.dragDrop(),fe(o.wrapper.ownerDocument,"mouseup",l),fe(o.wrapper.ownerDocument,"mousemove",u),fe(o.scroller,"dragstart",f),fe(o.scroller,"drop",l),xr(e),setTimeout((function(){return o.input.focus()}),20)}(e,r,t,i):function(e,t,n,r){var o=e.display,i=e.doc;ye(t);var a,c,s=i.sel,l=s.ranges;if(r.addNew&&!r.extend?(c=i.sel.contains(n),a=c>-1?l[c]:new jo(n,n)):(a=i.sel.primary(),c=i.sel.primIndex),"rectangle"==r.unit)r.addNew||(a=new jo(n,n)),n=lr(e,t,!0,!0),c=-1;else{var u=za(e,n,r.unit);a=r.extend?Yo(a,u.anchor,u.head,r.extend):u}r.addNew?-1==c?(c=l.length,Jo(i,Mo(e,l.concat([a]),c),{scroll:!1,origin:"*mouse"})):l.length>1&&l[c].empty()&&"char"==r.unit&&!r.extend?(Jo(i,Mo(e,l.slice(0,c).concat(l.slice(c+1)),0),{scroll:!1,origin:"*mouse"}),s=i.sel):Xo(i,c,a,B):(c=0,Jo(i,new xo([a],0),B),s=i.sel);var f=n;function d(t){if(0!=tt(f,t))if(f=t,"rectangle"==r.unit){for(var o=[],l=e.options.tabSize,u=V(Ge(i,n.line).text,n.ch,l),d=V(Ge(i,t.line).text,t.ch,l),h=Math.min(u,d),p=Math.max(u,d),z=Math.min(n.line,t.line),v=Math.min(e.lastLine(),Math.max(n.line,t.line));z<=v;z++){var g=Ge(i,z).text,m=W(g,h,l);h==p?o.push(new jo(et(z,m),et(z,m))):g.length>m&&o.push(new jo(et(z,m),et(z,W(g,p,l))))}o.length||o.push(new jo(n,n)),Jo(i,Mo(e,s.ranges.slice(0,c).concat(o),c),{origin:"*mouse",scroll:!1}),e.scrollIntoView(t)}else{var y,b=a,w=za(e,t,r.unit),k=b.anchor;tt(w.anchor,k)>0?(y=w.head,k=it(b.from(),w.anchor)):(y=w.anchor,k=ot(b.to(),w.head));var x=s.ranges.slice(0);x[c]=function(e,t){var n=t.anchor,r=t.head,o=Ge(e.doc,n.line);if(0==tt(n,r)&&n.sticky==r.sticky)return t;var i=le(o);if(!i)return t;var a=ce(i,n.ch,n.sticky),c=i[a];if(c.from!=n.ch&&c.to!=n.ch)return t;var s,l=a+(c.from==n.ch==(1!=c.level)?0:1);if(0==l||l==i.length)return t;if(r.line!=n.line)s=(r.line-n.line)*("ltr"==e.doc.direction?1:-1)>0;else{var u=ce(i,r.ch,r.sticky),f=u-a||(r.ch-n.ch)*(1==c.level?-1:1);s=u==l-1||u==l?f<0:f>0}var d=i[l+(s?-1:0)],h=s==(1==d.level),p=h?d.from:d.to,z=h?"after":"before";return n.ch==p&&n.sticky==z?t:new jo(new et(n.line,p,z),r)}(e,new jo(ct(i,k),y)),Jo(i,Mo(e,x,c),B)}}var h=o.wrapper.getBoundingClientRect(),p=0;function z(t){e.state.selectingText=!1,p=1/0,t&&(ye(t),o.input.focus()),he(o.wrapper.ownerDocument,"mousemove",v),he(o.wrapper.ownerDocument,"mouseup",g),i.history.lastSelOrigin=null}var v=eo(e,(function(t){0!==t.buttons&&je(t)?function t(n){var a=++p,c=lr(e,n,!0,"rectangle"==r.unit);if(c)if(0!=tt(c,f)){e.curOp.focus=T(),d(c);var s=qr(o,i);(c.line>=s.to||c.lineh.bottom?20:0;l&&setTimeout(eo(e,(function(){p==a&&(o.scroller.scrollTop+=l,t(n))})),50)}}(t):z(t)})),g=eo(e,z);e.state.selectingText=g,fe(o.wrapper.ownerDocument,"mousemove",v),fe(o.wrapper.ownerDocument,"mouseup",g)}(e,r,t,i)}(t,r,i,e):xe(e)==n.scroller&&ye(e):2==o?(r&&$o(t.doc,r),setTimeout((function(){return n.input.focus()}),20)):3==o&&(x?t.display.input.onContextMenu(e):xr(t)))}}function za(e,t,n){if("char"==n)return new jo(t,t);if("word"==n)return e.findWordAt(t);if("line"==n)return new jo(et(t.line,0),ct(e.doc,et(t.line+1,0)));var r=n(e,t);return new jo(r.from,r.to)}function va(e,t,n,r){var o,i;if(t.touches)o=t.touches[0].clientX,i=t.touches[0].clientY;else try{o=t.clientX,i=t.clientY}catch(e){return!1}if(o>=Math.floor(e.display.gutters.getBoundingClientRect().right))return!1;r&&ye(t);var a=e.display,c=a.lineDiv.getBoundingClientRect();if(i>c.bottom||!ge(e,n))return we(t);i-=c.top-a.viewOffset;for(var s=0;s=o)return pe(e,n,e,Ke(e.doc,i),e.display.gutterSpecs[s].className,t),we(t)}}function ga(e,t){return va(e,t,"gutterClick",!0)}function ma(e,t){kn(e.display,t)||function(e,t){return!!ge(e,"gutterContextMenu")&&va(e,t,"gutterContextMenu",!1)}(e,t)||ze(e,t,"contextmenu")||x||e.display.input.onContextMenu(t)}function ya(e){e.display.wrapper.className=e.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+e.options.theme.replace(/(^|\s)\s*/g," cm-s-"),In(e)}ha.prototype.compare=function(e,t,n){return this.time+400>e&&0==tt(t,this.pos)&&n==this.button};var ba={toString:function(){return"CodeMirror.Init"}},wa={},ka={};function xa(e,t,n){if(!t!=!(n&&n!=ba)){var r=e.display.dragFunctions,o=t?fe:he;o(e.display.scroller,"dragstart",r.start),o(e.display.scroller,"dragenter",r.enter),o(e.display.scroller,"dragover",r.over),o(e.display.scroller,"dragleave",r.leave),o(e.display.scroller,"drop",r.drop)}}function ja(e){e.options.lineWrapping?(A(e.display.wrapper,"CodeMirror-wrap"),e.display.sizer.style.minWidth="",e.display.sizerWidth=null):(C(e.display.wrapper,"CodeMirror-wrap"),Wt(e)),sr(e),fr(e),In(e),setTimeout((function(){return Nr(e)}),100)}function Ma(e,t){var n=this;if(!(this instanceof Ma))return new Ma(e,t);this.options=t=t?P(t):{},P(wa,t,!1);var r=t.value;"string"==typeof r?r=new Oi(r,t.mode,null,t.lineSeparator,t.direction):t.mode&&(r.modeOption=t.mode),this.doc=r;var o=new Ma.inputStyles[t.inputStyle](this),i=this.display=new go(e,r,o,t);for(var l in i.wrapper.CodeMirror=this,ya(this),t.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap"),Ur(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,delayingBlurEvent:!1,focused:!1,suppressEdits:!1,pasteIncoming:-1,cutIncoming:-1,selectingText:!1,draggingText:!1,highlight:new R,keySeq:null,specialChars:null},t.autofocus&&!g&&i.input.focus(),a&&c<11&&setTimeout((function(){return n.display.input.reset(!0)}),20),function(e){var t=e.display;fe(t.scroller,"mousedown",eo(e,pa)),fe(t.scroller,"dblclick",a&&c<11?eo(e,(function(t){if(!ze(e,t)){var n=lr(e,t);if(n&&!ga(e,t)&&!kn(e.display,t)){ye(t);var r=e.findWordAt(n);$o(e.doc,r.anchor,r.head)}}})):function(t){return ze(e,t)||ye(t)}),fe(t.scroller,"contextmenu",(function(t){return ma(e,t)})),fe(t.input.getField(),"contextmenu",(function(n){t.scroller.contains(n.target)||ma(e,n)}));var n,r={end:0};function o(){t.activeTouch&&(n=setTimeout((function(){return t.activeTouch=null}),1e3),(r=t.activeTouch).end=+new Date)}function i(e,t){if(null==t.left)return!0;var n=t.left-e.left,r=t.top-e.top;return n*n+r*r>400}fe(t.scroller,"touchstart",(function(o){if(!ze(e,o)&&!function(e){if(1!=e.touches.length)return!1;var t=e.touches[0];return t.radiusX<=1&&t.radiusY<=1}(o)&&!ga(e,o)){t.input.ensurePolled(),clearTimeout(n);var i=+new Date;t.activeTouch={start:i,moved:!1,prev:i-r.end<=300?r:null},1==o.touches.length&&(t.activeTouch.left=o.touches[0].pageX,t.activeTouch.top=o.touches[0].pageY)}})),fe(t.scroller,"touchmove",(function(){t.activeTouch&&(t.activeTouch.moved=!0)})),fe(t.scroller,"touchend",(function(n){var r=t.activeTouch;if(r&&!kn(t,n)&&null!=r.left&&!r.moved&&new Date-r.start<300){var a,c=e.coordsChar(t.activeTouch,"page");a=!r.prev||i(r,r.prev)?new jo(c,c):!r.prev.prev||i(r,r.prev.prev)?e.findWordAt(c):new jo(et(c.line,0),ct(e.doc,et(c.line+1,0))),e.setSelection(a.anchor,a.head),e.focus(),ye(n)}o()})),fe(t.scroller,"touchcancel",o),fe(t.scroller,"scroll",(function(){t.scroller.clientHeight&&(Hr(e,t.scroller.scrollTop),Pr(e,t.scroller.scrollLeft,!0),pe(e,"scroll",e))})),fe(t.scroller,"mousewheel",(function(t){return ko(e,t)})),fe(t.scroller,"DOMMouseScroll",(function(t){return ko(e,t)})),fe(t.wrapper,"scroll",(function(){return t.wrapper.scrollTop=t.wrapper.scrollLeft=0})),t.dragFunctions={enter:function(t){ze(e,t)||ke(t)},over:function(t){ze(e,t)||(function(e,t){var n=lr(e,t);if(n){var r=document.createDocumentFragment();mr(e,n,r),e.display.dragCursor||(e.display.dragCursor=S("div",null,"CodeMirror-cursors CodeMirror-dragcursors"),e.display.lineSpace.insertBefore(e.display.dragCursor,e.display.cursorDiv)),q(e.display.dragCursor,r)}}(e,t),ke(t))},start:function(t){return function(e,t){if(a&&(!e.state.draggingText||+new Date-Ei<100))ke(t);else if(!ze(e,t)&&!kn(e.display,t)&&(t.dataTransfer.setData("Text",e.getSelection()),t.dataTransfer.effectAllowed="copyMove",t.dataTransfer.setDragImage&&!d)){var n=S("img",null,null,"position: fixed; left: 0; top: 0;");n.src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",f&&(n.width=n.height=1,e.display.wrapper.appendChild(n),n._top=n.offsetTop),t.dataTransfer.setDragImage(n,0,0),f&&n.parentNode.removeChild(n)}}(e,t)},drop:eo(e,Ti),leave:function(t){ze(e,t)||Ai(e)}};var s=t.input.getField();fe(s,"keyup",(function(t){return la.call(e,t)})),fe(s,"keydown",eo(e,sa)),fe(s,"keypress",eo(e,ua)),fe(s,"focus",(function(t){return jr(e,t)})),fe(s,"blur",(function(t){return Mr(e,t)}))}(this),function(){var e;Hi||(fe(window,"resize",(function(){null==e&&(e=setTimeout((function(){e=null,Li(Di)}),100))})),fe(window,"blur",(function(){return Li(Mr)})),Hi=!0)}(),Gr(this),this.curOp.forceUpdate=!0,Do(this,r),t.autofocus&&!g||this.hasFocus()?setTimeout(D(jr,this),20):Mr(this),ka)ka.hasOwnProperty(l)&&ka[l](this,t[l],ba);ho(this),t.finishInit&&t.finishInit(this);for(var u=0;u150)){if(!r)return;n="prev"}}else l=0,n="not";"prev"==n?l=t>i.first?V(Ge(i,t-1).text,null,a):0:"add"==n?l=s+e.options.indentUnit:"subtract"==n?l=s-e.options.indentUnit:"number"==typeof n&&(l=s+n),l=Math.max(0,l);var f="",d=0;if(e.options.indentWithTabs)for(var h=Math.floor(l/a);h;--h)d+=a,f+="\t";if(da,s=Ee(t),l=null;if(c&&r.ranges.length>1)if(qa&&qa.text.join("\n")==t){if(r.ranges.length%qa.text.length==0){l=[];for(var u=0;u=0;d--){var h=r.ranges[d],p=h.from(),z=h.to();h.empty()&&(n&&n>0?p=et(p.line,p.ch-n):e.state.overwrite&&!c?z=et(z.line,Math.min(Ge(i,z.line).text.length,z.ch+$(s).length)):c&&qa&&qa.lineWise&&qa.text.join("\n")==s.join("\n")&&(p=z=et(p.line,0)));var v={from:p,to:z,text:l?l[d%l.length]:s,origin:o||(c?"paste":e.state.cutIncoming>a?"cut":"+input")};li(e.doc,v),sn(e,"inputRead",e,v)}t&&!c&&Ta(e,t),Er(e),e.curOp.updateInput<2&&(e.curOp.updateInput=f),e.curOp.typing=!0,e.state.pasteIncoming=e.state.cutIncoming=-1}function Ea(e,t){var n=e.clipboardData&&e.clipboardData.getData("Text");if(n)return e.preventDefault(),t.isReadOnly()||t.options.disableInput||Jr(t,(function(){return Oa(t,n,0,null,"paste")})),!0}function Ta(e,t){if(e.options.electricChars&&e.options.smartIndent)for(var n=e.doc.sel,r=n.ranges.length-1;r>=0;r--){var o=n.ranges[r];if(!(o.head.ch>100||r&&n.ranges[r-1].head.line==o.head.line)){var i=e.getModeAt(o.head),a=!1;if(i.electricChars){for(var c=0;c-1){a=_a(e,o.head.line,"smart");break}}else i.electricInput&&i.electricInput.test(Ge(e.doc,o.head.line).text.slice(0,o.head.ch))&&(a=_a(e,o.head.line,"smart"));a&&sn(e,"electricInput",e,o.head.line)}}}function Aa(e){for(var t=[],n=[],r=0;r=t.text.length?(n.ch=t.text.length,n.sticky="before"):n.ch<=0&&(n.ch=0,n.sticky="after");var i=ce(o,n.ch,n.sticky),a=o[i];if("ltr"==e.doc.direction&&a.level%2==0&&(r>0?a.to>n.ch:a.from=a.from&&d>=u.begin)){var h=f?"before":"after";return new et(n.line,d,h)}}var p=function(e,t,r){for(var i=function(e,t){return t?new et(n.line,s(e,1),"before"):new et(n.line,e,"after")};e>=0&&e0==(1!=a.level),l=c?r.begin:s(r.end,-1);if(a.from<=l&&l0?u.end:s(u.begin,-1);return null==v||r>0&&v==t.text.length||!(z=p(r>0?0:o.length-1,r,l(v)))?null:z}(e.cm,c,t,n):Ki(c,t,n))){if(r||(a=t.line+s)=e.first+e.size||(t=new et(a,t.ch,t.sticky),!(c=Ge(e,a))))return!1;t=Qi(o,e.cm,c,t.line,s)}else t=i;return!0}if("char"==r)l();else if("column"==r)l(!0);else if("word"==r||"group"==r)for(var u=null,f="group"==r,d=e.cm&&e.cm.getHelper(t,"wordChars"),h=!0;!(n<0)||l(!h);h=!1){var p=c.text.charAt(t.ch)||"\n",z=ee(p,d)?"w":f&&"\n"==p?"n":!f||/\s/.test(p)?null:"p";if(!f||h||z||(z="s"),u&&u!=z){n<0&&(n=1,l(),t.sticky="after");break}if(z&&(u=z),n>0&&!l(!h))break}var v=ii(e,t,i,a,!0);return nt(i,v)&&(v.hitSide=!0),v}function Pa(e,t,n,r){var o,i,a=e.doc,c=t.left;if("page"==r){var s=Math.min(e.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight),l=Math.max(s-.5*rr(e.display),3);o=(n>0?t.bottom:t.top)+n*l}else"line"==r&&(o=n>0?t.bottom+3:t.top-3);for(;(i=Xn(e,c,o)).outside;){if(n<0?o<=0:o>=a.height){i.hitSide=!0;break}o+=5*n}return i}var Va=function(e){this.cm=e,this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null,this.polling=new R,this.composing=null,this.gracePeriod=!1,this.readDOMTimeout=null};function Ra(e,t){var n=En(e,t.line);if(!n||n.hidden)return null;var r=Ge(e.doc,t.line),o=Sn(n,r,t.line),i=le(r,e.doc.direction),a="left";i&&(a=ce(i,t.ch)%2?"right":"left");var c=Dn(o.map,t.ch,a);return c.offset="right"==c.collapse?c.end:c.start,c}function Ia(e,t){return t&&(e.bad=!0),e}function Na(e,t,n){var r;if(t==e.display.lineDiv){if(!(r=e.display.lineDiv.childNodes[n]))return Ia(e.clipPos(et(e.display.viewTo-1)),!0);t=null,n=0}else for(r=t;;r=r.parentNode){if(!r||r==e.display.lineDiv)return null;if(r.parentNode&&r.parentNode==e.display.lineDiv)break}for(var o=0;o=t.display.viewTo||i.line=t.display.viewFrom&&Ra(t,o)||{node:s[0].measure.map[2],offset:0},u=i.liner.firstLine()&&(a=et(a.line-1,Ge(r.doc,a.line-1).length)),c.ch==Ge(r.doc,c.line).text.length&&c.lineo.viewTo-1)return!1;a.line==o.viewFrom||0==(e=ur(r,a.line))?(t=Xe(o.view[0].line),n=o.view[0].node):(t=Xe(o.view[e].line),n=o.view[e-1].node.nextSibling);var s,l,u=ur(r,c.line);if(u==o.view.length-1?(s=o.viewTo-1,l=o.lineDiv.lastChild):(s=Xe(o.view[u+1].line)-1,l=o.view[u+1].node.previousSibling),!n)return!1;for(var f=r.doc.splitLines(function(e,t,n,r,o){var i="",a=!1,c=e.doc.lineSeparator(),s=!1;function l(){a&&(i+=c,s&&(i+=c),a=s=!1)}function u(e){e&&(l(),i+=e)}function f(t){if(1==t.nodeType){var n=t.getAttribute("cm-text");if(n)return void u(n);var i,d=t.getAttribute("cm-marker");if(d){var h=e.findMarks(et(r,0),et(o+1,0),(v=+d,function(e){return e.id==v}));return void(h.length&&(i=h[0].find(0))&&u(Ye(e.doc,i.from,i.to).join(c)))}if("false"==t.getAttribute("contenteditable"))return;var p=/^(pre|div|p|li|table|br)$/i.test(t.nodeName);if(!/^br$/i.test(t.nodeName)&&0==t.textContent.length)return;p&&l();for(var z=0;z1&&d.length>1;)if($(f)==$(d))f.pop(),d.pop(),s--;else{if(f[0]!=d[0])break;f.shift(),d.shift(),t++}for(var h=0,p=0,z=f[0],v=d[0],g=Math.min(z.length,v.length);ha.ch&&m.charCodeAt(m.length-p-1)==y.charCodeAt(y.length-p-1);)h--,p++;f[f.length-1]=m.slice(0,m.length-p).replace(/^\u200b+/,""),f[0]=f[0].slice(h).replace(/\u200b+$/,"");var w=et(t,h),k=et(s,d.length?$(d).length-p:0);return f.length>1||f[0]||tt(w,k)?(pi(r.doc,f,w,k,"+input"),!0):void 0},Va.prototype.ensurePolled=function(){this.forceCompositionEnd()},Va.prototype.reset=function(){this.forceCompositionEnd()},Va.prototype.forceCompositionEnd=function(){this.composing&&(clearTimeout(this.readDOMTimeout),this.composing=null,this.updateFromDOM(),this.div.blur(),this.div.focus())},Va.prototype.readFromDOMSoon=function(){var e=this;null==this.readDOMTimeout&&(this.readDOMTimeout=setTimeout((function(){if(e.readDOMTimeout=null,e.composing){if(!e.composing.done)return;e.composing=null}e.updateFromDOM()}),80))},Va.prototype.updateFromDOM=function(){var e=this;!this.cm.isReadOnly()&&this.pollContent()||Jr(this.cm,(function(){return fr(e.cm)}))},Va.prototype.setUneditable=function(e){e.contentEditable="false"},Va.prototype.onKeyPress=function(e){0==e.charCode||this.composing||(e.preventDefault(),this.cm.isReadOnly()||eo(this.cm,Oa)(this.cm,String.fromCharCode(null==e.charCode?e.keyCode:e.charCode),0))},Va.prototype.readOnlyChanged=function(e){this.div.contentEditable=String("nocursor"!=e)},Va.prototype.onContextMenu=function(){},Va.prototype.resetPosition=function(){},Va.prototype.needsContentAttribute=!0;var Ba=function(e){this.cm=e,this.prevInput="",this.pollingFast=!1,this.polling=new R,this.hasSelection=!1,this.composing=null};Ba.prototype.init=function(e){var t=this,n=this,r=this.cm;this.createField(e);var o=this.textarea;function i(e){if(!ze(r,e)){if(r.somethingSelected())Sa({lineWise:!1,text:r.getSelections()});else{if(!r.options.lineWiseCopyCut)return;var t=Aa(r);Sa({lineWise:!0,text:t.text}),"cut"==e.type?r.setSelections(t.ranges,null,F):(n.prevInput="",o.value=t.text.join("\n"),H(o))}"cut"==e.type&&(r.state.cutIncoming=+new Date)}}e.wrapper.insertBefore(this.wrapper,e.wrapper.firstChild),z&&(o.style.width="0px"),fe(o,"input",(function(){a&&c>=9&&t.hasSelection&&(t.hasSelection=null),n.poll()})),fe(o,"paste",(function(e){ze(r,e)||Ea(e,r)||(r.state.pasteIncoming=+new Date,n.fastPoll())})),fe(o,"cut",i),fe(o,"copy",i),fe(e.scroller,"paste",(function(t){if(!kn(e,t)&&!ze(r,t)){if(!o.dispatchEvent)return r.state.pasteIncoming=+new Date,void n.focus();var i=new Event("paste");i.clipboardData=t.clipboardData,o.dispatchEvent(i)}})),fe(e.lineSpace,"selectstart",(function(t){kn(e,t)||ye(t)})),fe(o,"compositionstart",(function(){var e=r.getCursor("from");n.composing&&n.composing.range.clear(),n.composing={start:e,range:r.markText(e,r.getCursor("to"),{className:"CodeMirror-composing"})}})),fe(o,"compositionend",(function(){n.composing&&(n.poll(),n.composing.range.clear(),n.composing=null)}))},Ba.prototype.createField=function(e){this.wrapper=Ha(),this.textarea=this.wrapper.firstChild},Ba.prototype.screenReaderLabelChanged=function(e){e?this.textarea.setAttribute("aria-label",e):this.textarea.removeAttribute("aria-label")},Ba.prototype.prepareSelection=function(){var e=this.cm,t=e.display,n=e.doc,r=gr(e);if(e.options.moveInputWithCursor){var o=Yn(e,n.sel.primary().head,"div"),i=t.wrapper.getBoundingClientRect(),a=t.lineDiv.getBoundingClientRect();r.teTop=Math.max(0,Math.min(t.wrapper.clientHeight-10,o.top+a.top-i.top)),r.teLeft=Math.max(0,Math.min(t.wrapper.clientWidth-10,o.left+a.left-i.left))}return r},Ba.prototype.showSelection=function(e){var t=this.cm.display;q(t.cursorDiv,e.cursors),q(t.selectionDiv,e.selection),null!=e.teTop&&(this.wrapper.style.top=e.teTop+"px",this.wrapper.style.left=e.teLeft+"px")},Ba.prototype.reset=function(e){if(!this.contextMenuPending&&!this.composing){var t=this.cm;if(t.somethingSelected()){this.prevInput="";var n=t.getSelection();this.textarea.value=n,t.state.focused&&H(this.textarea),a&&c>=9&&(this.hasSelection=n)}else e||(this.prevInput=this.textarea.value="",a&&c>=9&&(this.hasSelection=null))}},Ba.prototype.getField=function(){return this.textarea},Ba.prototype.supportsTouch=function(){return!1},Ba.prototype.focus=function(){if("nocursor"!=this.cm.options.readOnly&&(!g||T()!=this.textarea))try{this.textarea.focus()}catch(e){}},Ba.prototype.blur=function(){this.textarea.blur()},Ba.prototype.resetPosition=function(){this.wrapper.style.top=this.wrapper.style.left=0},Ba.prototype.receivedFocus=function(){this.slowPoll()},Ba.prototype.slowPoll=function(){var e=this;this.pollingFast||this.polling.set(this.cm.options.pollInterval,(function(){e.poll(),e.cm.state.focused&&e.slowPoll()}))},Ba.prototype.fastPoll=function(){var e=!1,t=this;t.pollingFast=!0,t.polling.set(20,(function n(){t.poll()||e?(t.pollingFast=!1,t.slowPoll()):(e=!0,t.polling.set(60,n))}))},Ba.prototype.poll=function(){var e=this,t=this.cm,n=this.textarea,r=this.prevInput;if(this.contextMenuPending||!t.state.focused||Te(n)&&!r&&!this.composing||t.isReadOnly()||t.options.disableInput||t.state.keySeq)return!1;var o=n.value;if(o==r&&!t.somethingSelected())return!1;if(a&&c>=9&&this.hasSelection===o||m&&/[\uf700-\uf7ff]/.test(o))return t.display.input.reset(),!1;if(t.doc.sel==t.display.selForContextMenu){var i=o.charCodeAt(0);if(8203!=i||r||(r="\u200b"),8666==i)return this.reset(),this.cm.execCommand("undo")}for(var s=0,l=Math.min(r.length,o.length);s1e3||o.indexOf("\n")>-1?n.value=e.prevInput="":e.prevInput=o,e.composing&&(e.composing.range.clear(),e.composing.range=t.markText(e.composing.start,t.getCursor("to"),{className:"CodeMirror-composing"}))})),!0},Ba.prototype.ensurePolled=function(){this.pollingFast&&this.poll()&&(this.pollingFast=!1)},Ba.prototype.onKeyPress=function(){a&&c>=9&&(this.hasSelection=null),this.fastPoll()},Ba.prototype.onContextMenu=function(e){var t=this,n=t.cm,r=n.display,o=t.textarea;t.contextMenuPending&&t.contextMenuPending();var i=lr(n,e),l=r.scroller.scrollTop;if(i&&!f){n.options.resetSelectionOnContextMenu&&-1==n.doc.sel.contains(i)&&eo(n,Jo)(n.doc,Co(i),F);var u,d=o.style.cssText,h=t.wrapper.style.cssText,p=t.wrapper.offsetParent.getBoundingClientRect();t.wrapper.style.cssText="position: static",o.style.cssText="position: absolute; width: 30px; height: 30px;\n top: "+(e.clientY-p.top-5)+"px; left: "+(e.clientX-p.left-5)+"px;\n z-index: 1000; background: "+(a?"rgba(255, 255, 255, .05)":"transparent")+";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);",s&&(u=window.scrollY),r.input.focus(),s&&window.scrollTo(null,u),r.input.reset(),n.somethingSelected()||(o.value=t.prevInput=" "),t.contextMenuPending=v,r.selForContextMenu=n.doc.sel,clearTimeout(r.detectingSelectAll),a&&c>=9&&z(),x?(ke(e),fe(window,"mouseup",(function e(){he(window,"mouseup",e),setTimeout(v,20)}))):setTimeout(v,50)}function z(){if(null!=o.selectionStart){var e=n.somethingSelected(),i="\u200b"+(e?o.value:"");o.value="\u21da",o.value=i,t.prevInput=e?"":"\u200b",o.selectionStart=1,o.selectionEnd=i.length,r.selForContextMenu=n.doc.sel}}function v(){if(t.contextMenuPending==v&&(t.contextMenuPending=!1,t.wrapper.style.cssText=h,o.style.cssText=d,a&&c<9&&r.scrollbars.setScrollTop(r.scroller.scrollTop=l),null!=o.selectionStart)){(!a||a&&c<9)&&z();var e=0;r.detectingSelectAll=setTimeout((function i(){r.selForContextMenu==n.doc.sel&&0==o.selectionStart&&o.selectionEnd>0&&"\u200b"==t.prevInput?eo(n,ci)(n):e++<10?r.detectingSelectAll=setTimeout(i,500):(r.selForContextMenu=null,r.input.reset())}),200)}}},Ba.prototype.readOnlyChanged=function(e){e||this.reset(),this.textarea.disabled="nocursor"==e},Ba.prototype.setUneditable=function(){},Ba.prototype.needsContentAttribute=!1,function(e){var t=e.optionHandlers;function n(n,r,o,i){e.defaults[n]=r,o&&(t[n]=i?function(e,t,n){n!=ba&&o(e,t,n)}:o)}e.defineOption=n,e.Init=ba,n("value","",(function(e,t){return e.setValue(t)}),!0),n("mode",null,(function(e,t){e.doc.modeOption=t,Eo(e)}),!0),n("indentUnit",2,Eo,!0),n("indentWithTabs",!1),n("smartIndent",!0),n("tabSize",4,(function(e){To(e),In(e),fr(e)}),!0),n("lineSeparator",null,(function(e,t){if(e.doc.lineSep=t,t){var n=[],r=e.doc.first;e.doc.iter((function(e){for(var o=0;;){var i=e.text.indexOf(t,o);if(-1==i)break;o=i+t.length,n.push(et(r,i))}r++}));for(var o=n.length-1;o>=0;o--)pi(e.doc,t,n[o],et(n[o].line,n[o].ch+t.length))}})),n("specialChars",/[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200c\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g,(function(e,t,n){e.state.specialChars=new RegExp(t.source+(t.test("\t")?"":"|\t"),"g"),n!=ba&&e.refresh()})),n("specialCharPlaceholder",Qt,(function(e){return e.refresh()}),!0),n("electricChars",!0),n("inputStyle",g?"contenteditable":"textarea",(function(){throw new Error("inputStyle can not (yet) be changed in a running editor")}),!0),n("spellcheck",!1,(function(e,t){return e.getInputField().spellcheck=t}),!0),n("autocorrect",!1,(function(e,t){return e.getInputField().autocorrect=t}),!0),n("autocapitalize",!1,(function(e,t){return e.getInputField().autocapitalize=t}),!0),n("rtlMoveVisually",!b),n("wholeLineUpdateBefore",!0),n("theme","default",(function(e){ya(e),vo(e)}),!0),n("keyMap","default",(function(e,t,n){var r=$i(t),o=n!=ba&&$i(n);o&&o.detach&&o.detach(e,r),r.attach&&r.attach(e,o||null)})),n("extraKeys",null),n("configureMouse",null),n("lineWrapping",!1,ja,!0),n("gutters",[],(function(e,t){e.display.gutterSpecs=po(t,e.options.lineNumbers),vo(e)}),!0),n("fixedGutter",!0,(function(e,t){e.display.gutters.style.left=t?ar(e.display)+"px":"0",e.refresh()}),!0),n("coverGutterNextToScrollbar",!1,(function(e){return Nr(e)}),!0),n("scrollbarStyle","native",(function(e){Ur(e),Nr(e),e.display.scrollbars.setScrollTop(e.doc.scrollTop),e.display.scrollbars.setScrollLeft(e.doc.scrollLeft)}),!0),n("lineNumbers",!1,(function(e,t){e.display.gutterSpecs=po(e.options.gutters,t),vo(e)}),!0),n("firstLineNumber",1,vo,!0),n("lineNumberFormatter",(function(e){return e}),vo,!0),n("showCursorWhenSelecting",!1,vr,!0),n("resetSelectionOnContextMenu",!0),n("lineWiseCopyCut",!0),n("pasteLinesPerSelection",!0),n("selectionsMayTouch",!1),n("readOnly",!1,(function(e,t){"nocursor"==t&&(Mr(e),e.display.input.blur()),e.display.input.readOnlyChanged(t)})),n("screenReaderLabel",null,(function(e,t){t=""===t?null:t,e.display.input.screenReaderLabelChanged(t)})),n("disableInput",!1,(function(e,t){t||e.display.input.reset()}),!0),n("dragDrop",!0,xa),n("allowDropFileTypes",null),n("cursorBlinkRate",530),n("cursorScrollMargin",0),n("cursorHeight",1,vr,!0),n("singleCursorHeightPerLine",!0,vr,!0),n("workTime",100),n("workDelay",100),n("flattenSpans",!0,To,!0),n("addModeClass",!1,To,!0),n("pollInterval",100),n("undoDepth",200,(function(e,t){return e.doc.history.undoDepth=t})),n("historyEventDelay",1250),n("viewportMargin",10,(function(e){return e.refresh()}),!0),n("maxHighlightLength",1e4,To,!0),n("moveInputWithCursor",!0,(function(e,t){t||e.display.input.resetPosition()})),n("tabindex",null,(function(e,t){return e.display.input.getField().tabIndex=t||""})),n("autofocus",null),n("direction","ltr",(function(e,t){return e.doc.setDirection(t)}),!0),n("phrases",null)}(Ma),function(e){var t=e.optionHandlers,n=e.helpers={};e.prototype={constructor:e,focus:function(){window.focus(),this.display.input.focus()},setOption:function(e,n){var r=this.options,o=r[e];r[e]==n&&"mode"!=e||(r[e]=n,t.hasOwnProperty(e)&&eo(this,t[e])(this,n,o),pe(this,"optionChange",this,e))},getOption:function(e){return this.options[e]},getDoc:function(){return this.doc},addKeyMap:function(e,t){this.state.keyMaps[t?"push":"unshift"]($i(e))},removeKeyMap:function(e){for(var t=this.state.keyMaps,n=0;nn&&(_a(this,o.head.line,e,!0),n=o.head.line,r==this.doc.sel.primIndex&&Er(this));else{var i=o.from(),a=o.to(),c=Math.max(n,i.line);n=Math.min(this.lastLine(),a.line-(a.ch?0:1))+1;for(var s=c;s0&&Xo(this.doc,r,new jo(i,l[r].to()),F)}}})),getTokenAt:function(e,t){return mt(this,e,t)},getLineTokens:function(e,t){return mt(this,et(e),t,!0)},getTokenTypeAt:function(e){e=ct(this.doc,e);var t,n=dt(this,Ge(this.doc,e.line)),r=0,o=(n.length-1)/2,i=e.ch;if(0==i)t=n[2];else for(;;){var a=r+o>>1;if((a?n[2*a-1]:0)>=i)o=a;else{if(!(n[2*a+1]i&&(e=i,o=!0),r=Ge(this.doc,e)}else r=e;return Un(this,r,{top:0,left:0},t||"page",n||o).top+(o?this.doc.height-Bt(r):0)},defaultTextHeight:function(){return rr(this.display)},defaultCharWidth:function(){return or(this.display)},getViewport:function(){return{from:this.display.viewFrom,to:this.display.viewTo}},addWidget:function(e,t,n,r,o){var i,a,c=this.display,s=(e=Yn(this,ct(this.doc,e))).bottom,l=e.left;if(t.style.position="absolute",t.setAttribute("cm-ignore-events","true"),this.display.input.setUneditable(t),c.sizer.appendChild(t),"over"==r)s=e.top;else if("above"==r||"near"==r){var u=Math.max(c.wrapper.clientHeight,this.doc.height),f=Math.max(c.sizer.clientWidth,c.lineSpace.clientWidth);("above"==r||e.bottom+t.offsetHeight>u)&&e.top>t.offsetHeight?s=e.top-t.offsetHeight:e.bottom+t.offsetHeight<=u&&(s=e.bottom),l+t.offsetWidth>f&&(l=f-t.offsetWidth)}t.style.top=s+"px",t.style.left=t.style.right="","right"==o?(l=c.sizer.clientWidth-t.offsetWidth,t.style.right="0px"):("left"==o?l=0:"middle"==o&&(l=(c.sizer.clientWidth-t.offsetWidth)/2),t.style.left=l+"px"),n&&(null!=(a=Sr(i=this,{left:l,top:s,right:l+t.offsetWidth,bottom:s+t.offsetHeight})).scrollTop&&Hr(i,a.scrollTop),null!=a.scrollLeft&&Pr(i,a.scrollLeft))},triggerOnKeyDown:to(sa),triggerOnKeyPress:to(ua),triggerOnKeyUp:la,triggerOnMouseDown:to(pa),execCommand:function(e){if(Ji.hasOwnProperty(e))return Ji[e].call(null,this)},triggerElectric:to((function(e){Ta(this,e)})),findPosH:function(e,t,n,r){var o=1;t<0&&(o=-1,t=-t);for(var i=ct(this.doc,e),a=0;a0&&a(t.charAt(n-1));)--n;for(;r.5||this.options.lineWrapping)&&sr(this),pe(this,"refresh",this)})),swapDoc:to((function(e){var t=this.doc;return t.cm=null,this.state.selectingText&&this.state.selectingText(),Do(this,e),In(this),this.display.input.reset(),Tr(this,e.scrollLeft,e.scrollTop),this.curOp.forceScroll=!0,sn(this,"swapDoc",this,t),t})),phrase:function(e){var t=this.options.phrases;return t&&Object.prototype.hasOwnProperty.call(t,e)?t[e]:e},getInputField:function(){return this.display.input.getField()},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}},me(e),e.registerHelper=function(t,r,o){n.hasOwnProperty(t)||(n[t]=e[t]={_global:[]}),n[t][r]=o},e.registerGlobalHelper=function(t,r,o,i){e.registerHelper(t,r,i),n[t]._global.push({pred:o,val:i})}}(Ma);var Ua="iter insert remove copy getEditor constructor".split(" ");for(var Wa in Oi.prototype)Oi.prototype.hasOwnProperty(Wa)&&I(Ua,Wa)<0&&(Ma.prototype[Wa]=function(e){return function(){return e.apply(this.doc,arguments)}}(Oi.prototype[Wa]));return me(Oi),Ma.inputStyles={textarea:Ba,contenteditable:Va},Ma.defineMode=function(e){Ma.defaults.mode||"null"==e||(Ma.defaults.mode=e),Pe.apply(this,arguments)},Ma.defineMIME=function(e,t){De[e]=t},Ma.defineMode("null",(function(){return{token:function(e){return e.skipToEnd()}}})),Ma.defineMIME("text/plain","null"),Ma.defineExtension=function(e,t){Ma.prototype[e]=t},Ma.defineDocExtension=function(e,t){Oi.prototype[e]=t},Ma.fromTextArea=function(e,t){if((t=t?P(t):{}).value=e.value,!t.tabindex&&e.tabIndex&&(t.tabindex=e.tabIndex),!t.placeholder&&e.placeholder&&(t.placeholder=e.placeholder),null==t.autofocus){var n=T();t.autofocus=n==e||null!=e.getAttribute("autofocus")&&n==document.body}function r(){e.value=c.getValue()}var o;if(e.form&&(fe(e.form,"submit",r),!t.leaveSubmitMethodAlone)){var i=e.form;o=i.submit;try{var a=i.submit=function(){r(),i.submit=o,i.submit(),i.submit=a}}catch(e){}}t.finishInit=function(n){n.save=r,n.getTextArea=function(){return e},n.toTextArea=function(){n.toTextArea=isNaN,r(),e.parentNode.removeChild(n.getWrapperElement()),e.style.display="",e.form&&(he(e.form,"submit",r),t.leaveSubmitMethodAlone||"function"!=typeof e.form.submit||(e.form.submit=o))}},e.style.display="none";var c=Ma((function(t){return e.parentNode.insertBefore(t,e.nextSibling)}),t);return c},function(e){e.off=he,e.on=fe,e.wheelEventPixels=wo,e.Doc=Oi,e.splitLines=Ee,e.countColumn=V,e.findColumn=W,e.isWordChar=J,e.Pass=N,e.signal=pe,e.Line=Gt,e.changeEnd=_o,e.scrollbarModel=Br,e.Pos=et,e.cmpPos=tt,e.modes=He,e.mimeModes=De,e.resolveMode=Ve,e.getMode=Re,e.modeExtensions=Ie,e.extendMode=Ne,e.copyState=Fe,e.startState=Ue,e.innerMode=Be,e.commands=Ji,e.keyMap=Ni,e.keyName=Yi,e.isModifierKey=Wi,e.lookupKey=Ui,e.normalizeKeyMap=Bi,e.StringStream=We,e.SharedTextMarker=Ci,e.TextMarker=ji,e.LineWidget=wi,e.e_preventDefault=ye,e.e_stopPropagation=be,e.e_stop=ke,e.addClass=A,e.contains=E,e.rmClass=C,e.keyNames=Pi}(Ma),Ma.version="5.57.0",Ma}()},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(0),o=function(){function e(){}return e.prototype.isSeparator=function(e){return" "==e||"\r"==e||"\n"==e||"\t"==e||"("==e||")"==e},e.prototype.isWhiteSpace=function(e){return" "==e||"\r"==e||"\n"==e||"\t"==e},e.prototype.findLastSeparatorIndex=function(e){var t=this;return r.findLastIndex(e,(function(e){return t.isSeparator(e)}))},e.prototype.needSpaceAfter=function(e){return!("("==e)},e.prototype.isLastCharacterWhiteSpace=function(e){return!!e&&this.isWhiteSpace(e[e.length-1])},e.prototype.stripEndWithNonSeparatorCharacters=function(e){if(!e)return e;if(this.isSeparator(e[e.length-1]))return e;var t=this.findLastSeparatorIndex(e);return t<0?"":e.substr(0,t+1)},e.prototype.getEndNotSeparatorCharacers=function(e){if(!e)return e;if(this.isSeparator(e[e.length-1]))return"";var t=this.findLastSeparatorIndex(e);return t<0?e:e.substr(t+1)},e}();t.default=new o},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(0),o=function(){function e(){}return e.prototype.quote=function(e){return/\s/g.test(e)?'"'+e+'"':e},e.prototype.buildDefaultObjOrGetOriginal=function(e,t){return r.isString(e)?{value:this.quote(e),type:t}:{value:e,type:t}},e.prototype.handleParseError=function(e,t,n){var o=this,i=t;return r.flatMap(n.expected,(function(e){var t=[];if("literal"==e.type&&(t=r.map([e.text||e.value],(function(e){return{value:e,type:"literal"}}))),"other"==e.type){var n=i.getLastTokenType()||"value";"value"==n&&(t=r.map(o.needCategories(),(function(e){return o.buildDefaultObjOrGetOriginal(e,"category")}))),"category"==n&&(t=r.map(o.needOperators(i.getLastCategory()),(function(e){return o.buildDefaultObjOrGetOriginal(e,"operator")}))),"operator"==n&&(t=r.map(o.needValues(i.getLastCategory(),i.getLastOperator()),(function(e){return o.buildDefaultObjOrGetOriginal(e,"value")})))}return t}))},e.prototype.hasCategory=function(e){return!1},e.prototype.hasOperator=function(e,t){return!1},e.prototype.needCategories=function(){return[]},e.prototype.needOperators=function(e){return[]},e.prototype.needValues=function(e,t){return[]},e}();t.default=o},function(e,t){e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=function(e,t){var n,r=e[1]||"",o=e[3];if(!o)return r;if(t&&"function"==typeof btoa){var i=(n=o,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(n))))+" */"),a=o.sources.map((function(e){return"/*# sourceURL="+o.sourceRoot+e+" */"}));return[r].concat(a).concat([i]).join("\n")}return[r].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n})).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},o=0;o=0&&f.splice(t,1)}function g(e){var t=document.createElement("style");if(void 0===e.attrs.type&&(e.attrs.type="text/css"),void 0===e.attrs.nonce){var r=n.nc;r&&(e.attrs.nonce=r)}return m(t,e.attrs),z(e,t),t}function m(e,t){Object.keys(t).forEach((function(n){e.setAttribute(n,t[n])}))}function y(e,t){var n,r,o,i;if(t.transform&&e.css){if(!(i="function"==typeof t.transform?t.transform(e.css):t.transform.default(e.css)))return function(){};e.css=i}if(t.singleton){var a=u++;n=l||(l=g(t)),r=k.bind(null,n,a,!1),o=k.bind(null,n,a,!0)}else e.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(e){var t=document.createElement("link");return void 0===e.attrs.type&&(e.attrs.type="text/css"),e.attrs.rel="stylesheet",m(t,e.attrs),z(e,t),t}(t),r=j.bind(null,n,t),o=function(){v(n),n.href&&URL.revokeObjectURL(n.href)}):(n=g(t),r=x.bind(null,n),o=function(){v(n)});return r(e),function(t){if(t){if(t.css===e.css&&t.media===e.media&&t.sourceMap===e.sourceMap)return;r(e=t)}else o()}}e.exports=function(e,t){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(t=t||{}).attrs="object"==typeof t.attrs?t.attrs:{},t.singleton||"boolean"==typeof t.singleton||(t.singleton=a()),t.insertInto||(t.insertInto="head"),t.insertAt||(t.insertAt="bottom");var n=p(e,t);return h(n,t),function(e){for(var r=[],o=0;o1)){if(this.somethingSelected()){if(!n.hint.supportsSelection)return;for(var o=0;ol.clientHeight+1,E=a.getScrollInfo();if(S>0){var T=q.bottom-q.top;if(g.top-(g.bottom-q.top)-T>0)l.style.top=(y=g.top-T-k)+"px",b=!1;else if(T>_){l.style.height=_-5+"px",l.style.top=(y=g.bottom-q.top-k)+"px";var A=a.getCursor();n.from.ch!=A.ch&&(g=a.cursorCoords(A),l.style.left=(m=g.left-w)+"px",q=l.getBoundingClientRect())}}var L,H=q.right-C;if(H>0&&(q.right-q.left>C&&(l.style.width=C-5+"px",H-=q.right-q.left-C),l.style.left=(m=g.left-H-w)+"px"),O)for(var D=l.firstChild;D;D=D.nextSibling)D.style.paddingRight=a.display.nativeBarWidth+"px";return a.addKeyMap(this.keyMap=function(e,t){var n={Up:function(){t.moveFocus(-1)},Down:function(){t.moveFocus(1)},PageUp:function(){t.moveFocus(1-t.menuSize(),!0)},PageDown:function(){t.moveFocus(t.menuSize()-1,!0)},Home:function(){t.setFocus(0)},End:function(){t.setFocus(t.length-1)},Enter:t.pick,Tab:t.pick,Esc:t.close};/Mac/.test(navigator.platform)&&(n["Ctrl-P"]=function(){t.moveFocus(-1)},n["Ctrl-N"]=function(){t.moveFocus(1)});var r=e.options.customKeys,o=r?{}:n;function i(e,r){var i;i="string"!=typeof r?function(e){return r(e,t)}:n.hasOwnProperty(r)?n[r]:r,o[e]=i}if(r)for(var a in r)r.hasOwnProperty(a)&&i(a,r[a]);var c=e.options.extraKeys;if(c)for(var a in c)c.hasOwnProperty(a)&&i(a,c[a]);return o}(t,{moveFocus:function(e,t){r.changeActive(r.selectedHint+e,t)},setFocus:function(e){r.changeActive(e)},menuSize:function(){return r.screenAmount()},length:f.length,close:function(){t.close()},pick:function(){r.pick()},data:n})),t.options.closeOnUnfocus&&(a.on("blur",this.onBlur=function(){L=setTimeout((function(){t.close()}),100)}),a.on("focus",this.onFocus=function(){clearTimeout(L)})),a.on("scroll",this.onScroll=function(){var e=a.getScrollInfo(),n=a.getWrapperElement().getBoundingClientRect(),r=y+E.top-e.top,o=r-(s.pageYOffset||(c.documentElement||c.body).scrollTop);if(b||(o+=l.offsetHeight),o<=n.top||o>=n.bottom)return t.close();l.style.top=r+"px",l.style.left=m+E.left-e.left+"px"}),e.on(l,"dblclick",(function(e){var t=i(l,e.target||e.srcElement);t&&null!=t.hintId&&(r.changeActive(t.hintId),r.pick())})),e.on(l,"click",(function(e){var n=i(l,e.target||e.srcElement);n&&null!=n.hintId&&(r.changeActive(n.hintId),t.options.completeOnSingleClick&&r.pick())})),e.on(l,"mousedown",(function(){setTimeout((function(){a.focus()}),20)})),this.scrollToActive(),e.signal(n,"select",f[this.selectedHint],l.childNodes[this.selectedHint]),!0}function c(e,t,n,r){if(e.async)e(t,r,n);else{var o=e(t,n);o&&o.then?o.then(r):r(o)}}t.prototype={close:function(){this.active()&&(this.cm.state.completionActive=null,this.tick=null,this.cm.off("cursorActivity",this.activityFunc),this.widget&&this.data&&e.signal(this.data,"close"),this.widget&&this.widget.close(),e.signal(this.cm,"endCompletion",this.cm))},active:function(){return this.cm.state.completionActive==this},pick:function(t,n){var r=t.list[n],i=this;this.cm.operation((function(){r.hint?r.hint(i.cm,t,r):i.cm.replaceRange(o(r),r.from||t.from,r.to||t.to,"complete"),e.signal(t,"pick",r),i.cm.scrollIntoView()})),this.close()},cursorActivity:function(){this.debounce&&(r(this.debounce),this.debounce=0);var e=this.startPos;this.data&&(e=this.data.from);var t=this.cm.getCursor(),o=this.cm.getLine(t.line);if(t.line!=this.startPos.line||o.length-t.ch!=this.startLen-this.startPos.ch||t.ch=this.data.list.length?t=n?this.data.list.length-1:0:t<0&&(t=n?0:this.data.list.length-1),this.selectedHint!=t){var r=this.hints.childNodes[this.selectedHint];r&&(r.className=r.className.replace(" CodeMirror-hint-active","")),(r=this.hints.childNodes[this.selectedHint=t]).className+=" CodeMirror-hint-active",this.scrollToActive(),e.signal(this.data,"select",this.data.list[this.selectedHint],r)}},scrollToActive:function(){var e=this.completion.options.scrollMargin||0,t=this.hints.childNodes[Math.max(0,this.selectedHint-e)],n=this.hints.childNodes[Math.min(this.data.list.length-1,this.selectedHint+e)],r=this.hints.firstChild;t.offsetTopthis.hints.scrollTop+this.hints.clientHeight&&(this.hints.scrollTop=n.offsetTop+n.offsetHeight-this.hints.clientHeight+r.offsetTop)},screenAmount:function(){return Math.floor(this.hints.clientHeight/this.hints.firstChild.offsetHeight)||1}},e.registerHelper("hint","auto",{resolve:function(t,n){var r,o=t.getHelpers(n,"hint");if(o.length){var i=function(e,t,n){var r=function(e,t){if(!e.somethingSelected())return t;for(var n=[],r=0;r0?t(e):o(i+1)}))}(0)};return i.async=!0,i.supportsSelection=!0,i}return(r=t.getHelper(t.getCursor(),"hintWords"))?function(t){return e.hint.fromList(t,{words:r})}:e.hint.anyword?function(t,n){return e.hint.anyword(t,n)}:function(){}}}),e.registerHelper("hint","fromList",(function(t,n){var r,o=t.getCursor(),i=t.getTokenAt(o),a=e.Pos(o.line,i.start),c=o;i.start,]/,closeOnUnfocus:!0,completeOnSingleClick:!0,container:null,customKeys:null,extraKeys:null};e.defineOption("hintOptions",null)}(n(2))},function(e,t,n){!function(e){function t(e){e.state.placeholder&&(e.state.placeholder.parentNode.removeChild(e.state.placeholder),e.state.placeholder=null)}function n(e){t(e);var n=e.state.placeholder=document.createElement("pre");n.style.cssText="height: 0; overflow: visible",n.style.direction=e.getOption("direction"),n.className="CodeMirror-placeholder CodeMirror-line-like";var r=e.getOption("placeholder");"string"==typeof r&&(r=document.createTextNode(r)),n.appendChild(r),e.display.lineSpace.insertBefore(n,e.display.lineSpace.firstChild)}function r(e){i(e)&&n(e)}function o(e){var r=e.getWrapperElement(),o=i(e);r.className=r.className.replace(" CodeMirror-empty","")+(o?" CodeMirror-empty":""),o?n(e):t(e)}function i(e){return 1===e.lineCount()&&""===e.getLine(0)}e.defineOption("placeholder","",(function(n,i,a){var c=a&&a!=e.Init;if(i&&!c)n.on("blur",r),n.on("change",o),n.on("swapDoc",o),o(n);else if(!i&&c){n.off("blur",r),n.off("change",o),n.off("swapDoc",o),t(n);var s=n.getWrapperElement();s.className=s.className.replace(" CodeMirror-empty","")}i&&!n.hasFocus()&&r(n)}))}(n(2))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),n(2).defineMode("filter-mode",(function(e,t){function n(e){var t,n=(t=e.fieldState)==r.category?r.operator:t==r.operator?r.value:t==r.value?r.category:void 0,o=e.fieldState;return e.fieldState=n,o.toString()}return{startState:function(){return{inString:!1,fieldState:r.category}},token:function(e,t){return" "==(r=e.peek())||"\r"==r||"\n"==r||"\t"==r?(e.eatSpace(),null):"("==e.peek()||")"==e.peek()?(e.next(),"bracket"):e.match("AND",!0,!0)||e.match("OR",!0,!0)?"condition":(t.inString||'"'!=e.peek()||(e.next(),t.inString=!0),t.inString?(e.skipTo('"')?(e.next(),t.inString=!1):e.skipToEnd(),n(t)):(e.eatWhile(/[^\r\n\t\s\(\)]+/),n(t)));var r}}}));var r=function(){function e(){}return e.none="none",e.category="category",e.operator="operator",e.value="value",e}()},function(e,t,n){var r=n(16);"string"==typeof r&&(r=[[e.i,r,""]]),n(6)(r,{hmr:!0,transform:void 0,insertInto:void 0}),r.locals&&(e.exports=r.locals)},function(e,t,n){(e.exports=n(5)(!1)).push([e.i,".CodeMirror{font-family:monospace;height:300px;color:black;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{padding:0 4px}.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{background-color:white}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:black}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid black;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0 !important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-fat-cursor-mark{background-color:rgba(20,255,20,0.5);-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{position:absolute;left:0;right:0;top:-50px;bottom:0;overflow:hidden}.CodeMirror-ruler{border-left:1px solid #ccc;top:0;bottom:0;position:absolute}.cm-s-default .cm-header{color:blue}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:bold}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3,.cm-s-default .cm-type{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-s-default .cm-error{color:#f00}.cm-invalidchar{color:#f00}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:rgba(255,150,0,0.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:white}.CodeMirror-scroll{overflow:scroll !important;margin-bottom:-50px;margin-right:-50px;padding-bottom:50px;height:100%;outline:none;position:relative}.CodeMirror-sizer{position:relative;border-right:50px solid transparent}.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-50px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:none !important;border:none !important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:transparent;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual}.CodeMirror-wrap pre.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;padding:.1px}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:none}.CodeMirror-scroll,.CodeMirror-sizer,.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute;pointer-events:none}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-focused div.CodeMirror-cursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background-color:#ffa;background-color:rgba(255,255,0,0.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:none}",""])},function(e,t){e.exports=function(e){var t="undefined"!=typeof window&&window.location;if(!t)throw new Error("fixUrls requires window.location");if(!e||"string"!=typeof e)return e;var n=t.protocol+"//"+t.host,r=n+t.pathname.replace(/\/[^\/]*$/,"/");return e.replace(/url\s*\(((?:[^)(]|\((?:[^)(]+|\([^)(]*\))*\))*)\)/gi,(function(e,t){var o,i=t.trim().replace(/^"(.*)"$/,(function(e,t){return t})).replace(/^'(.*)'$/,(function(e,t){return t}));return/^(#|data:|http:\/\/|https:\/\/|file:\/\/\/|\s*$)/i.test(i)?e:(o=0===i.indexOf("//")?i:0===i.indexOf("/")?n+i:r+i.replace(/^\.\//,""),"url("+JSON.stringify(o)+")")}))}},function(e,t,n){var r=n(19);"string"==typeof r&&(r=[[e.i,r,""]]),n(6)(r,{hmr:!0,transform:void 0,insertInto:void 0}),r.locals&&(e.exports=r.locals)},function(e,t,n){(e.exports=n(5)(!1)).push([e.i,".CodeMirror-hints{position:absolute;z-index:10;overflow:hidden;list-style:none;margin:0;padding:2px;-webkit-box-shadow:2px 3px 5px rgba(0,0,0,0.2);-moz-box-shadow:2px 3px 5px rgba(0,0,0,0.2);box-shadow:2px 3px 5px rgba(0,0,0,0.2);border-radius:3px;border:1px solid silver;background:white;font-size:90%;font-family:monospace;max-height:20em;overflow-y:auto}.CodeMirror-hint{margin:0;padding:0 4px;border-radius:2px;white-space:pre;color:black;cursor:pointer}li.CodeMirror-hint-active{background:#08f;color:white}",""])},function(e,t,n){"use strict";(function(e){var r,o=Object.assign||function(e){for(var t=1;t=0;case"!contains":return e[o].toLowerCase().indexOf(r.toLowerCase())<0}return!1},t}(n(8).default);t.default=a},function(e,t,n){"use strict";var r,o=this&&this.__extends||(r=function(e,t){return(r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)},function(e,t){function n(){this.constructor=e}r(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)});Object.defineProperty(t,"__esModule",{value:!0});var i=n(4),a=n(0),c=function(e){function t(t,n){var r=e.call(this)||this;return r.data=t,r.options=n,r.cache={},r.parseResult=null,r.categories=a.map(r.options,(function(e){return e.columnText?e.columnText:e.columnField})),r}return o(t,e),t.prototype.hasCategory=function(e){return void 0!==a.find(this.options,(function(t){return e===t.columnField||e===t.columnText}))},t.prototype.hasOperator=function(e,t){return this.needOperators(e).indexOf(t)>=0},t.prototype.needCategories=function(){return this.categories},t.prototype.needOperators=function(e){var t=a.find(this.options,(function(t){return null!=t.customOperatorFunc&&(t.columnText==e||t.columnField==e)}));return t?t.customOperatorFunc(e):["==","!=","contains","!contains"]},t.prototype.needValues=function(e,t){var n=a.find(this.options,(function(t){return t.columnField==e||t.columnText==e}));return null!=n&&"selection"==n.type&&null!=this.data?(this.cache[e]||(this.cache[e]=a.chain(this.data).map((function(t){return t[e]})).uniq().value()),this.cache[e]):null!=n&&n.customValuesFunc?n.customValuesFunc(e,t):[]},t}(i.default);t.default=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(28),o=n(0),i=n(4),a=n(29),c=n(3),s=function(){function e(){this.autoCompleteHandler=new i.default,this.lastError=null,this.parseTrace=new a.default}return e.prototype.parse=function(e){if(e=o.trim(e),o.isEmpty(e))return[];try{return this.parseQuery(e)}catch(e){return e.isError=!0,e}},e.prototype.parseQuery=function(e){return this.parseTrace.clear(),r.parse(e,{parseTrace:this.parseTrace})},e.prototype.getSuggestions=function(e){e=c.default.stripEndWithNonSeparatorCharacters(e);try{return this.parseQuery(e),!e||c.default.isLastCharacterWhiteSpace(e)?o.map(["AND","OR"],(function(e){return{value:e,type:"literal"}})):[]}catch(e){return this.autoCompleteHandler.handleParseError(r,this.parseTrace,e)}},e.prototype.setAutoCompleteHandler=function(e){this.autoCompleteHandler=e},e}();t.default=s},function(e,t,n){"use strict";function r(e,t,n,o){this.message=e,this.expected=t,this.found=n,this.location=o,this.name="SyntaxError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,r)}!function(e,t){function n(){this.constructor=e}n.prototype=t.prototype,e.prototype=new n}(r,Error),r.buildMessage=function(e,t){var n={literal:function(e){return'"'+o(e.text)+'"'},class:function(e){var t,n="";for(t=0;t0){for(t=1,r=1;tM&&(M=k,C=[]),C.push(e))}function H(){var t,n,r,i,a,l,u,f;if(t=k,R()!==o)if((n=D())!==o){for(r=[],i=k,(a=V())!==o?("and"===e.substr(k,3).toLowerCase()?(l=e.substr(k,3),k+=3):(l=o,0===_&&L(c)),l===o&&("or"===e.substr(k,2).toLowerCase()?(l=e.substr(k,2),k+=2):(l=o,0===_&&L(s))),l!==o&&(u=V())!==o&&(f=D())!==o?i=a=[a,l,u,f]:(k=i,i=o)):(k=i,i=o);i!==o;)r.push(i),i=k,(a=V())!==o?("and"===e.substr(k,3).toLowerCase()?(l=e.substr(k,3),k+=3):(l=o,0===_&&L(c)),l===o&&("or"===e.substr(k,2).toLowerCase()?(l=e.substr(k,2),k+=2):(l=o,0===_&&L(s))),l!==o&&(u=V())!==o&&(f=D())!==o?i=a=[a,l,u,f]:(k=i,i=o)):(k=i,i=o);r!==o&&(i=R())!==o?(x=t,t=function(e,t){for(var n=[e],r=0;r=1&&d<=7))throw new RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var h=new Date(0);h.setUTCFullYear(c+1,0,d),h.setUTCHours(0,0,0,0);var p=Object(i.a)(h,t),z=new Date(0);z.setUTCFullYear(c,0,d),z.setUTCHours(0,0,0,0);var v=Object(i.a)(z,t);return n.getTime()>=p.getTime()?c+1:n.getTime()>=v.getTime()?c:c-1}},function(e,t,n){"use strict";n.d(t,"a",(function(){return a}));var r=n(15),o=n(10),i=n(6);function a(e,t){Object(i.a)(2,arguments);var n=Object(o.default)(e).getTime(),a=Object(r.a)(t);return new Date(n+a)}},function(e,t,n){"use strict";n.d(t,"a",(function(){return c}));var r=n(15),o=n(10),i=n(73),a=n(6);function c(e,t){Object(a.a)(1,arguments);var n=Object(o.default)(e,t),c=n.getUTCFullYear(),s=t||{},l=s.locale,u=l&&l.options&&l.options.firstWeekContainsDate,f=null==u?1:Object(r.a)(u),d=null==s.firstWeekContainsDate?f:Object(r.a)(s.firstWeekContainsDate);if(!(d>=1&&d<=7))throw new RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var h=new Date(0);h.setUTCFullYear(c+1,0,d),h.setUTCHours(0,0,0,0);var p=Object(i.a)(h,t),z=new Date(0);z.setUTCFullYear(c,0,d),z.setUTCHours(0,0,0,0);var v=Object(i.a)(z,t);return n.getTime()>=p.getTime()?c+1:n.getTime()>=v.getTime()?c:c-1}},function(e,t,n){"use strict";n.r(t),n.d(t,"default",(function(){return a}));var r=n(10),o=n(15),i=n(6);function a(e,t){Object(i.a)(1,arguments);var n=t||{},a=n.locale,c=a&&a.options&&a.options.weekStartsOn,s=null==c?0:Object(o.a)(c),l=null==n.weekStartsOn?s:Object(o.a)(n.weekStartsOn);if(!(l>=0&&l<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var u=Object(r.default)(e),f=u.getDay(),d=(f0&&void 0!==arguments[0]?arguments[0]:{},n=t.width?String(t.width):e.defaultWidth,r=e.formats[n]||e.formats[e.defaultWidth];return r}}var i={date:o({formats:{full:"EEEE, MMMM do, y",long:"MMMM do, y",medium:"MMM d, y",short:"MM/dd/yyyy"},defaultWidth:"full"}),time:o({formats:{full:"h:mm:ss a zzzz",long:"h:mm:ss a z",medium:"h:mm:ss a",short:"h:mm a"},defaultWidth:"full"}),dateTime:o({formats:{full:"{{date}} 'at' {{time}}",long:"{{date}} 'at' {{time}}",medium:"{{date}}, {{time}}",short:"{{date}}, {{time}}"},defaultWidth:"full"})},a={lastWeek:"'last' eeee 'at' p",yesterday:"'yesterday at' p",today:"'today at' p",tomorrow:"'tomorrow at' p",nextWeek:"eeee 'at' p",other:"P"};function c(e){return function(t,n){var r,o=n||{};if("formatting"===(o.context?String(o.context):"standalone")&&e.formattingValues){var i=e.defaultFormattingWidth||e.defaultWidth,a=o.width?String(o.width):i;r=e.formattingValues[a]||e.formattingValues[i]}else{var c=e.defaultWidth,s=o.width?String(o.width):e.defaultWidth;r=e.values[s]||e.values[c]}return r[e.argumentCallback?e.argumentCallback(t):t]}}function s(e){return function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=n.width,o=r&&e.matchPatterns[r]||e.matchPatterns[e.defaultMatchWidth],i=t.match(o);if(!i)return null;var a,c=i[0],s=r&&e.parsePatterns[r]||e.parsePatterns[e.defaultParseWidth],f=Array.isArray(s)?u(s,(function(e){return e.test(c)})):l(s,(function(e){return e.test(c)}));a=e.valueCallback?e.valueCallback(f):f,a=n.valueCallback?n.valueCallback(a):a;var d=t.slice(c.length);return{value:a,rest:d}}}function l(e,t){for(var n in e)if(e.hasOwnProperty(n)&&t(e[n]))return n}function u(e,t){for(var n=0;n0?"in "+o:o+" ago":o},formatLong:i,formatRelative:function(e,t,n,r){return a[e]},localize:{ordinalNumber:function(e,t){var n=Number(e),r=n%100;if(r>20||r<10)switch(r%10){case 1:return n+"st";case 2:return n+"nd";case 3:return n+"rd"}return n+"th"},era:c({values:{narrow:["B","A"],abbreviated:["BC","AD"],wide:["Before Christ","Anno Domini"]},defaultWidth:"wide"}),quarter:c({values:{narrow:["1","2","3","4"],abbreviated:["Q1","Q2","Q3","Q4"],wide:["1st quarter","2nd quarter","3rd quarter","4th quarter"]},defaultWidth:"wide",argumentCallback:function(e){return Number(e)-1}}),month:c({values:{narrow:["J","F","M","A","M","J","J","A","S","O","N","D"],abbreviated:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],wide:["January","February","March","April","May","June","July","August","September","October","November","December"]},defaultWidth:"wide"}),day:c({values:{narrow:["S","M","T","W","T","F","S"],short:["Su","Mo","Tu","We","Th","Fr","Sa"],abbreviated:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],wide:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},defaultWidth:"wide"}),dayPeriod:c({values:{narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"}},defaultWidth:"wide",formattingValues:{narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"}},defaultFormattingWidth:"wide"})},match:{ordinalNumber:(f={matchPattern:/^(\d+)(th|st|nd|rd)?/i,parsePattern:/\d+/i,valueCallback:function(e){return parseInt(e,10)}},function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=e.match(f.matchPattern);if(!n)return null;var r=n[0],o=e.match(f.parsePattern);if(!o)return null;var i=f.valueCallback?f.valueCallback(o[0]):o[0];i=t.valueCallback?t.valueCallback(i):i;var a=e.slice(r.length);return{value:i,rest:a}}),era:s({matchPatterns:{narrow:/^(b|a)/i,abbreviated:/^(b\.?\s?c\.?|b\.?\s?c\.?\s?e\.?|a\.?\s?d\.?|c\.?\s?e\.?)/i,wide:/^(before christ|before common era|anno domini|common era)/i},defaultMatchWidth:"wide",parsePatterns:{any:[/^b/i,/^(a|c)/i]},defaultParseWidth:"any"}),quarter:s({matchPatterns:{narrow:/^[1234]/i,abbreviated:/^q[1234]/i,wide:/^[1234](th|st|nd|rd)? quarter/i},defaultMatchWidth:"wide",parsePatterns:{any:[/1/i,/2/i,/3/i,/4/i]},defaultParseWidth:"any",valueCallback:function(e){return e+1}}),month:s({matchPatterns:{narrow:/^[jfmasond]/i,abbreviated:/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i,wide:/^(january|february|march|april|may|june|july|august|september|october|november|december)/i},defaultMatchWidth:"wide",parsePatterns:{narrow:[/^j/i,/^f/i,/^m/i,/^a/i,/^m/i,/^j/i,/^j/i,/^a/i,/^s/i,/^o/i,/^n/i,/^d/i],any:[/^ja/i,/^f/i,/^mar/i,/^ap/i,/^may/i,/^jun/i,/^jul/i,/^au/i,/^s/i,/^o/i,/^n/i,/^d/i]},defaultParseWidth:"any"}),day:s({matchPatterns:{narrow:/^[smtwf]/i,short:/^(su|mo|tu|we|th|fr|sa)/i,abbreviated:/^(sun|mon|tue|wed|thu|fri|sat)/i,wide:/^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)/i},defaultMatchWidth:"wide",parsePatterns:{narrow:[/^s/i,/^m/i,/^t/i,/^w/i,/^t/i,/^f/i,/^s/i],any:[/^su/i,/^m/i,/^tu/i,/^w/i,/^th/i,/^f/i,/^sa/i]},defaultParseWidth:"any"}),dayPeriod:s({matchPatterns:{narrow:/^(a|p|mi|n|(in the|at) (morning|afternoon|evening|night))/i,any:/^([ap]\.?\s?m\.?|midnight|noon|(in the|at) (morning|afternoon|evening|night))/i},defaultMatchWidth:"any",parsePatterns:{any:{am:/^a/i,pm:/^p/i,midnight:/^mi/i,noon:/^no/i,morning:/morning/i,afternoon:/afternoon/i,evening:/evening/i,night:/night/i}},defaultParseWidth:"any"})},options:{weekStartsOn:0,firstWeekContainsDate:1}};t.a=d},function(e,t,n){"use strict";var r=n(33),o=n(29),i=n(47),a=n(23),c=n(11),s=n(48);function l(){var e={};return e.promise=new Promise((function(t,n){e.resolve=t,e.reject=n})),e}var u=l,f=(n(155),[]),d=0;function h(e){try{v(),e()}finally{g()}}function p(e){f.push(e),d||(v(),m())}function z(e){try{return v(),e()}finally{m()}}function v(){d++}function g(){d--}function m(){var e;for(g();!d&&void 0!==(e=f.shift());)h(e)}var y=function(e){return function(t){return e.some((function(e){return j(e)(t)}))}},b=function(e){return function(t){return e(t)}},w=function(e){return function(t){return t.type===String(e)}},k=function(e){return function(t){return t.type===e}},x=function(){return c.H};function j(e){var t="*"===e?x:Object(a.k)(e)?w:Object(a.a)(e)?y:Object(a.l)(e)?w:Object(a.d)(e)?b:Object(a.m)(e)?k:null;if(null===t)throw new Error("invalid pattern: "+e);return t(e)}var M={type:r.b},C=function(e){return e&&e.type===r.b};function _(e){void 0===e&&(e=Object(c.B)());var t=!1,n=[];return{take:function(r){t&&e.isEmpty()?r(M):e.isEmpty()?(n.push(r),r.cancel=function(){Object(c.O)(n,r)}):r(e.take())},put:function(r){if(!t){if(0===n.length)return e.put(r);n.shift()(r)}},flush:function(n){t&&e.isEmpty()?n(M):n(e.flush())},close:function(){if(!t){t=!0;var e=n;n=[];for(var r=0,o=e.length;r2?p-2:0),g=2;g=arguments.length)?u=n[l]:(u=arguments[c],c+=1),i[l]=u,Object(a.a)(u)||(s-=1),l+=1}return s<=0?o.apply(this,i):Object(r.a)(s,e(t,i,o))}}(e,[],t))}));t.a=c},function(e,t,n){"use strict";var r=n(104),o={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},i={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},a={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},c={};function s(e){return r.isMemo(e)?a:c[e.$$typeof]||o}c[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0};var l=Object.defineProperty,u=Object.getOwnPropertyNames,f=Object.getOwnPropertySymbols,d=Object.getOwnPropertyDescriptor,h=Object.getPrototypeOf,p=Object.prototype;e.exports=function e(t,n,r){if("string"!==typeof n){if(p){var o=h(n);o&&o!==p&&e(t,o,r)}var a=u(n);f&&(a=a.concat(f(n)));for(var c=s(t),z=s(n),v=0;v=0;){if(n[i]===e)return r[i]===t;i-=1}switch(o){case"Map":return e.size===t.size&&f(e.entries(),t.entries(),n.concat([e]),r.concat([t]));case"Set":return e.size===t.size&&f(e.values(),t.values(),n.concat([e]),r.concat([t]));case"Arguments":case"Array":case"Object":case"Boolean":case"Number":case"String":case"Date":case"Error":case"RegExp":case"Int8Array":case"Uint8Array":case"Uint8ClampedArray":case"Int16Array":case"Uint16Array":case"Int32Array":case"Uint32Array":case"Float32Array":case"Float64Array":case"ArrayBuffer":break;default:return!1}var l=Object(s.a)(e);if(l.length!==Object(s.a)(t).length)return!1;var h=n.concat([e]),p=r.concat([t]);for(i=l.length-1;i>=0;){var z=l[i];if(!Object(a.a)(z,t)||!d(t[z],e[z],h,p))return!1;i-=1}return!0}var h=Object(r.a)((function(e,t){return d(e,t,[],[])}));t.a=h},function(e,t,n){"use strict";var r=n(31),o=n(148),i=n(133);function a(e){return'"'+e.replace(/\\/g,"\\\\").replace(/[\b]/g,"\\b").replace(/\f/g,"\\f").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t").replace(/\v/g,"\\v").replace(/\0/g,"\\0").replace(/"/g,'\\"')+'"'}var c=function(e){return(e<10?"0":"")+e},s="function"===typeof Date.prototype.toISOString?function(e){return e.toISOString()}:function(e){return e.getUTCFullYear()+"-"+c(e.getUTCMonth()+1)+"-"+c(e.getUTCDate())+"T"+c(e.getUTCHours())+":"+c(e.getUTCMinutes())+":"+c(e.getUTCSeconds())+"."+(e.getUTCMilliseconds()/1e3).toFixed(3).slice(2,5)+"Z"},l=n(59);var u=n(19),f=n(308),d=Object(u.a)((function(e,t){return Object(f.a)((n=e,function(){return!n.apply(this,arguments)}),t);var n}));var h=Object(r.a)((function(e){return function e(t,n){var r=function(r){var i=n.concat([t]);return Object(o.a)(r,i)?"":e(r,i)},c=function(e,t){return Object(i.a)((function(t){return a(t)+": "+r(e[t])}),t.slice().sort())};switch(Object.prototype.toString.call(t)){case"[object Arguments]":return"(function() { return arguments; }("+Object(i.a)(r,t).join(", ")+"))";case"[object Array]":return"["+Object(i.a)(r,t).concat(c(t,d((function(e){return/^\d+$/.test(e)}),Object(l.a)(t)))).join(", ")+"]";case"[object Boolean]":return"object"===typeof t?"new Boolean("+r(t.valueOf())+")":t.toString();case"[object Date]":return"new Date("+(isNaN(t.valueOf())?r(NaN):a(s(t)))+")";case"[object Null]":return"null";case"[object Number]":return"object"===typeof t?"new Number("+r(t.valueOf())+")":1/t===-1/0?"-0":t.toString(10);case"[object String]":return"object"===typeof t?"new String("+r(t.valueOf())+")":a(t);case"[object Undefined]":return"undefined";default:if("function"===typeof t.toString){var u=t.toString();if("[object Object]"!==u)return u}return"{"+c(t,Object(l.a)(t)).join(", ")+"}"}}(e,[])}));t.a=h},function(e,t,n){"use strict";var r=n(29),o=n(47),i=n(79),a=n(0),c=n.n(a),s=n(13),l=n.n(s),u=n(34),f=n.n(u),d=n(30),h=n.n(d),p=!1,z=c.a.createContext(null),v="unmounted",g="exited",m="entering",y="entered",b=function(e){function t(t,n){var r;r=e.call(this,t,n)||this;var o,i=n&&!n.isMounting?t.enter:t.appear;return r.appearStatus=null,t.in?i?(o=g,r.appearStatus=m):o=y:o=t.unmountOnExit||t.mountOnEnter?v:g,r.state={status:o},r.nextCallback=null,r}Object(i.a)(t,e),t.getDerivedStateFromProps=function(e,t){return e.in&&t.status===v?{status:g}:null};var n=t.prototype;return n.componentDidMount=function(){this.updateStatus(!0,this.appearStatus)},n.componentDidUpdate=function(e){var t=null;if(e!==this.props){var n=this.state.status;this.props.in?n!==m&&n!==y&&(t=m):n!==m&&n!==y||(t="exiting")}this.updateStatus(!1,t)},n.componentWillUnmount=function(){this.cancelNextCallback()},n.getTimeouts=function(){var e,t,n,r=this.props.timeout;return e=t=n=r,null!=r&&"number"!==typeof r&&(e=r.exit,t=r.enter,n=void 0!==r.appear?r.appear:t),{exit:e,enter:t,appear:n}},n.updateStatus=function(e,t){if(void 0===e&&(e=!1),null!==t){this.cancelNextCallback();var n=h.a.findDOMNode(this);t===m?this.performEnter(n,e):this.performExit(n)}else this.props.unmountOnExit&&this.state.status===g&&this.setState({status:v})},n.performEnter=function(e,t){var n=this,r=this.props.enter,o=this.context?this.context.isMounting:t,i=this.getTimeouts(),a=o?i.appear:i.enter;!t&&!r||p?this.safeSetState({status:y},(function(){n.props.onEntered(e)})):(this.props.onEnter(e,o),this.safeSetState({status:m},(function(){n.props.onEntering(e,o),n.onTransitionEnd(e,a,(function(){n.safeSetState({status:y},(function(){n.props.onEntered(e,o)}))}))})))},n.performExit=function(e){var t=this,n=this.props.exit,r=this.getTimeouts();n&&!p?(this.props.onExit(e),this.safeSetState({status:"exiting"},(function(){t.props.onExiting(e),t.onTransitionEnd(e,r.exit,(function(){t.safeSetState({status:g},(function(){t.props.onExited(e)}))}))}))):this.safeSetState({status:g},(function(){t.props.onExited(e)}))},n.cancelNextCallback=function(){null!==this.nextCallback&&(this.nextCallback.cancel(),this.nextCallback=null)},n.safeSetState=function(e,t){t=this.setNextCallback(t),this.setState(e,t)},n.setNextCallback=function(e){var t=this,n=!0;return this.nextCallback=function(r){n&&(n=!1,t.nextCallback=null,e(r))},this.nextCallback.cancel=function(){n=!1},this.nextCallback},n.onTransitionEnd=function(e,t,n){this.setNextCallback(n);var r=null==t&&!this.props.addEndListener;e&&!r?(this.props.addEndListener&&this.props.addEndListener(e,this.nextCallback),null!=t&&setTimeout(this.nextCallback,t)):setTimeout(this.nextCallback,0)},n.render=function(){var e=this.state.status;if(e===v)return null;var t=this.props,n=t.children,r=Object(o.a)(t,["children"]);if(delete r.in,delete r.mountOnEnter,delete r.unmountOnExit,delete r.appear,delete r.enter,delete r.exit,delete r.timeout,delete r.addEndListener,delete r.onEnter,delete r.onEntering,delete r.onEntered,delete r.onExit,delete r.onExiting,delete r.onExited,"function"===typeof n)return c.a.createElement(z.Provider,{value:null},n(e,r));var i=c.a.Children.only(n);return(c.a.createElement(z.Provider,{value:null},c.a.cloneElement(i,r)))},t}(c.a.Component);function w(){}b.contextType=z,b.propTypes={},b.defaultProps={in:!1,mountOnEnter:!1,unmountOnExit:!1,appear:!1,enter:!0,exit:!0,onEnter:w,onEntering:w,onEntered:w,onExit:w,onExiting:w,onExited:w},b.UNMOUNTED=0,b.EXITED=1,b.ENTERING=2,b.ENTERED=3,b.EXITING=4;var k=b,x=n(100);function j(e,t){var n=Object.create(null);return e&&a.Children.map(e,(function(e){return e})).forEach((function(e){n[e.key]=function(e){return t&&Object(a.isValidElement)(e)?t(e):e}(e)})),n}function M(e,t,n){return null!=n[t]?n[t]:e.props[t]}function C(e,t,n){var r=j(e.children),o=function(e,t){function n(n){return n in t?t[n]:e[n]}e=e||{},t=t||{};var r,o=Object.create(null),i=[];for(var a in e)a in t?i.length&&(o[a]=i,i=[]):i.push(a);var c={};for(var s in t){if(o[s])for(r=0;r0}function D(e){return Object.keys(e).map((function(t){return e[t]}))}var P=!("undefined"===typeof window||!window.document||!window.document.createElement);var V,R=((V=function(e,t,n){var r=e[t];return!1===r||H(r)?null:new Error(n+" expect "+t+" \n to be a valid Number > 0 or equal to false. "+r+" given.")}).isRequired=function(e,t,n){if("undefined"===typeof e[t])return new Error("The prop "+t+" is marked as required in \n "+n+", but its value is undefined.");V(e,t,n)},V),I={list:new Map,emitQueue:new Map,on:function(e,t){return this.list.has(e)||this.list.set(e,[]),this.list.get(e).push(t),this},off:function(e){return this.list.delete(e),this},cancelEmit:function(e){var t=this.emitQueue.get(e);return t&&(t.forEach((function(e){return clearTimeout(e)})),this.emitQueue.delete(e)),this},emit:function(e){for(var t=this,n=arguments.length,r=new Array(n>1?n-1:0),o=1;o=1?"onTransitionEnd":"onAnimationEnd"]=h&&p<1?null:a,n);return c.a.createElement("div",Object(r.a)({className:g,style:v},m))}function B(e){return e.targetTouches&&e.targetTouches.length>=1?e.targetTouches[0].clientX:e.clientX}F.propTypes={delay:R.isRequired,isRunning:l.a.bool.isRequired,closeToast:l.a.func.isRequired,rtl:l.a.bool.isRequired,type:l.a.string,hide:l.a.bool,className:l.a.oneOfType([l.a.string,l.a.object]),progress:l.a.number,controlledProgress:l.a.bool},F.defaultProps={type:E.DEFAULT,hide:!1};var U=P&&/(msie|trident)/i.test(navigator.userAgent),W=function(e){function t(){for(var t,n=arguments.length,r=new Array(n),o=0;o=1?e.targetTouches[0].clientY:e.clientY}(e),t.drag.start!==t.drag.x&&(t.flag.canCloseOnClick=!1),t.ref.style.transform="translateX("+t.drag.deltaX+"px)",t.ref.style.opacity=1-Math.abs(t.drag.deltaX/t.drag.removalDistance))},t.onDragEnd=function(e){if(t.flag.canDrag){if(t.flag.canDrag=!1,Math.abs(t.drag.deltaX)>t.drag.removalDistance)return void t.setState({preventExitTransition:!0},t.props.closeToast);t.ref.style.transition="transform 0.2s, opacity 0.2s",t.ref.style.transform="translateX(0)",t.ref.style.opacity=1}},t.onDragTransitionEnd=function(){if(t.boundingRect){var e=t.boundingRect,n=e.top,r=e.bottom,o=e.left,i=e.right;t.props.pauseOnHover&&t.drag.x>=o&&t.drag.x<=i&&t.drag.y>=n&&t.drag.y<=r?t.pauseToast():t.playToast()}},t.onExitTransitionEnd=function(){if(U)t.props.onExited();else{var e=t.ref.scrollHeight,n=t.ref.style;requestAnimationFrame((function(){n.minHeight="initial",n.height=e+"px",n.transition="all 0.4s ",requestAnimationFrame((function(){n.height=0,n.padding=0,n.margin=0})),setTimeout((function(){return t.props.onExited()}),400)}))}},t}Object(i.a)(t,e);var n=t.prototype;return n.componentDidMount=function(){this.props.onOpen(this.props.children.props),this.props.draggable&&this.bindDragEvents(),this.props.pauseOnFocusLoss&&this.bindFocusEvents()},n.componentDidUpdate=function(e){e.draggable!==this.props.draggable&&(this.props.draggable?this.bindDragEvents():this.unbindDragEvents()),e.pauseOnFocusLoss!==this.props.pauseOnFocusLoss&&(this.props.pauseOnFocusLoss?this.bindFocusEvents():this.unbindFocusEvents())},n.componentWillUnmount=function(){this.props.onClose(this.props.children.props),this.props.draggable&&this.unbindDragEvents(),this.props.pauseOnFocusLoss&&this.unbindFocusEvents()},n.bindFocusEvents=function(){window.addEventListener("focus",this.playToast),window.addEventListener("blur",this.pauseToast)},n.unbindFocusEvents=function(){window.removeEventListener("focus",this.playToast),window.removeEventListener("blur",this.pauseToast)},n.bindDragEvents=function(){document.addEventListener("mousemove",this.onDragMove),document.addEventListener("mouseup",this.onDragEnd),document.addEventListener("touchmove",this.onDragMove),document.addEventListener("touchend",this.onDragEnd)},n.unbindDragEvents=function(){document.removeEventListener("mousemove",this.onDragMove),document.removeEventListener("mouseup",this.onDragEnd),document.removeEventListener("touchmove",this.onDragMove),document.removeEventListener("touchend",this.onDragEnd)},n.render=function(){var e,t=this,n=this.props,o=n.closeButton,i=n.children,a=n.autoClose,s=n.pauseOnHover,l=n.onClick,u=n.closeOnClick,d=n.type,h=n.hideProgressBar,p=n.closeToast,z=n.transition,v=n.position,g=n.className,m=n.bodyClassName,y=n.progressClassName,b=n.progressStyle,w=n.updateId,k=n.role,x=n.progress,j=n.rtl,M={className:f()(L+"__toast",L+"__toast--"+d,(e={},e[L+"__toast--rtl"]=j,e),g)};a&&s&&(M.onMouseEnter=this.pauseToast,M.onMouseLeave=this.playToast),u&&(M.onClick=function(e){l&&l(e),t.flag.canCloseOnClick&&p()});var C=parseFloat(x)===x;return c.a.createElement(z,{in:this.props.in,appear:!0,onExited:this.onExitTransitionEnd,position:v,preventExitTransition:this.state.preventExitTransition},c.a.createElement("div",Object(r.a)({onClick:l},M,{ref:function(e){return t.ref=e},onMouseDown:this.onDragStart,onTouchStart:this.onDragStart,onMouseUp:this.onDragTransitionEnd,onTouchEnd:this.onDragTransitionEnd}),c.a.createElement("div",Object(r.a)({},this.props.in&&{role:k},{className:f()(L+"__toast-body",m)}),i),o&&o,(a||C)&&c.a.createElement(F,Object(r.a)({},w&&!C?{key:"pb-"+w}:{},{rtl:j,delay:a,isRunning:this.state.isRunning,closeToast:p,hide:h,type:d,style:b,className:y,controlledProgress:C,progress:x}))))},t}(a.Component);function G(e){var t=e.closeToast,n=e.type,r=e.ariaLabel;return c.a.createElement("button",{className:L+"__close-button "+L+"__close-button--"+n,type:"button",onClick:function(e){e.stopPropagation(),t(e)},"aria-label":r},"\u2716\ufe0e")}W.propTypes={closeButton:l.a.oneOfType([l.a.node,l.a.bool]).isRequired,autoClose:R.isRequired,children:l.a.node.isRequired,closeToast:l.a.func.isRequired,position:l.a.oneOf(D(O)).isRequired,pauseOnHover:l.a.bool.isRequired,pauseOnFocusLoss:l.a.bool.isRequired,closeOnClick:l.a.bool.isRequired,transition:l.a.func.isRequired,rtl:l.a.bool.isRequired,hideProgressBar:l.a.bool.isRequired,draggable:l.a.bool.isRequired,draggablePercent:l.a.number.isRequired,in:l.a.bool,onExited:l.a.func,onOpen:l.a.func,onClose:l.a.func,type:l.a.oneOf(D(E)),className:l.a.oneOfType([l.a.string,l.a.object]),bodyClassName:l.a.oneOfType([l.a.string,l.a.object]),progressClassName:l.a.oneOfType([l.a.string,l.a.object]),progressStyle:l.a.object,progress:l.a.number,updateId:l.a.oneOfType([l.a.string,l.a.number]),ariaLabel:l.a.string,containerId:l.a.oneOfType([l.a.string,l.a.number]),role:l.a.string},W.defaultProps={type:E.DEFAULT,in:!0,onOpen:A,onClose:A,className:null,bodyClassName:null,progressClassName:null,updateId:null},G.propTypes={closeToast:l.a.func,arialLabel:l.a.string},G.defaultProps={ariaLabel:"close"};var Y=N({enter:L+"__bounce-enter",exit:L+"__bounce-exit",appendPosition:!0}),$=(N({enter:L+"__slide-enter",exit:L+"__slide-exit",duration:[450,750],appendPosition:!0}),N({enter:L+"__zoom-enter",exit:L+"__zoom-exit"}),N({enter:L+"__flip-enter",exit:L+"__flip-exit"}),function(e){function t(){for(var t,n=arguments.length,r=new Array(n),o=0;o0}function ne(e,t){var n=function(e){return te()?e?Z.get(e):Z.get(X):null}(t.containerId);if(!n)return null;var r=n.collection[e];return"undefined"===typeof r?null:r}function re(e,t){return Object(r.a)({},e,{type:t,toastId:ie(e)})}function oe(){return(Math.random().toString(36)+Date.now().toString(36)).substr(2,10)}function ie(e){return e&&("string"===typeof e.toastId||"number"===typeof e.toastId&&!isNaN(e.toastId))?e.toastId:oe()}function ae(e,t){return te()?I.emit(T.SHOW,e,t):(J.push({action:T.SHOW,content:e,options:t}),ee&&P&&(ee=!1,K=document.createElement("div"),document.body.appendChild(K),Object(d.render)(c.a.createElement($,Q),K))),t.toastId}var ce=function(e,t){return ae(e,re(t,t&&t.type||E.DEFAULT))},se=function(e){E[e]!==E.DEFAULT&&(ce[E[e].toLowerCase()]=function(t,n){return ae(t,re(n,n&&n.type||E[e]))})};for(var le in E)se(le);ce.warn=ce.warning,ce.dismiss=function(e){return void 0===e&&(e=null),te()&&I.emit(T.CLEAR,e)},ce.isActive=function(e){var t=!1;return Z.size>0&&Z.forEach((function(n){n.isToastActive(e)&&(t=!0)})),t},ce.update=function(e,t){void 0===t&&(t={}),setTimeout((function(){var n=ne(e,t);if(n){var o=n.options,i=n.content,a=Object(r.a)({},o,{},t,{toastId:t.toastId||e});t.toastId&&t.toastId!==e?a.staleToastId=e:a.updateId=oe();var c="undefined"!==typeof a.render?a.render:i;delete a.render,ae(c,a)}}),0)},ce.done=function(e){ce.update(e,{progress:1})},ce.onChange=function(e){"function"===typeof e&&I.on(T.ON_CHANGE,e)},ce.configure=function(e){ee=!0,Q=e},ce.POSITION=O,ce.TYPE=E,I.on(T.DID_MOUNT,(function(e){X=e.props.containerId||e,Z.set(X,e),J.forEach((function(e){I.emit(e.action,e.content,e.options)})),J=[]})).on(T.WILL_UNMOUNT,(function(e){e?Z.delete(e.props.containerId||e):Z.clear(),0===Z.size&&I.off(T.SHOW).off(T.CLEAR),P&&K&&document.body.removeChild(K)}))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.invariant=t.UNICODE_EXTENSION_SEQUENCE_REGEX=void 0,t.UNICODE_EXTENSION_SEQUENCE_REGEX=/-u(?:-[0-9a-z]{2,8})+/gi,t.invariant=function(e,t,n){if(void 0===n&&(n=Error),!e)throw new n(t)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.shortMorePenalty=t.shortLessPenalty=t.longMorePenalty=t.longLessPenalty=t.differentNumericTypePenalty=t.additionPenalty=t.removalPenalty=t.DATE_TIME_PROPS=void 0,t.DATE_TIME_PROPS=["weekday","era","year","month","day","hour","minute","second","timeZoneName"],t.removalPenalty=120,t.additionPenalty=20,t.differentNumericTypePenalty=15,t.longLessPenalty=8,t.longMorePenalty=6,t.shortLessPenalty=6,t.shortMorePenalty=3},function(e,t,n){var r=n(409),o=n(413),i=n(415),a=n(419);function c(e){var t=function(e,t){if("object"!==typeof e||null===e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var r=n.call(e,t||"default");if("object"!==typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"===typeof t?t:String(t)}function s(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t0&&e(a,o-1)}(t.map((function(e,t){return h(n,e,{depth:c,index:t})})),c-1),s.reverse(),function e(t){return t.filter((function(e){return e.column.getIsVisible()})).map((function(t){var n=0,r=0,o=[0];return t.subHeaders&&t.subHeaders.length?(o=[],e(t.subHeaders).forEach((function(e){var t=e.colSpan,r=e.rowSpan;n+=t,o.push(r)}))):n=1,r+=Math.min.apply(Math,i(o)),t.colSpan=n,t.rowSpan=r,{colSpan:n,rowSpan:r}}))}(null!=(o=null==(a=s[0])?void 0:a.headers)?o:[]),s}var v={size:150,minSize:20,maxSize:Number.MAX_SAFE_INTEGER},g={getDefaultColumnDef:function(){return v},getInitialState:function(e){return l({columnSizing:{},columnSizingInfo:{startOffset:null,startSize:null,deltaOffset:null,deltaPercentage:null,isResizingColumn:!1,columnSizingStart:[]}},e)},getDefaultOptions:function(e){return{columnResizeMode:"onEnd",onColumnSizingChange:n("columnSizing",e),onColumnSizingInfoChange:n("columnSizingInfo",e)}},createColumn:function(e,t){return{getSize:function(){var n,r,o,i=t.getState().columnSizing[e.id];return Math.min(Math.max(null!=(n=e.columnDef.minSize)?n:v.minSize,null!=(r=null!=i?i:e.columnDef.size)?r:v.size),null!=(o=e.columnDef.maxSize)?o:v.maxSize)},getStart:function(n){var r=n?"left"===n?t.getLeftVisibleLeafColumns():t.getRightVisibleLeafColumns():t.getVisibleLeafColumns(),o=r.findIndex((function(t){return t.id===e.id}));if(o>0){var i=r[o-1];return i.getStart(n)+i.getSize()}return 0},resetSize:function(){t.setColumnSizing((function(t){var n=e.id;return t[n],o(t,[n].map(c))}))},getCanResize:function(){var n,r;return(null==(n=e.columnDef.enableResizing)||n)&&(null==(r=t.options.enableColumnResizing)||r)},getIsResizing:function(){return t.getState().columnSizingInfo.isResizingColumn===e.id}}},createHeader:function(e,t){return{getSize:function(){var t=0;return function e(n){var r;n.subHeaders.length?n.subHeaders.forEach(e):t+=null!=(r=n.column.getSize())?r:0}(e),t},getStart:function(){if(e.index>0){var t=e.headerGroup.headers[e.index-1];return t.getStart()+t.getSize()}return 0},getResizeHandler:function(){var n=t.getColumn(e.column.id),o=n.getCanResize();return function(i){if(o&&(null==i.persist||i.persist(),!(b(i)&&i.touches&&i.touches.length>1))){var a=e.getSize(),c=e?e.getLeafHeaders().map((function(e){return[e.column.id,e.column.getSize()]})):[[n.id,n.getSize()]],s=b(i)?Math.round(i.touches[0].clientX):i.clientX,u=function(e,n){if("number"==typeof n){var o={};t.setColumnSizingInfo((function(e){var t,i,a=n-(null!=(t=null==e?void 0:e.startOffset)?t:0),c=Math.max(a/(null!=(i=null==e?void 0:e.startSize)?i:0),-.999999);return e.columnSizingStart.forEach((function(e){var t=r(e,2),n=t[0],i=t[1];o[n]=Math.round(100*Math.max(i+i*c,0))/100})),l({},e,{deltaOffset:a,deltaPercentage:c})})),"onChange"!==t.options.columnResizeMode&&"end"!==e||t.setColumnSizing((function(e){return l({},e,{},o)}))}},f={moveHandler:function(e){return t=e.clientX,u("move",t);var t},upHandler:function(e){var n;document.removeEventListener("mousemove",f.moveHandler),document.removeEventListener("mouseup",f.upHandler),n=e.clientX,u("end",n),t.setColumnSizingInfo((function(e){return l({},e,{isResizingColumn:!1,startOffset:null,startSize:null,deltaOffset:null,deltaPercentage:null,columnSizingStart:[]})}))}},d=!!y()&&{passive:!1};b(i)||(document.addEventListener("mousemove",f.moveHandler,d),document.addEventListener("mouseup",f.upHandler,d)),t.setColumnSizingInfo((function(e){return l({},e,{startOffset:s,startSize:a,deltaOffset:0,deltaPercentage:0,columnSizingStart:c,isResizingColumn:n.id})}))}}}}},createInstance:function(e){return{setColumnSizing:function(t){return null==e.options.onColumnSizingChange?void 0:e.options.onColumnSizingChange(t)},setColumnSizingInfo:function(t){return null==e.options.onColumnSizingInfoChange?void 0:e.options.onColumnSizingInfoChange(t)},resetColumnSizing:function(t){var n;e.setColumnSizing(t?{}:null!=(n=e.initialState.columnSizing)?n:{})},resetHeaderSizeInfo:function(t){var n;e.setColumnSizingInfo(t?{startOffset:null,startSize:null,deltaOffset:null,deltaPercentage:null,isResizingColumn:!1,columnSizingStart:[]}:null!=(n=e.initialState.columnSizingInfo)?n:{startOffset:null,startSize:null,deltaOffset:null,deltaPercentage:null,isResizingColumn:!1,columnSizingStart:[]})},getTotalSize:function(){var t,n;return null!=(t=null==(n=e.getHeaderGroups()[0])?void 0:n.headers.reduce((function(e,t){return e+t.getSize()}),0))?t:0},getLeftTotalSize:function(){var t,n;return null!=(t=null==(n=e.getLeftHeaderGroups()[0])?void 0:n.headers.reduce((function(e,t){return e+t.getSize()}),0))?t:0},getCenterTotalSize:function(){var t,n;return null!=(t=null==(n=e.getCenterHeaderGroups()[0])?void 0:n.headers.reduce((function(e,t){return e+t.getSize()}),0))?t:0},getRightTotalSize:function(){var t,n;return null!=(t=null==(n=e.getRightHeaderGroups()[0])?void 0:n.headers.reduce((function(e,t){return e+t.getSize()}),0))?t:0}}}},m=null;function y(){if("boolean"==typeof m)return m;var e=!1;try{var n={get passive(){return e=!0,!1}},r=function(){};window.addEventListener("test",r,n),window.removeEventListener("test",r)}catch(t){e=!1}return m=e}function b(e){return"touchstart"===e.type}var w={getInitialState:function(e){return l({expanded:{}},e)},getDefaultOptions:function(e){return{onExpandedChange:n("expanded",e),paginateExpandedRows:!0}},createInstance:function(e){var t=!1,n=!1;return{_autoResetExpanded:function(){var r,o;if(t){if(null!=(r=null!=(o=e.options.autoResetAll)?o:e.options.autoResetExpanded)?r:!e.options.manualExpanding){if(n)return;n=!0,e._queue((function(){e.resetExpanded(),n=!1}))}}else e._queue((function(){t=!0}))},setExpanded:function(t){return null==e.options.onExpandedChange?void 0:e.options.onExpandedChange(t)},toggleAllRowsExpanded:function(t){(null!=t?t:!e.getIsAllRowsExpanded())?e.setExpanded(!0):e.setExpanded({})},resetExpanded:function(t){var n,r;e.setExpanded(t?{}:null!=(n=null==(r=e.initialState)?void 0:r.expanded)?n:{})},getCanSomeRowsExpand:function(){return e.getRowModel().flatRows.some((function(e){return e.getCanExpand()}))},getToggleAllRowsExpandedHandler:function(){return function(t){null==t.persist||t.persist(),e.toggleAllRowsExpanded()}},getIsSomeRowsExpanded:function(){var t=e.getState().expanded;return!0===t||Object.values(t).some(Boolean)},getIsAllRowsExpanded:function(){var t=e.getState().expanded;return"boolean"==typeof t?!0===t:!!Object.keys(t).length&&!e.getRowModel().flatRows.some((function(e){return e.getIsExpanded()}))},getExpandedDepth:function(){var t=0;return(!0===e.getState().expanded?Object.keys(e.getRowModel().rowsById):Object.keys(e.getState().expanded)).forEach((function(e){var n=e.split(".");t=Math.max(t,n.length)})),t},getPreExpandedRowModel:function(){return e.getSortedRowModel()},getExpandedRowModel:function(){return!e._getExpandedRowModel&&e.options.getExpandedRowModel&&(e._getExpandedRowModel=e.options.getExpandedRowModel(e)),e.options.manualExpanding||!e._getExpandedRowModel?e.getPreExpandedRowModel():e._getExpandedRowModel()}}},createRow:function(e,t){return{toggleExpanded:function(n){t.setExpanded((function(r){var i,s=!0===r||!(null==r||!r[e.id]),u={};if(!0===r?Object.keys(t.getRowModel().rowsById).forEach((function(e){u[e]=!0})):u=r,n=null!=(i=n)?i:!s,!s&&n)return l({},u,a({},e.id,!0));if(s&&!n){var f=u,d=e.id;return f[d],o(f,[d].map(c))}return r}))},getIsExpanded:function(){var n,r=t.getState().expanded;return!!(null!=(n=null==t.options.getIsRowExpanded?void 0:t.options.getIsRowExpanded(e))?n:!0===r||(null==r?void 0:r[e.id]))},getCanExpand:function(){var n,r,o;return(null==(n=null==t.options.getRowCanExpand?void 0:t.options.getRowCanExpand(e))||n)&&(null==(r=t.options.enableExpanding)||r)&&!(null==(o=e.subRows)||!o.length)},getToggleExpandedHandler:function(){var t=e.getCanExpand();return function(){t&&e.toggleExpanded()}}}}},k=function(e,t,n){var r,o=n.toLowerCase();return null==(r=e.getValue(t))?void 0:r.toLowerCase().includes(o)};k.autoRemove=function(e){return T(e)};var x=function(e,t,n){var r;return null==(r=e.getValue(t))?void 0:r.includes(n)};x.autoRemove=function(e){return T(e)};var j=function(e,t,n){var r;return(null==(r=e.getValue(t))?void 0:r.toLowerCase())===n.toLowerCase()};j.autoRemove=function(e){return T(e)};var M=function(e,t,n){var r;return null==(r=e.getValue(t))?void 0:r.includes(n)};M.autoRemove=function(e){return T(e)||!(null!=e&&e.length)};var C=function(e,t,n){return!n.some((function(n){var r;return!(null!=(r=e.getValue(t))&&r.includes(n))}))};C.autoRemove=function(e){return T(e)||!(null!=e&&e.length)};var _=function(e,t,n){return n.some((function(n){var r;return null==(r=e.getValue(t))?void 0:r.includes(n)}))};_.autoRemove=function(e){return T(e)||!(null!=e&&e.length)};var q=function(e,t,n){return e.getValue(t)===n};q.autoRemove=function(e){return T(e)};var S=function(e,t,n){return e.getValue(t)==n};S.autoRemove=function(e){return T(e)};var O=function(e,t,n){var o=r(n,2),i=o[0],a=o[1],c=e.getValue(t);return c>=i&&c<=a};O.resolveFilterValue=function(e){var t=r(e,2),n=t[0],o=t[1],i="number"!=typeof n?parseFloat(n):n,a="number"!=typeof o?parseFloat(o):o,c=null===n||Number.isNaN(i)?-1/0:i,s=null===o||Number.isNaN(a)?1/0:a;if(c>s){var l=c;c=s,s=l}return[c,s]},O.autoRemove=function(e){return T(e)||T(e[0])&&T(e[1])};var E={includesString:k,includesStringSensitive:x,equalsString:j,arrIncludes:M,arrIncludesAll:C,arrIncludesSome:_,equals:q,weakEquals:S,inNumberRange:O};function T(e){return null==e||""===e}var A={getDefaultColumnDef:function(){return{filterFn:"auto"}},getInitialState:function(e){return l({columnFilters:[],globalFilter:void 0},e)},getDefaultOptions:function(e){return{onColumnFiltersChange:n("columnFilters",e),onGlobalFilterChange:n("globalFilter",e),filterFromLeafRows:!1,globalFilterFn:"auto",getColumnCanGlobalFilter:function(t){var n,r;return"string"==typeof(null==(n=e.getCoreRowModel().flatRows[0])||null==(r=n._getAllCellsByColumnId()[t.id])?void 0:r.getValue())}}},createColumn:function(e,n){return{getAutoFilterFn:function(){var t=n.getCoreRowModel().flatRows[0],r=null==t?void 0:t.getValue(e.id);return"string"==typeof r?E.includesString:"number"==typeof r?E.inNumberRange:"boolean"==typeof r||null!==r&&"object"==typeof r?E.equals:Array.isArray(r)?E.arrIncludes:E.weakEquals},getFilterFn:function(){var t,r=n.options.filterFns;return s(e.columnDef.filterFn)?e.columnDef.filterFn:"auto"===e.columnDef.filterFn?e.getAutoFilterFn():null!=(t=null==r?void 0:r[e.columnDef.filterFn])?t:E[e.columnDef.filterFn]},getCanFilter:function(){var t,r,o;return(null==(t=e.columnDef.enableColumnFilter)||t)&&(null==(r=n.options.enableColumnFilters)||r)&&(null==(o=n.options.enableFilters)||o)&&!!e.accessorFn},getCanGlobalFilter:function(){var t,r,o,i;return(null==(t=e.columnDef.enableGlobalFilter)||t)&&(null==(r=n.options.enableGlobalFilter)||r)&&(null==(o=n.options.enableFilters)||o)&&(null==(i=null==n.options.getColumnCanGlobalFilter?void 0:n.options.getColumnCanGlobalFilter(e))||i)&&!!e.accessorFn},getIsFiltered:function(){return e.getFilterIndex()>-1},getFilterValue:function(){var t,r;return null==(t=n.getState().columnFilters)||null==(r=t.find((function(t){return t.id===e.id})))?void 0:r.value},getFilterIndex:function(){var t,r;return null!=(t=null==(r=n.getState().columnFilters)?void 0:r.findIndex((function(t){return t.id===e.id})))?t:-1},setFilterValue:function(r){n.setColumnFilters((function(n){var o,a=e.getFilterFn(),c=null==n?void 0:n.find((function(t){return t.id===e.id})),s=t(r,c?c.value:void 0);if(L(a,s,e))return null!=(o=null==n?void 0:n.filter((function(t){return t.id!==e.id})))?o:[];var l,u={id:e.id,value:s};return c?null!=(l=null==n?void 0:n.map((function(t){return t.id===e.id?u:t})))?l:[]:null!=n&&n.length?[].concat(i(n),[u]):[u]}))},_getFacetedRowModel:n.options.getFacetedRowModel&&n.options.getFacetedRowModel(n,e.id),getFacetedRowModel:function(){return e._getFacetedRowModel?e._getFacetedRowModel():n.getPreFilteredRowModel()},_getFacetedUniqueValues:n.options.getFacetedUniqueValues&&n.options.getFacetedUniqueValues(n,e.id),getFacetedUniqueValues:function(){return e._getFacetedUniqueValues?e._getFacetedUniqueValues():new Map},_getFacetedMinMaxValues:n.options.getFacetedMinMaxValues&&n.options.getFacetedMinMaxValues(n,e.id),getFacetedMinMaxValues:function(){if(e._getFacetedMinMaxValues)return e._getFacetedMinMaxValues()}}},createRow:function(e,t){return{columnFilters:{},columnFiltersMeta:{}}},createInstance:function(e){return{getGlobalAutoFilterFn:function(){return E.includesString},getGlobalFilterFn:function(){var t,n=e.options,r=n.filterFns,o=n.globalFilterFn;return s(o)?o:"auto"===o?e.getGlobalAutoFilterFn():null!=(t=null==r?void 0:r[o])?t:E[o]},setColumnFilters:function(n){var r=e.getAllLeafColumns();null==e.options.onColumnFiltersChange||e.options.onColumnFiltersChange((function(e){var o;return null==(o=t(n,e))?void 0:o.filter((function(e){var t=r.find((function(t){return t.id===e.id}));return!t||!L(t.getFilterFn(),e.value,t)}))}))},setGlobalFilter:function(t){null==e.options.onGlobalFilterChange||e.options.onGlobalFilterChange(t)},resetGlobalFilter:function(t){e.setGlobalFilter(t?void 0:e.initialState.globalFilter)},resetColumnFilters:function(t){var n,r;e.setColumnFilters(t?[]:null!=(n=null==(r=e.initialState)?void 0:r.columnFilters)?n:[])},getPreFilteredRowModel:function(){return e.getCoreRowModel()},_getFilteredRowModel:e.options.getFilteredRowModel&&e.options.getFilteredRowModel(e),getFilteredRowModel:function(){return e.options.manualFiltering||!e._getFilteredRowModel?e.getPreFilteredRowModel():e._getFilteredRowModel()},_getGlobalFacetedRowModel:e.options.getFacetedRowModel&&e.options.getFacetedRowModel(e,"__global__"),getGlobalFacetedRowModel:function(){return e.options.manualFiltering||!e._getGlobalFacetedRowModel?e.getPreFilteredRowModel():e._getGlobalFacetedRowModel()},_getGlobalFacetedUniqueValues:e.options.getFacetedUniqueValues&&e.options.getFacetedUniqueValues(e,"__global__"),getGlobalFacetedUniqueValues:function(){return e._getGlobalFacetedUniqueValues?e._getGlobalFacetedUniqueValues():new Map},_getGlobalFacetedMinMaxValues:e.options.getFacetedMinMaxValues&&e.options.getFacetedMinMaxValues(e,"__global__"),getGlobalFacetedMinMaxValues:function(){if(e._getGlobalFacetedMinMaxValues)return e._getGlobalFacetedMinMaxValues()}}}};function L(e,t,n){return!(!e||!e.autoRemove)&&e.autoRemove(t,n)||void 0===t||"string"==typeof t&&!t}var H={sum:function(e,t,n){return n.reduce((function(e,t){return e+("number"==typeof t?t:0)}),0)},min:function(e,t,n){var r;return n.forEach((function(t){var n=t.getValue(e);null!=n&&(r>n||void 0===r&&n>=n)&&(r=n)})),r},max:function(e,t,n){var r;return n.forEach((function(t){var n=t.getValue(e);null!=n&&(r=n)&&(r=n)})),r},extent:function(e,t,n){var r,o;return n.forEach((function(t){var n=t.getValue(e);null!=n&&(void 0===r?n>=n&&(r=o=n):(r>n&&(r=n),o=o&&(++n,r+=o)})),n)return r/n},median:function(e,t){if(t.length){var n=0,r=0;return t.forEach((function(t){var o=t.getValue(e);"number"==typeof o&&(n=Math.min(n,o),r=Math.max(r,o))})),(n+r)/2}},unique:function(e,t){return Array.from(new Set(t.map((function(t){return t.getValue(e)}))).values())},uniqueCount:function(e,t){return new Set(t.map((function(t){return t.getValue(e)}))).size},count:function(e,t){return t.length}},D={getDefaultColumnDef:function(){return{aggregatedCell:function(e){var t,n;return null!=(t=null==(n=e.getValue())||null==n.toString?void 0:n.toString())?t:null},aggregationFn:"auto"}},getInitialState:function(e){return l({grouping:[]},e)},getDefaultOptions:function(e){return{onGroupingChange:n("grouping",e),groupedColumnMode:"reorder"}},createColumn:function(e,t){return{toggleGrouping:function(){t.setGrouping((function(t){return null!=t&&t.includes(e.id)?t.filter((function(t){return t!==e.id})):[].concat(i(null!=t?t:[]),[e.id])}))},getCanGroup:function(){var n,r,o,i;return null!=(n=null==(r=null!=(o=null==(i=e.columnDef.enableGrouping)||i)?o:t.options.enableGrouping)||r)?n:!!e.accessorFn},getIsGrouped:function(){var n;return null==(n=t.getState().grouping)?void 0:n.includes(e.id)},getGroupedIndex:function(){var n;return null==(n=t.getState().grouping)?void 0:n.indexOf(e.id)},getToggleGroupingHandler:function(){var t=e.getCanGroup();return function(){t&&e.toggleGrouping()}},getAutoAggregationFn:function(){var n=t.getCoreRowModel().flatRows[0],r=null==n?void 0:n.getValue(e.id);return"number"==typeof r?H.sum:"[object Date]"===Object.prototype.toString.call(r)?H.extent:void 0},getAggregationFn:function(){var n,r=t.options.aggregationFns;if(!e)throw new Error;return s(e.columnDef.aggregationFn)?e.columnDef.aggregationFn:"auto"===e.columnDef.aggregationFn?e.getAutoAggregationFn():null!=(n=null==r?void 0:r[e.columnDef.aggregationFn])?n:H[e.columnDef.aggregationFn]}}},createInstance:function(e){return{setGrouping:function(t){return null==e.options.onGroupingChange?void 0:e.options.onGroupingChange(t)},resetGrouping:function(t){var n,r;e.setGrouping(t?[]:null!=(n=null==(r=e.initialState)?void 0:r.grouping)?n:[])},getPreGroupedRowModel:function(){return e.getFilteredRowModel()},getGroupedRowModel:function(){return!e._getGroupedRowModel&&e.options.getGroupedRowModel&&(e._getGroupedRowModel=e.options.getGroupedRowModel(e)),e.options.manualGrouping||!e._getGroupedRowModel?e.getPreGroupedRowModel():e._getGroupedRowModel()}}},createRow:function(e){return{getIsGrouped:function(){return!!e.groupingColumnId},_groupingValuesCache:{}}},createCell:function(e,t,n,r){var o=function(){var t;return null!=(t=e.getValue())?t:r.options.renderFallbackValue};return{getIsGrouped:function(){return t.getIsGrouped()&&t.id===n.groupingColumnId},getIsPlaceholder:function(){return!e.getIsGrouped()&&t.getIsGrouped()},getIsAggregated:function(){var t;return!e.getIsGrouped()&&!e.getIsPlaceholder()&&!(null==(t=n.subRows)||!t.length)},renderAggregatedCell:function(){var i=t.columnDef.aggregatedCell||t.columnDef.cell;return i?r._render(i,{instance:r,column:t,row:n,cell:e,getValue:o}):null}}}};function P(e,t,n){if(null==t||!t.length||!n)return e;var r=e.filter((function(e){return!t.includes(e.id)}));return"remove"===n?r:[].concat(i(t.map((function(t){return e.find((function(e){return e.id===t}))})).filter(Boolean)),i(r))}var V={getInitialState:function(e){return l({columnOrder:[]},e)},getDefaultOptions:function(e){return{onColumnOrderChange:n("columnOrder",e)}},createInstance:function(e){return{setColumnOrder:function(t){return null==e.options.onColumnOrderChange?void 0:e.options.onColumnOrderChange(t)},resetColumnOrder:function(t){var n;e.setColumnOrder(t?[]:null!=(n=e.initialState.columnOrder)?n:[])},_getOrderColumnsFn:f((function(){return[e.getState().columnOrder,e.getState().grouping,e.options.groupedColumnMode]}),(function(e,t,n){return function(r){var o=[];if(null!=e&&e.length){for(var a=i(e),c=i(r),s=function(){var e=a.shift(),t=c.findIndex((function(t){return t.id===e}));t>-1&&o.push(c.splice(t,1)[0])};c.length&&a.length;)s();o=[].concat(i(o),i(c))}else o=r;return P(o,t,n)}}),{key:!1})}}},R={getInitialState:function(e){return l({},e,{pagination:l({pageIndex:0,pageSize:10},null==e?void 0:e.pagination)})},getDefaultOptions:function(e){return{onPaginationChange:n("pagination",e)}},createInstance:function(e){var n=!1,r=!1;return{_autoResetPageIndex:function(){var t,o;if(n){if(null!=(t=null!=(o=e.options.autoResetAll)?o:e.options.autoResetPageIndex)?t:!e.options.manualPagination){if(r)return;r=!0,e._queue((function(){e.resetPageIndex(),r=!1}))}}else e._queue((function(){n=!0}))},setPagination:function(n){return null==e.options.onPaginationChange?void 0:e.options.onPaginationChange((function(e){return t(n,e)}))},resetPagination:function(t){var n;e.setPagination(t?{pageIndex:0,pageSize:10}:null!=(n=e.initialState.pagination)?n:{pageIndex:0,pageSize:10})},setPageIndex:function(n){e.setPagination((function(r){var o=t(n,r.pageIndex),i=void 0===e.options.pageCount||-1===e.options.pageCount?Number.MAX_SAFE_INTEGER:e.options.pageCount-1;return l({},r,{pageIndex:o=Math.min(Math.max(0,o),i)})}))},resetPageIndex:function(t){var n,r,o;e.setPageIndex(t?0:null!=(n=null==(r=e.initialState)||null==(o=r.pagination)?void 0:o.pageIndex)?n:0)},resetPageSize:function(t){var n,r,o;e.setPageSize(t?10:null!=(n=null==(r=e.initialState)||null==(o=r.pagination)?void 0:o.pageSize)?n:10)},setPageSize:function(n){e.setPagination((function(e){var r=Math.max(1,t(n,e.pageSize)),o=e.pageSize*e.pageIndex;return l({},e,{pageIndex:Math.floor(o/r),pageSize:r})}))},setPageCount:function(n){return e.setPagination((function(r){var o,i=t(n,null!=(o=e.options.pageCount)?o:-1);return"number"==typeof i&&(i=Math.max(-1,i)),l({},r,{pageCount:i})}))},getPageOptions:f((function(){return[e.getPageCount()]}),(function(e){var t=[];return e&&e>0&&(t=i(new Array(e)).fill(null).map((function(e,t){return t}))),t}),{key:!1,debug:function(){var t;return null!=(t=e.options.debugAll)?t:e.options.debugTable}}),getCanPreviousPage:function(){return e.getState().pagination.pageIndex>0},getCanNextPage:function(){var t=e.getState().pagination.pageIndex,n=e.getPageCount();return-1===n||0!==n&&tt?1:-1}function Y(e){return"number"==typeof e?isNaN(e)||e===1/0||e===-1/0?"":String(e):"string"==typeof e?e:""}function $(e,t){for(var n=e.split(W).filter(Boolean),r=t.split(W).filter(Boolean);n.length&&r.length;){var o=n.shift(),i=r.shift(),a=parseInt(o,10),c=parseInt(i,10),s=[a,c].sort();if(isNaN(s[0])){if(o>i)return 1;if(i>o)return-1}else{if(isNaN(s[1]))return isNaN(a)?-1:1;if(a>c)return 1;if(c>a)return-1}}return n.length-r.length}var Z={alphanumeric:function(e,t,n){return $(Y(e.getValue(n)).toLowerCase(),Y(t.getValue(n)).toLowerCase())},alphanumericCaseSensitive:function(e,t,n){return $(Y(e.getValue(n)),Y(t.getValue(n)))},text:function(e,t,n){return G(Y(e.getValue(n)).toLowerCase(),Y(t.getValue(n)).toLowerCase())},textCaseSensitive:function(e,t,n){return G(Y(e.getValue(n)),Y(t.getValue(n)))},datetime:function(e,t,n){return G(e.getValue(n).getTime(),t.getValue(n).getTime())},basic:function(e,t,n){return G(e.getValue(n),t.getValue(n))}},X={getInitialState:function(e){return l({sorting:[]},e)},getDefaultColumnDef:function(){return{sortingFn:"auto"}},getDefaultOptions:function(e){return{onSortingChange:n("sorting",e),isMultiSortEvent:function(e){return e.shiftKey}}},createColumn:function(e,t){return{getAutoSortingFn:function(){var n=t.getFilteredRowModel().flatRows.slice(10),r=!1,o=!0,i=!1,a=void 0;try{for(var c,s=n[Symbol.iterator]();!(o=(c=s.next()).done);o=!0){var l=c.value,u=null==l?void 0:l.getValue(e.id);if("[object Date]"===Object.prototype.toString.call(u))return Z.datetime;if("string"==typeof u&&(r=!0,u.split(W).length>1))return Z.alphanumeric}}catch(f){i=!0,a=f}finally{try{o||null==s.return||s.return()}finally{if(i)throw a}}return r?Z.text:Z.basic},getAutoSortDir:function(){var n=t.getFilteredRowModel().flatRows[0];return"string"==typeof(null==n?void 0:n.getValue(e.id))?"asc":"desc"},getSortingFn:function(){var n,r=t.options.sortingFns;if(!e)throw new Error;return s(e.columnDef.sortingFn)?e.columnDef.sortingFn:"auto"===e.columnDef.sortingFn?e.getAutoSortingFn():null!=(n=null==r?void 0:r[e.columnDef.sortingFn])?n:Z[e.columnDef.sortingFn]},toggleSorting:function(n,r){var o=e.getNextSortingOrder();t.setSorting((function(a){var c,s,u,f=null==a?void 0:a.find((function(t){return t.id===e.id})),d=null==a?void 0:a.findIndex((function(t){return t.id===e.id})),h=null!=n,p=[];if("toggle"!==(u=e.getCanMultiSort()&&r?f?"toggle":"add":null!=a&&a.length&&d!==a.length-1?"replace":f?"toggle":"replace")||null!=(c=t.options.enableSortingRemoval)&&!c||h||r&&null!=(s=t.options.enableMultiRemove)&&!s||o||(u="remove"),"replace"===u)p=[{id:e.id,desc:h?n:"desc"===o}];else if("add"===u&&null!=a&&a.length){var z;(p=[].concat(i(a),[{id:e.id,desc:h?n:"desc"===o}])).splice(0,p.length-(null!=(z=t.options.maxMultiSortColCount)?z:Number.MAX_SAFE_INTEGER))}else"toggle"===u&&null!=a&&a.length?p=a.map((function(t){return t.id===e.id?l({},t,{desc:h?n:"desc"===o}):t})):"remove"===u&&null!=a&&a.length&&(p=a.filter((function(t){return t.id!==e.id})));return p}))},getNextSortingOrder:function(){var n,r,o=(null!=(n=null!=(r=e.columnDef.sortDescFirst)?r:t.options.sortDescFirst)?n:"desc"===e.getAutoSortDir())?"desc":"asc",i=e.getIsSorted();return i?i===o&&("desc"===i?"asc":"desc"):o},getCanSort:function(){var n,r;return(null==(n=e.columnDef.enableSorting)||n)&&(null==(r=t.options.enableSorting)||r)&&!!e.accessorFn},getCanMultiSort:function(){var n,r;return null!=(n=null!=(r=e.columnDef.enableMultiSort)?r:t.options.enableMultiSort)?n:!!e.accessorFn},getIsSorted:function(){var n,r=null==(n=t.getState().sorting)?void 0:n.find((function(t){return t.id===e.id}));return!!r&&(r.desc?"desc":"asc")},getSortIndex:function(){var n,r;return null!=(n=null==(r=t.getState().sorting)?void 0:r.findIndex((function(t){return t.id===e.id})))?n:-1},clearSorting:function(){t.setSorting((function(t){return null!=t&&t.length?t.filter((function(t){return t.id!==e.id})):[]}))},getToggleSortingHandler:function(){var n=e.getCanSort();return function(r){n&&(null==r.persist||r.persist(),null==e.toggleSorting||e.toggleSorting(void 0,!!e.getCanMultiSort()&&(null==t.options.isMultiSortEvent?void 0:t.options.isMultiSortEvent(r))))}}}},createInstance:function(e){return{setSorting:function(t){return null==e.options.onSortingChange?void 0:e.options.onSortingChange(t)},resetSorting:function(t){var n,r;e.setSorting(t?[]:null!=(n=null==(r=e.initialState)?void 0:r.sorting)?n:[])},getPreSortedRowModel:function(){return e.getGroupedRowModel()},getSortedRowModel:function(){return!e._getSortedRowModel&&e.options.getSortedRowModel&&(e._getSortedRowModel=e.options.getSortedRowModel(e)),e.options.manualSorting||!e._getSortedRowModel?e.getPreSortedRowModel():e._getSortedRowModel()}}}},K={getInitialState:function(e){return l({columnVisibility:{}},e)},getDefaultOptions:function(e){return{onColumnVisibilityChange:n("columnVisibility",e)}},createColumn:function(e,t){return{toggleVisibility:function(n){e.getCanHide()&&t.setColumnVisibility((function(t){return l({},t,a({},e.id,null!=n?n:!e.getIsVisible()))}))},getIsVisible:function(){var n,r;return null==(n=null==(r=t.getState().columnVisibility)?void 0:r[e.id])||n},getCanHide:function(){var n,r;return(null==(n=e.columnDef.enableHiding)||n)&&(null==(r=t.options.enableHiding)||r)},getToggleVisibilityHandler:function(){return function(t){null==e.toggleVisibility||e.toggleVisibility(t.target.checked)}}}},createRow:function(e,t){return{_getAllVisibleCells:f((function(){return[e.getAllCells(),t.getState().columnVisibility]}),(function(e){return e.filter((function(e){return e.column.getIsVisible()}))}),{key:"row._getAllVisibleCells",debug:function(){var e;return null!=(e=t.options.debugAll)?e:t.options.debugRows}}),getVisibleCells:f((function(){return[e.getLeftVisibleCells(),e.getCenterVisibleCells(),e.getRightVisibleCells()]}),(function(e,t,n){return[].concat(i(e),i(t),i(n))}),{key:!1,debug:function(){var e;return null!=(e=t.options.debugAll)?e:t.options.debugRows}})}},createInstance:function(e){var t=function(t,n){return f((function(){return[n(),n().filter((function(e){return e.getIsVisible()})).map((function(e){return e.id})).join("_")]}),(function(e){return e.filter((function(e){return null==e.getIsVisible?void 0:e.getIsVisible()}))}),{key:t,debug:function(){var t;return null!=(t=e.options.debugAll)?t:e.options.debugColumns}})};return{getVisibleFlatColumns:t("getVisibleFlatColumns",(function(){return e.getAllFlatColumns()})),getVisibleLeafColumns:t("getVisibleLeafColumns",(function(){return e.getAllLeafColumns()})),getLeftVisibleLeafColumns:t("getLeftVisibleLeafColumns",(function(){return e.getLeftLeafColumns()})),getRightVisibleLeafColumns:t("getRightVisibleLeafColumns",(function(){return e.getRightLeafColumns()})),getCenterVisibleLeafColumns:t("getCenterVisibleLeafColumns",(function(){return e.getCenterLeafColumns()})),setColumnVisibility:function(t){return null==e.options.onColumnVisibilityChange?void 0:e.options.onColumnVisibilityChange(t)},resetColumnVisibility:function(t){var n;e.setColumnVisibility(t?{}:null!=(n=e.initialState.columnVisibility)?n:{})},toggleAllColumnsVisible:function(t){var n;t=null!=(n=t)?n:!e.getIsAllColumnsVisible(),e.setColumnVisibility(e.getAllLeafColumns().reduce((function(e,n){return l({},e,a({},n.id,t||!(null!=n.getCanHide&&n.getCanHide())))}),{}))},getIsAllColumnsVisible:function(){return!e.getAllLeafColumns().some((function(e){return!(null!=e.getIsVisible&&e.getIsVisible())}))},getIsSomeColumnsVisible:function(){return e.getAllLeafColumns().some((function(e){return null==e.getIsVisible?void 0:e.getIsVisible()}))},getToggleAllColumnsVisibilityHandler:function(){return function(t){var n;e.toggleAllColumnsVisible(null==(n=t.target)?void 0:n.checked)}}}}},Q=[p,K,V,I,A,X,D,w,R,N,g],J=function(e,t,n,r,o,i){for(var a={id:t,index:r,original:n,depth:o,_valuesCache:{},getValue:function(t){if(a._valuesCache.hasOwnProperty(t))return a._valuesCache[t];var n=e.getColumn(t);return n.accessorFn?(a._valuesCache[t]=n.accessorFn(a.original,r),a._valuesCache[t]):void 0},subRows:null!=i?i:[],getLeafRows:function(){return u(a.subRows,(function(e){return e.subRows}))},getAllCells:f((function(){return[e.getAllLeafColumns()]}),(function(t){return t.map((function(t){return function(e,t,n,r){var o=function(){var t;return null!=(t=i.getValue())?t:e.options.renderFallbackValue},i={id:t.id+"_"+n.id,row:t,column:n,getValue:function(){return t.getValue(r)},renderCell:function(){return n.columnDef.cell?e._render(n.columnDef.cell,{instance:e,column:n,row:t,cell:i,getValue:o}):null}};return e._features.forEach((function(r){Object.assign(i,null==r.createCell?void 0:r.createCell(i,n,t,e))}),{}),i}(e,a,t,t.id)}))}),{key:!1,debug:function(){var t;return null!=(t=e.options.debugAll)?t:e.options.debugRows}}),_getAllCellsByColumnId:f((function(){return[a.getAllCells()]}),(function(e){return e.reduce((function(e,t){return e[t.column.id]=t,e}),{})}),{key:"row.getAllCellsByColumnId",debug:function(){var t;return null!=(t=e.options.debugAll)?t:e.options.debugRows}})},c=0;co[1]&&(o[1]=c)}return o}}),{key:!1,debug:function(){var t;return null!=(t=e.options.debugAll)?t:e.options.debugTable},onChange:function(){}})}},e.getFacetedRowModel=function(){return function(e,t){return f((function(){return[e.getPreFilteredRowModel(),e.getState().columnFilters,e.getState().globalFilter,e.getFilteredRowModel()]}),(function(n,r,o){if(!n.rows.length||(null==r||!r.length)&&!o)return n;var a=[].concat(i(r.map((function(e){return e.id})).filter((function(e){return e!==t}))),[o?"__global__":void 0]).filter(Boolean);return ee(n.rows,(function(e){for(var t=0;t"+z:z),g=s?u(p,(function(e){return e.subRows})):p,m=J(e,z,void 0,i,s);return Object.assign(m,{groupingColumnId:f,groupingValue:h,subRows:v,leafRows:g,getValue:function(t){var n;if(o.includes(t))return m._valuesCache.hasOwnProperty(t)?m._valuesCache[t]:(p[0]&&(m._valuesCache[t]=null!=(n=p[0].getValue(t))?n:void 0),m._valuesCache[t]);if(m._groupingValuesCache.hasOwnProperty(t))return m._groupingValuesCache[t];var r=e.getColumn(t).getAggregationFn();return r?(m._groupingValuesCache[t]=r(t,g,p),m._groupingValuesCache[t]):void 0}}),v.forEach((function(e){a.push(e),c[e.id]=e})),m}))}(n.rows,0,"");return s.forEach((function(e){a.push(e),c[e.id]=e})),{rows:s,flatRows:a,rowsById:c}}),{key:!1,debug:function(){var t;return null!=(t=e.options.debugAll)?t:e.options.debugTable},onChange:function(){e._queue((function(){e._autoResetExpanded(),e._autoResetPageIndex()}))}})}},e.getPaginationRowModel=function(e){return function(e){return f((function(){return[e.getState().pagination,e.getPrePaginationRowModel()]}),(function(t,n){if(!n.rows.length)return n;var r,o=t.pageSize,i=t.pageIndex,a=n.rows,c=n.flatRows,s=n.rowsById,l=o*i,u=l+o;return a=a.slice(l,u),(r=e.options.paginateExpandedRows?{rows:a,flatRows:c,rowsById:s}:te({rows:a,flatRows:c,rowsById:s})).flatRows=[],r.rows.forEach((function e(t){r.flatRows.push(t),t.subRows.length&&t.subRows.forEach(e)})),r}),{key:!1,debug:function(){var t;return null!=(t=e.options.debugAll)?t:e.options.debugTable}})}},e.getSortedRowModel=function(){return function(e){return f((function(){return[e.getState().sorting,e.getPreSortedRowModel()]}),(function(t,n){if(!n.rows.length||null==t||!t.length)return n;var r=e.getState().sorting,o=[],i=r.filter((function(t){return e.getColumn(t.id).getCanSort()})),a={};return i.forEach((function(t){var n=e.getColumn(t.id);a[t.id]={sortUndefined:n.columnDef.sortUndefined,invertSorting:n.columnDef.invertSorting,sortingFn:n.getSortingFn()}})),{rows:function e(t){var n=t.slice();return n.sort((function(e,t){for(var n=0;n=s.getDate()?s:(n.setFullYear(s.getFullYear(),s.getMonth(),c),n)}},,function(e,t,n){"use strict";function r(e){return"[object Function]"===Object.prototype.toString.call(e)}n.d(t,"a",(function(){return r}))},,function(e,t,n){"use strict";(function(e){n.d(t,"a",(function(){return o})),n.d(t,"b",(function(){return i}));var r=function(){return(r=Object.assign||function(e){for(var t,n=1,r=arguments.length;nt?1:e>=t?0:NaN},o=function(e){var t;return 1===e.length&&(t=e,e=function(e,n){return r(t(e),n)}),{left:function(t,n,r,o){for(null==r&&(r=0),null==o&&(o=t.length);r>>1;e(t[i],n)<0?r=i+1:o=i}return r},right:function(t,n,r,o){for(null==r&&(r=0),null==o&&(o=t.length);r>>1;e(t[i],n)>0?o=i:r=i+1}return r}}};var i=o(r),a=i.right,c=i.left,s=a,l=function(e,t){null==t&&(t=u);for(var n=0,r=e.length-1,o=e[0],i=new Array(r<0?0:r);ne?1:t>=e?0:NaN},h=function(e){return null===e?NaN:+e},p=function(e,t){var n,r,o=e.length,i=0,a=-1,c=0,s=0;if(null==t)for(;++a1)return s/(i-1)},z=function(e,t){var n=p(e,t);return n?Math.sqrt(n):n},v=function(e,t){var n,r,o,i=e.length,a=-1;if(null==t){for(;++a=n)for(r=o=n;++an&&(r=n),o=n)for(r=o=n;++an&&(r=n),o0)return[e];if((r=t0)for(e=Math.ceil(e/a),t=Math.floor(t/a),i=new Array(o=Math.ceil(t-e+1));++c=0?(i>=x?10:i>=j?5:i>=M?2:1)*Math.pow(10,o):-Math.pow(10,-o)/(i>=x?10:i>=j?5:i>=M?2:1)}function q(e,t,n){var r=Math.abs(t-e)/Math.max(0,n),o=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),i=r/o;return i>=x?o*=10:i>=j?o*=5:i>=M&&(o*=2),tf;)d.pop(),--h;var p,z=new Array(h+1);for(o=0;o<=h;++o)(p=z[o]=[]).x0=o>0?d[o-1]:u,p.x1=o=1)return+n(e[r-1],r-1,e);var r,o=(r-1)*t,i=Math.floor(o),a=+n(e[i],i,e);return a+(+n(e[i+1],i+1,e)-a)*(o-i)}},T=function(e,t,n){return e=y.call(e,h).sort(r),Math.ceil((n-t)/(2*(E(e,.75)-E(e,.25))*Math.pow(e.length,-1/3)))},A=function(e,t,n){return Math.ceil((n-t)/(3.5*z(e)*Math.pow(e.length,-1/3)))},L=function(e,t){var n,r,o=e.length,i=-1;if(null==t){for(;++i=n)for(r=n;++ir&&(r=n)}else for(;++i=n)for(r=n;++ir&&(r=n);return r},H=function(e,t){var n,r=e.length,o=r,i=-1,a=0;if(null==t)for(;++i=0;)for(t=(r=e[o]).length;--t>=0;)n[--a]=r[t];return n},V=function(e,t){var n,r,o=e.length,i=-1;if(null==t){for(;++i=n)for(r=n;++in&&(r=n)}else for(;++i=n)for(r=n;++in&&(r=n);return r},R=function(e,t){for(var n=t.length,r=new Array(n);n--;)r[n]=e[t[n]];return r},I=function(e,t){if(n=e.length){var n,o,i=0,a=0,c=e[a];for(null==t&&(t=r);++i=0&&(n=e.slice(r+1),e=e.slice(0,r)),e&&!t.hasOwnProperty(e))throw new Error("unknown type: "+e);return{type:e,name:n}}))}function he(e,t){for(var n,r=0,o=e.length;r0)for(var n,r,o=new Array(n),i=0;i=0&&"xmlns"!==(t=e.slice(0,n))&&(e=e.slice(n+1)),ge.hasOwnProperty(t)?{space:ge[t],local:e}:e};function ye(e){return function(){var t=this.ownerDocument,n=this.namespaceURI;return n===ve&&t.documentElement.namespaceURI===ve?t.createElement(e):t.createElementNS(n,e)}}function be(e){return function(){return this.ownerDocument.createElementNS(e.space,e.local)}}var we=function(e){var t=me(e);return(t.local?be:ye)(t)},ke=0;function xe(){return new je}function je(){this._="@"+(++ke).toString(36)}je.prototype=xe.prototype={constructor:je,get:function(e){for(var t=this._;!(t in e);)if(!(e=e.parentNode))return;return e[t]},set:function(e,t){return e[this._]=t},remove:function(e){return this._ in e&&delete e[this._]},toString:function(){return this._}};var Me=function(e){return function(){return this.matches(e)}};if("undefined"!==typeof document){var Ce=document.documentElement;if(!Ce.matches){var _e=Ce.webkitMatchesSelector||Ce.msMatchesSelector||Ce.mozMatchesSelector||Ce.oMatchesSelector;Me=function(e){return function(){return _e.call(this,e)}}}}var qe=Me,Se={},Oe=null;"undefined"!==typeof document&&("onmouseenter"in document.documentElement||(Se={mouseenter:"mouseover",mouseleave:"mouseout"}));function Ee(e,t,n){return e=Te(e,t,n),function(t){var n=t.relatedTarget;n&&(n===this||8&n.compareDocumentPosition(this))||e.call(this,t)}}function Te(e,t,n){return function(r){var o=Oe;Oe=r;try{e.call(this,this.__data__,t,n)}finally{Oe=o}}}function Ae(e){return e.trim().split(/^|\s+/).map((function(e){var t="",n=e.indexOf(".");return n>=0&&(t=e.slice(n+1),e=e.slice(0,n)),{type:e,name:t}}))}function Le(e){return function(){var t=this.__on;if(t){for(var n,r=0,o=-1,i=t.length;rt?1:e>=t?0:NaN}function Xe(e){return function(){this.removeAttribute(e)}}function Ke(e){return function(){this.removeAttributeNS(e.space,e.local)}}function Qe(e,t){return function(){this.setAttribute(e,t)}}function Je(e,t){return function(){this.setAttributeNS(e.space,e.local,t)}}function et(e,t){return function(){var n=t.apply(this,arguments);null==n?this.removeAttribute(e):this.setAttribute(e,n)}}function tt(e,t){return function(){var n=t.apply(this,arguments);null==n?this.removeAttributeNS(e.space,e.local):this.setAttributeNS(e.space,e.local,n)}}var nt=function(e){return e.ownerDocument&&e.ownerDocument.defaultView||e.document&&e||e.defaultView};function rt(e){return function(){this.style.removeProperty(e)}}function ot(e,t,n){return function(){this.style.setProperty(e,t,n)}}function it(e,t,n){return function(){var r=t.apply(this,arguments);null==r?this.style.removeProperty(e):this.style.setProperty(e,r,n)}}function at(e,t){return e.style.getPropertyValue(t)||nt(e).getComputedStyle(e,null).getPropertyValue(t)}function ct(e){return function(){delete this[e]}}function st(e,t){return function(){this[e]=t}}function lt(e,t){return function(){var n=t.apply(this,arguments);null==n?delete this[e]:this[e]=n}}function ut(e){return e.trim().split(/^|\s+/)}function ft(e){return e.classList||new dt(e)}function dt(e){this._node=e,this._names=ut(e.getAttribute("class")||"")}function ht(e,t){for(var n=ft(e),r=-1,o=t.length;++r=0&&(this._names.splice(t,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(e){return this._names.indexOf(e)>=0}};function mt(){this.textContent=""}function yt(e){return function(){this.textContent=e}}function bt(e){return function(){var t=e.apply(this,arguments);this.textContent=null==t?"":t}}function wt(){this.innerHTML=""}function kt(e){return function(){this.innerHTML=e}}function xt(e){return function(){var t=e.apply(this,arguments);this.innerHTML=null==t?"":t}}function jt(){this.nextSibling&&this.parentNode.appendChild(this)}function Mt(){this.previousSibling&&this.parentNode.insertBefore(this,this.parentNode.firstChild)}function Ct(){return null}function _t(){var e=this.parentNode;e&&e.removeChild(this)}function qt(e,t,n){var r=nt(e),o=r.CustomEvent;"function"===typeof o?o=new o(t,n):(o=r.document.createEvent("Event"),n?(o.initEvent(t,n.bubbles,n.cancelable),o.detail=n.detail):o.initEvent(t,!1,!1)),e.dispatchEvent(o)}function St(e,t){return function(){return qt(this,e,t)}}function Ot(e,t){return function(){return qt(this,e,t.apply(this,arguments))}}var Et=[null];function Tt(e,t){this._groups=e,this._parents=t}function At(){return new Tt([[document.documentElement]],Et)}Tt.prototype=At.prototype={constructor:Tt,select:function(e){"function"!==typeof e&&(e=Ne(e));for(var t=this._groups,n=t.length,r=new Array(n),o=0;o=w&&(w=b+1);!(y=g[w])&&++w=0;)(r=o[i])&&(a&&a!==r.nextSibling&&a.parentNode.insertBefore(r,a),a=r);return this},sort:function(e){function t(t,n){return t&&n?e(t.__data__,n.__data__):!t-!n}e||(e=Ze);for(var n=this._groups,r=n.length,o=new Array(r),i=0;i1?this.each((null==t?rt:"function"===typeof t?it:ot)(e,t,null==n?"":n)):at(this.node(),e)},property:function(e,t){return arguments.length>1?this.each((null==t?ct:"function"===typeof t?lt:st)(e,t)):this.node()[e]},classed:function(e,t){var n=ut(e+"");if(arguments.length<2){for(var r=ft(this.node()),o=-1,i=n.length;++of}s.mouse("drag")}function z(){Ht(Oe.view).on("mousemove.drag mouseup.drag",null),Ft(Oe.view,n),It(),s.mouse("end")}function v(){if(o.apply(this,arguments)){var e,t,n=Oe.changedTouches,r=i.apply(this,arguments),a=n.length;for(e=0;e>8&15|t>>4&240,t>>4&15|240&t,(15&t)<<4|15&t,1):(t=rn.exec(e))?hn(parseInt(t[1],16)):(t=on.exec(e))?new gn(t[1],t[2],t[3],1):(t=an.exec(e))?new gn(255*t[1]/100,255*t[2]/100,255*t[3]/100,1):(t=cn.exec(e))?pn(t[1],t[2],t[3],t[4]):(t=sn.exec(e))?pn(255*t[1]/100,255*t[2]/100,255*t[3]/100,t[4]):(t=ln.exec(e))?mn(t[1],t[2]/100,t[3]/100,1):(t=un.exec(e))?mn(t[1],t[2]/100,t[3]/100,t[4]):fn.hasOwnProperty(e)?hn(fn[e]):"transparent"===e?new gn(NaN,NaN,NaN,0):null}function hn(e){return new gn(e>>16&255,e>>8&255,255&e,1)}function pn(e,t,n,r){return r<=0&&(e=t=n=NaN),new gn(e,t,n,r)}function zn(e){return e instanceof Qt||(e=dn(e)),e?new gn((e=e.rgb()).r,e.g,e.b,e.opacity):new gn}function vn(e,t,n,r){return 1===arguments.length?zn(e):new gn(e,t,n,null==r?1:r)}function gn(e,t,n,r){this.r=+e,this.g=+t,this.b=+n,this.opacity=+r}function mn(e,t,n,r){return r<=0?e=t=n=NaN:n<=0||n>=1?e=t=NaN:t<=0&&(e=NaN),new bn(e,t,n,r)}function yn(e,t,n,r){return 1===arguments.length?function(e){if(e instanceof bn)return new bn(e.h,e.s,e.l,e.opacity);if(e instanceof Qt||(e=dn(e)),!e)return new bn;if(e instanceof bn)return e;var t=(e=e.rgb()).r/255,n=e.g/255,r=e.b/255,o=Math.min(t,n,r),i=Math.max(t,n,r),a=NaN,c=i-o,s=(i+o)/2;return c?(a=t===i?(n-r)/c+6*(n0&&s<1?0:a,new bn(a,c,s,e.opacity)}(e):new bn(e,t,n,null==r?1:r)}function bn(e,t,n,r){this.h=+e,this.s=+t,this.l=+n,this.opacity=+r}function wn(e,t,n){return 255*(e<60?t+(n-t)*e/60:e<180?n:e<240?t+(n-t)*(240-e)/60:t)}Xt(Qt,dn,{displayable:function(){return this.rgb().displayable()},toString:function(){return this.rgb()+""}}),Xt(gn,vn,Kt(Qt,{brighter:function(e){return e=null==e?1/.7:Math.pow(1/.7,e),new gn(this.r*e,this.g*e,this.b*e,this.opacity)},darker:function(e){return e=null==e?.7:Math.pow(.7,e),new gn(this.r*e,this.g*e,this.b*e,this.opacity)},rgb:function(){return this},displayable:function(){return 0<=this.r&&this.r<=255&&0<=this.g&&this.g<=255&&0<=this.b&&this.b<=255&&0<=this.opacity&&this.opacity<=1},toString:function(){var e=this.opacity;return(1===(e=isNaN(e)?1:Math.max(0,Math.min(1,e)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===e?")":", "+e+")")}})),Xt(bn,yn,Kt(Qt,{brighter:function(e){return e=null==e?1/.7:Math.pow(1/.7,e),new bn(this.h,this.s,this.l*e,this.opacity)},darker:function(e){return e=null==e?.7:Math.pow(.7,e),new bn(this.h,this.s,this.l*e,this.opacity)},rgb:function(){var e=this.h%360+360*(this.h<0),t=isNaN(e)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*t,o=2*n-r;return new gn(wn(e>=240?e-240:e+120,o,r),wn(e,o,r),wn(e<120?e+240:e-120,o,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1}}));var kn=Math.PI/180,xn=180/Math.PI,jn=.95047,Mn=1,Cn=1.08883,_n=4/29,qn=6/29,Sn=3*qn*qn,On=qn*qn*qn;function En(e){if(e instanceof An)return new An(e.l,e.a,e.b,e.opacity);if(e instanceof Rn){var t=e.h*kn;return new An(e.l,Math.cos(t)*e.c,Math.sin(t)*e.c,e.opacity)}e instanceof gn||(e=zn(e));var n=Pn(e.r),r=Pn(e.g),o=Pn(e.b),i=Ln((.4124564*n+.3575761*r+.1804375*o)/jn),a=Ln((.2126729*n+.7151522*r+.072175*o)/Mn);return new An(116*a-16,500*(i-a),200*(a-Ln((.0193339*n+.119192*r+.9503041*o)/Cn)),e.opacity)}function Tn(e,t,n,r){return 1===arguments.length?En(e):new An(e,t,n,null==r?1:r)}function An(e,t,n,r){this.l=+e,this.a=+t,this.b=+n,this.opacity=+r}function Ln(e){return e>On?Math.pow(e,1/3):e/Sn+_n}function Hn(e){return e>qn?e*e*e:Sn*(e-_n)}function Dn(e){return 255*(e<=.0031308?12.92*e:1.055*Math.pow(e,1/2.4)-.055)}function Pn(e){return(e/=255)<=.04045?e/12.92:Math.pow((e+.055)/1.055,2.4)}function Vn(e,t,n,r){return 1===arguments.length?function(e){if(e instanceof Rn)return new Rn(e.h,e.c,e.l,e.opacity);e instanceof An||(e=En(e));var t=Math.atan2(e.b,e.a)*xn;return new Rn(t<0?t+360:t,Math.sqrt(e.a*e.a+e.b*e.b),e.l,e.opacity)}(e):new Rn(e,t,n,null==r?1:r)}function Rn(e,t,n,r){this.h=+e,this.c=+t,this.l=+n,this.opacity=+r}Xt(An,Tn,Kt(Qt,{brighter:function(e){return new An(this.l+18*(null==e?1:e),this.a,this.b,this.opacity)},darker:function(e){return new An(this.l-18*(null==e?1:e),this.a,this.b,this.opacity)},rgb:function(){var e=(this.l+16)/116,t=isNaN(this.a)?e:e+this.a/500,n=isNaN(this.b)?e:e-this.b/200;return e=Mn*Hn(e),new gn(Dn(3.2404542*(t=jn*Hn(t))-1.5371385*e-.4985314*(n=Cn*Hn(n))),Dn(-.969266*t+1.8760108*e+.041556*n),Dn(.0556434*t-.2040259*e+1.0572252*n),this.opacity)}})),Xt(Rn,Vn,Kt(Qt,{brighter:function(e){return new Rn(this.h,this.c,this.l+18*(null==e?1:e),this.opacity)},darker:function(e){return new Rn(this.h,this.c,this.l-18*(null==e?1:e),this.opacity)},rgb:function(){return En(this).rgb()}}));var In=-.29227,Nn=-.90649,Fn=1.97294,Bn=Fn*Nn,Un=1.78277*Fn,Wn=1.78277*In- -.14861*Nn;function Gn(e,t,n,r){return 1===arguments.length?function(e){if(e instanceof Yn)return new Yn(e.h,e.s,e.l,e.opacity);e instanceof gn||(e=zn(e));var t=e.r/255,n=e.g/255,r=e.b/255,o=(Wn*r+Bn*t-Un*n)/(Wn+Bn-Un),i=r-o,a=(Fn*(n-o)-In*i)/Nn,c=Math.sqrt(a*a+i*i)/(Fn*o*(1-o)),s=c?Math.atan2(a,i)*xn-120:NaN;return new Yn(s<0?s+360:s,c,o,e.opacity)}(e):new Yn(e,t,n,null==r?1:r)}function Yn(e,t,n,r){this.h=+e,this.s=+t,this.l=+n,this.opacity=+r}function $n(e,t,n,r,o){var i=e*e,a=i*e;return((1-3*e+3*i-a)*t+(4-6*i+3*a)*n+(1+3*e+3*i-3*a)*r+a*o)/6}Xt(Yn,Gn,Kt(Qt,{brighter:function(e){return e=null==e?1/.7:Math.pow(1/.7,e),new Yn(this.h,this.s,this.l*e,this.opacity)},darker:function(e){return e=null==e?.7:Math.pow(.7,e),new Yn(this.h,this.s,this.l*e,this.opacity)},rgb:function(){var e=isNaN(this.h)?0:(this.h+120)*kn,t=+this.l,n=isNaN(this.s)?0:this.s*t*(1-t),r=Math.cos(e),o=Math.sin(e);return new gn(255*(t+n*(-.14861*r+1.78277*o)),255*(t+n*(In*r+Nn*o)),255*(t+n*(Fn*r)),this.opacity)}}));var Zn=function(e){var t=e.length-1;return function(n){var r=n<=0?n=0:n>=1?(n=1,t-1):Math.floor(n*t),o=e[r],i=e[r+1],a=r>0?e[r-1]:2*o-i,c=r180||n<-180?n-360*Math.round(n/360):n):Kn(isNaN(e)?t:e)}function er(e){return 1===(e=+e)?tr:function(t,n){return n-t?function(e,t,n){return e=Math.pow(e,n),t=Math.pow(t,n)-e,n=1/n,function(r){return Math.pow(e+r*t,n)}}(t,n,e):Kn(isNaN(t)?n:t)}}function tr(e,t){var n=t-e;return n?Qn(e,n):Kn(isNaN(e)?t:e)}var nr=function e(t){var n=er(t);function r(e,t){var r=n((e=vn(e)).r,(t=vn(t)).r),o=n(e.g,t.g),i=n(e.b,t.b),a=tr(e.opacity,t.opacity);return function(t){return e.r=r(t),e.g=o(t),e.b=i(t),e.opacity=a(t),e+""}}return r.gamma=e,r}(1);function rr(e){return function(t){var n,r,o=t.length,i=new Array(o),a=new Array(o),c=new Array(o);for(n=0;ni&&(o=t.slice(i,o),c[a]?c[a]+=o:c[++a]=o),(n=n[0])===(r=r[0])?c[a]?c[a]+=r:c[++a]=r:(c[++a]=null,s.push({i:a,x:sr(n,r)})),i=fr.lastIndex;return i180?t+=360:t-e>180&&(e+=360),i.push({i:n.push(o(n)+"rotate(",null,r)-2,x:sr(e,t)})):t&&n.push(o(n)+"rotate("+t+r)}(i.rotate,a.rotate,c,s),function(e,t,n,i){e!==t?i.push({i:n.push(o(n)+"skewX(",null,r)-2,x:sr(e,t)}):t&&n.push(o(n)+"skewX("+t+r)}(i.skewX,a.skewX,c,s),function(e,t,n,r,i,a){if(e!==n||t!==r){var c=i.push(o(i)+"scale(",null,",",null,")");a.push({i:c-4,x:sr(e,n)},{i:c-2,x:sr(t,r)})}else 1===n&&1===r||i.push(o(i)+"scale("+n+","+r+")")}(i.scaleX,i.scaleY,a.scaleX,a.scaleY,c,s),i=a=null,function(e){for(var t,n=-1,r=s.length;++n=0&&t._call.call(null,e),t=t._next;--Nr}function to(){Gr=(Wr=$r.now())+Yr,Nr=Fr=0;try{eo()}finally{Nr=0,function(){var e,t,n=Dr,r=1/0;for(;n;)n._call?(r>n._time&&(r=n._time),e=n,n=n._next):(t=n._next,n._next=null,n=e?e._next=t:Dr=t);Pr=e,ro(r)}(),Gr=0}}function no(){var e=$r.now(),t=e-Wr;t>Ur&&(Yr-=t,Wr=e)}function ro(e){Nr||(Fr&&(Fr=clearTimeout(Fr)),e-Gr>24?(e<1/0&&(Fr=setTimeout(to,e-$r.now()-Yr)),Br&&(Br=clearInterval(Br))):(Br||(Wr=$r.now(),Br=setInterval(no,Ur)),Nr=1,Zr(to)))}Qr.prototype=Jr.prototype={constructor:Qr,restart:function(e,t,n){if("function"!==typeof e)throw new TypeError("callback is not a function");n=(null==n?Xr():+n)+(null==t?0:+t),this._next||Pr===this||(Pr?Pr._next=this:Dr=this,Pr=this),this._call=e,this._time=n,ro()},stop:function(){this._call&&(this._call=null,this._time=1/0,ro())}};var oo=function(e,t,n){var r=new Qr;return t=null==t?0:+t,r.restart((function(n){r.stop(),e(n+t)}),t,n),r},io=function(e,t,n){var r=new Qr,o=t;return null==t?(r.restart(e,t,n),r):(t=+t,n=null==n?Xr():+n,r.restart((function i(a){a+=o,r.restart(i,o+=t,n),e(a)}),t,n),r)},ao=ze("start","end","interrupt"),co=[],so=0,lo=1,uo=2,fo=3,ho=4,po=5,zo=6,vo=function(e,t,n,r,o,i){var a=e.__transition;if(a){if(n in a)return}else e.__transition={};!function(e,t,n){var r,o=e.__transition;function i(s){var l,u,f,d;if(n.state!==lo)return c();for(l in o)if((d=o[l]).name===n.name){if(d.state===fo)return oo(i);d.state===ho?(d.state=zo,d.timer.stop(),d.on.call("interrupt",e,e.__data__,d.index,d.group),delete o[l]):+lso)throw new Error("too late; already scheduled");return n}function mo(e,t){var n=yo(e,t);if(n.state>uo)throw new Error("too late; already started");return n}function yo(e,t){var n=e.__transition;if(!n||!(n=n[t]))throw new Error("transition not found");return n}var bo=function(e,t){var n,r,o,i=e.__transition,a=!0;if(i){for(o in t=null==t?null:t+"",i)(n=i[o]).name===t?(r=n.state>uo&&n.state=0&&(e=e.slice(0,t)),!e||"start"===e}))}(t)?go:mo;return function(){var a=i(this,e),c=a.on;c!==r&&(o=(r=c).copy()).on(t,n),a.on=o}}var Ro=Lt.prototype.constructor;function Io(e,t,n){function r(){var r=this,o=t.apply(r,arguments);return o&&function(t){r.style.setProperty(e,o(t),n)}}return r._value=t,r}var No=0;function Fo(e,t,n,r){this._groups=e,this._parents=t,this._name=n,this._id=r}function Bo(e){return Lt().transition(e)}function Uo(){return++No}var Wo=Lt.prototype;function Go(e){return+e}function Yo(e){return e*e}function $o(e){return e*(2-e)}function Zo(e){return((e*=2)<=1?e*e:--e*(2-e)+1)/2}function Xo(e){return e*e*e}function Ko(e){return--e*e*e+1}function Qo(e){return((e*=2)<=1?e*e*e:(e-=2)*e*e+2)/2}Fo.prototype=Bo.prototype={constructor:Fo,select:function(e){var t=this._name,n=this._id;"function"!==typeof e&&(e=Ne(e));for(var r=this._groups,o=r.length,i=new Array(o),a=0;alo&&n.name===t)return new Fo([[e]],Hi,t,+r);return null},Pi=function(e){return function(){return e}},Vi=function(e,t,n){this.target=e,this.type=t,this.selection=n};function Ri(){Oe.stopImmediatePropagation()}var Ii=function(){Oe.preventDefault(),Oe.stopImmediatePropagation()},Ni={name:"drag"},Fi={name:"space"},Bi={name:"handle"},Ui={name:"center"},Wi={name:"x",handles:["e","w"].map(Ji),input:function(e,t){return e&&[[e[0],t[0][1]],[e[1],t[1][1]]]},output:function(e){return e&&[e[0][0],e[1][0]]}},Gi={name:"y",handles:["n","s"].map(Ji),input:function(e,t){return e&&[[t[0][0],e[0]],[t[1][0],e[1]]]},output:function(e){return e&&[e[0][1],e[1][1]]}},Yi={name:"xy",handles:["n","e","s","w","nw","ne","se","sw"].map(Ji),input:function(e){return e},output:function(e){return e}},$i={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Zi={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},Xi={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},Ki={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},Qi={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function Ji(e){return{type:e}}function ea(){return!Oe.button}function ta(){var e=this.ownerSVGElement||this;return[[0,0],[e.width.baseVal.value,e.height.baseVal.value]]}function na(e){for(;!e.__brush;)if(!(e=e.parentNode))return;return e.__brush}function ra(e){return e[0][0]===e[1][0]||e[0][1]===e[1][1]}function oa(e){var t=e.__brush;return t?t.dim.output(t.selection):null}function ia(){return sa(Wi)}function aa(){return sa(Gi)}var ca=function(){return sa(Yi)};function sa(e){var t,n=ta,r=ea,o=ze(a,"start","brush","end"),i=6;function a(t){var n=t.property("__brush",f).selectAll(".overlay").data([Ji("overlay")]);n.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",$i.overlay).merge(n).each((function(){var e=na(this).extent;Ht(this).attr("x",e[0][0]).attr("y",e[0][1]).attr("width",e[1][0]-e[0][0]).attr("height",e[1][1]-e[0][1])})),t.selectAll(".selection").data([Ji("selection")]).enter().append("rect").attr("class","selection").attr("cursor",$i.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var r=t.selectAll(".handle").data(e.handles,(function(e){return e.type}));r.exit().remove(),r.enter().append("rect").attr("class",(function(e){return"handle handle--"+e.type})).attr("cursor",(function(e){return $i[e.type]})),t.each(c).attr("fill","none").attr("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush touchstart.brush",u)}function c(){var e=Ht(this),t=na(this).selection;t?(e.selectAll(".selection").style("display",null).attr("x",t[0][0]).attr("y",t[0][1]).attr("width",t[1][0]-t[0][0]).attr("height",t[1][1]-t[0][1]),e.selectAll(".handle").style("display",null).attr("x",(function(e){return"e"===e.type[e.type.length-1]?t[1][0]-i/2:t[0][0]-i/2})).attr("y",(function(e){return"s"===e.type[0]?t[1][1]-i/2:t[0][1]-i/2})).attr("width",(function(e){return"n"===e.type||"s"===e.type?t[1][0]-t[0][0]+i:i})).attr("height",(function(e){return"e"===e.type||"w"===e.type?t[1][1]-t[0][1]+i:i}))):e.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function s(e,t){return e.__brush.emitter||new l(e,t)}function l(e,t){this.that=e,this.args=t,this.state=e.__brush,this.active=0}function u(){if(Oe.touches){if(Oe.changedTouches.length0&&(n=o-h),k<0?f=d-p:k>0&&(i=a-p),b=Fi,H.attr("cursor",$i.selection),V());break;default:return}Ii()}),!0).on("keyup.brush",(function(){switch(Oe.keyCode){case 16:O&&(v=g=O=!1,V());break;case 18:b===Ui&&(w<0?l=u:w>0&&(n=o),k<0?f=d:k>0&&(i=a),b=Bi,V());break;case 32:b===Fi&&(Oe.altKey?(w&&(l=u-h*w,n=o+h*w),k&&(f=d-p*k,i=a+p*k),b=Ui):(w<0?l=u:w>0&&(n=o),k<0?f=d:k>0&&(i=a),b=Bi),H.attr("cursor",$i[y]),V());break;default:return}Ii()}),!0).on("mousemove.brush",P,!0).on("mouseup.brush",R,!0);Nt(Oe.view)}Ri(),bo(m),c.call(m),A.start()}function P(){var e=Re(m);!O||v||g||(Math.abs(e[0]-T[0])>Math.abs(e[1]-T[1])?g=!0:v=!0),T=e,z=!0,Ii(),V()}function V(){var e;switch(h=T[0]-E[0],p=T[1]-E[1],b){case Fi:case Ni:w&&(h=Math.max(C-n,Math.min(q-l,h)),o=n+h,u=l+h),k&&(p=Math.max(_-i,Math.min(S-f,p)),a=i+p,d=f+p);break;case Bi:w<0?(h=Math.max(C-n,Math.min(q-n,h)),o=n+h,u=l):w>0&&(h=Math.max(C-l,Math.min(q-l,h)),o=n,u=l+h),k<0?(p=Math.max(_-i,Math.min(S-i,p)),a=i+p,d=f):k>0&&(p=Math.max(_-f,Math.min(S-f,p)),a=i,d=f+p);break;case Ui:w&&(o=Math.max(C,Math.min(q,n-h*w)),u=Math.max(C,Math.min(q,l+h*w))),k&&(a=Math.max(_,Math.min(S,i-p*k)),d=Math.max(_,Math.min(S,f+p*k)))}u1e-6)if(Math.abs(u*c-s*l)>1e-6&&o){var d=n-i,h=r-a,p=c*c+s*s,z=d*d+h*h,v=Math.sqrt(p),g=Math.sqrt(f),m=o*Math.tan((ya-Math.acos((p+f-z)/(2*v*g)))/2),y=m/g,b=m/v;Math.abs(y-1)>1e-6&&(this._+="L"+(e+y*l)+","+(t+y*u)),this._+="A"+o+","+o+",0,0,"+ +(u*d>l*h)+","+(this._x1=e+b*c)+","+(this._y1=t+b*s)}else this._+="L"+(this._x1=e)+","+(this._y1=t);else;},arc:function(e,t,n,r,o,i){e=+e,t=+t;var a=(n=+n)*Math.cos(r),c=n*Math.sin(r),s=e+a,l=t+c,u=1^i,f=i?r-o:o-r;if(n<0)throw new Error("negative radius: "+n);null===this._x1?this._+="M"+s+","+l:(Math.abs(this._x1-s)>1e-6||Math.abs(this._y1-l)>1e-6)&&(this._+="L"+s+","+l),n&&(f<0&&(f=f%ba+ba),f>wa?this._+="A"+n+","+n+",0,1,"+u+","+(e-a)+","+(t-c)+"A"+n+","+n+",0,1,"+u+","+(this._x1=s)+","+(this._y1=l):f>1e-6&&(this._+="A"+n+","+n+",0,"+ +(f>=ya)+","+u+","+(this._x1=e+n*Math.cos(o))+","+(this._y1=t+n*Math.sin(o))))},rect:function(e,t,n,r){this._+="M"+(this._x0=this._x1=+e)+","+(this._y0=this._y1=+t)+"h"+ +n+"v"+ +r+"h"+-n+"Z"},toString:function(){return this._}};var ja=xa;function Ma(e){return e.source}function Ca(e){return e.target}function _a(e){return e.radius}function qa(e){return e.startAngle}function Sa(e){return e.endAngle}var Oa=function(){var e=Ma,t=Ca,n=_a,r=qa,o=Sa,i=null;function a(){var a,c=ga.call(arguments),s=e.apply(this,c),l=t.apply(this,c),u=+n.apply(this,(c[0]=s,c)),f=r.apply(this,c)-da,d=o.apply(this,c)-da,h=u*la(f),p=u*ua(f),z=+n.apply(this,(c[0]=l,c)),v=r.apply(this,c)-da,g=o.apply(this,c)-da;if(i||(i=a=ja()),i.moveTo(h,p),i.arc(0,0,u,f,d),f===v&&d===g||(i.quadraticCurveTo(0,0,z*la(v),z*ua(v)),i.arc(0,0,z,v,g)),i.quadraticCurveTo(0,0,h,p),i.closePath(),a)return i=null,a+""||null}return a.radius=function(e){return arguments.length?(n="function"===typeof e?e:ma(+e),a):n},a.startAngle=function(e){return arguments.length?(r="function"===typeof e?e:ma(+e),a):r},a.endAngle=function(e){return arguments.length?(o="function"===typeof e?e:ma(+e),a):o},a.source=function(t){return arguments.length?(e=t,a):e},a.target=function(e){return arguments.length?(t=e,a):t},a.context=function(e){return arguments.length?(i=null==e?null:e,a):i},a};function Ea(){}function Ta(e,t){var n=new Ea;if(e instanceof Ea)e.each((function(e,t){n.set(t,e)}));else if(Array.isArray(e)){var r,o=-1,i=e.length;if(null==t)for(;++o=r.length)return null!=e&&n.sort(e),null!=t?t(n):n;for(var s,l,u,f=-1,d=n.length,h=r[o++],p=Aa(),z=a();++fr.length)return n;var a,c=o[i-1];return null!=t&&i>=r.length?a=n.entries():(a=[],n.each((function(t,n){a.push({key:n,values:e(t,i)})}))),null!=c?a.sort((function(e,t){return c(e.key,t.key)})):a}(i(e,0,Pa,Va),0)},key:function(e){return r.push(e),n},sortKeys:function(e){return o[r.length-1]=e,n},sortValues:function(t){return e=t,n},rollup:function(e){return t=e,n}}};function Ha(){return{}}function Da(e,t,n){e[t]=n}function Pa(){return Aa()}function Va(e,t,n){e.set(t,n)}function Ra(){}var Ia=Aa.prototype;function Na(e,t){var n=new Ra;if(e instanceof Ra)e.each((function(e){n.add(e)}));else if(e){var r=-1,o=e.length;if(null==t)for(;++r=i?s=!0:(r=e.charCodeAt(a++))===Za?l=!0:r===Xa&&(l=!0,e.charCodeAt(a)===Za&&++a),e.slice(o+1,t-1).replace(/""/g,'"')}for(;a=(i=(z+g)/2))?z=i:g=i,(u=n>=(a=(v+m)/2))?v=a:m=a,o=h,!(h=h[f=u<<1|l]))return o[f]=p,e;if(c=+e._x.call(null,h.data),s=+e._y.call(null,h.data),t===c&&n===s)return p.next=h,o?o[f]=p:e._root=p,e;do{o=o?o[f]=new Array(4):e._root=new Array(4),(l=t>=(i=(z+g)/2))?z=i:g=i,(u=n>=(a=(v+m)/2))?v=a:m=a}while((f=u<<1|l)===(d=(s>=a)<<1|c>=i));return o[d]=h,o[f]=p,e}var hc=function(e,t,n,r,o){this.node=e,this.x0=t,this.y0=n,this.x1=r,this.y1=o};function pc(e){return e[0]}function zc(e){return e[1]}function vc(e,t,n){var r=new gc(null==t?pc:t,null==n?zc:n,NaN,NaN,NaN,NaN);return null==e?r:r.addAll(e)}function gc(e,t,n,r,o,i){this._x=e,this._y=t,this._x0=n,this._y0=r,this._x1=o,this._y1=i,this._root=void 0}function mc(e){for(var t={data:e.data},n=t;e=e.next;)n=n.next={data:e.data};return t}var yc=vc.prototype=gc.prototype;function bc(e){return e.x+e.vx}function wc(e){return e.y+e.vy}yc.copy=function(){var e,t,n=new gc(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return n;if(!r.length)return n._root=mc(r),n;for(e=[{source:r,target:n._root=new Array(4)}];r=e.pop();)for(var o=0;o<4;++o)(t=r.source[o])&&(t.length?e.push({source:t,target:r.target[o]=new Array(4)}):r.target[o]=mc(t));return n},yc.add=function(e){var t=+this._x.call(null,e),n=+this._y.call(null,e);return dc(this.cover(t,n),t,n,e)},yc.addAll=function(e){var t,n,r,o,i=e.length,a=new Array(i),c=new Array(i),s=1/0,l=1/0,u=-1/0,f=-1/0;for(n=0;nu&&(u=r),of&&(f=o));for(ue||e>o||r>t||t>i))return this;var a,c,s=o-n,l=this._root;switch(c=(t<(r+i)/2)<<1|e<(n+o)/2){case 0:do{(a=new Array(4))[c]=l,l=a}while(i=r+(s*=2),e>(o=n+s)||t>i);break;case 1:do{(a=new Array(4))[c]=l,l=a}while(i=r+(s*=2),(n=o-s)>e||t>i);break;case 2:do{(a=new Array(4))[c]=l,l=a}while(r=i-(s*=2),e>(o=n+s)||r>t);break;case 3:do{(a=new Array(4))[c]=l,l=a}while(r=i-(s*=2),(n=o-s)>e||r>t)}this._root&&this._root.length&&(this._root=l)}return this._x0=n,this._y0=r,this._x1=o,this._y1=i,this},yc.data=function(){var e=[];return this.visit((function(t){if(!t.length)do{e.push(t.data)}while(t=t.next)})),e},yc.extent=function(e){return arguments.length?this.cover(+e[0][0],+e[0][1]).cover(+e[1][0],+e[1][1]):isNaN(this._x0)?void 0:[[this._x0,this._y0],[this._x1,this._y1]]},yc.find=function(e,t,n){var r,o,i,a,c,s,l,u=this._x0,f=this._y0,d=this._x1,h=this._y1,p=[],z=this._root;for(z&&p.push(new hc(z,u,f,d,h)),null==n?n=1/0:(u=e-n,f=t-n,d=e+n,h=t+n,n*=n);s=p.pop();)if(!(!(z=s.node)||(o=s.x0)>d||(i=s.y0)>h||(a=s.x1)=g)<<1|e>=v)&&(s=p[p.length-1],p[p.length-1]=p[p.length-1-l],p[p.length-1-l]=s)}else{var m=e-+this._x.call(null,z.data),y=t-+this._y.call(null,z.data),b=m*m+y*y;if(b=(c=(p+v)/2))?p=c:v=c,(u=a>=(s=(z+g)/2))?z=s:g=s,t=h,!(h=h[f=u<<1|l]))return this;if(!h.length)break;(t[f+1&3]||t[f+2&3]||t[f+3&3])&&(n=t,d=f)}for(;h.data!==e;)if(r=h,!(h=h.next))return this;return(o=h.next)&&delete h.next,r?(o?r.next=o:delete r.next,this):t?(o?t[f]=o:delete t[f],(h=t[0]||t[1]||t[2]||t[3])&&h===(t[3]||t[2]||t[1]||t[0])&&!h.length&&(n?n[d]=h:this._root=h),this):(this._root=o,this)},yc.removeAll=function(e){for(var t=0,n=e.length;ts+h||ol+h||ic.index){var p=s-a.x-a.vx,z=l-a.y-a.vy,v=p*p+z*z;ve.r&&(e.r=e[t].r)}function c(){if(t){var r,o,i=t.length;for(n=new Array(i),r=0;r1?(null==n?c.remove(e):c.set(e,h(n)),t):c.get(e)},find:function(t,n,r){var o,i,a,c,s,l=0,u=e.length;for(null==r?r=1/0:r*=r,l=0;l1?(l.on(e,n),t):l.on(e)}}},Tc=function(){var e,t,n,r,o=uc(-30),i=1,a=1/0,c=.81;function s(r){var o,i=e.length,a=vc(e,Cc,_c).visitAfter(u);for(n=r,o=0;o=a)){(e.data!==t||e.next)&&(0===u&&(h+=(u=fc())*u),0===f&&(h+=(f=fc())*f),h1?r[0]+r.slice(2):r,+e.slice(n+1)]},Pc=function(e){return(e=Dc(Math.abs(e)))?e[1]:NaN},Vc=function(e,t){var n=Dc(e,t);if(!n)return e+"";var r=n[0],o=n[1];return o<0?"0."+new Array(-o).join("0")+r:r.length>o+1?r.slice(0,o+1)+"."+r.slice(o+1):r+new Array(o-r.length+2).join("0")},Rc={"":function(e,t){e:for(var n,r=(e=e.toPrecision(t)).length,o=1,i=-1;o0&&(i=0)}return i>0?e.slice(0,i)+e.slice(n+1):e},"%":function(e,t){return(100*e).toFixed(t)},b:function(e){return Math.round(e).toString(2)},c:function(e){return e+""},d:function(e){return Math.round(e).toString(10)},e:function(e,t){return e.toExponential(t)},f:function(e,t){return e.toFixed(t)},g:function(e,t){return e.toPrecision(t)},o:function(e){return Math.round(e).toString(8)},p:function(e,t){return Vc(100*e,t)},r:Vc,s:function(e,t){var n=Dc(e,t);if(!n)return e+"";var r=n[0],o=n[1],i=o-(qc=3*Math.max(-8,Math.min(8,Math.floor(o/3))))+1,a=r.length;return i===a?r:i>a?r+new Array(i-a+1).join("0"):i>0?r.slice(0,i)+"."+r.slice(i):"0."+new Array(1-i).join("0")+Dc(e,Math.max(0,t+i-1))[0]},X:function(e){return Math.round(e).toString(16).toUpperCase()},x:function(e){return Math.round(e).toString(16)}},Ic=/^(?:(.)?([<>=^]))?([+\-\( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?([a-z%])?$/i;function Nc(e){return new Fc(e)}function Fc(e){if(!(t=Ic.exec(e)))throw new Error("invalid format: "+e);var t,n=t[1]||" ",r=t[2]||">",o=t[3]||"-",i=t[4]||"",a=!!t[5],c=t[6]&&+t[6],s=!!t[7],l=t[8]&&+t[8].slice(1),u=t[9]||"";"n"===u?(s=!0,u="g"):Rc[u]||(u=""),(a||"0"===n&&"="===r)&&(a=!0,n="0",r="="),this.fill=n,this.align=r,this.sign=o,this.symbol=i,this.zero=a,this.width=c,this.comma=s,this.precision=l,this.type=u}Nc.prototype=Fc.prototype,Fc.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(null==this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(null==this.precision?"":"."+Math.max(0,0|this.precision))+this.type};var Bc,Uc,Wc,Gc=function(e){return e},Yc=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"],$c=function(e){var t,n,r=e.grouping&&e.thousands?(t=e.grouping,n=e.thousands,function(e,r){for(var o=e.length,i=[],a=0,c=t[0],s=0;o>0&&c>0&&(s+c+1>r&&(c=Math.max(1,r-s)),i.push(e.substring(o-=c,o+c)),!((s+=c+1)>r));)c=t[a=(a+1)%t.length];return i.reverse().join(n)}):Gc,o=e.currency,i=e.decimal,a=e.numerals?function(e){return function(t){return t.replace(/[0-9]/g,(function(t){return e[+t]}))}}(e.numerals):Gc,c=e.percent||"%";function s(e){var t=(e=Nc(e)).fill,n=e.align,s=e.sign,l=e.symbol,u=e.zero,f=e.width,d=e.comma,h=e.precision,p=e.type,z="$"===l?o[0]:"#"===l&&/[boxX]/.test(p)?"0"+p.toLowerCase():"",v="$"===l?o[1]:/[%p]/.test(p)?c:"",g=Rc[p],m=!p||/[defgprs%]/.test(p);function y(e){var o,c,l,y=z,b=v;if("c"===p)b=g(e)+b,e="";else{var w=(e=+e)<0;if(e=g(Math.abs(e),h),w&&0===+e&&(w=!1),y=(w?"("===s?s:"-":"-"===s||"("===s?"":s)+y,b=b+("s"===p?Yc[8+qc/3]:"")+(w&&"("===s?")":""),m)for(o=-1,c=e.length;++o(l=e.charCodeAt(o))||l>57){b=(46===l?i+e.slice(o+1):e.slice(o))+b,e=e.slice(0,o);break}}d&&!u&&(e=r(e,1/0));var k=y.length+e.length+b.length,x=k>1)+y+e+b+x.slice(k);break;default:e=x+y+e+b}return a(e)}return h=null==h?p?6:12:/[gprs]/.test(p)?Math.max(1,Math.min(21,h)):Math.max(0,Math.min(20,h)),y.toString=function(){return e+""},y}return{format:s,formatPrefix:function(e,t){var n=s(((e=Nc(e)).type="f",e)),r=3*Math.max(-8,Math.min(8,Math.floor(Pc(t)/3))),o=Math.pow(10,-r),i=Yc[8+r/3];return function(e){return n(o*e)+i}}}};function Zc(e){return Bc=$c(e),Uc=Bc.format,Wc=Bc.formatPrefix,Bc}Zc({decimal:".",thousands:",",grouping:[3],currency:["$",""]});var Xc=function(e){return Math.max(0,-Pc(Math.abs(e)))},Kc=function(e,t){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(Pc(t)/3)))-Pc(Math.abs(e)))},Qc=function(e,t){return e=Math.abs(e),t=Math.abs(t)-e,Math.max(0,Pc(t)-Pc(e))+1},Jc=function(){return new es};function es(){this.reset()}es.prototype={constructor:es,reset:function(){this.s=this.t=0},add:function(e){ns(ts,e,this.t),ns(this,ts.s,this.s),this.s?this.t+=ts.t:this.s=ts.t},valueOf:function(){return this.s}};var ts=new es;function ns(e,t,n){var r=e.s=t+n,o=r-t,i=r-o;e.t=t-i+(n-o)}var rs=1e-6,os=Math.PI,is=os/2,as=os/4,cs=2*os,ss=180/os,ls=os/180,us=Math.abs,fs=Math.atan,ds=Math.atan2,hs=Math.cos,ps=Math.ceil,zs=Math.exp,vs=(Math.floor,Math.log),gs=Math.pow,ms=Math.sin,ys=Math.sign||function(e){return e>0?1:e<0?-1:0},bs=Math.sqrt,ws=Math.tan;function ks(e){return e>1?0:e<-1?os:Math.acos(e)}function xs(e){return e>1?is:e<-1?-is:Math.asin(e)}function js(e){return(e=ms(e/2))*e}function Ms(){}function Cs(e,t){e&&qs.hasOwnProperty(e.type)&&qs[e.type](e,t)}var _s={Feature:function(e,t){Cs(e.geometry,t)},FeatureCollection:function(e,t){for(var n=e.features,r=-1,o=n.length;++r=0?1:-1,o=r*n,i=hs(t=(t*=ls)/2+as),a=ms(t),c=Hs*a,s=Ls*i+c*hs(o),l=c*r*ms(o);Ps.add(ds(l,s)),As=e,Ls=i,Hs=a}var Us=function(e){return Vs.reset(),Ds(e,Rs),2*Vs};function Ws(e){return[ds(e[1],e[0]),xs(e[2])]}function Gs(e){var t=e[0],n=e[1],r=hs(n);return[r*hs(t),r*ms(t),ms(n)]}function Ys(e,t){return e[0]*t[0]+e[1]*t[1]+e[2]*t[2]}function $s(e,t){return[e[1]*t[2]-e[2]*t[1],e[2]*t[0]-e[0]*t[2],e[0]*t[1]-e[1]*t[0]]}function Zs(e,t){e[0]+=t[0],e[1]+=t[1],e[2]+=t[2]}function Xs(e,t){return[e[0]*t,e[1]*t,e[2]*t]}function Ks(e){var t=bs(e[0]*e[0]+e[1]*e[1]+e[2]*e[2]);e[0]/=t,e[1]/=t,e[2]/=t}var Qs,Js,el,tl,nl,rl,ol,il,al,cl,sl=Jc(),ll={point:ul,lineStart:dl,lineEnd:hl,polygonStart:function(){ll.point=pl,ll.lineStart=zl,ll.lineEnd=vl,sl.reset(),Rs.polygonStart()},polygonEnd:function(){Rs.polygonEnd(),ll.point=ul,ll.lineStart=dl,ll.lineEnd=hl,Ps<0?(Qs=-(el=180),Js=-(tl=90)):sl>rs?tl=90:sl<-rs&&(Js=-90),cl[0]=Qs,cl[1]=el}};function ul(e,t){al.push(cl=[Qs=e,el=e]),ttl&&(tl=t)}function fl(e,t){var n=Gs([e*ls,t*ls]);if(il){var r=$s(il,n),o=$s([r[1],-r[0],0],r);Ks(o),o=Ws(o);var i,a=e-nl,c=a>0?1:-1,s=o[0]*ss*c,l=us(a)>180;l^(c*nltl&&(tl=i):l^(c*nl<(s=(s+360)%360-180)&&stl&&(tl=t)),l?egl(Qs,el)&&(el=e):gl(e,el)>gl(Qs,el)&&(Qs=e):el>=Qs?(eel&&(el=e)):e>nl?gl(Qs,e)>gl(Qs,el)&&(el=e):gl(e,el)>gl(Qs,el)&&(Qs=e)}else al.push(cl=[Qs=e,el=e]);ttl&&(tl=t),il=n,nl=e}function dl(){ll.point=fl}function hl(){cl[0]=Qs,cl[1]=el,ll.point=ul,il=null}function pl(e,t){if(il){var n=e-nl;sl.add(us(n)>180?n+(n>0?360:-360):n)}else rl=e,ol=t;Rs.point(e,t),fl(e,t)}function zl(){Rs.lineStart()}function vl(){pl(rl,ol),Rs.lineEnd(),us(sl)>rs&&(Qs=-(el=180)),cl[0]=Qs,cl[1]=el,il=null}function gl(e,t){return(t-=e)<0?t+360:t}function ml(e,t){return e[0]-t[0]}function yl(e,t){return e[0]<=e[1]?e[0]<=t&&t<=e[1]:tgl(r[0],r[1])&&(r[1]=o[1]),gl(o[0],r[1])>gl(r[0],r[1])&&(r[0]=o[0])):i.push(r=o);for(a=-1/0,t=0,r=i[n=i.length-1];t<=n;r=o,++t)o=i[t],(c=gl(r[1],o[0]))>a&&(a=c,Qs=o[0],el=r[1])}return al=cl=null,Qs===1/0||Js===1/0?[[NaN,NaN],[NaN,NaN]]:[[Qs,Js],[el,tl]]},Pl={sphere:Ms,point:Vl,lineStart:Il,lineEnd:Bl,polygonStart:function(){Pl.lineStart=Ul,Pl.lineEnd=Wl},polygonEnd:function(){Pl.lineStart=Il,Pl.lineEnd=Bl}};function Vl(e,t){e*=ls;var n=hs(t*=ls);Rl(n*hs(e),n*ms(e),ms(t))}function Rl(e,t,n){++bl,kl+=(e-kl)/bl,xl+=(t-xl)/bl,jl+=(n-jl)/bl}function Il(){Pl.point=Nl}function Nl(e,t){e*=ls;var n=hs(t*=ls);Al=n*hs(e),Ll=n*ms(e),Hl=ms(t),Pl.point=Fl,Rl(Al,Ll,Hl)}function Fl(e,t){e*=ls;var n=hs(t*=ls),r=n*hs(e),o=n*ms(e),i=ms(t),a=ds(bs((a=Ll*i-Hl*o)*a+(a=Hl*r-Al*i)*a+(a=Al*o-Ll*r)*a),Al*r+Ll*o+Hl*i);wl+=a,Ml+=a*(Al+(Al=r)),Cl+=a*(Ll+(Ll=o)),_l+=a*(Hl+(Hl=i)),Rl(Al,Ll,Hl)}function Bl(){Pl.point=Vl}function Ul(){Pl.point=Gl}function Wl(){Yl(El,Tl),Pl.point=Vl}function Gl(e,t){El=e,Tl=t,e*=ls,t*=ls,Pl.point=Yl;var n=hs(t);Al=n*hs(e),Ll=n*ms(e),Hl=ms(t),Rl(Al,Ll,Hl)}function Yl(e,t){e*=ls;var n=hs(t*=ls),r=n*hs(e),o=n*ms(e),i=ms(t),a=Ll*i-Hl*o,c=Hl*r-Al*i,s=Al*o-Ll*r,l=bs(a*a+c*c+s*s),u=xs(l),f=l&&-u/l;ql+=f*a,Sl+=f*c,Ol+=f*s,wl+=u,Ml+=u*(Al+(Al=r)),Cl+=u*(Ll+(Ll=o)),_l+=u*(Hl+(Hl=i)),Rl(Al,Ll,Hl)}var $l=function(e){bl=wl=kl=xl=jl=Ml=Cl=_l=ql=Sl=Ol=0,Ds(e,Pl);var t=ql,n=Sl,r=Ol,o=t*t+n*n+r*r;return o<1e-12&&(t=Ml,n=Cl,r=_l,wlos?e-cs:e<-os?e+cs:e,t]}function Ql(e,t,n){return(e%=cs)?t||n?Xl(eu(e),tu(t,n)):eu(e):t||n?tu(t,n):Kl}function Jl(e){return function(t,n){return[(t+=e)>os?t-cs:t<-os?t+cs:t,n]}}function eu(e){var t=Jl(e);return t.invert=Jl(-e),t}function tu(e,t){var n=hs(e),r=ms(e),o=hs(t),i=ms(t);function a(e,t){var a=hs(t),c=hs(e)*a,s=ms(e)*a,l=ms(t),u=l*n+c*r;return[ds(s*o-u*i,c*n-l*r),xs(u*o+s*i)]}return a.invert=function(e,t){var a=hs(t),c=hs(e)*a,s=ms(e)*a,l=ms(t),u=l*o-s*i;return[ds(s*o+l*i,c*n+u*r),xs(u*n-c*r)]},a}Kl.invert=Kl;var nu=function(e){function t(t){return(t=e(t[0]*ls,t[1]*ls))[0]*=ss,t[1]*=ss,t}return e=Ql(e[0]*ls,e[1]*ls,e.length>2?e[2]*ls:0),t.invert=function(t){return(t=e.invert(t[0]*ls,t[1]*ls))[0]*=ss,t[1]*=ss,t},t};function ru(e,t,n,r,o,i){if(n){var a=hs(t),c=ms(t),s=r*n;null==o?(o=t+r*cs,i=t-s/2):(o=ou(a,o),i=ou(a,i),(r>0?oi)&&(o+=r*cs));for(var l,u=o;r>0?u>i:u1&&t.push(t.pop().concat(t.shift()))},result:function(){var n=t;return t=[],e=null,n}}},cu=function(e,t){return us(e[0]-t[0])=0;--i)o.point((u=l[i])[0],u[1]);else r(d.x,d.p.x,-1,o);d=d.p}l=(d=d.o).z,h=!h}while(!d.v);o.lineEnd()}}};function uu(e){if(t=e.length){for(var t,n,r=0,o=e[0];++r=0?1:-1,j=x*k,M=j>os,C=p*b;if(fu.add(ds(C*x*ms(j),z*w+C*hs(j))),i+=M?k+x*cs:k,M^d>=n^m>=n){var _=$s(Gs(f),Gs(g));Ks(_);var q=$s(o,_);Ks(q);var S=(M^k>=0?-1:1)*xs(q[2]);(r>S||r===S&&(_[0]||_[1]))&&(a+=M^k>=0?1:-1)}}return(i<-rs||i0){for(f||(o.polygonStart(),f=!0),o.lineStart(),e=0;e1&&2&s&&d.push(d.pop().concat(d.shift())),a.push(d.filter(pu))}return d}};function pu(e){return e.length>1}function zu(e,t){return((e=e.x)[0]<0?e[1]-is-rs:is-e[1])-((t=t.x)[0]<0?t[1]-is-rs:is-t[1])}var vu=hu((function(){return!0}),(function(e){var t,n=NaN,r=NaN,o=NaN;return{lineStart:function(){e.lineStart(),t=1},point:function(i,a){var c=i>0?os:-os,s=us(i-n);us(s-os)0?is:-is),e.point(o,r),e.lineEnd(),e.lineStart(),e.point(c,r),e.point(i,r),t=0):o!==c&&s>=os&&(us(n-o)rs?fs((ms(t)*(i=hs(r))*ms(n)-ms(r)*(o=hs(t))*ms(e))/(o*i*a)):(t+r)/2}(n,r,i,a),e.point(o,r),e.lineEnd(),e.lineStart(),e.point(c,r),t=0),e.point(n=i,r=a),o=c},lineEnd:function(){e.lineEnd(),n=r=NaN},clean:function(){return 2-t}}}),(function(e,t,n,r){var o;if(null==e)o=n*is,r.point(-os,o),r.point(0,o),r.point(os,o),r.point(os,0),r.point(os,-o),r.point(0,-o),r.point(-os,-o),r.point(-os,0),r.point(-os,o);else if(us(e[0]-t[0])>rs){var i=e[0]0,o=us(t)>rs;function i(e,n){return hs(e)*hs(n)>t}function a(e,n,r){var o=[1,0,0],i=$s(Gs(e),Gs(n)),a=Ys(i,i),c=i[0],s=a-c*c;if(!s)return!r&&e;var l=t*a/s,u=-t*c/s,f=$s(o,i),d=Xs(o,l);Zs(d,Xs(i,u));var h=f,p=Ys(d,h),z=Ys(h,h),v=p*p-z*(Ys(d,d)-1);if(!(v<0)){var g=bs(v),m=Xs(h,(-p-g)/z);if(Zs(m,d),m=Ws(m),!r)return m;var y,b=e[0],w=n[0],k=e[1],x=n[1];w0^m[1]<(us(m[0]-b)os^(b<=m[0]&&m[0]<=w)){var C=Xs(h,(-p+g)/z);return Zs(C,d),[m,Ws(C)]}}}function c(t,n){var o=r?e:os-e,i=0;return t<-o?i|=1:t>o&&(i|=2),n<-o?i|=4:n>o&&(i|=8),i}return hu(i,(function(e){var t,n,s,l,u;return{lineStart:function(){l=s=!1,u=1},point:function(f,d){var h,p=[f,d],z=i(f,d),v=r?z?0:c(f,d):z?c(f+(f<0?os:-os),d):0;if(!t&&(l=s=z)&&e.lineStart(),z!==s&&(!(h=a(t,p))||cu(t,h)||cu(p,h))&&(p[0]+=rs,p[1]+=rs,z=i(p[0],p[1])),z!==s)u=0,z?(e.lineStart(),h=a(p,t),e.point(h[0],h[1])):(h=a(t,p),e.point(h[0],h[1]),e.lineEnd()),t=h;else if(o&&t&&r^z){var g;v&n||!(g=a(p,t,!0))||(u=0,r?(e.lineStart(),e.point(g[0][0],g[0][1]),e.point(g[1][0],g[1][1]),e.lineEnd()):(e.point(g[1][0],g[1][1]),e.lineEnd(),e.lineStart(),e.point(g[0][0],g[0][1])))}!z||t&&cu(t,p)||e.point(p[0],p[1]),t=p,s=z,n=v},lineEnd:function(){s&&e.lineEnd(),t=null},clean:function(){return u|(l&&s)<<1}}}),(function(t,r,o,i){ru(i,e,n,o,t,r)}),r?[0,-e]:[-os,e-os])},mu=function(e,t,n,r,o,i){var a,c=e[0],s=e[1],l=0,u=1,f=t[0]-c,d=t[1]-s;if(a=n-c,f||!(a>0)){if(a/=f,f<0){if(a0){if(a>u)return;a>l&&(l=a)}if(a=o-c,f||!(a<0)){if(a/=f,f<0){if(a>u)return;a>l&&(l=a)}else if(f>0){if(a0)){if(a/=d,d<0){if(a0){if(a>u)return;a>l&&(l=a)}if(a=i-s,d||!(a<0)){if(a/=d,d<0){if(a>u)return;a>l&&(l=a)}else if(d>0){if(a0&&(e[0]=c+l*f,e[1]=s+l*d),u<1&&(t[0]=c+u*f,t[1]=s+u*d),!0}}}}},yu=1e9,bu=-yu;function wu(e,t,n,r){function o(o,i){return e<=o&&o<=n&&t<=i&&i<=r}function i(o,i,c,l){var u=0,f=0;if(null==o||(u=a(o,c))!==(f=a(i,c))||s(o,i)<0^c>0)do{l.point(0===u||3===u?e:n,u>1?r:t)}while((u=(u+c+4)%4)!==f);else l.point(i[0],i[1])}function a(r,o){return us(r[0]-e)0?0:3:us(r[0]-n)0?2:1:us(r[1]-t)0?1:0:o>0?3:2}function c(e,t){return s(e.x,t.x)}function s(e,t){var n=a(e,1),r=a(t,1);return n!==r?n-r:0===n?t[1]-e[1]:1===n?e[0]-t[0]:2===n?e[1]-t[1]:t[0]-e[0]}return function(a){var s,l,u,f,d,h,p,z,v,g,m,y=a,b=au(),w={point:k,lineStart:function(){w.point=x,l&&l.push(u=[]);g=!0,v=!1,p=z=NaN},lineEnd:function(){s&&(x(f,d),h&&v&&b.rejoin(),s.push(b.result()));w.point=k,v&&y.lineEnd()},polygonStart:function(){y=b,s=[],l=[],m=!0},polygonEnd:function(){var t=function(){for(var t=0,n=0,o=l.length;nr&&(d-i)*(r-a)>(h-a)*(e-i)&&++t:h<=r&&(d-i)*(r-a)<(h-a)*(e-i)&&--t;return t}(),n=m&&t,o=(s=P(s)).length;(n||o)&&(a.polygonStart(),n&&(a.lineStart(),i(null,null,1,a),a.lineEnd()),o&&lu(s,c,t,i,a),a.polygonEnd());y=a,s=l=u=null}};function k(e,t){o(e,t)&&y.point(e,t)}function x(i,a){var c=o(i,a);if(l&&u.push([i,a]),g)f=i,d=a,h=c,g=!1,c&&(y.lineStart(),y.point(i,a));else if(c&&v)y.point(i,a);else{var s=[p=Math.max(bu,Math.min(yu,p)),z=Math.max(bu,Math.min(yu,z))],b=[i=Math.max(bu,Math.min(yu,i)),a=Math.max(bu,Math.min(yu,a))];mu(s,b,e,t,n,r)?(v||(y.lineStart(),y.point(s[0],s[1])),y.point(b[0],b[1]),c||y.lineEnd(),m=!1):c&&(y.lineStart(),y.point(i,a),m=!1)}p=i,z=a,v=c}return w}}var ku,xu,ju,Mu=function(){var e,t,n,r=0,o=0,i=960,a=500;return n={stream:function(n){return e&&t===n?e:e=wu(r,o,i,a)(t=n)},extent:function(c){return arguments.length?(r=+c[0][0],o=+c[0][1],i=+c[1][0],a=+c[1][1],e=t=null,n):[[r,o],[i,a]]}}},Cu=Jc(),_u={sphere:Ms,point:Ms,lineStart:function(){_u.point=Su,_u.lineEnd=qu},lineEnd:Ms,polygonStart:Ms,polygonEnd:Ms};function qu(){_u.point=_u.lineEnd=Ms}function Su(e,t){ku=e*=ls,xu=ms(t*=ls),ju=hs(t),_u.point=Ou}function Ou(e,t){e*=ls;var n=ms(t*=ls),r=hs(t),o=us(e-ku),i=hs(o),a=r*ms(o),c=ju*n-xu*r*i,s=xu*n+ju*r*i;Cu.add(ds(bs(a*a+c*c),s)),ku=e,xu=n,ju=r}var Eu=function(e){return Cu.reset(),Ds(e,_u),+Cu},Tu=[null,null],Au={type:"LineString",coordinates:Tu},Lu=function(e,t){return Tu[0]=e,Tu[1]=t,Eu(Au)},Hu={Feature:function(e,t){return Pu(e.geometry,t)},FeatureCollection:function(e,t){for(var n=e.features,r=-1,o=n.length;++rrs})).map(s)).concat(k(ps(i/h)*h,o,h).filter((function(e){return us(e%z)>rs})).map(l))}return g.lines=function(){return m().map((function(e){return{type:"LineString",coordinates:e}}))},g.outline=function(){return{type:"Polygon",coordinates:[u(r).concat(f(a).slice(1),u(n).reverse().slice(1),f(c).reverse().slice(1))]}},g.extent=function(e){return arguments.length?g.extentMajor(e).extentMinor(e):g.extentMinor()},g.extentMajor=function(e){return arguments.length?(r=+e[0][0],n=+e[1][0],c=+e[0][1],a=+e[1][1],r>n&&(e=r,r=n,n=e),c>a&&(e=c,c=a,a=e),g.precision(v)):[[r,c],[n,a]]},g.extentMinor=function(n){return arguments.length?(t=+n[0][0],e=+n[1][0],i=+n[0][1],o=+n[1][1],t>e&&(n=t,t=e,e=n),i>o&&(n=i,i=o,o=n),g.precision(v)):[[t,i],[e,o]]},g.step=function(e){return arguments.length?g.stepMajor(e).stepMinor(e):g.stepMinor()},g.stepMajor=function(e){return arguments.length?(p=+e[0],z=+e[1],g):[p,z]},g.stepMinor=function(e){return arguments.length?(d=+e[0],h=+e[1],g):[d,h]},g.precision=function(d){return arguments.length?(v=+d,s=Uu(i,o,90),l=Wu(t,e,v),u=Uu(c,a,90),f=Wu(r,n,v),g):v},g.extentMajor([[-180,-90+rs],[180,90-rs]]).extentMinor([[-180,-80-rs],[180,80+rs]])}function Yu(){return Gu()()}var $u,Zu,Xu,Ku,Qu=function(e,t){var n=e[0]*ls,r=e[1]*ls,o=t[0]*ls,i=t[1]*ls,a=hs(r),c=ms(r),s=hs(i),l=ms(i),u=a*hs(n),f=a*ms(n),d=s*hs(o),h=s*ms(o),p=2*xs(bs(js(i-r)+a*s*js(o-n))),z=ms(p),v=p?function(e){var t=ms(e*=p)/z,n=ms(p-e)/z,r=n*u+t*d,o=n*f+t*h,i=n*c+t*l;return[ds(o,r)*ss,ds(i,bs(r*r+o*o))*ss]}:function(){return[n*ss,r*ss]};return v.distance=p,v},Ju=function(e){return e},ef=Jc(),tf=Jc(),nf={point:Ms,lineStart:Ms,lineEnd:Ms,polygonStart:function(){nf.lineStart=rf,nf.lineEnd=cf},polygonEnd:function(){nf.lineStart=nf.lineEnd=nf.point=Ms,ef.add(us(tf)),tf.reset()},result:function(){var e=ef/2;return ef.reset(),e}};function rf(){nf.point=of}function of(e,t){nf.point=af,$u=Xu=e,Zu=Ku=t}function af(e,t){tf.add(Ku*e-Xu*t),Xu=e,Ku=t}function cf(){af($u,Zu)}var sf=nf,lf=1/0,uf=lf,ff=-lf,df=ff;var hf,pf,zf,vf,gf={point:function(e,t){eff&&(ff=e);tdf&&(df=t)},lineStart:Ms,lineEnd:Ms,polygonStart:Ms,polygonEnd:Ms,result:function(){var e=[[lf,uf],[ff,df]];return ff=df=-(uf=lf=1/0),e}},mf=0,yf=0,bf=0,wf=0,kf=0,xf=0,jf=0,Mf=0,Cf=0,_f={point:qf,lineStart:Sf,lineEnd:Tf,polygonStart:function(){_f.lineStart=Af,_f.lineEnd=Lf},polygonEnd:function(){_f.point=qf,_f.lineStart=Sf,_f.lineEnd=Tf},result:function(){var e=Cf?[jf/Cf,Mf/Cf]:xf?[wf/xf,kf/xf]:bf?[mf/bf,yf/bf]:[NaN,NaN];return mf=yf=bf=wf=kf=xf=jf=Mf=Cf=0,e}};function qf(e,t){mf+=e,yf+=t,++bf}function Sf(){_f.point=Of}function Of(e,t){_f.point=Ef,qf(zf=e,vf=t)}function Ef(e,t){var n=e-zf,r=t-vf,o=bs(n*n+r*r);wf+=o*(zf+e)/2,kf+=o*(vf+t)/2,xf+=o,qf(zf=e,vf=t)}function Tf(){_f.point=qf}function Af(){_f.point=Hf}function Lf(){Df(hf,pf)}function Hf(e,t){_f.point=Df,qf(hf=zf=e,pf=vf=t)}function Df(e,t){var n=e-zf,r=t-vf,o=bs(n*n+r*r);wf+=o*(zf+e)/2,kf+=o*(vf+t)/2,xf+=o,jf+=(o=vf*e-zf*t)*(zf+e),Mf+=o*(vf+t),Cf+=3*o,qf(zf=e,vf=t)}var Pf=_f;function Vf(e){this._context=e}Vf.prototype={_radius:4.5,pointRadius:function(e){return this._radius=e,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(e,t){switch(this._point){case 0:this._context.moveTo(e,t),this._point=1;break;case 1:this._context.lineTo(e,t);break;default:this._context.moveTo(e+this._radius,t),this._context.arc(e,t,this._radius,0,cs)}},result:Ms};var Rf,If,Nf,Ff,Bf,Uf=Jc(),Wf={point:Ms,lineStart:function(){Wf.point=Gf},lineEnd:function(){Rf&&Yf(If,Nf),Wf.point=Ms},polygonStart:function(){Rf=!0},polygonEnd:function(){Rf=null},result:function(){var e=+Uf;return Uf.reset(),e}};function Gf(e,t){Wf.point=Yf,If=Ff=e,Nf=Bf=t}function Yf(e,t){Ff-=e,Bf-=t,Uf.add(bs(Ff*Ff+Bf*Bf)),Ff=e,Bf=t}var $f=Wf;function Zf(){this._string=[]}function Xf(e){return"m0,"+e+"a"+e+","+e+" 0 1,1 0,"+-2*e+"a"+e+","+e+" 0 1,1 0,"+2*e+"z"}Zf.prototype={_radius:4.5,_circle:Xf(4.5),pointRadius:function(e){return(e=+e)!==this._radius&&(this._radius=e,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(e,t){switch(this._point){case 0:this._string.push("M",e,",",t),this._point=1;break;case 1:this._string.push("L",e,",",t);break;default:null==this._circle&&(this._circle=Xf(this._radius)),this._string.push("M",e,",",t,this._circle)}},result:function(){if(this._string.length){var e=this._string.join("");return this._string=[],e}return null}};var Kf=function(e,t){var n,r,o=4.5;function i(e){return e&&("function"===typeof o&&r.pointRadius(+o.apply(this,arguments)),Ds(e,n(r))),r.result()}return i.area=function(e){return Ds(e,n(sf)),sf.result()},i.measure=function(e){return Ds(e,n($f)),$f.result()},i.bounds=function(e){return Ds(e,n(gf)),gf.result()},i.centroid=function(e){return Ds(e,n(Pf)),Pf.result()},i.projection=function(t){return arguments.length?(n=null==t?(e=null,Ju):(e=t).stream,i):e},i.context=function(e){return arguments.length?(r=null==e?(t=null,new Zf):new Vf(t=e),"function"!==typeof o&&r.pointRadius(o),i):t},i.pointRadius=function(e){return arguments.length?(o="function"===typeof e?e:(r.pointRadius(+e),+e),i):o},i.projection(e).context(t)},Qf=function(e){return{stream:Jf(e)}};function Jf(e){return function(t){var n=new ed;for(var r in e)n[r]=e[r];return n.stream=t,n}}function ed(){}function td(e,t,n){var r=e.clipExtent&&e.clipExtent();return e.scale(150).translate([0,0]),null!=r&&e.clipExtent(null),Ds(n,e.stream(gf)),t(gf.result()),null!=r&&e.clipExtent(r),e}function nd(e,t,n){return td(e,(function(n){var r=t[1][0]-t[0][0],o=t[1][1]-t[0][1],i=Math.min(r/(n[1][0]-n[0][0]),o/(n[1][1]-n[0][1])),a=+t[0][0]+(r-i*(n[1][0]+n[0][0]))/2,c=+t[0][1]+(o-i*(n[1][1]+n[0][1]))/2;e.scale(150*i).translate([a,c])}),n)}function rd(e,t,n){return nd(e,[[0,0],t],n)}function od(e,t,n){return td(e,(function(n){var r=+t,o=r/(n[1][0]-n[0][0]),i=(r-o*(n[1][0]+n[0][0]))/2,a=-o*n[0][1];e.scale(150*o).translate([i,a])}),n)}function id(e,t,n){return td(e,(function(n){var r=+t,o=r/(n[1][1]-n[0][1]),i=-o*n[0][0],a=(r-o*(n[1][1]+n[0][1]))/2;e.scale(150*o).translate([i,a])}),n)}ed.prototype={constructor:ed,point:function(e,t){this.stream.point(e,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var ad=16,cd=hs(30*ls),sd=function(e,t){return+t?function(e,t){function n(r,o,i,a,c,s,l,u,f,d,h,p,z,v){var g=l-r,m=u-o,y=g*g+m*m;if(y>4*t&&z--){var b=a+d,w=c+h,k=s+p,x=bs(b*b+w*w+k*k),j=xs(k/=x),M=us(us(k)-1)t||us((g*S+m*O)/y-.5)>.3||a*d+c*h+s*p2?e[2]%360*ls:0,q()):[v*ss,g*ss,m*ss]},M.precision=function(e){return arguments.length?(j=sd(_,x=e*e),S()):bs(x)},M.fitExtent=function(e,t){return nd(M,e,t)},M.fitSize=function(e,t){return rd(M,e,t)},M.fitWidth=function(e,t){return od(M,e,t)},M.fitHeight=function(e,t){return id(M,e,t)},function(){return t=e.apply(this,arguments),M.invert=t.invert&&C,q()}}function dd(e){var t=0,n=os/3,r=fd(e),o=r(t,n);return o.parallels=function(e){return arguments.length?r(t=e[0]*ls,n=e[1]*ls):[t*ss,n*ss]},o}function hd(e,t){var n=ms(e),r=(n+ms(t))/2;if(us(r)=.12&&o<.234&&r>=-.425&&r<-.214?c:o>=.166&&o<.234&&r>=-.214&&r<-.115?s:a).invert(e)},u.stream=function(n){return e&&t===n?e:e=function(e){var t=e.length;return{point:function(n,r){for(var o=-1;++o0?t<-is+rs&&(t=-is+rs):t>is-rs&&(t=is-rs);var n=o/gs(Cd(t),r);return[n*ms(r*e),o-n*hs(r*e)]}return i.invert=function(e,t){var n=o-t,i=ys(r)*bs(e*e+n*n);return[ds(e,us(n))/r*ys(n),2*fs(gs(o/i,1/r))-is]},i}var qd=function(){return dd(_d).scale(109.5).parallels([30,30])};function Sd(e,t){return[e,t]}Sd.invert=Sd;var Od=function(){return ud(Sd).scale(152.63)};function Ed(e,t){var n=hs(e),r=e===t?ms(e):(n-hs(t))/(t-e),o=n/r+e;if(us(r)rs&&--o>0);return[e/(.8707+(i=r*r)*(i*(i*i*i*(.003971-.001529*i)-.013791)-.131979)),r]};var Vd=function(){return ud(Pd).scale(175.295)};function Rd(e,t){return[hs(t)*ms(e),ms(t)]}Rd.invert=md(xs);var Id=function(){return ud(Rd).scale(249.5).clipAngle(90+rs)};function Nd(e,t){var n=hs(t),r=1+hs(e)*n;return[n*ms(e)/r,ms(t)/r]}Nd.invert=md((function(e){return 2*fs(e)}));var Fd=function(){return ud(Nd).scale(250).clipAngle(142)};function Bd(e,t){return[vs(ws((is+t)/2)),-e]}Bd.invert=function(e,t){return[-t,2*fs(zs(e))-is]};var Ud=function(){var e=Md(Bd),t=e.center,n=e.rotate;return e.center=function(e){return arguments.length?t([-e[1],e[0]]):[(e=t())[1],-e[0]]},e.rotate=function(e){return arguments.length?n([e[0],e[1],e.length>2?e[2]+90:90]):[(e=n())[0],e[1],e[2]-90]},n([0,0,90]).scale(159.155)};function Wd(e,t){return e.parent===t.parent?1:2}function Gd(e,t){return e+t.x}function Yd(e,t){return Math.max(e,t.y)}var $d=function(){var e=Wd,t=1,n=1,r=!1;function o(o){var i,a=0;o.eachAfter((function(t){var n=t.children;n?(t.x=function(e){return e.reduce(Gd,0)/e.length}(n),t.y=function(e){return 1+e.reduce(Yd,0)}(n)):(t.x=i?a+=e(t,i):0,t.y=0,i=t)}));var c=function(e){for(var t;t=e.children;)e=t[0];return e}(o),s=function(e){for(var t;t=e.children;)e=t[t.length-1];return e}(o),l=c.x-e(c,s)/2,u=s.x+e(s,c)/2;return o.eachAfter(r?function(e){e.x=(e.x-o.x)*t,e.y=(o.y-e.y)*n}:function(e){e.x=(e.x-l)/(u-l)*t,e.y=(1-(o.y?e.y/o.y:1))*n})}return o.separation=function(t){return arguments.length?(e=t,o):e},o.size=function(e){return arguments.length?(r=!1,t=+e[0],n=+e[1],o):r?null:[t,n]},o.nodeSize=function(e){return arguments.length?(r=!0,t=+e[0],n=+e[1],o):r?[t,n]:null},o};function Zd(e){var t=0,n=e.children,r=n&&n.length;if(r)for(;--r>=0;)t+=n[r].value;else t=1;e.value=t}function Xd(e,t){var n,r,o,i,a,c=new eh(e),s=+e.value&&(c.value=e.value),l=[c];for(null==t&&(t=Kd);n=l.pop();)if(s&&(n.value=+n.data.value),(o=t(n.data))&&(a=o.length))for(n.children=new Array(a),i=a-1;i>=0;--i)l.push(r=n.children[i]=new eh(o[i])),r.parent=n,r.depth=n.depth+1;return c.eachBefore(Jd)}function Kd(e){return e.children}function Qd(e){e.data=e.data.data}function Jd(e){var t=0;do{e.height=t}while((e=e.parent)&&e.height<++t)}function eh(e){this.data=e,this.depth=this.height=0,this.parent=null}eh.prototype=Xd.prototype={constructor:eh,count:function(){return this.eachAfter(Zd)},each:function(e){var t,n,r,o,i=this,a=[i];do{for(t=a.reverse(),a=[];i=t.pop();)if(e(i),n=i.children)for(r=0,o=n.length;r=0;--n)o.push(t[n]);return this},sum:function(e){return this.eachAfter((function(t){for(var n=+e(t.data)||0,r=t.children,o=r&&r.length;--o>=0;)n+=r[o].value;t.value=n}))},sort:function(e){return this.eachBefore((function(t){t.children&&t.children.sort(e)}))},path:function(e){for(var t=this,n=function(e,t){if(e===t)return e;var n=e.ancestors(),r=t.ancestors(),o=null;e=n.pop(),t=r.pop();for(;e===t;)o=e,e=n.pop(),t=r.pop();return o}(t,e),r=[t];t!==n;)t=t.parent,r.push(t);for(var o=r.length;e!==n;)r.splice(o,0,e),e=e.parent;return r},ancestors:function(){for(var e=this,t=[e];e=e.parent;)t.push(e);return t},descendants:function(){var e=[];return this.each((function(t){e.push(t)})),e},leaves:function(){var e=[];return this.eachBefore((function(t){t.children||e.push(t)})),e},links:function(){var e=this,t=[];return e.each((function(n){n!==e&&t.push({source:n.parent,target:n})})),t},copy:function(){return Xd(this).eachBefore(Qd)}};var th=Array.prototype.slice;var nh=function(e){for(var t,n,r=0,o=(e=function(e){for(var t,n,r=e.length;r;)n=Math.random()*r--|0,t=e[r],e[r]=e[n],e[n]=t;return e}(th.call(e))).length,i=[];r0&&n*n>r*r+o*o}function ah(e,t){for(var n=0;nn*n+r*r}function dh(e){var t=e._,n=e.next._,r=t.r+n.r,o=(t.x*n.r+n.x*t.r)/r,i=(t.y*n.r+n.y*t.r)/r;return o*o+i*i}function hh(e){this._=e,this.next=null,this.previous=null}function ph(e){if(!(o=e.length))return 0;var t,n,r,o,i,a,c,s,l,u,f;if((t=e[0]).x=0,t.y=0,!(o>1))return t.r;if(n=e[1],t.x=-n.r,n.x=t.r,n.y=0,!(o>2))return t.r+n.r;uh(n,t,r=e[2]),t=new hh(t),n=new hh(n),r=new hh(r),t.next=r.previous=n,n.next=t.previous=r,r.next=n.previous=t;e:for(c=3;c0)throw new Error("cycle");return i}return n.id=function(t){return arguments.length?(e=gh(t),n):e},n.parentId=function(e){return arguments.length?(t=gh(e),n):t},n};function Lh(e,t){return e.parent===t.parent?1:2}function Hh(e){var t=e.children;return t?t[0]:e.t}function Dh(e){var t=e.children;return t?t[t.length-1]:e.t}function Ph(e,t,n){var r=n/(t.i-e.i);t.c-=r,t.s+=n,e.c+=r,t.z+=n,t.m+=n}function Vh(e,t,n){return e.a.parent===t.parent?e.a:n}function Rh(e,t){this._=e,this.parent=null,this.children=null,this.A=null,this.a=this,this.z=0,this.m=0,this.c=0,this.s=0,this.t=null,this.i=t}Rh.prototype=Object.create(eh.prototype);var Ih=function(){var e=Lh,t=1,n=1,r=null;function o(o){var s=function(e){for(var t,n,r,o,i,a=new Rh(e,0),c=[a];t=c.pop();)if(r=t._.children)for(t.children=new Array(i=r.length),o=i-1;o>=0;--o)c.push(n=t.children[o]=new Rh(r[o],o)),n.parent=t;return(a.parent=new Rh(null,0)).children=[a],a}(o);if(s.eachAfter(i),s.parent.m=-s.z,s.eachBefore(a),r)o.eachBefore(c);else{var l=o,u=o,f=o;o.eachBefore((function(e){e.xu.x&&(u=e),e.depth>f.depth&&(f=e)}));var d=l===u?1:e(l,u)/2,h=d-l.x,p=t/(u.x+d+h),z=n/(f.depth||1);o.eachBefore((function(e){e.x=(e.x+h)*p,e.y=e.depth*z}))}return o}function i(t){var n=t.children,r=t.parent.children,o=t.i?r[t.i-1]:null;if(n){!function(e){for(var t,n=0,r=0,o=e.children,i=o.length;--i>=0;)(t=o[i]).z+=n,t.m+=n,n+=t.s+(r+=t.c)}(t);var i=(n[0].z+n[n.length-1].z)/2;o?(t.z=o.z+e(t._,o._),t.m=t.z-i):t.z=i}else o&&(t.z=o.z+e(t._,o._));t.parent.A=function(t,n,r){if(n){for(var o,i=t,a=t,c=n,s=i.parent.children[0],l=i.m,u=a.m,f=c.m,d=s.m;c=Dh(c),i=Hh(i),c&&i;)s=Hh(s),(a=Dh(a)).a=t,(o=c.z+f-i.z-l+e(c._,i._))>0&&(Ph(Vh(c,t,r),t,o),l+=o,u+=o),f+=c.m,l+=i.m,d+=s.m,u+=a.m;c&&!Dh(a)&&(a.t=c,a.m+=f-u),i&&!Hh(s)&&(s.t=i,s.m+=l-d,r=t)}return r}(t,o,t.parent.A||r[0])}function a(e){e._.x=e.z+e.parent.m,e.m+=e.parent.m}function c(e){e.x*=t,e.y=e.depth*n}return o.separation=function(t){return arguments.length?(e=t,o):e},o.size=function(e){return arguments.length?(r=!1,t=+e[0],n=+e[1],o):r?null:[t,n]},o.nodeSize=function(e){return arguments.length?(r=!0,t=+e[0],n=+e[1],o):r?[t,n]:null},o},Nh=function(e,t,n,r,o){for(var i,a=e.children,c=-1,s=a.length,l=e.value&&(o-n)/e.value;++cd&&(d=c),v=u*u*z,(h=Math.max(d/v,v/f))>p){u-=c;break}p=h}g.push(a={value:u,dice:s1?t:1)},n}(Fh),Wh=function(){var e=Uh,t=!1,n=1,r=1,o=[0],i=mh,a=mh,c=mh,s=mh,l=mh;function u(e){return e.x0=e.y0=0,e.x1=n,e.y1=r,e.eachBefore(f),o=[0],t&&e.eachBefore(Mh),e}function f(t){var n=o[t.depth],r=t.x0+n,u=t.y0+n,f=t.x1-n,d=t.y1-n;f=n-1){var u=c[t];return u.x0=o,u.y0=i,u.x1=a,void(u.y1=s)}var f=l[t],d=r/2+f,h=t+1,p=n-1;for(;h>>1;l[z]s-i){var m=(o*g+a*v)/r;e(t,h,v,o,i,m,s),e(h,n,g,m,i,a,s)}else{var y=(i*g+s*v)/r;e(t,h,v,o,i,a,y),e(h,n,g,o,y,a,s)}}(0,s,e.value,t,n,r,o)},Yh=function(e,t,n,r,o){(1&e.depth?Nh:Ch)(e,t,n,r,o)},$h=function e(t){function n(e,n,r,o,i){if((a=e._squarify)&&a.ratio===t)for(var a,c,s,l,u,f=-1,d=a.length,h=e.value;++f1?t:1)},n}(Fh),Zh=function(e){for(var t,n=-1,r=e.length,o=e[r-1],i=0;++n1&&Kh(e[n[r-2]],e[n[r-1]],e[o])<=0;)--r;n[r++]=o}return n.slice(0,r)}var ep=function(e){if((n=e.length)<3)return null;var t,n,r=new Array(n),o=new Array(n);for(t=0;t=0;--t)l.push(e[r[i[t]][2]]);for(t=+c;tc!==l>c&&a<(s-n)*(c-r)/(l-r)+n&&(u=!u),s=n,l=r;return u},np=function(e){for(var t,n,r=-1,o=e.length,i=e[o-1],a=i[0],c=i[1],s=0;++r=0;)if((n=e._tasks[r])&&(e._tasks[r]=null,n.abort))try{n.abort()}catch(t){}e._active=NaN,lp(e)}function lp(e){if(!e._active&&e._call){var t=e._data;e._data=void 0,e._call(e._error,t)}}function up(e){if(null==e)e=1/0;else if(!((e=+e)>=1))throw new Error("invalid concurrency");return new ip(e)}ip.prototype=up.prototype={constructor:ip,defer:function(e){if("function"!==typeof e)throw new Error("invalid callback");if(this._call)throw new Error("defer after await");if(null!=this._error)return this;var t=rp.call(arguments,1);return t.push(e),++this._waiting,this._tasks.push(t),ap(this),this},abort:function(){return null==this._error&&sp(this,new Error("abort")),this},await:function(e){if("function"!==typeof e)throw new Error("invalid callback");if(this._call)throw new Error("multiple await");return this._call=function(t,n){e.apply(null,[t].concat(n))},lp(this),this},awaitAll:function(e){if("function"!==typeof e)throw new Error("invalid callback");if(this._call)throw new Error("multiple await");return this._call=e,lp(this),this}};var fp=function(){return Math.random()},dp=function e(t){function n(e,n){return e=null==e?0:+e,n=null==n?1:+n,1===arguments.length?(n=e,e=0):n-=e,function(){return t()*n+e}}return n.source=e,n}(fp),hp=function e(t){function n(e,n){var r,o;return e=null==e?0:+e,n=null==n?1:+n,function(){var i;if(null!=r)i=r,r=null;else do{r=2*t()-1,i=2*t()-1,o=r*r+i*i}while(!o||o>1);return e+n*i*Math.sqrt(-2*Math.log(o)/o)}}return n.source=e,n}(fp),pp=function e(t){function n(){var e=hp.source(t).apply(this,arguments);return function(){return Math.exp(e())}}return n.source=e,n}(fp),zp=function e(t){function n(e){return function(){for(var n=0,r=0;r=200&&r<300||304===r){if(o)try{t=o.call(n,s)}catch(i){return void a.call("error",n,i)}else t=s;a.call("load",n,t)}else a.call("error",n,e)}if("undefined"===typeof XDomainRequest||"withCredentials"in s||!/^(http(s)?:)?\/\//.test(e)||(s=new XDomainRequest),"onload"in s?s.onload=s.onerror=s.ontimeout=d:s.onreadystatechange=function(e){s.readyState>3&&d(e)},s.onprogress=function(e){a.call("progress",n,e)},n={header:function(e,t){return e=(e+"").toLowerCase(),arguments.length<2?c.get(e):(null==t?c.remove(e):c.set(e,t+""),n)},mimeType:function(e){return arguments.length?(r=null==e?null:e+"",n):r},responseType:function(e){return arguments.length?(i=e,n):i},timeout:function(e){return arguments.length?(f=+e,n):f},user:function(e){return arguments.length<1?l:(l=null==e?null:e+"",n)},password:function(e){return arguments.length<1?u:(u=null==e?null:e+"",n)},response:function(e){return o=e,n},get:function(e,t){return n.send("GET",e,t)},post:function(e,t){return n.send("POST",e,t)},send:function(t,o,d){return s.open(t,e,!0,l,u),null==r||c.has("accept")||c.set("accept",r+",*/*"),s.setRequestHeader&&c.each((function(e,t){s.setRequestHeader(t,e)})),null!=r&&s.overrideMimeType&&s.overrideMimeType(r),null!=i&&(s.responseType=i),f>0&&(s.timeout=f),null==d&&"function"===typeof o&&(d=o,o=null),null!=d&&1===d.length&&(d=function(e){return function(t,n){e(null==t?n:null)}}(d)),null!=d&&n.on("error",d).on("load",(function(e){d(null,e)})),a.call("beforesend",n,s),s.send(null==o?null:o),n},abort:function(){return s.abort(),n},on:function(){var e=a.on.apply(a,arguments);return e===a?n:e}},null!=t){if("function"!==typeof t)throw new Error("invalid callback: "+t);return n.get(t)}return n};var yp=function(e,t){return function(n,r){var o=mp(n).mimeType(e).response(t);if(null!=r){if("function"!==typeof r)throw new Error("invalid callback: "+r);return o.get(r)}return o}},bp=yp("text/html",(function(e){return document.createRange().createContextualFragment(e.responseText)})),wp=yp("application/json",(function(e){return JSON.parse(e.responseText)})),kp=yp("text/plain",(function(e){return e.responseText})),xp=yp("application/xml",(function(e){var t=e.responseXML;if(!t)throw new Error("parse error");return t})),jp=function(e,t){return function(n,r,o){arguments.length<3&&(o=r,r=null);var i=mp(n).mimeType(e);return i.row=function(e){return arguments.length?i.response(Mp(t,r=e)):r},i.row(r),o?i.get(o):i}};function Mp(e,t){return function(n){return e(n.responseText,t)}}var Cp=jp("text/csv",ec),_p=jp("text/tab-separated-values",ic),qp=Array.prototype,Sp=qp.map,Op=qp.slice,Ep={name:"implicit"};function Tp(e){var t=Aa(),n=[],r=Ep;function o(o){var i=o+"",a=t.get(i);if(!a){if(r!==Ep)return r;t.set(i,a=n.push(o))}return e[(a-1)%e.length]}return e=null==e?[]:Op.call(e),o.domain=function(e){if(!arguments.length)return n.slice();n=[],t=Aa();for(var r,i,a=-1,c=e.length;++a2?Ip:Rp,r=o=null,u}function u(t){return(r||(r=n(i,a,s?function(e){return function(t,n){var r=e(t=+t,n=+n);return function(e){return e<=t?0:e>=n?1:r(e)}}}(e):e,c)))(+t)}return u.invert=function(e){return(o||(o=n(a,i,Vp,s?function(e){return function(t,n){var r=e(t=+t,n=+n);return function(e){return e<=0?t:e>=1?n:r(e)}}}(t):t)))(+e)},u.domain=function(e){return arguments.length?(i=Sp.call(e,Dp),l()):i.slice()},u.range=function(e){return arguments.length?(a=Op.call(e),l()):a.slice()},u.rangeRound=function(e){return a=Op.call(e),c=mr,l()},u.clamp=function(e){return arguments.length?(s=!!e,l()):s},u.interpolate=function(e){return arguments.length?(c=e,l()):c},l()}var Bp=function(e,t,n){var r,o=e[0],i=e[e.length-1],a=q(o,i,null==t?10:t);switch((n=Nc(null==n?",f":n)).type){case"s":var c=Math.max(Math.abs(o),Math.abs(i));return null!=n.precision||isNaN(r=Kc(a,c))||(n.precision=r),Wc(n,c);case"":case"e":case"g":case"p":case"r":null!=n.precision||isNaN(r=Qc(a,Math.max(Math.abs(o),Math.abs(i))))||(n.precision=r-("e"===n.type));break;case"f":case"%":null!=n.precision||isNaN(r=Xc(a))||(n.precision=r-2*("%"===n.type))}return Uc(n)};function Up(e){var t=e.domain;return e.ticks=function(e){var n=t();return C(n[0],n[n.length-1],null==e?10:e)},e.tickFormat=function(e,n){return Bp(t(),e,n)},e.nice=function(n){null==n&&(n=10);var r,o=t(),i=0,a=o.length-1,c=o[i],s=o[a];return s0?r=_(c=Math.floor(c/r)*r,s=Math.ceil(s/r)*r,n):r<0&&(r=_(c=Math.ceil(c*r)/r,s=Math.floor(s*r)/r,n)),r>0?(o[i]=Math.floor(c/r)*r,o[a]=Math.ceil(s/r)*r,t(o)):r<0&&(o[i]=Math.ceil(c*r)/r,o[a]=Math.floor(s*r)/r,t(o)),e},e}function Wp(){var e=Fp(Vp,sr);return e.copy=function(){return Np(e,Wp())},Up(e)}function Gp(){var e=[0,1];function t(e){return+e}return t.invert=t,t.domain=t.range=function(n){return arguments.length?(e=Sp.call(n,Dp),t):e.slice()},t.copy=function(){return Gp().domain(e)},Up(t)}var Yp=function(e,t){var n,r=0,o=(e=e.slice()).length-1,i=e[r],a=e[o];return a0){for(;ds)break;z.push(f)}}else for(;d=1;--u)if(!((f=l*u)s)break;z.push(f)}}else z=C(d,h,Math.min(h-d,p)).map(o);return i?z.reverse():z},e.tickFormat=function(t,i){if(null==i&&(i=10===n?".0e":","),"function"!==typeof i&&(i=Uc(i)),t===1/0)return i;null==t&&(t=10);var a=Math.max(1,n*t/e.ticks().length);return function(e){var t=e/o(Math.round(r(e)));return t*n0?n[o-1]:e[0],o=n?[r[n-1],t]:[r[a-1],r[a]]},i.copy=function(){return iz().domain([e,t]).range(o)},Up(i)}function az(){var e=[.5],t=[0,1],n=1;function r(r){if(r<=r)return t[s(e,r,0,n)]}return r.domain=function(o){return arguments.length?(e=Op.call(o),n=Math.min(e.length,t.length-1),r):e.slice()},r.range=function(o){return arguments.length?(t=Op.call(o),n=Math.min(e.length,t.length-1),r):t.slice()},r.invertExtent=function(n){var r=t.indexOf(n);return[e[r-1],e[r]]},r.copy=function(){return az().domain(e).range(t)},r}var cz=new Date,sz=new Date;function lz(e,t,n,r){function o(t){return e(t=new Date(+t)),t}return o.floor=o,o.ceil=function(n){return e(n=new Date(n-1)),t(n,1),e(n),n},o.round=function(e){var t=o(e),n=o.ceil(e);return e-t0))return c;do{c.push(a=new Date(+n)),t(n,i),e(n)}while(a=t)for(;e(t),!n(t);)t.setTime(t-1)}),(function(e,r){if(e>=e)if(r<0)for(;++r<=0;)for(;t(e,-1),!n(e););else for(;--r>=0;)for(;t(e,1),!n(e););}))},n&&(o.count=function(t,r){return cz.setTime(+t),sz.setTime(+r),e(cz),e(sz),Math.floor(n(cz,sz))},o.every=function(e){return e=Math.floor(e),isFinite(e)&&e>0?e>1?o.filter(r?function(t){return r(t)%e===0}:function(t){return o.count(0,t)%e===0}):o:null}),o}var uz=lz((function(){}),(function(e,t){e.setTime(+e+t)}),(function(e,t){return t-e}));uz.every=function(e){return e=Math.floor(e),isFinite(e)&&e>0?e>1?lz((function(t){t.setTime(Math.floor(t/e)*e)}),(function(t,n){t.setTime(+t+n*e)}),(function(t,n){return(n-t)/e})):uz:null};var fz=uz,dz=uz.range,hz=6e4,pz=6048e5,zz=lz((function(e){e.setTime(1e3*Math.floor(e/1e3))}),(function(e,t){e.setTime(+e+1e3*t)}),(function(e,t){return(t-e)/1e3}),(function(e){return e.getUTCSeconds()})),vz=zz,gz=zz.range,mz=lz((function(e){e.setTime(Math.floor(e/hz)*hz)}),(function(e,t){e.setTime(+e+t*hz)}),(function(e,t){return(t-e)/hz}),(function(e){return e.getMinutes()})),yz=mz,bz=mz.range,wz=lz((function(e){var t=e.getTimezoneOffset()*hz%36e5;t<0&&(t+=36e5),e.setTime(36e5*Math.floor((+e-t)/36e5)+t)}),(function(e,t){e.setTime(+e+36e5*t)}),(function(e,t){return(t-e)/36e5}),(function(e){return e.getHours()})),kz=wz,xz=wz.range,jz=lz((function(e){e.setHours(0,0,0,0)}),(function(e,t){e.setDate(e.getDate()+t)}),(function(e,t){return(t-e-(t.getTimezoneOffset()-e.getTimezoneOffset())*hz)/864e5}),(function(e){return e.getDate()-1})),Mz=jz,Cz=jz.range;function _z(e){return lz((function(t){t.setDate(t.getDate()-(t.getDay()+7-e)%7),t.setHours(0,0,0,0)}),(function(e,t){e.setDate(e.getDate()+7*t)}),(function(e,t){return(t-e-(t.getTimezoneOffset()-e.getTimezoneOffset())*hz)/pz}))}var qz=_z(0),Sz=_z(1),Oz=_z(2),Ez=_z(3),Tz=_z(4),Az=_z(5),Lz=_z(6),Hz=qz.range,Dz=Sz.range,Pz=Oz.range,Vz=Ez.range,Rz=Tz.range,Iz=Az.range,Nz=Lz.range,Fz=lz((function(e){e.setDate(1),e.setHours(0,0,0,0)}),(function(e,t){e.setMonth(e.getMonth()+t)}),(function(e,t){return t.getMonth()-e.getMonth()+12*(t.getFullYear()-e.getFullYear())}),(function(e){return e.getMonth()})),Bz=Fz,Uz=Fz.range,Wz=lz((function(e){e.setMonth(0,1),e.setHours(0,0,0,0)}),(function(e,t){e.setFullYear(e.getFullYear()+t)}),(function(e,t){return t.getFullYear()-e.getFullYear()}),(function(e){return e.getFullYear()}));Wz.every=function(e){return isFinite(e=Math.floor(e))&&e>0?lz((function(t){t.setFullYear(Math.floor(t.getFullYear()/e)*e),t.setMonth(0,1),t.setHours(0,0,0,0)}),(function(t,n){t.setFullYear(t.getFullYear()+n*e)})):null};var Gz=Wz,Yz=Wz.range,$z=lz((function(e){e.setUTCSeconds(0,0)}),(function(e,t){e.setTime(+e+t*hz)}),(function(e,t){return(t-e)/hz}),(function(e){return e.getUTCMinutes()})),Zz=$z,Xz=$z.range,Kz=lz((function(e){e.setUTCMinutes(0,0,0)}),(function(e,t){e.setTime(+e+36e5*t)}),(function(e,t){return(t-e)/36e5}),(function(e){return e.getUTCHours()})),Qz=Kz,Jz=Kz.range,ev=lz((function(e){e.setUTCHours(0,0,0,0)}),(function(e,t){e.setUTCDate(e.getUTCDate()+t)}),(function(e,t){return(t-e)/864e5}),(function(e){return e.getUTCDate()-1})),tv=ev,nv=ev.range;function rv(e){return lz((function(t){t.setUTCDate(t.getUTCDate()-(t.getUTCDay()+7-e)%7),t.setUTCHours(0,0,0,0)}),(function(e,t){e.setUTCDate(e.getUTCDate()+7*t)}),(function(e,t){return(t-e)/pz}))}var ov=rv(0),iv=rv(1),av=rv(2),cv=rv(3),sv=rv(4),lv=rv(5),uv=rv(6),fv=ov.range,dv=iv.range,hv=av.range,pv=cv.range,zv=sv.range,vv=lv.range,gv=uv.range,mv=lz((function(e){e.setUTCDate(1),e.setUTCHours(0,0,0,0)}),(function(e,t){e.setUTCMonth(e.getUTCMonth()+t)}),(function(e,t){return t.getUTCMonth()-e.getUTCMonth()+12*(t.getUTCFullYear()-e.getUTCFullYear())}),(function(e){return e.getUTCMonth()})),yv=mv,bv=mv.range,wv=lz((function(e){e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)}),(function(e,t){e.setUTCFullYear(e.getUTCFullYear()+t)}),(function(e,t){return t.getUTCFullYear()-e.getUTCFullYear()}),(function(e){return e.getUTCFullYear()}));wv.every=function(e){return isFinite(e=Math.floor(e))&&e>0?lz((function(t){t.setUTCFullYear(Math.floor(t.getUTCFullYear()/e)*e),t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),(function(t,n){t.setUTCFullYear(t.getUTCFullYear()+n*e)})):null};var kv=wv,xv=wv.range;function jv(e){if(0<=e.y&&e.y<100){var t=new Date(-1,e.m,e.d,e.H,e.M,e.S,e.L);return t.setFullYear(e.y),t}return new Date(e.y,e.m,e.d,e.H,e.M,e.S,e.L)}function Mv(e){if(0<=e.y&&e.y<100){var t=new Date(Date.UTC(-1,e.m,e.d,e.H,e.M,e.S,e.L));return t.setUTCFullYear(e.y),t}return new Date(Date.UTC(e.y,e.m,e.d,e.H,e.M,e.S,e.L))}function Cv(e){return{y:e,m:0,d:1,H:0,M:0,S:0,L:0}}function _v(e){var t=e.dateTime,n=e.date,r=e.time,o=e.periods,i=e.days,a=e.shortDays,c=e.months,s=e.shortMonths,l=Rv(o),u=Iv(o),f=Rv(i),d=Iv(i),h=Rv(a),p=Iv(a),z=Rv(c),v=Iv(c),g=Rv(s),m=Iv(s),y={a:function(e){return a[e.getDay()]},A:function(e){return i[e.getDay()]},b:function(e){return s[e.getMonth()]},B:function(e){return c[e.getMonth()]},c:null,d:ag,e:ag,f:fg,H:cg,I:sg,j:lg,L:ug,m:dg,M:hg,p:function(e){return o[+(e.getHours()>=12)]},Q:Ng,s:Fg,S:pg,u:zg,U:vg,V:gg,w:mg,W:yg,x:null,X:null,y:bg,Y:wg,Z:kg,"%":Ig},b={a:function(e){return a[e.getUTCDay()]},A:function(e){return i[e.getUTCDay()]},b:function(e){return s[e.getUTCMonth()]},B:function(e){return c[e.getUTCMonth()]},c:null,d:xg,e:xg,f:qg,H:jg,I:Mg,j:Cg,L:_g,m:Sg,M:Og,p:function(e){return o[+(e.getUTCHours()>=12)]},Q:Ng,s:Fg,S:Eg,u:Tg,U:Ag,V:Lg,w:Hg,W:Dg,x:null,X:null,y:Pg,Y:Vg,Z:Rg,"%":Ig},w={a:function(e,t,n){var r=h.exec(t.slice(n));return r?(e.w=p[r[0].toLowerCase()],n+r[0].length):-1},A:function(e,t,n){var r=f.exec(t.slice(n));return r?(e.w=d[r[0].toLowerCase()],n+r[0].length):-1},b:function(e,t,n){var r=g.exec(t.slice(n));return r?(e.m=m[r[0].toLowerCase()],n+r[0].length):-1},B:function(e,t,n){var r=z.exec(t.slice(n));return r?(e.m=v[r[0].toLowerCase()],n+r[0].length):-1},c:function(e,n,r){return j(e,t,n,r)},d:Xv,e:Xv,f:ng,H:Qv,I:Qv,j:Kv,L:tg,m:Zv,M:Jv,p:function(e,t,n){var r=l.exec(t.slice(n));return r?(e.p=u[r[0].toLowerCase()],n+r[0].length):-1},Q:og,s:ig,S:eg,u:Fv,U:Bv,V:Uv,w:Nv,W:Wv,x:function(e,t,r){return j(e,n,t,r)},X:function(e,t,n){return j(e,r,t,n)},y:Yv,Y:Gv,Z:$v,"%":rg};function k(e,t){return function(n){var r,o,i,a=[],c=-1,s=0,l=e.length;for(n instanceof Date||(n=new Date(+n));++c53)return null;"w"in i||(i.w=1),"Z"in i?(o=(r=Mv(Cv(i.y))).getUTCDay(),r=o>4||0===o?iv.ceil(r):iv(r),r=tv.offset(r,7*(i.V-1)),i.y=r.getUTCFullYear(),i.m=r.getUTCMonth(),i.d=r.getUTCDate()+(i.w+6)%7):(o=(r=t(Cv(i.y))).getDay(),r=o>4||0===o?Sz.ceil(r):Sz(r),r=Mz.offset(r,7*(i.V-1)),i.y=r.getFullYear(),i.m=r.getMonth(),i.d=r.getDate()+(i.w+6)%7)}else("W"in i||"U"in i)&&("w"in i||(i.w="u"in i?i.u%7:"W"in i?1:0),o="Z"in i?Mv(Cv(i.y)).getUTCDay():t(Cv(i.y)).getDay(),i.m=0,i.d="W"in i?(i.w+6)%7+7*i.W-(o+5)%7:i.w+7*i.U-(o+6)%7);return"Z"in i?(i.H+=i.Z/100|0,i.M+=i.Z%100,Mv(i)):t(i)}}function j(e,t,n,r){for(var o,i,a=0,c=t.length,s=n.length;a=s)return-1;if(37===(o=t.charCodeAt(a++))){if(o=t.charAt(a++),!(i=w[o in Av?t.charAt(a++):o])||(r=i(e,n,r))<0)return-1}else if(o!=n.charCodeAt(r++))return-1}return r}return(y.x=k(n,y),y.X=k(r,y),y.c=k(t,y),b.x=k(n,b),b.X=k(r,b),b.c=k(t,b),{format:function(e){var t=k(e+="",y);return t.toString=function(){return e},t},parse:function(e){var t=x(e+="",jv);return t.toString=function(){return e},t},utcFormat:function(e){var t=k(e+="",b);return t.toString=function(){return e},t},utcParse:function(e){var t=x(e,Mv);return t.toString=function(){return e},t}})}var qv,Sv,Ov,Ev,Tv,Av={"-":"",_:" ",0:"0"},Lv=/^\s*\d+/,Hv=/^%/,Dv=/[\\^$*+?|[\]().{}]/g;function Pv(e,t,n){var r=e<0?"-":"",o=(r?-e:e)+"",i=o.length;return r+(i68?1900:2e3),n+r[0].length):-1}function $v(e,t,n){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(t.slice(n,n+6));return r?(e.Z=r[1]?0:-(r[2]+(r[3]||"00")),n+r[0].length):-1}function Zv(e,t,n){var r=Lv.exec(t.slice(n,n+2));return r?(e.m=r[0]-1,n+r[0].length):-1}function Xv(e,t,n){var r=Lv.exec(t.slice(n,n+2));return r?(e.d=+r[0],n+r[0].length):-1}function Kv(e,t,n){var r=Lv.exec(t.slice(n,n+3));return r?(e.m=0,e.d=+r[0],n+r[0].length):-1}function Qv(e,t,n){var r=Lv.exec(t.slice(n,n+2));return r?(e.H=+r[0],n+r[0].length):-1}function Jv(e,t,n){var r=Lv.exec(t.slice(n,n+2));return r?(e.M=+r[0],n+r[0].length):-1}function eg(e,t,n){var r=Lv.exec(t.slice(n,n+2));return r?(e.S=+r[0],n+r[0].length):-1}function tg(e,t,n){var r=Lv.exec(t.slice(n,n+3));return r?(e.L=+r[0],n+r[0].length):-1}function ng(e,t,n){var r=Lv.exec(t.slice(n,n+6));return r?(e.L=Math.floor(r[0]/1e3),n+r[0].length):-1}function rg(e,t,n){var r=Hv.exec(t.slice(n,n+1));return r?n+r[0].length:-1}function og(e,t,n){var r=Lv.exec(t.slice(n));return r?(e.Q=+r[0],n+r[0].length):-1}function ig(e,t,n){var r=Lv.exec(t.slice(n));return r?(e.Q=1e3*+r[0],n+r[0].length):-1}function ag(e,t){return Pv(e.getDate(),t,2)}function cg(e,t){return Pv(e.getHours(),t,2)}function sg(e,t){return Pv(e.getHours()%12||12,t,2)}function lg(e,t){return Pv(1+Mz.count(Gz(e),e),t,3)}function ug(e,t){return Pv(e.getMilliseconds(),t,3)}function fg(e,t){return ug(e,t)+"000"}function dg(e,t){return Pv(e.getMonth()+1,t,2)}function hg(e,t){return Pv(e.getMinutes(),t,2)}function pg(e,t){return Pv(e.getSeconds(),t,2)}function zg(e){var t=e.getDay();return 0===t?7:t}function vg(e,t){return Pv(qz.count(Gz(e),e),t,2)}function gg(e,t){var n=e.getDay();return e=n>=4||0===n?Tz(e):Tz.ceil(e),Pv(Tz.count(Gz(e),e)+(4===Gz(e).getDay()),t,2)}function mg(e){return e.getDay()}function yg(e,t){return Pv(Sz.count(Gz(e),e),t,2)}function bg(e,t){return Pv(e.getFullYear()%100,t,2)}function wg(e,t){return Pv(e.getFullYear()%1e4,t,4)}function kg(e){var t=e.getTimezoneOffset();return(t>0?"-":(t*=-1,"+"))+Pv(t/60|0,"0",2)+Pv(t%60,"0",2)}function xg(e,t){return Pv(e.getUTCDate(),t,2)}function jg(e,t){return Pv(e.getUTCHours(),t,2)}function Mg(e,t){return Pv(e.getUTCHours()%12||12,t,2)}function Cg(e,t){return Pv(1+tv.count(kv(e),e),t,3)}function _g(e,t){return Pv(e.getUTCMilliseconds(),t,3)}function qg(e,t){return _g(e,t)+"000"}function Sg(e,t){return Pv(e.getUTCMonth()+1,t,2)}function Og(e,t){return Pv(e.getUTCMinutes(),t,2)}function Eg(e,t){return Pv(e.getUTCSeconds(),t,2)}function Tg(e){var t=e.getUTCDay();return 0===t?7:t}function Ag(e,t){return Pv(ov.count(kv(e),e),t,2)}function Lg(e,t){var n=e.getUTCDay();return e=n>=4||0===n?sv(e):sv.ceil(e),Pv(sv.count(kv(e),e)+(4===kv(e).getUTCDay()),t,2)}function Hg(e){return e.getUTCDay()}function Dg(e,t){return Pv(iv.count(kv(e),e),t,2)}function Pg(e,t){return Pv(e.getUTCFullYear()%100,t,2)}function Vg(e,t){return Pv(e.getUTCFullYear()%1e4,t,4)}function Rg(){return"+0000"}function Ig(){return"%"}function Ng(e){return+e}function Fg(e){return Math.floor(+e/1e3)}function Bg(e){return qv=_v(e),Sv=qv.format,Ov=qv.parse,Ev=qv.utcFormat,Tv=qv.utcParse,qv}Bg({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var Ug=Date.prototype.toISOString?function(e){return e.toISOString()}:Ev("%Y-%m-%dT%H:%M:%S.%LZ");var Wg=+new Date("2000-01-01T00:00:00.000Z")?function(e){var t=new Date(e);return isNaN(t)?null:t}:Tv("%Y-%m-%dT%H:%M:%S.%LZ"),Gg=1e3,Yg=60*Gg,$g=60*Yg,Zg=24*$g,Xg=7*Zg,Kg=30*Zg,Qg=365*Zg;function Jg(e){return new Date(e)}function em(e){return e instanceof Date?+e:+new Date(+e)}function tm(e,t,n,r,i,a,c,s,l){var u=Fp(Vp,sr),f=u.invert,d=u.domain,h=l(".%L"),p=l(":%S"),z=l("%I:%M"),v=l("%I %p"),g=l("%a %d"),m=l("%b %d"),y=l("%B"),b=l("%Y"),w=[[c,1,Gg],[c,5,5*Gg],[c,15,15*Gg],[c,30,30*Gg],[a,1,Yg],[a,5,5*Yg],[a,15,15*Yg],[a,30,30*Yg],[i,1,$g],[i,3,3*$g],[i,6,6*$g],[i,12,12*$g],[r,1,Zg],[r,2,2*Zg],[n,1,Xg],[t,1,Kg],[t,3,3*Kg],[e,1,Qg]];function k(o){return(c(o)1)&&(e-=Math.floor(e));var t=Math.abs(e-.5);return dm.h=360*e-100,dm.s=1.5-1.5*t,dm.l=.8-.9*t,dm+""};function pm(e){var t=e.length;return function(n){return e[Math.max(0,Math.min(t-1,Math.floor(n*t)))]}}var zm=pm(om("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")),vm=pm(om("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),gm=pm(om("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),mm=pm(om("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921"));function ym(e){var t=0,n=1,r=!1;function o(o){var i=(o-t)/(n-t);return e(r?Math.max(0,Math.min(1,i)):i)}return o.domain=function(e){return arguments.length?(t=+e[0],n=+e[1],o):[t,n]},o.clamp=function(e){return arguments.length?(r=!!e,o):r},o.interpolator=function(t){return arguments.length?(e=t,o):e},o.copy=function(){return ym(e).domain([t,n]).clamp(r)},Up(o)}var bm=function(e){return function(){return e}},wm=Math.abs,km=Math.atan2,xm=Math.cos,jm=Math.max,Mm=Math.min,Cm=Math.sin,_m=Math.sqrt,qm=1e-12,Sm=Math.PI,Om=Sm/2,Em=2*Sm;function Tm(e){return e>=1?Om:e<=-1?-Om:Math.asin(e)}function Am(e){return e.innerRadius}function Lm(e){return e.outerRadius}function Hm(e){return e.startAngle}function Dm(e){return e.endAngle}function Pm(e){return e&&e.padAngle}function Vm(e,t,n,r,o,i,a){var c=e-n,s=t-r,l=(a?i:-i)/_m(c*c+s*s),u=l*s,f=-l*c,d=e+u,h=t+f,p=n+u,z=r+f,v=(d+p)/2,g=(h+z)/2,m=p-d,y=z-h,b=m*m+y*y,w=o-i,k=d*z-p*h,x=(y<0?-1:1)*_m(jm(0,w*w*b-k*k)),j=(k*y-m*x)/b,M=(-k*m-y*x)/b,C=(k*y+m*x)/b,_=(-k*m+y*x)/b,q=j-v,S=M-g,O=C-v,E=_-g;return q*q+S*S>O*O+E*E&&(j=C,M=_),{cx:j,cy:M,x01:-u,y01:-f,x11:j*(o/w-1),y11:M*(o/w-1)}}var Rm=function(){var e=Am,t=Lm,n=bm(0),r=null,o=Hm,i=Dm,a=Pm,c=null;function s(){var s,l,u,f=+e.apply(this,arguments),d=+t.apply(this,arguments),h=o.apply(this,arguments)-Om,p=i.apply(this,arguments)-Om,z=wm(p-h),v=p>h;if(c||(c=s=ja()),dqm)if(z>Em-qm)c.moveTo(d*xm(h),d*Cm(h)),c.arc(0,0,d,h,p,!v),f>qm&&(c.moveTo(f*xm(p),f*Cm(p)),c.arc(0,0,f,p,h,v));else{var g,m,y=h,b=p,w=h,k=p,x=z,j=z,M=a.apply(this,arguments)/2,C=M>qm&&(r?+r.apply(this,arguments):_m(f*f+d*d)),_=Mm(wm(d-f)/2,+n.apply(this,arguments)),q=_,S=_;if(C>qm){var O=Tm(C/f*Cm(M)),E=Tm(C/d*Cm(M));(x-=2*O)>qm?(w+=O*=v?1:-1,k-=O):(x=0,w=k=(h+p)/2),(j-=2*E)>qm?(y+=E*=v?1:-1,b-=E):(j=0,y=b=(h+p)/2)}var T=d*xm(y),A=d*Cm(y),L=f*xm(k),H=f*Cm(k);if(_>qm){var D=d*xm(b),P=d*Cm(b),V=f*xm(w),R=f*Cm(w);if(zqm?function(e,t,n,r,o,i,a,c){var s=n-e,l=r-t,u=a-o,f=c-i,d=(u*(t-i)-f*(e-o))/(f*s-u*l);return[e+d*s,t+d*l]}(T,A,V,R,D,P,L,H):[L,H],N=T-I[0],F=A-I[1],B=D-I[0],U=P-I[1],W=1/Cm(((u=(N*B+F*U)/(_m(N*N+F*F)*_m(B*B+U*U)))>1?0:u<-1?Sm:Math.acos(u))/2),G=_m(I[0]*I[0]+I[1]*I[1]);q=Mm(_,(f-G)/(W-1)),S=Mm(_,(d-G)/(W+1))}}j>qm?S>qm?(g=Vm(V,R,T,A,d,S,v),m=Vm(D,P,L,H,d,S,v),c.moveTo(g.cx+g.x01,g.cy+g.y01),S<_?c.arc(g.cx,g.cy,S,km(g.y01,g.x01),km(m.y01,m.x01),!v):(c.arc(g.cx,g.cy,S,km(g.y01,g.x01),km(g.y11,g.x11),!v),c.arc(0,0,d,km(g.cy+g.y11,g.cx+g.x11),km(m.cy+m.y11,m.cx+m.x11),!v),c.arc(m.cx,m.cy,S,km(m.y11,m.x11),km(m.y01,m.x01),!v))):(c.moveTo(T,A),c.arc(0,0,d,y,b,!v)):c.moveTo(T,A),f>qm&&x>qm?q>qm?(g=Vm(L,H,D,P,f,-q,v),m=Vm(T,A,V,R,f,-q,v),c.lineTo(g.cx+g.x01,g.cy+g.y01),q<_?c.arc(g.cx,g.cy,q,km(g.y01,g.x01),km(m.y01,m.x01),!v):(c.arc(g.cx,g.cy,q,km(g.y01,g.x01),km(g.y11,g.x11),!v),c.arc(0,0,f,km(g.cy+g.y11,g.cx+g.x11),km(m.cy+m.y11,m.cx+m.x11),v),c.arc(m.cx,m.cy,q,km(m.y11,m.x11),km(m.y01,m.x01),!v))):c.arc(0,0,f,k,w,v):c.lineTo(L,H)}else c.moveTo(0,0);if(c.closePath(),s)return c=null,s+""||null}return s.centroid=function(){var n=(+e.apply(this,arguments)+ +t.apply(this,arguments))/2,r=(+o.apply(this,arguments)+ +i.apply(this,arguments))/2-Sm/2;return[xm(r)*n,Cm(r)*n]},s.innerRadius=function(t){return arguments.length?(e="function"===typeof t?t:bm(+t),s):e},s.outerRadius=function(e){return arguments.length?(t="function"===typeof e?e:bm(+e),s):t},s.cornerRadius=function(e){return arguments.length?(n="function"===typeof e?e:bm(+e),s):n},s.padRadius=function(e){return arguments.length?(r=null==e?null:"function"===typeof e?e:bm(+e),s):r},s.startAngle=function(e){return arguments.length?(o="function"===typeof e?e:bm(+e),s):o},s.endAngle=function(e){return arguments.length?(i="function"===typeof e?e:bm(+e),s):i},s.padAngle=function(e){return arguments.length?(a="function"===typeof e?e:bm(+e),s):a},s.context=function(e){return arguments.length?(c=null==e?null:e,s):c},s};function Im(e){this._context=e}Im.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:this._context.lineTo(e,t)}}};var Nm=function(e){return new Im(e)};function Fm(e){return e[0]}function Bm(e){return e[1]}var Um=function(){var e=Fm,t=Bm,n=bm(!0),r=null,o=Nm,i=null;function a(a){var c,s,l,u=a.length,f=!1;for(null==r&&(i=o(l=ja())),c=0;c<=u;++c)!(c=u;--f)c.point(v[f],g[f]);c.lineEnd(),c.areaEnd()}z&&(v[l]=+e(d,l,s),g[l]=+n(d,l,s),c.point(t?+t(d,l,s):v[l],r?+r(d,l,s):g[l]))}if(h)return c=null,h+""||null}function l(){return Um().defined(o).curve(a).context(i)}return s.x=function(n){return arguments.length?(e="function"===typeof n?n:bm(+n),t=null,s):e},s.x0=function(t){return arguments.length?(e="function"===typeof t?t:bm(+t),s):e},s.x1=function(e){return arguments.length?(t=null==e?null:"function"===typeof e?e:bm(+e),s):t},s.y=function(e){return arguments.length?(n="function"===typeof e?e:bm(+e),r=null,s):n},s.y0=function(e){return arguments.length?(n="function"===typeof e?e:bm(+e),s):n},s.y1=function(e){return arguments.length?(r=null==e?null:"function"===typeof e?e:bm(+e),s):r},s.lineX0=s.lineY0=function(){return l().x(e).y(n)},s.lineY1=function(){return l().x(e).y(r)},s.lineX1=function(){return l().x(t).y(n)},s.defined=function(e){return arguments.length?(o="function"===typeof e?e:bm(!!e),s):o},s.curve=function(e){return arguments.length?(a=e,null!=i&&(c=a(i)),s):a},s.context=function(e){return arguments.length?(null==e?i=c=null:c=a(i=e),s):i},s},Gm=function(e,t){return te?1:t>=e?0:NaN},Ym=function(e){return e},$m=function(){var e=Ym,t=Gm,n=null,r=bm(0),o=bm(Em),i=bm(0);function a(a){var c,s,l,u,f,d=a.length,h=0,p=new Array(d),z=new Array(d),v=+r.apply(this,arguments),g=Math.min(Em,Math.max(-Em,o.apply(this,arguments)-v)),m=Math.min(Math.abs(g)/d,i.apply(this,arguments)),y=m*(g<0?-1:1);for(c=0;c0&&(h+=f);for(null!=t?p.sort((function(e,n){return t(z[e],z[n])})):null!=n&&p.sort((function(e,t){return n(a[e],a[t])})),c=0,l=h?(g-d*y)/h:0;c0?f*l:0)+y,z[s]={data:a[s],index:c,value:f,startAngle:v,endAngle:u,padAngle:m};return z}return a.value=function(t){return arguments.length?(e="function"===typeof t?t:bm(+t),a):e},a.sortValues=function(e){return arguments.length?(t=e,n=null,a):t},a.sort=function(e){return arguments.length?(n=e,t=null,a):n},a.startAngle=function(e){return arguments.length?(r="function"===typeof e?e:bm(+e),a):r},a.endAngle=function(e){return arguments.length?(o="function"===typeof e?e:bm(+e),a):o},a.padAngle=function(e){return arguments.length?(i="function"===typeof e?e:bm(+e),a):i},a},Zm=Km(Nm);function Xm(e){this._curve=e}function Km(e){function t(t){return new Xm(e(t))}return t._curve=e,t}function Qm(e){var t=e.curve;return e.angle=e.x,delete e.x,e.radius=e.y,delete e.y,e.curve=function(e){return arguments.length?t(Km(e)):t()._curve},e}Xm.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(e,t){this._curve.point(t*Math.sin(e),t*-Math.cos(e))}};var Jm=function(){return Qm(Um().curve(Zm))},ey=function(){var e=Wm().curve(Zm),t=e.curve,n=e.lineX0,r=e.lineX1,o=e.lineY0,i=e.lineY1;return e.angle=e.x,delete e.x,e.startAngle=e.x0,delete e.x0,e.endAngle=e.x1,delete e.x1,e.radius=e.y,delete e.y,e.innerRadius=e.y0,delete e.y0,e.outerRadius=e.y1,delete e.y1,e.lineStartAngle=function(){return Qm(n())},delete e.lineX0,e.lineEndAngle=function(){return Qm(r())},delete e.lineX1,e.lineInnerRadius=function(){return Qm(o())},delete e.lineY0,e.lineOuterRadius=function(){return Qm(i())},delete e.lineY1,e.curve=function(e){return arguments.length?t(Km(e)):t()._curve},e},ty=function(e,t){return[(t=+t)*Math.cos(e-=Math.PI/2),t*Math.sin(e)]},ny=Array.prototype.slice;function ry(e){return e.source}function oy(e){return e.target}function iy(e){var t=ry,n=oy,r=Fm,o=Bm,i=null;function a(){var a,c=ny.call(arguments),s=t.apply(this,c),l=n.apply(this,c);if(i||(i=a=ja()),e(i,+r.apply(this,(c[0]=s,c)),+o.apply(this,c),+r.apply(this,(c[0]=l,c)),+o.apply(this,c)),a)return i=null,a+""||null}return a.source=function(e){return arguments.length?(t=e,a):t},a.target=function(e){return arguments.length?(n=e,a):n},a.x=function(e){return arguments.length?(r="function"===typeof e?e:bm(+e),a):r},a.y=function(e){return arguments.length?(o="function"===typeof e?e:bm(+e),a):o},a.context=function(e){return arguments.length?(i=null==e?null:e,a):i},a}function ay(e,t,n,r,o){e.moveTo(t,n),e.bezierCurveTo(t=(t+r)/2,n,t,o,r,o)}function cy(e,t,n,r,o){e.moveTo(t,n),e.bezierCurveTo(t,n=(n+o)/2,r,n,r,o)}function sy(e,t,n,r,o){var i=ty(t,n),a=ty(t,n=(n+o)/2),c=ty(r,n),s=ty(r,o);e.moveTo(i[0],i[1]),e.bezierCurveTo(a[0],a[1],c[0],c[1],s[0],s[1])}function ly(){return iy(ay)}function uy(){return iy(cy)}function fy(){var e=iy(sy);return e.angle=e.x,delete e.x,e.radius=e.y,delete e.y,e}var dy={draw:function(e,t){var n=Math.sqrt(t/Sm);e.moveTo(n,0),e.arc(0,0,n,0,Em)}},hy={draw:function(e,t){var n=Math.sqrt(t/5)/2;e.moveTo(-3*n,-n),e.lineTo(-n,-n),e.lineTo(-n,-3*n),e.lineTo(n,-3*n),e.lineTo(n,-n),e.lineTo(3*n,-n),e.lineTo(3*n,n),e.lineTo(n,n),e.lineTo(n,3*n),e.lineTo(-n,3*n),e.lineTo(-n,n),e.lineTo(-3*n,n),e.closePath()}},py=Math.sqrt(1/3),zy=2*py,vy={draw:function(e,t){var n=Math.sqrt(t/zy),r=n*py;e.moveTo(0,-n),e.lineTo(r,0),e.lineTo(0,n),e.lineTo(-r,0),e.closePath()}},gy=Math.sin(Sm/10)/Math.sin(7*Sm/10),my=Math.sin(Em/10)*gy,yy=-Math.cos(Em/10)*gy,by={draw:function(e,t){var n=Math.sqrt(.8908130915292852*t),r=my*n,o=yy*n;e.moveTo(0,-n),e.lineTo(r,o);for(var i=1;i<5;++i){var a=Em*i/5,c=Math.cos(a),s=Math.sin(a);e.lineTo(s*n,-c*n),e.lineTo(c*r-s*o,s*r+c*o)}e.closePath()}},wy={draw:function(e,t){var n=Math.sqrt(t),r=-n/2;e.rect(r,r,n,n)}},ky=Math.sqrt(3),xy={draw:function(e,t){var n=-Math.sqrt(t/(3*ky));e.moveTo(0,2*n),e.lineTo(-ky*n,-n),e.lineTo(ky*n,-n),e.closePath()}},jy=Math.sqrt(3)/2,My=1/Math.sqrt(12),Cy=3*(My/2+1),_y={draw:function(e,t){var n=Math.sqrt(t/Cy),r=n/2,o=n*My,i=r,a=n*My+n,c=-i,s=a;e.moveTo(r,o),e.lineTo(i,a),e.lineTo(c,s),e.lineTo(-.5*r-jy*o,jy*r+-.5*o),e.lineTo(-.5*i-jy*a,jy*i+-.5*a),e.lineTo(-.5*c-jy*s,jy*c+-.5*s),e.lineTo(-.5*r+jy*o,-.5*o-jy*r),e.lineTo(-.5*i+jy*a,-.5*a-jy*i),e.lineTo(-.5*c+jy*s,-.5*s-jy*c),e.closePath()}},qy=[dy,hy,vy,wy,by,xy,_y],Sy=function(){var e=bm(dy),t=bm(64),n=null;function r(){var r;if(n||(n=r=ja()),e.apply(this,arguments).draw(n,+t.apply(this,arguments)),r)return n=null,r+""||null}return r.type=function(t){return arguments.length?(e="function"===typeof t?t:bm(t),r):e},r.size=function(e){return arguments.length?(t="function"===typeof e?e:bm(+e),r):t},r.context=function(e){return arguments.length?(n=null==e?null:e,r):n},r},Oy=function(){};function Ey(e,t,n){e._context.bezierCurveTo((2*e._x0+e._x1)/3,(2*e._y0+e._y1)/3,(e._x0+2*e._x1)/3,(e._y0+2*e._y1)/3,(e._x0+4*e._x1+t)/6,(e._y0+4*e._y1+n)/6)}function Ty(e){this._context=e}Ty.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:Ey(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:Ey(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};var Ay=function(e){return new Ty(e)};function Ly(e){this._context=e}Ly.prototype={areaStart:Oy,areaEnd:Oy,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._x2=e,this._y2=t;break;case 1:this._point=2,this._x3=e,this._y3=t;break;case 2:this._point=3,this._x4=e,this._y4=t,this._context.moveTo((this._x0+4*this._x1+e)/6,(this._y0+4*this._y1+t)/6);break;default:Ey(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};var Hy=function(e){return new Ly(e)};function Dy(e){this._context=e}Dy.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+e)/6,r=(this._y0+4*this._y1+t)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:Ey(this,e,t)}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};var Py=function(e){return new Dy(e)};function Vy(e,t){this._basis=new Ty(e),this._beta=t}Vy.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var e=this._x,t=this._y,n=e.length-1;if(n>0)for(var r,o=e[0],i=t[0],a=e[n]-o,c=t[n]-i,s=-1;++s<=n;)r=s/n,this._basis.point(this._beta*e[s]+(1-this._beta)*(o+r*a),this._beta*t[s]+(1-this._beta)*(i+r*c));this._x=this._y=null,this._basis.lineEnd()},point:function(e,t){this._x.push(+e),this._y.push(+t)}};var Ry=function e(t){function n(e){return 1===t?new Ty(e):new Vy(e,t)}return n.beta=function(t){return e(+t)},n}(.85);function Iy(e,t,n){e._context.bezierCurveTo(e._x1+e._k*(e._x2-e._x0),e._y1+e._k*(e._y2-e._y0),e._x2+e._k*(e._x1-t),e._y2+e._k*(e._y1-n),e._x2,e._y2)}function Ny(e,t){this._context=e,this._k=(1-t)/6}Ny.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:Iy(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2,this._x1=e,this._y1=t;break;case 2:this._point=3;default:Iy(this,e,t)}this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};var Fy=function e(t){function n(e){return new Ny(e,t)}return n.tension=function(t){return e(+t)},n}(0);function By(e,t){this._context=e,this._k=(1-t)/6}By.prototype={areaStart:Oy,areaEnd:Oy,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._x3=e,this._y3=t;break;case 1:this._point=2,this._context.moveTo(this._x4=e,this._y4=t);break;case 2:this._point=3,this._x5=e,this._y5=t;break;default:Iy(this,e,t)}this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};var Uy=function e(t){function n(e){return new By(e,t)}return n.tension=function(t){return e(+t)},n}(0);function Wy(e,t){this._context=e,this._k=(1-t)/6}Wy.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Iy(this,e,t)}this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};var Gy=function e(t){function n(e){return new Wy(e,t)}return n.tension=function(t){return e(+t)},n}(0);function Yy(e,t,n){var r=e._x1,o=e._y1,i=e._x2,a=e._y2;if(e._l01_a>qm){var c=2*e._l01_2a+3*e._l01_a*e._l12_a+e._l12_2a,s=3*e._l01_a*(e._l01_a+e._l12_a);r=(r*c-e._x0*e._l12_2a+e._x2*e._l01_2a)/s,o=(o*c-e._y0*e._l12_2a+e._y2*e._l01_2a)/s}if(e._l23_a>qm){var l=2*e._l23_2a+3*e._l23_a*e._l12_a+e._l12_2a,u=3*e._l23_a*(e._l23_a+e._l12_a);i=(i*l+e._x1*e._l23_2a-t*e._l12_2a)/u,a=(a*l+e._y1*e._l23_2a-n*e._l12_2a)/u}e._context.bezierCurveTo(r,o,i,a,e._x2,e._y2)}function $y(e,t){this._context=e,this._alpha=t}$y.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){if(e=+e,t=+t,this._point){var n=this._x2-e,r=this._y2-t;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3;default:Yy(this,e,t)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};var Zy=function e(t){function n(e){return t?new $y(e,t):new Ny(e,0)}return n.alpha=function(t){return e(+t)},n}(.5);function Xy(e,t){this._context=e,this._alpha=t}Xy.prototype={areaStart:Oy,areaEnd:Oy,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(e,t){if(e=+e,t=+t,this._point){var n=this._x2-e,r=this._y2-t;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=e,this._y3=t;break;case 1:this._point=2,this._context.moveTo(this._x4=e,this._y4=t);break;case 2:this._point=3,this._x5=e,this._y5=t;break;default:Yy(this,e,t)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};var Ky=function e(t){function n(e){return t?new Xy(e,t):new By(e,0)}return n.alpha=function(t){return e(+t)},n}(.5);function Qy(e,t){this._context=e,this._alpha=t}Qy.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){if(e=+e,t=+t,this._point){var n=this._x2-e,r=this._y2-t;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Yy(this,e,t)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};var Jy=function e(t){function n(e){return t?new Qy(e,t):new Wy(e,0)}return n.alpha=function(t){return e(+t)},n}(.5);function eb(e){this._context=e}eb.prototype={areaStart:Oy,areaEnd:Oy,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(e,t){e=+e,t=+t,this._point?this._context.lineTo(e,t):(this._point=1,this._context.moveTo(e,t))}};var tb=function(e){return new eb(e)};function nb(e){return e<0?-1:1}function rb(e,t,n){var r=e._x1-e._x0,o=t-e._x1,i=(e._y1-e._y0)/(r||o<0&&-0),a=(n-e._y1)/(o||r<0&&-0),c=(i*o+a*r)/(r+o);return(nb(i)+nb(a))*Math.min(Math.abs(i),Math.abs(a),.5*Math.abs(c))||0}function ob(e,t){var n=e._x1-e._x0;return n?(3*(e._y1-e._y0)/n-t)/2:t}function ib(e,t,n){var r=e._x0,o=e._y0,i=e._x1,a=e._y1,c=(i-r)/3;e._context.bezierCurveTo(r+c,o+c*t,i-c,a-c*n,i,a)}function ab(e){this._context=e}function cb(e){this._context=new sb(e)}function sb(e){this._context=e}function lb(e){return new ab(e)}function ub(e){return new cb(e)}function fb(e){this._context=e}function db(e){var t,n,r=e.length-1,o=new Array(r),i=new Array(r),a=new Array(r);for(o[0]=0,i[0]=2,a[0]=e[0]+2*e[1],t=1;t=0;--t)o[t]=(a[t]-o[t+1])/i[t];for(i[r-1]=(e[r]+o[r-1])/2,t=0;t=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,t),this._context.lineTo(e,t);else{var n=this._x*(1-this._t)+e*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,t)}}this._x=e,this._y=t}};var zb=function(e){return new pb(e,.5)};function vb(e){return new pb(e,0)}function gb(e){return new pb(e,1)}var mb=function(e,t){if((o=e.length)>1)for(var n,r,o,i=1,a=e[t[0]],c=a.length;i=0;)n[t]=t;return n};function bb(e,t){return e[t]}var wb=function(){var e=bm([]),t=yb,n=mb,r=bb;function o(o){var i,a,c=e.apply(this,arguments),s=o.length,l=c.length,u=new Array(l);for(i=0;i0){for(var n,r,o,i=0,a=e[0].length;i1)for(var n,r,o,i,a,c,s=0,l=e[t[0]].length;s=0?(r[0]=i,r[1]=i+=o):o<0?(r[1]=a,r[0]=a+=o):r[0]=i},jb=function(e,t){if((n=e.length)>0){for(var n,r=0,o=e[t[0]],i=o.length;r0&&(r=(n=e[t[0]]).length)>0){for(var n,r,o,i=0,a=1;a0)){if(i/=d,d<0){if(i0){if(i>f)return;i>u&&(u=i)}if(i=r-s,d||!(i<0)){if(i/=d,d<0){if(i>f)return;i>u&&(u=i)}else if(d>0){if(i0)){if(i/=h,h<0){if(i0){if(i>f)return;i>u&&(u=i)}if(i=o-l,h||!(i<0)){if(i/=h,h<0){if(i>f)return;i>u&&(u=i)}else if(h>0){if(i0||f<1)||(u>0&&(e[0]=[s+u*d,l+u*h]),f<1&&(e[1]=[s+f*d,l+f*h]),!0)}}}}}function Ub(e,t,n,r,o){var i=e[1];if(i)return!0;var a,c,s=e[0],l=e.left,u=e.right,f=l[0],d=l[1],h=u[0],p=u[1],z=(f+h)/2,v=(d+p)/2;if(p===d){if(z=r)return;if(f>h){if(s){if(s[1]>=o)return}else s=[z,n];i=[z,o]}else{if(s){if(s[1]1)if(f>h){if(s){if(s[1]>=o)return}else s=[(n-c)/a,n];i=[(o-c)/a,o]}else{if(s){if(s[1]=r)return}else s=[t,a*t+c];i=[r,a*r+c]}else{if(s){if(s[0]=-dw)){var h=s*s+l*l,p=u*u+f*f,z=(f*h-l*p)/d,v=(s*p-u*h)/d,g=Zb.pop()||new Xb;g.arc=e,g.site=o,g.x=z+a,g.y=(g.cy=v+c)+Math.sqrt(z*z+v*v),e.circle=g;for(var m=null,y=lw._;y;)if(g.yfw)c=c.L;else{if(!((o=i-aw(c,a))>fw)){r>-fw?(t=c.P,n=c):o>-fw?(t=c,n=c.N):t=n=c;break}if(!c.R){t=c;break}c=c.R}!function(e){sw[e.index]={site:e,halfedges:[]}}(e);var s=tw(e);if(cw.insert(t,s),t||n){if(t===n)return Qb(t),n=tw(t.site),cw.insert(s,n),s.edge=n.edge=Ib(t.site,s.site),Kb(t),void Kb(n);if(n){Qb(t),Qb(n);var l=t.site,u=l[0],f=l[1],d=e[0]-u,h=e[1]-f,p=n.site,z=p[0]-u,v=p[1]-f,g=2*(d*v-h*z),m=d*d+h*h,y=z*z+v*v,b=[(v*m-h*y)/g+u,(d*y-z*m)/g+f];Fb(n.edge,l,p,b),s.edge=Ib(l,e,null,b),n.edge=Ib(e,p,null,b),Kb(t),Kb(n)}else s.edge=Ib(t.site,s.site)}}function iw(e,t){var n=e.site,r=n[0],o=n[1],i=o-t;if(!i)return r;var a=e.P;if(!a)return-1/0;var c=(n=a.site)[0],s=n[1],l=s-t;if(!l)return c;var u=c-r,f=1/i-1/l,d=u/l;return f?(-d+Math.sqrt(d*d-2*f*(u*u/(-2*l)-s+l/2+o-i/2)))/f+r:(r+c)/2}function aw(e,t){var n=e.N;if(n)return iw(n,t);var r=e.site;return r[1]===t?r[0]:1/0}var cw,sw,lw,uw,fw=1e-6,dw=1e-12;function hw(e,t){return t[1]-e[1]||t[0]-e[0]}function pw(e,t){var n,r,o,i=e.sort(hw).pop();for(uw=[],sw=new Array(e.length),cw=new Rb,lw=new Rb;;)if(o=$b,i&&(!o||i[1]fw||Math.abs(o[0][1]-o[1][1])>fw)||delete uw[i]}(a,c,s,l),function(e,t,n,r){var o,i,a,c,s,l,u,f,d,h,p,z,v=sw.length,g=!0;for(o=0;ofw||Math.abs(z-d)>fw)&&(s.splice(c,0,uw.push(Nb(a,h,Math.abs(p-e)fw?[e,Math.abs(f-e)fw?[Math.abs(d-r)fw?[n,Math.abs(f-n)fw?[Math.abs(d-t)=c)return null;var s=e-o.site[0],l=t-o.site[1],u=s*s+l*l;do{o=i.cells[r=a],a=null,o.halfedges.forEach((function(n){var r=i.edges[n],c=r.left;if(c!==o.site&&c||(c=r.right)){var s=e-c[0],l=t-c[1],f=s*s+l*l;fr?(r+o)/2:Math.min(0,r)||Math.max(0,o),a>i?(i+a)/2:Math.min(0,i)||Math.max(0,a))}var Sw=function(){var e,t,n=xw,r=jw,o=qw,i=Cw,a=_w,c=[0,1/0],s=[[-1/0,-1/0],[1/0,1/0]],l=250,u=_r,f=[],d=ze("start","zoom","end"),h=500,p=150,z=0;function v(e){e.property("__zoom",Mw).on("wheel.zoom",x).on("mousedown.zoom",j).on("dblclick.zoom",M).filter(a).on("touchstart.zoom",C).on("touchmove.zoom",_).on("touchend.zoom touchcancel.zoom",q).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function g(e,t){return(t=Math.max(c[0],Math.min(c[1],t)))===e.k?e:new mw(t,e.x,e.y)}function m(e,t,n){var r=t[0]-n[0]*e.k,o=t[1]-n[1]*e.k;return r===e.x&&o===e.y?e:new mw(e.k,r,o)}function y(e){return[(+e[0][0]+ +e[1][0])/2,(+e[0][1]+ +e[1][1])/2]}function b(e,t,n){e.on("start.zoom",(function(){w(this,arguments).start()})).on("interrupt.zoom end.zoom",(function(){w(this,arguments).end()})).tween("zoom",(function(){var e=this,o=arguments,i=w(e,o),a=r.apply(e,o),c=n||y(a),s=Math.max(a[1][0]-a[0][0],a[1][1]-a[0][1]),l=e.__zoom,f="function"===typeof t?t.apply(e,o):t,d=u(l.invert(c).concat(s/l.k),f.invert(c).concat(s/f.k));return function(e){if(1===e)e=f;else{var t=d(e),n=s/t[2];e=new mw(n,c[0]-t[0]*n,c[1]-t[1]*n)}i.zoom(null,e)}}))}function w(e,t){for(var n,r=0,o=f.length;rz}e.zoom("mouse",o(m(e.that.__zoom,e.mouse[0]=Re(e.that),e.mouse[1]),e.extent,s))}),!0).on("mouseup.zoom",(function(){r.on("mousemove.zoom mouseup.zoom",null),Ft(Oe.view,e.moved),kw(),e.end()}),!0),i=Re(this),a=Oe.clientX,c=Oe.clientY;Nt(Oe.view),ww(),e.mouse=[i,this.__zoom.invert(i)],bo(this),e.start()}}function M(){if(n.apply(this,arguments)){var e=this.__zoom,t=Re(this),i=e.invert(t),a=e.k*(Oe.shiftKey?.5:2),c=o(m(g(e,a),t,i),r.apply(this,arguments),s);kw(),l>0?Ht(this).transition().duration(l).call(b,c,t):Ht(this).call(v.transform,c)}}function C(){if(n.apply(this,arguments)){var t,r,o,i,a=w(this,arguments),c=Oe.changedTouches,s=c.length;for(ww(),r=0;r=0}n.d(t,"a",(function(){return o}))},function(e,t,n){"use strict";var r=n(31);function o(e){return e}var i=Object(r.a)(o);t.a=i},,function(e,t,n){"use strict";var r;Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var o=(0,((r=n(231))&&r.__esModule?r:{default:r}).default)("Batch",(function(){for(var e=arguments.length,t=new Array(e),n=0;n=0&&n.splice(r,1),e.className=n.join(" ")}(e,t)},t.list=function(e){return e.classList?Array.prototype.slice.apply(e.classList):e.className.split(" ")}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=r(n(211)),i=r(n(267)),a=function(){o.default.call(this)};(a.prototype=new o.default).extractSeries=function(e,t,n){},a.prototype.rollingAverage=function(e,t,n){},a.prototype.onPointsCreated_=function(e,t){for(var n=0;nr&&(s=r),li)&&(i=l),(null===o||s0&&t-1 in e)}x.fn=x.prototype={jquery:"3.6.0",constructor:x,length:0,toArray:function(){return c.call(this)},get:function(e){return null==e?c.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return x.each(this,e)},map:function(e){return this.pushStack(x.map(this,(function(t,n){return e.call(t,n,t)})))},slice:function(){return this.pushStack(c.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(x.grep(this,(function(e,t){return(t+1)%2})))},odd:function(){return this.pushStack(x.grep(this,(function(e,t){return t%2})))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n+~]|"+P+")"+P+"*"),W=new RegExp(P+"|>"),G=new RegExp(I),Y=new RegExp("^"+V+"$"),$={ID:new RegExp("^#("+V+")"),CLASS:new RegExp("^\\.("+V+")"),TAG:new RegExp("^("+V+"|[*])"),ATTR:new RegExp("^"+R),PSEUDO:new RegExp("^"+I),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:new RegExp("^(?:"+D+")$","i"),needsContext:new RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},Z=/HTML$/i,X=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+P+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,oe=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},ie=function(){d()},ae=be((function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()}),{dir:"parentNode",next:"legend"});try{A.apply(O=L.call(w.childNodes),w.childNodes),O[w.childNodes.length].nodeType}catch(Me){A={apply:O.length?function(e,t){T.apply(e,L.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}function ce(e,t,r,o){var i,c,l,u,f,p,g,m=t&&t.ownerDocument,w=t?t.nodeType:9;if(r=r||[],"string"!==typeof e||!e||1!==w&&9!==w&&11!==w)return r;if(!o&&(d(t),t=t||h,z)){if(11!==w&&(f=J.exec(e)))if(i=f[1]){if(9===w){if(!(l=t.getElementById(i)))return r;if(l.id===i)return r.push(l),r}else if(m&&(l=m.getElementById(i))&&y(t,l)&&l.id===i)return r.push(l),r}else{if(f[2])return A.apply(r,t.getElementsByTagName(e)),r;if((i=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return A.apply(r,t.getElementsByClassName(i)),r}if(n.qsa&&!_[e+" "]&&(!v||!v.test(e))&&(1!==w||"object"!==t.nodeName.toLowerCase())){if(g=e,m=t,1===w&&(W.test(e)||U.test(e))){for((m=ee.test(e)&&ge(t.parentNode)||t)===t&&n.scope||((u=t.getAttribute("id"))?u=u.replace(re,oe):t.setAttribute("id",u=b)),c=(p=a(e)).length;c--;)p[c]=(u?"#"+u:":scope")+" "+ye(p[c]);g=p.join(",")}try{return A.apply(r,m.querySelectorAll(g)),r}catch(k){_(e,!0)}finally{u===b&&t.removeAttribute("id")}}}return s(e.replace(F,"$1"),t,r,o)}function se(){var e=[];return function t(n,o){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=o}}function le(e){return e[b]=!0,e}function ue(e){var t=h.createElement("fieldset");try{return!!e(t)}catch(Me){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){for(var n=e.split("|"),o=n.length;o--;)r.attrHandle[n[o]]=t}function de(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function he(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ze(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ae(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function ve(e){return le((function(t){return t=+t,le((function(n,r){for(var o,i=e([],n.length,t),a=i.length;a--;)n[o=i[a]]&&(n[o]=!(r[o]=n[o]))}))}))}function ge(e){return e&&"undefined"!==typeof e.getElementsByTagName&&e}for(t in n=ce.support={},i=ce.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Z.test(t||n&&n.nodeName||"HTML")},d=ce.setDocument=function(e){var t,o,a=e?e.ownerDocument||e:w;return a!=h&&9===a.nodeType&&a.documentElement?(p=(h=a).documentElement,z=!i(h),w!=h&&(o=h.defaultView)&&o.top!==o&&(o.addEventListener?o.addEventListener("unload",ie,!1):o.attachEvent&&o.attachEvent("onunload",ie)),n.scope=ue((function(e){return p.appendChild(e).appendChild(h.createElement("div")),"undefined"!==typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length})),n.attributes=ue((function(e){return e.className="i",!e.getAttribute("className")})),n.getElementsByTagName=ue((function(e){return e.appendChild(h.createComment("")),!e.getElementsByTagName("*").length})),n.getElementsByClassName=Q.test(h.getElementsByClassName),n.getById=ue((function(e){return p.appendChild(e).id=b,!h.getElementsByName||!h.getElementsByName(b).length})),n.getById?(r.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!==typeof t.getElementById&&z){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(te,ne);return function(e){var n="undefined"!==typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!==typeof t.getElementById&&z){var n,r,o,i=t.getElementById(e);if(i){if((n=i.getAttributeNode("id"))&&n.value===e)return[i];for(o=t.getElementsByName(e),r=0;i=o[r++];)if((n=i.getAttributeNode("id"))&&n.value===e)return[i]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!==typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],o=0,i=t.getElementsByTagName(e);if("*"===e){for(;n=i[o++];)1===n.nodeType&&r.push(n);return r}return i},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!==typeof t.getElementsByClassName&&z)return t.getElementsByClassName(e)},g=[],v=[],(n.qsa=Q.test(h.querySelectorAll))&&(ue((function(e){var t;p.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+P+"*(?:value|"+D+")"),e.querySelectorAll("[id~="+b+"-]").length||v.push("~="),(t=h.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+P+"*name"+P+"*="+P+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")})),ue((function(e){e.innerHTML="";var t=h.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+P+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),p.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")}))),(n.matchesSelector=Q.test(m=p.matches||p.webkitMatchesSelector||p.mozMatchesSelector||p.oMatchesSelector||p.msMatchesSelector))&&ue((function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),g.push("!=",I)})),v=v.length&&new RegExp(v.join("|")),g=g.length&&new RegExp(g.join("|")),t=Q.test(p.compareDocumentPosition),y=t||Q.test(p.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},q=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e==h||e.ownerDocument==w&&y(w,e)?-1:t==h||t.ownerDocument==w&&y(w,t)?1:u?H(u,e)-H(u,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,o=e.parentNode,i=t.parentNode,a=[e],c=[t];if(!o||!i)return e==h?-1:t==h?1:o?-1:i?1:u?H(u,e)-H(u,t):0;if(o===i)return de(e,t);for(n=e;n=n.parentNode;)a.unshift(n);for(n=t;n=n.parentNode;)c.unshift(n);for(;a[r]===c[r];)r++;return r?de(a[r],c[r]):a[r]==w?-1:c[r]==w?1:0},h):h},ce.matches=function(e,t){return ce(e,null,null,t)},ce.matchesSelector=function(e,t){if(d(e),n.matchesSelector&&z&&!_[t+" "]&&(!g||!g.test(t))&&(!v||!v.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(Me){_(t,!0)}return ce(t,h,null,[e]).length>0},ce.contains=function(e,t){return(e.ownerDocument||e)!=h&&d(e),y(e,t)},ce.attr=function(e,t){(e.ownerDocument||e)!=h&&d(e);var o=r.attrHandle[t.toLowerCase()],i=o&&S.call(r.attrHandle,t.toLowerCase())?o(e,t,!z):void 0;return void 0!==i?i:n.attributes||!z?e.getAttribute(t):(i=e.getAttributeNode(t))&&i.specified?i.value:null},ce.escape=function(e){return(e+"").replace(re,oe)},ce.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},ce.uniqueSort=function(e){var t,r=[],o=0,i=0;if(f=!n.detectDuplicates,u=!n.sortStable&&e.slice(0),e.sort(q),f){for(;t=e[i++];)t===e[i]&&(o=r.push(i));for(;o--;)e.splice(r[o],1)}return u=null,e},o=ce.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"===typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r++];)n+=o(t);return n},(r=ce.selectors={cacheLength:50,createPseudo:le,match:$,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ce.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ce.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return $.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&G.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=j[e+" "];return t||(t=new RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&j(e,(function(e){return t.test("string"===typeof e.className&&e.className||"undefined"!==typeof e.getAttribute&&e.getAttribute("class")||"")}))},ATTR:function(e,t,n){return function(r){var o=ce.attr(r,e);return null==o?"!="===t:!t||(o+="","="===t?o===n:"!="===t?o!==n:"^="===t?n&&0===o.indexOf(n):"*="===t?n&&o.indexOf(n)>-1:"$="===t?n&&o.slice(-n.length)===n:"~="===t?(" "+o.replace(N," ")+" ").indexOf(n)>-1:"|="===t&&(o===n||o.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,o){var i="nth"!==e.slice(0,3),a="last"!==e.slice(-4),c="of-type"===t;return 1===r&&0===o?function(e){return!!e.parentNode}:function(t,n,s){var l,u,f,d,h,p,z=i!==a?"nextSibling":"previousSibling",v=t.parentNode,g=c&&t.nodeName.toLowerCase(),m=!s&&!c,y=!1;if(v){if(i){for(;z;){for(d=t;d=d[z];)if(c?d.nodeName.toLowerCase()===g:1===d.nodeType)return!1;p=z="only"===e&&!p&&"nextSibling"}return!0}if(p=[a?v.firstChild:v.lastChild],a&&m){for(y=(h=(l=(u=(f=(d=v)[b]||(d[b]={}))[d.uniqueID]||(f[d.uniqueID]={}))[e]||[])[0]===k&&l[1])&&l[2],d=h&&v.childNodes[h];d=++h&&d&&d[z]||(y=h=0)||p.pop();)if(1===d.nodeType&&++y&&d===t){u[e]=[k,h,y];break}}else if(m&&(y=h=(l=(u=(f=(d=t)[b]||(d[b]={}))[d.uniqueID]||(f[d.uniqueID]={}))[e]||[])[0]===k&&l[1]),!1===y)for(;(d=++h&&d&&d[z]||(y=h=0)||p.pop())&&((c?d.nodeName.toLowerCase()!==g:1!==d.nodeType)||!++y||(m&&((u=(f=d[b]||(d[b]={}))[d.uniqueID]||(f[d.uniqueID]={}))[e]=[k,y]),d!==t)););return(y-=o)===r||y%r===0&&y/r>=0}}},PSEUDO:function(e,t){var n,o=r.pseudos[e]||r.setFilters[e.toLowerCase()]||ce.error("unsupported pseudo: "+e);return o[b]?o(t):o.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?le((function(e,n){for(var r,i=o(e,t),a=i.length;a--;)e[r=H(e,i[a])]=!(n[r]=i[a])})):function(e){return o(e,0,n)}):o}},pseudos:{not:le((function(e){var t=[],n=[],r=c(e.replace(F,"$1"));return r[b]?le((function(e,t,n,o){for(var i,a=r(e,null,o,[]),c=e.length;c--;)(i=a[c])&&(e[c]=!(t[c]=i))})):function(e,o,i){return t[0]=e,r(t,null,i,n),t[0]=null,!n.pop()}})),has:le((function(e){return function(t){return ce(e,t).length>0}})),contains:le((function(e){return e=e.replace(te,ne),function(t){return(t.textContent||o(t)).indexOf(e)>-1}})),lang:le((function(e){return Y.test(e||"")||ce.error("unsupported lang: "+e),e=e.replace(te,ne).toLowerCase(),function(t){var n;do{if(n=z?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}})),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===p},focus:function(e){return e===h.activeElement&&(!h.hasFocus||h.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ze(!1),disabled:ze(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return K.test(e.nodeName)},input:function(e){return X.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:ve((function(){return[0]})),last:ve((function(e,t){return[t-1]})),eq:ve((function(e,t,n){return[n<0?n+t:n]})),even:ve((function(e,t){for(var n=0;nt?t:n;--r>=0;)e.push(r);return e})),gt:ve((function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){for(var o=e.length;o--;)if(!e[o](t,n,r))return!1;return!0}:e[0]}function ke(e,t,n,r,o){for(var i,a=[],c=0,s=e.length,l=null!=t;c-1&&(i[l]=!(a[l]=f))}}else g=ke(g===a?g.splice(p,g.length):g),o?o(null,a,g,s):A.apply(a,g)}))}function je(e){for(var t,n,o,i=e.length,a=r.relative[e[0].type],c=a||r.relative[" "],s=a?1:0,u=be((function(e){return e===t}),c,!0),f=be((function(e){return H(t,e)>-1}),c,!0),d=[function(e,n,r){var o=!a&&(r||n!==l)||((t=n).nodeType?u(e,n,r):f(e,n,r));return t=null,o}];s1&&we(d),s>1&&ye(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace(F,"$1"),n,s0,o=e.length>0,i=function(i,a,c,s,u){var f,p,v,g=0,m="0",y=i&&[],b=[],w=l,x=i||o&&r.find.TAG("*",u),j=k+=null==w?1:Math.random()||.1,M=x.length;for(u&&(l=a==h||a||u);m!==M&&null!=(f=x[m]);m++){if(o&&f){for(p=0,a||f.ownerDocument==h||(d(f),c=!z);v=e[p++];)if(v(f,a||h,c)){s.push(f);break}u&&(k=j)}n&&((f=!v&&f)&&g--,i&&y.push(f))}if(g+=m,n&&m!==g){for(p=0;v=t[p++];)v(y,b,a,c);if(i){if(g>0)for(;m--;)y[m]||b[m]||(b[m]=E.call(s));b=ke(b)}A.apply(s,b),u&&!i&&b.length>0&&g+t.length>1&&ce.uniqueSort(s)}return u&&(k=j,l=w),y};return n?le(i):i}(i,o))).selector=e}return c},s=ce.select=function(e,t,n,o){var i,s,l,u,f,d="function"===typeof e&&e,h=!o&&a(e=d.selector||e);if(n=n||[],1===h.length){if((s=h[0]=h[0].slice(0)).length>2&&"ID"===(l=s[0]).type&&9===t.nodeType&&z&&r.relative[s[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(te,ne),t)||[])[0]))return n;d&&(t=t.parentNode),e=e.slice(s.shift().value.length)}for(i=$.needsContext.test(e)?0:s.length;i--&&(l=s[i],!r.relative[u=l.type]);)if((f=r.find[u])&&(o=f(l.matches[0].replace(te,ne),ee.test(s[0].type)&&ge(t.parentNode)||t))){if(s.splice(i,1),!(e=o.length&&ye(s)))return A.apply(n,o),n;break}}return(d||c(e,h))(o,t,!z,n,!t||ee.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(q).join("")===b,n.detectDuplicates=!!f,d(),n.sortDetached=ue((function(e){return 1&e.compareDocumentPosition(h.createElement("fieldset"))})),ue((function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")}))||fe("type|href|height|width",(function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)})),n.attributes&&ue((function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")}))||fe("value",(function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue})),ue((function(e){return null==e.getAttribute("disabled")}))||fe(D,(function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null})),ce}(n);x.find=M,x.expr=M.selectors,x.expr[":"]=x.expr.pseudos,x.uniqueSort=x.unique=M.uniqueSort,x.text=M.getText,x.isXMLDoc=M.isXML,x.contains=M.contains,x.escapeSelector=M.escape;var C=function(e,t,n){for(var r=[],o=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(o&&x(e).is(n))break;r.push(e)}return r},_=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},q=x.expr.match.needsContext;function S(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var O=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function E(e,t,n){return g(t)?x.grep(e,(function(e,r){return!!t.call(e,r,e)!==n})):t.nodeType?x.grep(e,(function(e){return e===t!==n})):"string"!==typeof t?x.grep(e,(function(e){return u.call(t,e)>-1!==n})):x.filter(t,e,n)}x.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,(function(e){return 1===e.nodeType})))},x.fn.extend({find:function(e){var t,n,r=this.length,o=this;if("string"!==typeof e)return this.pushStack(x(e).filter((function(){for(t=0;t1?x.uniqueSort(n):n},filter:function(e){return this.pushStack(E(this,e||[],!1))},not:function(e){return this.pushStack(E(this,e||[],!0))},is:function(e){return!!E(this,"string"===typeof e&&q.test(e)?x(e):e||[],!1).length}});var T,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(x.fn.init=function(e,t,n){var r,o;if(!e)return this;if(n=n||T,"string"===typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:A.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:y,!0)),O.test(r[1])&&x.isPlainObject(t))for(r in t)g(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(o=y.getElementById(r[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(x):x.makeArray(e,this)}).prototype=x.fn,T=x(y);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function D(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}x.fn.extend({has:function(e){var t=x(e,this),n=t.length;return this.filter((function(){for(var e=0;e-1:1===n.nodeType&&x.find.matchesSelector(n,e))){i.push(n);break}return this.pushStack(i.length>1?x.uniqueSort(i):i)},index:function(e){return e?"string"===typeof e?u.call(x(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(x.uniqueSort(x.merge(this.get(),x(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return C(e,"parentNode")},parentsUntil:function(e,t,n){return C(e,"parentNode",n)},next:function(e){return D(e,"nextSibling")},prev:function(e){return D(e,"previousSibling")},nextAll:function(e){return C(e,"nextSibling")},prevAll:function(e){return C(e,"previousSibling")},nextUntil:function(e,t,n){return C(e,"nextSibling",n)},prevUntil:function(e,t,n){return C(e,"previousSibling",n)},siblings:function(e){return _((e.parentNode||{}).firstChild,e)},children:function(e){return _(e.firstChild)},contents:function(e){return null!=e.contentDocument&&a(e.contentDocument)?e.contentDocument:(S(e,"template")&&(e=e.content||e),x.merge([],e.childNodes))}},(function(e,t){x.fn[e]=function(n,r){var o=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"===typeof r&&(o=x.filter(r,o)),this.length>1&&(H[e]||x.uniqueSort(o),L.test(e)&&o.reverse()),this.pushStack(o)}}));var P=/[^\x20\t\r\n\f]+/g;function V(e){return e}function R(e){throw e}function I(e,t,n,r){var o;try{e&&g(o=e.promise)?o.call(e).done(t).fail(n):e&&g(o=e.then)?o.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}x.Callbacks=function(e){e="string"===typeof e?function(e){var t={};return x.each(e.match(P)||[],(function(e,n){t[n]=!0})),t}(e):x.extend({},e);var t,n,r,o,i=[],a=[],c=-1,s=function(){for(o=o||e.once,r=t=!0;a.length;c=-1)for(n=a.shift();++c-1;)i.splice(n,1),n<=c&&c--})),this},has:function(e){return e?x.inArray(e,i)>-1:i.length>0},empty:function(){return i&&(i=[]),this},disable:function(){return o=a=[],i=n="",this},disabled:function(){return!i},lock:function(){return o=a=[],n||t||(i=n=""),this},locked:function(){return!!o},fireWith:function(e,n){return o||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||s()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l},x.extend({Deferred:function(e){var t=[["notify","progress",x.Callbacks("memory"),x.Callbacks("memory"),2],["resolve","done",x.Callbacks("once memory"),x.Callbacks("once memory"),0,"resolved"],["reject","fail",x.Callbacks("once memory"),x.Callbacks("once memory"),1,"rejected"]],r="pending",o={state:function(){return r},always:function(){return i.done(arguments).fail(arguments),this},catch:function(e){return o.then(null,e)},pipe:function(){var e=arguments;return x.Deferred((function(n){x.each(t,(function(t,r){var o=g(e[r[4]])&&e[r[4]];i[r[1]]((function(){var e=o&&o.apply(this,arguments);e&&g(e.promise)?e.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[r[0]+"With"](this,o?[e]:arguments)}))})),e=null})).promise()},then:function(e,r,o){var i=0;function a(e,t,r,o){return function(){var c=this,s=arguments,l=function(){var n,l;if(!(e=i&&(r!==R&&(c=void 0,s=[n]),t.rejectWith(c,s))}};e?u():(x.Deferred.getStackHook&&(u.stackTrace=x.Deferred.getStackHook()),n.setTimeout(u))}}return x.Deferred((function(n){t[0][3].add(a(0,n,g(o)?o:V,n.notifyWith)),t[1][3].add(a(0,n,g(e)?e:V)),t[2][3].add(a(0,n,g(r)?r:R))})).promise()},promise:function(e){return null!=e?x.extend(e,o):o}},i={};return x.each(t,(function(e,n){var a=n[2],c=n[5];o[n[1]]=a.add,c&&a.add((function(){r=c}),t[3-e][2].disable,t[3-e][3].disable,t[0][2].lock,t[0][3].lock),a.add(n[3].fire),i[n[0]]=function(){return i[n[0]+"With"](this===i?void 0:this,arguments),this},i[n[0]+"With"]=a.fireWith})),o.promise(i),e&&e.call(i,i),i},when:function(e){var t=arguments.length,n=t,r=Array(n),o=c.call(arguments),i=x.Deferred(),a=function(e){return function(n){r[e]=this,o[e]=arguments.length>1?c.call(arguments):n,--t||i.resolveWith(r,o)}};if(t<=1&&(I(e,i.done(a(n)).resolve,i.reject,!t),"pending"===i.state()||g(o[n]&&o[n].then)))return i.then();for(;n--;)I(o[n],a(n),i.reject);return i.promise()}});var N=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;x.Deferred.exceptionHook=function(e,t){n.console&&n.console.warn&&e&&N.test(e.name)&&n.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},x.readyException=function(e){n.setTimeout((function(){throw e}))};var F=x.Deferred();function B(){y.removeEventListener("DOMContentLoaded",B),n.removeEventListener("load",B),x.ready()}x.fn.ready=function(e){return F.then(e).catch((function(e){x.readyException(e)})),this},x.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--x.readyWait:x.isReady)||(x.isReady=!0,!0!==e&&--x.readyWait>0||F.resolveWith(y,[x]))}}),x.ready.then=F.then,"complete"===y.readyState||"loading"!==y.readyState&&!y.documentElement.doScroll?n.setTimeout(x.ready):(y.addEventListener("DOMContentLoaded",B),n.addEventListener("load",B));var U=function e(t,n,r,o,i,a,c){var s=0,l=t.length,u=null==r;if("object"===k(r))for(s in i=!0,r)e(t,n,s,r[s],!0,a,c);else if(void 0!==o&&(i=!0,g(o)||(c=!0),u&&(c?(n.call(t,o),n=null):(u=n,n=function(e,t,n){return u.call(x(e),n)})),n))for(;s1,null,!0)},removeData:function(e){return this.each((function(){Q.remove(this,e)}))}}),x.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=K.get(e,t),n&&(!r||Array.isArray(n)?r=K.access(e,t,x.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,o=n.shift(),i=x._queueHooks(e,t);"inprogress"===o&&(o=n.shift(),r--),o&&("fx"===t&&n.unshift("inprogress"),delete i.stop,o.call(e,(function(){x.dequeue(e,t)}),i)),!r&&i&&i.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return K.get(e,n)||K.access(e,n,{empty:x.Callbacks("once memory").add((function(){K.remove(e,[t+"queue",n])}))})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!==typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]*)/i,ze=/^$|^module$|\/(?:java|ecma)script/i;!function(){var e=y.createDocumentFragment().appendChild(y.createElement("div")),t=y.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),v.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",v.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue,e.innerHTML="",v.option=!!e.lastChild}();var ve={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ge(e,t){var n;return n="undefined"!==typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!==typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&S(e,t)?x.merge([e],n):n}function me(e,t){for(var n=0,r=e.length;n",""]);var ye=/<|&#?\w+;/;function be(e,t,n,r,o){for(var i,a,c,s,l,u,f=t.createDocumentFragment(),d=[],h=0,p=e.length;h-1)o&&o.push(i);else if(l=ae(i),a=ge(f.appendChild(i),"script"),l&&me(a),n)for(u=0;i=a[u++];)ze.test(i.type||"")&&n.push(i);return f}var we=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function xe(){return!1}function je(e,t){return e===function(){try{return y.activeElement}catch(e){}}()===("focus"===t)}function Me(e,t,n,r,o,i){var a,c;if("object"===typeof t){for(c in"string"!==typeof n&&(r=r||n,n=void 0),t)Me(e,c,n,r,t[c],i);return e}if(null==r&&null==o?(o=n,r=n=void 0):null==o&&("string"===typeof n?(o=r,r=void 0):(o=r,r=n,n=void 0)),!1===o)o=xe;else if(!o)return e;return 1===i&&(a=o,(o=function(e){return x().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=x.guid++)),e.each((function(){x.event.add(this,t,o,r,n)}))}function Ce(e,t,n){n?(K.set(e,t,!1),x.event.add(e,t,{namespace:!1,handler:function(e){var r,o,i=K.get(this,t);if(1&e.isTrigger&&this[t]){if(i.length)(x.event.special[t]||{}).delegateType&&e.stopPropagation();else if(i=c.call(arguments),K.set(this,t,i),r=n(this,t),this[t](),i!==(o=K.get(this,t))||r?K.set(this,t,!1):o={},i!==o)return e.stopImmediatePropagation(),e.preventDefault(),o&&o.value}else i.length&&(K.set(this,t,{value:x.event.trigger(x.extend(i[0],x.Event.prototype),i.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===K.get(e,t)&&x.event.add(e,t,ke)}x.event={global:{},add:function(e,t,n,r,o){var i,a,c,s,l,u,f,d,h,p,z,v=K.get(e);if(Z(e))for(n.handler&&(n=(i=n).handler,o=i.selector),o&&x.find.matchesSelector(ie,o),n.guid||(n.guid=x.guid++),(s=v.events)||(s=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(t){return"undefined"!==typeof x&&x.event.triggered!==t.type?x.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(P)||[""]).length;l--;)h=z=(c=we.exec(t[l])||[])[1],p=(c[2]||"").split(".").sort(),h&&(f=x.event.special[h]||{},h=(o?f.delegateType:f.bindType)||h,f=x.event.special[h]||{},u=x.extend({type:h,origType:z,data:r,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:p.join(".")},i),(d=s[h])||((d=s[h]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,p,a)||e.addEventListener&&e.addEventListener(h,a)),f.add&&(f.add.call(e,u),u.handler.guid||(u.handler.guid=n.guid)),o?d.splice(d.delegateCount++,0,u):d.push(u),x.event.global[h]=!0)},remove:function(e,t,n,r,o){var i,a,c,s,l,u,f,d,h,p,z,v=K.hasData(e)&&K.get(e);if(v&&(s=v.events)){for(l=(t=(t||"").match(P)||[""]).length;l--;)if(h=z=(c=we.exec(t[l])||[])[1],p=(c[2]||"").split(".").sort(),h){for(f=x.event.special[h]||{},d=s[h=(r?f.delegateType:f.bindType)||h]||[],c=c[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=i=d.length;i--;)u=d[i],!o&&z!==u.origType||n&&n.guid!==u.guid||c&&!c.test(u.namespace)||r&&r!==u.selector&&("**"!==r||!u.selector)||(d.splice(i,1),u.selector&&d.delegateCount--,f.remove&&f.remove.call(e,u));a&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,p,v.handle)||x.removeEvent(e,h,v.handle),delete s[h])}else for(h in s)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(s)&&K.remove(e,"handle events")}},dispatch:function(e){var t,n,r,o,i,a,c=new Array(arguments.length),s=x.event.fix(e),l=(K.get(this,"events")||Object.create(null))[s.type]||[],u=x.event.special[s.type]||{};for(c[0]=s,t=1;t=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(i=[],a={},n=0;n-1:x.find(o,this,null,[l]).length),a[o]&&i.push(r);i.length&&c.push({elem:l,handlers:i})}return l=this,s\s*$/g;function Oe(e,t){return S(e,"table")&&S(11!==t.nodeType?t:t.firstChild,"tr")&&x(e).children("tbody")[0]||e}function Ee(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Te(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Ae(e,t){var n,r,o,i,a,c;if(1===t.nodeType){if(K.hasData(e)&&(c=K.get(e).events))for(o in K.remove(t,"handle events"),c)for(n=0,r=c[o].length;n1&&"string"===typeof p&&!v.checkClone&&qe.test(p))return e.each((function(o){var i=e.eq(o);z&&(t[0]=p.call(this,o,i.html())),He(i,t,n,r)}));if(d&&(i=(o=be(t,e[0].ownerDocument,!1,e,r)).firstChild,1===o.childNodes.length&&(o=i),i||r)){for(c=(a=x.map(ge(o,"script"),Ee)).length;f0&&me(a,!s&&ge(e,"script")),c},cleanData:function(e){for(var t,n,r,o=x.event.special,i=0;void 0!==(n=e[i]);i++)if(Z(n)){if(t=n[K.expando]){if(t.events)for(r in t.events)o[r]?x.event.remove(n,r):x.removeEvent(n,r,t.handle);n[K.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),x.fn.extend({detach:function(e){return De(this,e,!0)},remove:function(e){return De(this,e)},text:function(e){return U(this,(function(e){return void 0===e?x.text(this):this.empty().each((function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)}))}),null,e,arguments.length)},append:function(){return He(this,arguments,(function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Oe(this,e).appendChild(e)}))},prepend:function(){return He(this,arguments,(function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Oe(this,e);t.insertBefore(e,t.firstChild)}}))},before:function(){return He(this,arguments,(function(e){this.parentNode&&this.parentNode.insertBefore(e,this)}))},after:function(){return He(this,arguments,(function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)}))},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(ge(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map((function(){return x.clone(this,e,t)}))},html:function(e){return U(this,(function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"===typeof e&&!_e.test(e)&&!ve[(pe.exec(e)||["",""])[1].toLowerCase()]){e=x.htmlPrefilter(e);try{for(;n=0&&(s+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-i-s-c-.5))||0),s}function Je(e,t,n){var r=Ve(e),o=(!v.boxSizingReliable()||n)&&"border-box"===x.css(e,"boxSizing",!1,r),i=o,a=Ne(e,t,r),c="offset"+t[0].toUpperCase()+t.slice(1);if(Pe.test(a)){if(!n)return a;a="auto"}return(!v.boxSizingReliable()&&o||!v.reliableTrDimensions()&&S(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===x.css(e,"display",!1,r))&&e.getClientRects().length&&(o="border-box"===x.css(e,"boxSizing",!1,r),(i=c in e)&&(a=e[c])),(a=parseFloat(a)||0)+Qe(e,t,n||(o?"border":"content"),i,r,a)+"px"}function et(e,t,n,r,o){return new et.prototype.init(e,t,n,r,o)}x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Ne(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,i,a,c=$(t),s=$e.test(t),l=e.style;if(s||(t=Ge(c)),a=x.cssHooks[t]||x.cssHooks[c],void 0===n)return a&&"get"in a&&void 0!==(o=a.get(e,!1,r))?o:l[t];"string"===(i=typeof n)&&(o=re.exec(n))&&o[1]&&(n=le(e,t,o),i="number"),null!=n&&n===n&&("number"!==i||s||(n+=o&&o[3]||(x.cssNumber[c]?"":"px")),v.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(s?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var o,i,a,c=$(t);return $e.test(t)||(t=Ge(c)),(a=x.cssHooks[t]||x.cssHooks[c])&&"get"in a&&(o=a.get(e,!0,n)),void 0===o&&(o=Ne(e,t,r)),"normal"===o&&t in Xe&&(o=Xe[t]),""===n||n?(i=parseFloat(o),!0===n||isFinite(i)?i||0:o):o}}),x.each(["height","width"],(function(e,t){x.cssHooks[t]={get:function(e,n,r){if(n)return!Ye.test(x.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?Je(e,t,r):Re(e,Ze,(function(){return Je(e,t,r)}))},set:function(e,n,r){var o,i=Ve(e),a=!v.scrollboxSize()&&"absolute"===i.position,c=(a||r)&&"border-box"===x.css(e,"boxSizing",!1,i),s=r?Qe(e,t,r,c,i):0;return c&&a&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(i[t])-Qe(e,t,"border",!1,i)-.5)),s&&(o=re.exec(n))&&"px"!==(o[3]||"px")&&(e.style[t]=n,n=x.css(e,t)),Ke(0,n,s)}}})),x.cssHooks.marginLeft=Fe(v.reliableMarginLeft,(function(e,t){if(t)return(parseFloat(Ne(e,"marginLeft"))||e.getBoundingClientRect().left-Re(e,{marginLeft:0},(function(){return e.getBoundingClientRect().left})))+"px"})),x.each({margin:"",padding:"",border:"Width"},(function(e,t){x.cssHooks[e+t]={expand:function(n){for(var r=0,o={},i="string"===typeof n?n.split(" "):[n];r<4;r++)o[e+oe[r]+t]=i[r]||i[r-2]||i[0];return o}},"margin"!==e&&(x.cssHooks[e+t].set=Ke)})),x.fn.extend({css:function(e,t){return U(this,(function(e,t,n){var r,o,i={},a=0;if(Array.isArray(t)){for(r=Ve(e),o=t.length;a1)}}),x.Tween=et,et.prototype={constructor:et,init:function(e,t,n,r,o,i){this.elem=e,this.prop=n,this.easing=o||x.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=i||(x.cssNumber[n]?"":"px")},cur:function(){var e=et.propHooks[this.prop];return e&&e.get?e.get(this):et.propHooks._default.get(this)},run:function(e){var t,n=et.propHooks[this.prop];return this.options.duration?this.pos=t=x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):et.propHooks._default.set(this),this}},et.prototype.init.prototype=et.prototype,et.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=x.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):1!==e.elem.nodeType||!x.cssHooks[e.prop]&&null==e.elem.style[Ge(e.prop)]?e.elem[e.prop]=e.now:x.style(e.elem,e.prop,e.now+e.unit)}}},et.propHooks.scrollTop=et.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},x.fx=et.prototype.init,x.fx.step={};var tt,nt,rt=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function it(){nt&&(!1===y.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(it):n.setTimeout(it,x.fx.interval),x.fx.tick())}function at(){return n.setTimeout((function(){tt=void 0})),tt=Date.now()}function ct(e,t){var n,r=0,o={height:e};for(t=t?1:0;r<4;r+=2-t)o["margin"+(n=oe[r])]=o["padding"+n]=e;return t&&(o.opacity=o.width=e),o}function st(e,t,n){for(var r,o=(lt.tweeners[t]||[]).concat(lt.tweeners["*"]),i=0,a=o.length;i1)},removeAttr:function(e){return this.each((function(){x.removeAttr(this,e)}))}}),x.extend({attr:function(e,t,n){var r,o,i=e.nodeType;if(3!==i&&8!==i&&2!==i)return"undefined"===typeof e.getAttribute?x.prop(e,t,n):(1===i&&x.isXMLDoc(e)||(o=x.attrHooks[t.toLowerCase()]||(x.expr.match.bool.test(t)?ut:void 0)),void 0!==n?null===n?void x.removeAttr(e,t):o&&"set"in o&&void 0!==(r=o.set(e,n,t))?r:(e.setAttribute(t,n+""),n):o&&"get"in o&&null!==(r=o.get(e,t))?r:null==(r=x.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!v.radioValue&&"radio"===t&&S(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,o=t&&t.match(P);if(o&&1===e.nodeType)for(;n=o[r++];)e.removeAttribute(n)}}),ut={set:function(e,t,n){return!1===t?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.bool.source.match(/\w+/g),(function(e,t){var n=ft[t]||x.find.attr;ft[t]=function(e,t,r){var o,i,a=t.toLowerCase();return r||(i=ft[a],ft[a]=o,o=null!=n(e,t,r)?a:null,ft[a]=i),o}}));var dt=/^(?:input|select|textarea|button)$/i,ht=/^(?:a|area)$/i;function pt(e){return(e.match(P)||[]).join(" ")}function zt(e){return e.getAttribute&&e.getAttribute("class")||""}function vt(e){return Array.isArray(e)?e:"string"===typeof e&&e.match(P)||[]}x.fn.extend({prop:function(e,t){return U(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each((function(){delete this[x.propFix[e]||e]}))}}),x.extend({prop:function(e,t,n){var r,o,i=e.nodeType;if(3!==i&&8!==i&&2!==i)return 1===i&&x.isXMLDoc(e)||(t=x.propFix[t]||t,o=x.propHooks[t]),void 0!==n?o&&"set"in o&&void 0!==(r=o.set(e,n,t))?r:e[t]=n:o&&"get"in o&&null!==(r=o.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):dt.test(e.nodeName)||ht.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),v.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],(function(){x.propFix[this.toLowerCase()]=this})),x.fn.extend({addClass:function(e){var t,n,r,o,i,a,c,s=0;if(g(e))return this.each((function(t){x(this).addClass(e.call(this,t,zt(this)))}));if((t=vt(e)).length)for(;n=this[s++];)if(o=zt(n),r=1===n.nodeType&&" "+pt(o)+" "){for(a=0;i=t[a++];)r.indexOf(" "+i+" ")<0&&(r+=i+" ");o!==(c=pt(r))&&n.setAttribute("class",c)}return this},removeClass:function(e){var t,n,r,o,i,a,c,s=0;if(g(e))return this.each((function(t){x(this).removeClass(e.call(this,t,zt(this)))}));if(!arguments.length)return this.attr("class","");if((t=vt(e)).length)for(;n=this[s++];)if(o=zt(n),r=1===n.nodeType&&" "+pt(o)+" "){for(a=0;i=t[a++];)for(;r.indexOf(" "+i+" ")>-1;)r=r.replace(" "+i+" "," ");o!==(c=pt(r))&&n.setAttribute("class",c)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"===typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each((function(n){x(this).toggleClass(e.call(this,n,zt(this),t),t)})):this.each((function(){var t,o,i,a;if(r)for(o=0,i=x(this),a=vt(e);t=a[o++];)i.hasClass(t)?i.removeClass(t):i.addClass(t);else void 0!==e&&"boolean"!==n||((t=zt(this))&&K.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":K.get(this,"__className__")||""))}))},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+pt(zt(n))+" ").indexOf(t)>-1)return!0;return!1}});var gt=/\r/g;x.fn.extend({val:function(e){var t,n,r,o=this[0];return arguments.length?(r=g(e),this.each((function(n){var o;1===this.nodeType&&(null==(o=r?e.call(this,n,x(this).val()):e)?o="":"number"===typeof o?o+="":Array.isArray(o)&&(o=x.map(o,(function(e){return null==e?"":e+""}))),(t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,o,"value")||(this.value=o))}))):o?(t=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(o,"value"))?n:"string"===typeof(n=o.value)?n.replace(gt,""):null==n?"":n:void 0}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:pt(x.text(e))}},select:{get:function(e){var t,n,r,o=e.options,i=e.selectedIndex,a="select-one"===e.type,c=a?null:[],s=a?i+1:o.length;for(r=i<0?s:a?i:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),i}}}}),x.each(["radio","checkbox"],(function(){x.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=x.inArray(x(e).val(),t)>-1}},v.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})})),v.focusin="onfocusin"in n;var mt=/^(?:focusinfocus|focusoutblur)$/,yt=function(e){e.stopPropagation()};x.extend(x.event,{trigger:function(e,t,r,o){var i,a,c,s,l,u,f,d,p=[r||y],z=h.call(e,"type")?e.type:e,v=h.call(e,"namespace")?e.namespace.split("."):[];if(a=d=c=r=r||y,3!==r.nodeType&&8!==r.nodeType&&!mt.test(z+x.event.triggered)&&(z.indexOf(".")>-1&&(v=z.split("."),z=v.shift(),v.sort()),l=z.indexOf(":")<0&&"on"+z,(e=e[x.expando]?e:new x.Event(z,"object"===typeof e&&e)).isTrigger=o?2:3,e.namespace=v.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+v.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=r),t=null==t?[e]:x.makeArray(t,[e]),f=x.event.special[z]||{},o||!f.trigger||!1!==f.trigger.apply(r,t))){if(!o&&!f.noBubble&&!m(r)){for(s=f.delegateType||z,mt.test(s+z)||(a=a.parentNode);a;a=a.parentNode)p.push(a),c=a;c===(r.ownerDocument||y)&&p.push(c.defaultView||c.parentWindow||n)}for(i=0;(a=p[i++])&&!e.isPropagationStopped();)d=a,e.type=i>1?s:f.bindType||z,(u=(K.get(a,"events")||Object.create(null))[e.type]&&K.get(a,"handle"))&&u.apply(a,t),(u=l&&a[l])&&u.apply&&Z(a)&&(e.result=u.apply(a,t),!1===e.result&&e.preventDefault());return e.type=z,o||e.isDefaultPrevented()||f._default&&!1!==f._default.apply(p.pop(),t)||!Z(r)||l&&g(r[z])&&!m(r)&&((c=r[l])&&(r[l]=null),x.event.triggered=z,e.isPropagationStopped()&&d.addEventListener(z,yt),r[z](),e.isPropagationStopped()&&d.removeEventListener(z,yt),x.event.triggered=void 0,c&&(r[l]=c)),e.result}},simulate:function(e,t,n){var r=x.extend(new x.Event,n,{type:e,isSimulated:!0});x.event.trigger(r,null,t)}}),x.fn.extend({trigger:function(e,t){return this.each((function(){x.event.trigger(e,t,this)}))},triggerHandler:function(e,t){var n=this[0];if(n)return x.event.trigger(e,t,n,!0)}}),v.focusin||x.each({focus:"focusin",blur:"focusout"},(function(e,t){var n=function(e){x.event.simulate(t,e.target,x.event.fix(e))};x.event.special[t]={setup:function(){var r=this.ownerDocument||this.document||this,o=K.access(r,t);o||r.addEventListener(e,n,!0),K.access(r,t,(o||0)+1)},teardown:function(){var r=this.ownerDocument||this.document||this,o=K.access(r,t)-1;o?K.access(r,t,o):(r.removeEventListener(e,n,!0),K.remove(r,t))}}}));var bt=n.location,wt={guid:Date.now()},kt=/\?/;x.parseXML=function(e){var t,r;if(!e||"string"!==typeof e)return null;try{t=(new n.DOMParser).parseFromString(e,"text/xml")}catch(o){}return r=t&&t.getElementsByTagName("parsererror")[0],t&&!r||x.error("Invalid XML: "+(r?x.map(r.childNodes,(function(e){return e.textContent})).join("\n"):e)),t};var xt=/\[\]$/,jt=/\r?\n/g,Mt=/^(?:submit|button|image|reset|file)$/i,Ct=/^(?:input|select|textarea|keygen)/i;function _t(e,t,n,r){var o;if(Array.isArray(t))x.each(t,(function(t,o){n||xt.test(e)?r(e,o):_t(e+"["+("object"===typeof o&&null!=o?t:"")+"]",o,n,r)}));else if(n||"object"!==k(t))r(e,t);else for(o in t)_t(e+"["+o+"]",t[o],n,r)}x.param=function(e,t){var n,r=[],o=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,(function(){o(this.name,this.value)}));else for(n in e)_t(n,e[n],t,o);return r.join("&")},x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map((function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this})).filter((function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&Ct.test(this.nodeName)&&!Mt.test(e)&&(this.checked||!he.test(e))})).map((function(e,t){var n=x(this).val();return null==n?null:Array.isArray(n)?x.map(n,(function(e){return{name:t.name,value:e.replace(jt,"\r\n")}})):{name:t.name,value:n.replace(jt,"\r\n")}})).get()}});var qt=/%20/g,St=/#.*$/,Ot=/([?&])_=[^&]*/,Et=/^(.*?):[ \t]*([^\r\n]*)$/gm,Tt=/^(?:GET|HEAD)$/,At=/^\/\//,Lt={},Ht={},Dt="*/".concat("*"),Pt=y.createElement("a");function Vt(e){return function(t,n){"string"!==typeof t&&(n=t,t="*");var r,o=0,i=t.toLowerCase().match(P)||[];if(g(n))for(;r=i[o++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function Rt(e,t,n,r){var o={},i=e===Ht;function a(c){var s;return o[c]=!0,x.each(e[c]||[],(function(e,c){var l=c(t,n,r);return"string"!==typeof l||i||o[l]?i?!(s=l):void 0:(t.dataTypes.unshift(l),a(l),!1)})),s}return a(t.dataTypes[0])||!o["*"]&&a("*")}function It(e,t){var n,r,o=x.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((o[n]?e:r||(r={}))[n]=t[n]);return r&&x.extend(!0,e,r),e}Pt.href=bt.href,x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:bt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(bt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?It(It(e,x.ajaxSettings),t):It(x.ajaxSettings,e)},ajaxPrefilter:Vt(Lt),ajaxTransport:Vt(Ht),ajax:function(e,t){"object"===typeof e&&(t=e,e=void 0),t=t||{};var r,o,i,a,c,s,l,u,f,d,h=x.ajaxSetup({},t),p=h.context||h,z=h.context&&(p.nodeType||p.jquery)?x(p):x.event,v=x.Deferred(),g=x.Callbacks("once memory"),m=h.statusCode||{},b={},w={},k="canceled",j={readyState:0,getResponseHeader:function(e){var t;if(l){if(!a)for(a={};t=Et.exec(i);)a[t[1].toLowerCase()+" "]=(a[t[1].toLowerCase()+" "]||[]).concat(t[2]);t=a[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return l?i:null},setRequestHeader:function(e,t){return null==l&&(e=w[e.toLowerCase()]=w[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==l&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(l)j.always(e[j.status]);else for(t in e)m[t]=[m[t],e[t]];return this},abort:function(e){var t=e||k;return r&&r.abort(t),M(0,t),this}};if(v.promise(j),h.url=((e||h.url||bt.href)+"").replace(At,bt.protocol+"//"),h.type=t.method||t.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(P)||[""],null==h.crossDomain){s=y.createElement("a");try{s.href=h.url,s.href=s.href,h.crossDomain=Pt.protocol+"//"+Pt.host!==s.protocol+"//"+s.host}catch(C){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!==typeof h.data&&(h.data=x.param(h.data,h.traditional)),Rt(Lt,h,t,j),l)return j;for(f in(u=x.event&&h.global)&&0===x.active++&&x.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Tt.test(h.type),o=h.url.replace(St,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"===typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ot,"$1"),d=(kt.test(o)?"&":"?")+"_="+wt.guid+++d),h.url=o+d),h.ifModified&&(x.lastModified[o]&&j.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&j.setRequestHeader("If-None-Match",x.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||t.contentType)&&j.setRequestHeader("Content-Type",h.contentType),j.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+Dt+"; q=0.01":""):h.accepts["*"]),h.headers)j.setRequestHeader(f,h.headers[f]);if(h.beforeSend&&(!1===h.beforeSend.call(p,j,h)||l))return j.abort();if(k="abort",g.add(h.complete),j.done(h.success),j.fail(h.error),r=Rt(Ht,h,t,j)){if(j.readyState=1,u&&z.trigger("ajaxSend",[j,h]),l)return j;h.async&&h.timeout>0&&(c=n.setTimeout((function(){j.abort("timeout")}),h.timeout));try{l=!1,r.send(b,M)}catch(C){if(l)throw C;M(-1,C)}}else M(-1,"No Transport");function M(e,t,a,s){var f,d,y,b,w,k=t;l||(l=!0,c&&n.clearTimeout(c),r=void 0,i=s||"",j.readyState=e>0?4:0,f=e>=200&&e<300||304===e,a&&(b=function(e,t,n){for(var r,o,i,a,c=e.contents,s=e.dataTypes;"*"===s[0];)s.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(o in c)if(c[o]&&c[o].test(r)){s.unshift(o);break}if(s[0]in n)i=s[0];else{for(o in n){if(!s[0]||e.converters[o+" "+s[0]]){i=o;break}a||(a=o)}i=i||a}if(i)return i!==s[0]&&s.unshift(i),n[i]}(h,j,a)),!f&&x.inArray("script",h.dataTypes)>-1&&x.inArray("json",h.dataTypes)<0&&(h.converters["text script"]=function(){}),b=function(e,t,n,r){var o,i,a,c,s,l={},u=e.dataTypes.slice();if(u[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];for(i=u.shift();i;)if(e.responseFields[i]&&(n[e.responseFields[i]]=t),!s&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),s=i,i=u.shift())if("*"===i)i=s;else if("*"!==s&&s!==i){if(!(a=l[s+" "+i]||l["* "+i]))for(o in l)if((c=o.split(" "))[1]===i&&(a=l[s+" "+c[0]]||l["* "+c[0]])){!0===a?a=l[o]:!0!==l[o]&&(i=c[0],u.unshift(c[1]));break}if(!0!==a)if(a&&e.throws)t=a(t);else try{t=a(t)}catch(C){return{state:"parsererror",error:a?C:"No conversion from "+s+" to "+i}}}return{state:"success",data:t}}(h,b,j,f),f?(h.ifModified&&((w=j.getResponseHeader("Last-Modified"))&&(x.lastModified[o]=w),(w=j.getResponseHeader("etag"))&&(x.etag[o]=w)),204===e||"HEAD"===h.type?k="nocontent":304===e?k="notmodified":(k=b.state,d=b.data,f=!(y=b.error))):(y=k,!e&&k||(k="error",e<0&&(e=0))),j.status=e,j.statusText=(t||k)+"",f?v.resolveWith(p,[d,k,j]):v.rejectWith(p,[j,k,y]),j.statusCode(m),m=void 0,u&&z.trigger(f?"ajaxSuccess":"ajaxError",[j,h,f?d:y]),g.fireWith(p,[j,k]),u&&(z.trigger("ajaxComplete",[j,h]),--x.active||x.event.trigger("ajaxStop")))}return j},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,t){return x.get(e,void 0,t,"script")}}),x.each(["get","post"],(function(e,t){x[t]=function(e,n,r,o){return g(n)&&(o=o||r,r=n,n=void 0),x.ajax(x.extend({url:e,type:t,dataType:o,data:n,success:r},x.isPlainObject(e)&&e))}})),x.ajaxPrefilter((function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")})),x._evalUrl=function(e,t,n){return x.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){x.globalEval(e,t,n)}})},x.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map((function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e})).append(this)),this},wrapInner:function(e){return g(e)?this.each((function(t){x(this).wrapInner(e.call(this,t))})):this.each((function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)}))},wrap:function(e){var t=g(e);return this.each((function(n){x(this).wrapAll(t?e.call(this,n):e)}))},unwrap:function(e){return this.parent(e).not("body").each((function(){x(this).replaceWith(this.childNodes)})),this}}),x.expr.pseudos.hidden=function(e){return!x.expr.pseudos.visible(e)},x.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},x.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(e){}};var Nt={0:200,1223:204},Ft=x.ajaxSettings.xhr();v.cors=!!Ft&&"withCredentials"in Ft,v.ajax=Ft=!!Ft,x.ajaxTransport((function(e){var t,r;if(v.cors||Ft&&!e.crossDomain)return{send:function(o,i){var a,c=e.xhr();if(c.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(a in e.xhrFields)c[a]=e.xhrFields[a];for(a in e.mimeType&&c.overrideMimeType&&c.overrideMimeType(e.mimeType),e.crossDomain||o["X-Requested-With"]||(o["X-Requested-With"]="XMLHttpRequest"),o)c.setRequestHeader(a,o[a]);t=function(e){return function(){t&&(t=r=c.onload=c.onerror=c.onabort=c.ontimeout=c.onreadystatechange=null,"abort"===e?c.abort():"error"===e?"number"!==typeof c.status?i(0,"error"):i(c.status,c.statusText):i(Nt[c.status]||c.status,c.statusText,"text"!==(c.responseType||"text")||"string"!==typeof c.responseText?{binary:c.response}:{text:c.responseText},c.getAllResponseHeaders()))}},c.onload=t(),r=c.onerror=c.ontimeout=t("error"),void 0!==c.onabort?c.onabort=r:c.onreadystatechange=function(){4===c.readyState&&n.setTimeout((function(){t&&r()}))},t=t("abort");try{c.send(e.hasContent&&e.data||null)}catch(s){if(t)throw s}},abort:function(){t&&t()}}})),x.ajaxPrefilter((function(e){e.crossDomain&&(e.contents.script=!1)})),x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",(function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")})),x.ajaxTransport("script",(function(e){var t,n;if(e.crossDomain||e.scriptAttrs)return{send:function(r,o){t=x("\n\n The CSV file is of the form\n\n Date,SeriesA,SeriesB,SeriesC\n YYYYMMDD,A1,B1,C1\n YYYYMMDD,A2,B2,C2\n\n If the 'errorBars' option is set in the constructor, the input should be of\n the form\n Date,SeriesA,SeriesB,...\n YYYYMMDD,A1,sigmaA1,B1,sigmaB1,...\n YYYYMMDD,A2,sigmaA2,B2,sigmaB2,...\n\n If the 'fractions' option is set, the input should be of the form:\n\n Date,SeriesA,SeriesB,...\n YYYYMMDD,A1/B1,A2/B2,...\n YYYYMMDD,A1/B1,A2/B2,...\n\n And error bars will be calculated automatically using a binomial distribution.\n\n For further documentation and examples, see http://dygraphs.com/\n */\n\n'use strict';\n\nObject.defineProperty(exports, '__esModule', {\n value: true\n});\n\nvar _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();\n\nfunction _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar _dygraphLayout = require('./dygraph-layout');\n\nvar _dygraphLayout2 = _interopRequireDefault(_dygraphLayout);\n\nvar _dygraphCanvas = require('./dygraph-canvas');\n\nvar _dygraphCanvas2 = _interopRequireDefault(_dygraphCanvas);\n\nvar _dygraphOptions = require('./dygraph-options');\n\nvar _dygraphOptions2 = _interopRequireDefault(_dygraphOptions);\n\nvar _dygraphInteractionModel = require('./dygraph-interaction-model');\n\nvar _dygraphInteractionModel2 = _interopRequireDefault(_dygraphInteractionModel);\n\nvar _dygraphTickers = require('./dygraph-tickers');\n\nvar DygraphTickers = _interopRequireWildcard(_dygraphTickers);\n\nvar _dygraphUtils = require('./dygraph-utils');\n\nvar utils = _interopRequireWildcard(_dygraphUtils);\n\nvar _dygraphDefaultAttrs = require('./dygraph-default-attrs');\n\nvar _dygraphDefaultAttrs2 = _interopRequireDefault(_dygraphDefaultAttrs);\n\nvar _dygraphOptionsReference = require('./dygraph-options-reference');\n\nvar _dygraphOptionsReference2 = _interopRequireDefault(_dygraphOptionsReference);\n\nvar _iframeTarp = require('./iframe-tarp');\n\nvar _iframeTarp2 = _interopRequireDefault(_iframeTarp);\n\nvar _datahandlerDefault = require('./datahandler/default');\n\nvar _datahandlerDefault2 = _interopRequireDefault(_datahandlerDefault);\n\nvar _datahandlerBarsError = require('./datahandler/bars-error');\n\nvar _datahandlerBarsError2 = _interopRequireDefault(_datahandlerBarsError);\n\nvar _datahandlerBarsCustom = require('./datahandler/bars-custom');\n\nvar _datahandlerBarsCustom2 = _interopRequireDefault(_datahandlerBarsCustom);\n\nvar _datahandlerDefaultFractions = require('./datahandler/default-fractions');\n\nvar _datahandlerDefaultFractions2 = _interopRequireDefault(_datahandlerDefaultFractions);\n\nvar _datahandlerBarsFractions = require('./datahandler/bars-fractions');\n\nvar _datahandlerBarsFractions2 = _interopRequireDefault(_datahandlerBarsFractions);\n\nvar _datahandlerBars = require('./datahandler/bars');\n\nvar _datahandlerBars2 = _interopRequireDefault(_datahandlerBars);\n\nvar _pluginsAnnotations = require('./plugins/annotations');\n\nvar _pluginsAnnotations2 = _interopRequireDefault(_pluginsAnnotations);\n\nvar _pluginsAxes = require('./plugins/axes');\n\nvar _pluginsAxes2 = _interopRequireDefault(_pluginsAxes);\n\nvar _pluginsChartLabels = require('./plugins/chart-labels');\n\nvar _pluginsChartLabels2 = _interopRequireDefault(_pluginsChartLabels);\n\nvar _pluginsGrid = require('./plugins/grid');\n\nvar _pluginsGrid2 = _interopRequireDefault(_pluginsGrid);\n\nvar _pluginsLegend = require('./plugins/legend');\n\nvar _pluginsLegend2 = _interopRequireDefault(_pluginsLegend);\n\nvar _pluginsRangeSelector = require('./plugins/range-selector');\n\nvar _pluginsRangeSelector2 = _interopRequireDefault(_pluginsRangeSelector);\n\nvar _dygraphGviz = require('./dygraph-gviz');\n\nvar _dygraphGviz2 = _interopRequireDefault(_dygraphGviz);\n\n\"use strict\";\n\n/**\n * Creates an interactive, zoomable chart.\n *\n * @constructor\n * @param {div | String} div A div or the id of a div into which to construct\n * the chart.\n * @param {String | Function} file A file containing CSV data or a function\n * that returns this data. The most basic expected format for each line is\n * \"YYYY/MM/DD,val1,val2,...\". For more information, see\n * http://dygraphs.com/data.html.\n * @param {Object} attrs Various other attributes, e.g. errorBars determines\n * whether the input data contains error ranges. For a complete list of\n * options, see http://dygraphs.com/options.html.\n */\nvar Dygraph = function Dygraph(div, data, opts) {\n this.__init__(div, data, opts);\n};\n\nDygraph.NAME = \"Dygraph\";\nDygraph.VERSION = \"2.0.0\";\n\n// Various default values\nDygraph.DEFAULT_ROLL_PERIOD = 1;\nDygraph.DEFAULT_WIDTH = 480;\nDygraph.DEFAULT_HEIGHT = 320;\n\n// For max 60 Hz. animation:\nDygraph.ANIMATION_STEPS = 12;\nDygraph.ANIMATION_DURATION = 200;\n\n/**\n * Standard plotters. These may be used by clients.\n * Available plotters are:\n * - Dygraph.Plotters.linePlotter: draws central lines (most common)\n * - Dygraph.Plotters.errorPlotter: draws error bars\n * - Dygraph.Plotters.fillPlotter: draws fills under lines (used with fillGraph)\n *\n * By default, the plotter is [fillPlotter, errorPlotter, linePlotter].\n * This causes all the lines to be drawn over all the fills/error bars.\n */\nDygraph.Plotters = _dygraphCanvas2['default']._Plotters;\n\n// Used for initializing annotation CSS rules only once.\nDygraph.addedAnnotationCSS = false;\n\n/**\n * Initializes the Dygraph. This creates a new DIV and constructs the PlotKit\n * and context <canvas> inside of it. See the constructor for details.\n * on the parameters.\n * @param {Element} div the Element to render the graph into.\n * @param {string | Function} file Source data\n * @param {Object} attrs Miscellaneous other options\n * @private\n */\nDygraph.prototype.__init__ = function (div, file, attrs) {\n this.is_initial_draw_ = true;\n this.readyFns_ = [];\n\n // Support two-argument constructor\n if (attrs === null || attrs === undefined) {\n attrs = {};\n }\n\n attrs = Dygraph.copyUserAttrs_(attrs);\n\n if (typeof div == 'string') {\n div = document.getElementById(div);\n }\n\n if (!div) {\n throw new Error('Constructing dygraph with a non-existent div!');\n }\n\n // Copy the important bits into the object\n // TODO(danvk): most of these should just stay in the attrs_ dictionary.\n this.maindiv_ = div;\n this.file_ = file;\n this.rollPeriod_ = attrs.rollPeriod || Dygraph.DEFAULT_ROLL_PERIOD;\n this.previousVerticalX_ = -1;\n this.fractions_ = attrs.fractions || false;\n this.dateWindow_ = attrs.dateWindow || null;\n\n this.annotations_ = [];\n\n // Clear the div. This ensure that, if multiple dygraphs are passed the same\n // div, then only one will be drawn.\n div.innerHTML = \"\";\n\n // For historical reasons, the 'width' and 'height' options trump all CSS\n // rules _except_ for an explicit 'width' or 'height' on the div.\n // As an added convenience, if the div has zero height (like
does\n // without any styles), then we use a default height/width.\n if (div.style.width === '' && attrs.width) {\n div.style.width = attrs.width + \"px\";\n }\n if (div.style.height === '' && attrs.height) {\n div.style.height = attrs.height + \"px\";\n }\n if (div.style.height === '' && div.clientHeight === 0) {\n div.style.height = Dygraph.DEFAULT_HEIGHT + \"px\";\n if (div.style.width === '') {\n div.style.width = Dygraph.DEFAULT_WIDTH + \"px\";\n }\n }\n // These will be zero if the dygraph's div is hidden. In that case,\n // use the user-specified attributes if present. If not, use zero\n // and assume the user will call resize to fix things later.\n this.width_ = div.clientWidth || attrs.width || 0;\n this.height_ = div.clientHeight || attrs.height || 0;\n\n // TODO(danvk): set fillGraph to be part of attrs_ here, not user_attrs_.\n if (attrs.stackedGraph) {\n attrs.fillGraph = true;\n // TODO(nikhilk): Add any other stackedGraph checks here.\n }\n\n // DEPRECATION WARNING: All option processing should be moved from\n // attrs_ and user_attrs_ to options_, which holds all this information.\n //\n // Dygraphs has many options, some of which interact with one another.\n // To keep track of everything, we maintain two sets of options:\n //\n // this.user_attrs_ only options explicitly set by the user.\n // this.attrs_ defaults, options derived from user_attrs_, data.\n //\n // Options are then accessed this.attr_('attr'), which first looks at\n // user_attrs_ and then computed attrs_. This way Dygraphs can set intelligent\n // defaults without overriding behavior that the user specifically asks for.\n this.user_attrs_ = {};\n utils.update(this.user_attrs_, attrs);\n\n // This sequence ensures that Dygraph.DEFAULT_ATTRS is never modified.\n this.attrs_ = {};\n utils.updateDeep(this.attrs_, _dygraphDefaultAttrs2['default']);\n\n this.boundaryIds_ = [];\n this.setIndexByName_ = {};\n this.datasetIndex_ = [];\n\n this.registeredEvents_ = [];\n this.eventListeners_ = {};\n\n this.attributes_ = new _dygraphOptions2['default'](this);\n\n // Create the containing DIV and other interactive elements\n this.createInterface_();\n\n // Activate plugins.\n this.plugins_ = [];\n var plugins = Dygraph.PLUGINS.concat(this.getOption('plugins'));\n for (var i = 0; i < plugins.length; i++) {\n // the plugins option may contain either plugin classes or instances.\n // Plugin instances contain an activate method.\n var Plugin = plugins[i]; // either a constructor or an instance.\n var pluginInstance;\n if (typeof Plugin.activate !== 'undefined') {\n pluginInstance = Plugin;\n } else {\n pluginInstance = new Plugin();\n }\n\n var pluginDict = {\n plugin: pluginInstance,\n events: {},\n options: {},\n pluginOptions: {}\n };\n\n var handlers = pluginInstance.activate(this);\n for (var eventName in handlers) {\n if (!handlers.hasOwnProperty(eventName)) continue;\n // TODO(danvk): validate eventName.\n pluginDict.events[eventName] = handlers[eventName];\n }\n\n this.plugins_.push(pluginDict);\n }\n\n // At this point, plugins can no longer register event handlers.\n // Construct a map from event -> ordered list of [callback, plugin].\n for (var i = 0; i < this.plugins_.length; i++) {\n var plugin_dict = this.plugins_[i];\n for (var eventName in plugin_dict.events) {\n if (!plugin_dict.events.hasOwnProperty(eventName)) continue;\n var callback = plugin_dict.events[eventName];\n\n var pair = [plugin_dict.plugin, callback];\n if (!(eventName in this.eventListeners_)) {\n this.eventListeners_[eventName] = [pair];\n } else {\n this.eventListeners_[eventName].push(pair);\n }\n }\n }\n\n this.createDragInterface_();\n\n this.start_();\n};\n\n/**\n * Triggers a cascade of events to the various plugins which are interested in them.\n * Returns true if the \"default behavior\" should be prevented, i.e. if one\n * of the event listeners called event.preventDefault().\n * @private\n */\nDygraph.prototype.cascadeEvents_ = function (name, extra_props) {\n if (!(name in this.eventListeners_)) return false;\n\n // QUESTION: can we use objects & prototypes to speed this up?\n var e = {\n dygraph: this,\n cancelable: false,\n defaultPrevented: false,\n preventDefault: function preventDefault() {\n if (!e.cancelable) throw \"Cannot call preventDefault on non-cancelable event.\";\n e.defaultPrevented = true;\n },\n propagationStopped: false,\n stopPropagation: function stopPropagation() {\n e.propagationStopped = true;\n }\n };\n utils.update(e, extra_props);\n\n var callback_plugin_pairs = this.eventListeners_[name];\n if (callback_plugin_pairs) {\n for (var i = callback_plugin_pairs.length - 1; i >= 0; i--) {\n var plugin = callback_plugin_pairs[i][0];\n var callback = callback_plugin_pairs[i][1];\n callback.call(plugin, e);\n if (e.propagationStopped) break;\n }\n }\n return e.defaultPrevented;\n};\n\n/**\n * Fetch a plugin instance of a particular class. Only for testing.\n * @private\n * @param {!Class} type The type of the plugin.\n * @return {Object} Instance of the plugin, or null if there is none.\n */\nDygraph.prototype.getPluginInstance_ = function (type) {\n for (var i = 0; i < this.plugins_.length; i++) {\n var p = this.plugins_[i];\n if (p.plugin instanceof type) {\n return p.plugin;\n }\n }\n return null;\n};\n\n/**\n * Returns the zoomed status of the chart for one or both axes.\n *\n * Axis is an optional parameter. Can be set to 'x' or 'y'.\n *\n * The zoomed status for an axis is set whenever a user zooms using the mouse\n * or when the dateWindow or valueRange are updated. Double-clicking or calling\n * resetZoom() resets the zoom status for the chart.\n */\nDygraph.prototype.isZoomed = function (axis) {\n var isZoomedX = !!this.dateWindow_;\n if (axis === 'x') return isZoomedX;\n\n var isZoomedY = this.axes_.map(function (axis) {\n return !!axis.valueRange;\n }).indexOf(true) >= 0;\n if (axis === null || axis === undefined) {\n return isZoomedX || isZoomedY;\n }\n if (axis === 'y') return isZoomedY;\n\n throw new Error('axis parameter is [' + axis + '] must be null, \\'x\\' or \\'y\\'.');\n};\n\n/**\n * Returns information about the Dygraph object, including its containing ID.\n */\nDygraph.prototype.toString = function () {\n var maindiv = this.maindiv_;\n var id = maindiv && maindiv.id ? maindiv.id : maindiv;\n return \"[Dygraph \" + id + \"]\";\n};\n\n/**\n * @private\n * Returns the value of an option. This may be set by the user (either in the\n * constructor or by calling updateOptions) or by dygraphs, and may be set to a\n * per-series value.\n * @param {string} name The name of the option, e.g. 'rollPeriod'.\n * @param {string} [seriesName] The name of the series to which the option\n * will be applied. If no per-series value of this option is available, then\n * the global value is returned. This is optional.\n * @return { ... } The value of the option.\n */\nDygraph.prototype.attr_ = function (name, seriesName) {\n // For \"production\" code, this gets removed by uglifyjs.\n if (typeof process !== 'undefined') {\n if (process.env.NODE_ENV != 'production') {\n if (typeof _dygraphOptionsReference2['default'] === 'undefined') {\n console.error('Must include options reference JS for testing');\n } else if (!_dygraphOptionsReference2['default'].hasOwnProperty(name)) {\n console.error('Dygraphs is using property ' + name + ', which has no ' + 'entry in the Dygraphs.OPTIONS_REFERENCE listing.');\n // Only log this error once.\n _dygraphOptionsReference2['default'][name] = true;\n }\n }\n }\n return seriesName ? this.attributes_.getForSeries(name, seriesName) : this.attributes_.get(name);\n};\n\n/**\n * Returns the current value for an option, as set in the constructor or via\n * updateOptions. You may pass in an (optional) series name to get per-series\n * values for the option.\n *\n * All values returned by this method should be considered immutable. If you\n * modify them, there is no guarantee that the changes will be honored or that\n * dygraphs will remain in a consistent state. If you want to modify an option,\n * use updateOptions() instead.\n *\n * @param {string} name The name of the option (e.g. 'strokeWidth')\n * @param {string=} opt_seriesName Series name to get per-series values.\n * @return {*} The value of the option.\n */\nDygraph.prototype.getOption = function (name, opt_seriesName) {\n return this.attr_(name, opt_seriesName);\n};\n\n/**\n * Like getOption(), but specifically returns a number.\n * This is a convenience function for working with the Closure Compiler.\n * @param {string} name The name of the option (e.g. 'strokeWidth')\n * @param {string=} opt_seriesName Series name to get per-series values.\n * @return {number} The value of the option.\n * @private\n */\nDygraph.prototype.getNumericOption = function (name, opt_seriesName) {\n return (/** @type{number} */this.getOption(name, opt_seriesName)\n );\n};\n\n/**\n * Like getOption(), but specifically returns a string.\n * This is a convenience function for working with the Closure Compiler.\n * @param {string} name The name of the option (e.g. 'strokeWidth')\n * @param {string=} opt_seriesName Series name to get per-series values.\n * @return {string} The value of the option.\n * @private\n */\nDygraph.prototype.getStringOption = function (name, opt_seriesName) {\n return (/** @type{string} */this.getOption(name, opt_seriesName)\n );\n};\n\n/**\n * Like getOption(), but specifically returns a boolean.\n * This is a convenience function for working with the Closure Compiler.\n * @param {string} name The name of the option (e.g. 'strokeWidth')\n * @param {string=} opt_seriesName Series name to get per-series values.\n * @return {boolean} The value of the option.\n * @private\n */\nDygraph.prototype.getBooleanOption = function (name, opt_seriesName) {\n return (/** @type{boolean} */this.getOption(name, opt_seriesName)\n );\n};\n\n/**\n * Like getOption(), but specifically returns a function.\n * This is a convenience function for working with the Closure Compiler.\n * @param {string} name The name of the option (e.g. 'strokeWidth')\n * @param {string=} opt_seriesName Series name to get per-series values.\n * @return {function(...)} The value of the option.\n * @private\n */\nDygraph.prototype.getFunctionOption = function (name, opt_seriesName) {\n return (/** @type{function(...)} */this.getOption(name, opt_seriesName)\n );\n};\n\nDygraph.prototype.getOptionForAxis = function (name, axis) {\n return this.attributes_.getForAxis(name, axis);\n};\n\n/**\n * @private\n * @param {string} axis The name of the axis (i.e. 'x', 'y' or 'y2')\n * @return { ... } A function mapping string -> option value\n */\nDygraph.prototype.optionsViewForAxis_ = function (axis) {\n var self = this;\n return function (opt) {\n var axis_opts = self.user_attrs_.axes;\n if (axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)) {\n return axis_opts[axis][opt];\n }\n\n // I don't like that this is in a second spot.\n if (axis === 'x' && opt === 'logscale') {\n // return the default value.\n // TODO(konigsberg): pull the default from a global default.\n return false;\n }\n\n // user-specified attributes always trump defaults, even if they're less\n // specific.\n if (typeof self.user_attrs_[opt] != 'undefined') {\n return self.user_attrs_[opt];\n }\n\n axis_opts = self.attrs_.axes;\n if (axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)) {\n return axis_opts[axis][opt];\n }\n // check old-style axis options\n // TODO(danvk): add a deprecation warning if either of these match.\n if (axis == 'y' && self.axes_[0].hasOwnProperty(opt)) {\n return self.axes_[0][opt];\n } else if (axis == 'y2' && self.axes_[1].hasOwnProperty(opt)) {\n return self.axes_[1][opt];\n }\n return self.attr_(opt);\n };\n};\n\n/**\n * Returns the current rolling period, as set by the user or an option.\n * @return {number} The number of points in the rolling window\n */\nDygraph.prototype.rollPeriod = function () {\n return this.rollPeriod_;\n};\n\n/**\n * Returns the currently-visible x-range. This can be affected by zooming,\n * panning or a call to updateOptions.\n * Returns a two-element array: [left, right].\n * If the Dygraph has dates on the x-axis, these will be millis since epoch.\n */\nDygraph.prototype.xAxisRange = function () {\n return this.dateWindow_ ? this.dateWindow_ : this.xAxisExtremes();\n};\n\n/**\n * Returns the lower- and upper-bound x-axis values of the data set.\n */\nDygraph.prototype.xAxisExtremes = function () {\n var pad = this.getNumericOption('xRangePad') / this.plotter_.area.w;\n if (this.numRows() === 0) {\n return [0 - pad, 1 + pad];\n }\n var left = this.rawData_[0][0];\n var right = this.rawData_[this.rawData_.length - 1][0];\n if (pad) {\n // Must keep this in sync with dygraph-layout _evaluateLimits()\n var range = right - left;\n left -= range * pad;\n right += range * pad;\n }\n return [left, right];\n};\n\n/**\n * Returns the lower- and upper-bound y-axis values for each axis. These are\n * the ranges you'll get if you double-click to zoom out or call resetZoom().\n * The return value is an array of [low, high] tuples, one for each y-axis.\n */\nDygraph.prototype.yAxisExtremes = function () {\n // TODO(danvk): this is pretty inefficient\n var packed = this.gatherDatasets_(this.rolledSeries_, null);\n var extremes = packed.extremes;\n\n var saveAxes = this.axes_;\n this.computeYAxisRanges_(extremes);\n var newAxes = this.axes_;\n this.axes_ = saveAxes;\n return newAxes.map(function (axis) {\n return axis.extremeRange;\n });\n};\n\n/**\n * Returns the currently-visible y-range for an axis. This can be affected by\n * zooming, panning or a call to updateOptions. Axis indices are zero-based. If\n * called with no arguments, returns the range of the first axis.\n * Returns a two-element array: [bottom, top].\n */\nDygraph.prototype.yAxisRange = function (idx) {\n if (typeof idx == \"undefined\") idx = 0;\n if (idx < 0 || idx >= this.axes_.length) {\n return null;\n }\n var axis = this.axes_[idx];\n return [axis.computedValueRange[0], axis.computedValueRange[1]];\n};\n\n/**\n * Returns the currently-visible y-ranges for each axis. This can be affected by\n * zooming, panning, calls to updateOptions, etc.\n * Returns an array of [bottom, top] pairs, one for each y-axis.\n */\nDygraph.prototype.yAxisRanges = function () {\n var ret = [];\n for (var i = 0; i < this.axes_.length; i++) {\n ret.push(this.yAxisRange(i));\n }\n return ret;\n};\n\n// TODO(danvk): use these functions throughout dygraphs.\n/**\n * Convert from data coordinates to canvas/div X/Y coordinates.\n * If specified, do this conversion for the coordinate system of a particular\n * axis. Uses the first axis by default.\n * Returns a two-element array: [X, Y]\n *\n * Note: use toDomXCoord instead of toDomCoords(x, null) and use toDomYCoord\n * instead of toDomCoords(null, y, axis).\n */\nDygraph.prototype.toDomCoords = function (x, y, axis) {\n return [this.toDomXCoord(x), this.toDomYCoord(y, axis)];\n};\n\n/**\n * Convert from data x coordinates to canvas/div X coordinate.\n * If specified, do this conversion for the coordinate system of a particular\n * axis.\n * Returns a single value or null if x is null.\n */\nDygraph.prototype.toDomXCoord = function (x) {\n if (x === null) {\n return null;\n }\n\n var area = this.plotter_.area;\n var xRange = this.xAxisRange();\n return area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w;\n};\n\n/**\n * Convert from data x coordinates to canvas/div Y coordinate and optional\n * axis. Uses the first axis by default.\n *\n * returns a single value or null if y is null.\n */\nDygraph.prototype.toDomYCoord = function (y, axis) {\n var pct = this.toPercentYCoord(y, axis);\n\n if (pct === null) {\n return null;\n }\n var area = this.plotter_.area;\n return area.y + pct * area.h;\n};\n\n/**\n * Convert from canvas/div coords to data coordinates.\n * If specified, do this conversion for the coordinate system of a particular\n * axis. Uses the first axis by default.\n * Returns a two-element array: [X, Y].\n *\n * Note: use toDataXCoord instead of toDataCoords(x, null) and use toDataYCoord\n * instead of toDataCoords(null, y, axis).\n */\nDygraph.prototype.toDataCoords = function (x, y, axis) {\n return [this.toDataXCoord(x), this.toDataYCoord(y, axis)];\n};\n\n/**\n * Convert from canvas/div x coordinate to data coordinate.\n *\n * If x is null, this returns null.\n */\nDygraph.prototype.toDataXCoord = function (x) {\n if (x === null) {\n return null;\n }\n\n var area = this.plotter_.area;\n var xRange = this.xAxisRange();\n\n if (!this.attributes_.getForAxis(\"logscale\", 'x')) {\n return xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]);\n } else {\n var pct = (x - area.x) / area.w;\n return utils.logRangeFraction(xRange[0], xRange[1], pct);\n }\n};\n\n/**\n * Convert from canvas/div y coord to value.\n *\n * If y is null, this returns null.\n * if axis is null, this uses the first axis.\n */\nDygraph.prototype.toDataYCoord = function (y, axis) {\n if (y === null) {\n return null;\n }\n\n var area = this.plotter_.area;\n var yRange = this.yAxisRange(axis);\n\n if (typeof axis == \"undefined\") axis = 0;\n if (!this.attributes_.getForAxis(\"logscale\", axis)) {\n return yRange[0] + (area.y + area.h - y) / area.h * (yRange[1] - yRange[0]);\n } else {\n // Computing the inverse of toDomCoord.\n var pct = (y - area.y) / area.h;\n // Note reversed yRange, y1 is on top with pct==0.\n return utils.logRangeFraction(yRange[1], yRange[0], pct);\n }\n};\n\n/**\n * Converts a y for an axis to a percentage from the top to the\n * bottom of the drawing area.\n *\n * If the coordinate represents a value visible on the canvas, then\n * the value will be between 0 and 1, where 0 is the top of the canvas.\n * However, this method will return values outside the range, as\n * values can fall outside the canvas.\n *\n * If y is null, this returns null.\n * if axis is null, this uses the first axis.\n *\n * @param {number} y The data y-coordinate.\n * @param {number} [axis] The axis number on which the data coordinate lives.\n * @return {number} A fraction in [0, 1] where 0 = the top edge.\n */\nDygraph.prototype.toPercentYCoord = function (y, axis) {\n if (y === null) {\n return null;\n }\n if (typeof axis == \"undefined\") axis = 0;\n\n var yRange = this.yAxisRange(axis);\n\n var pct;\n var logscale = this.attributes_.getForAxis(\"logscale\", axis);\n if (logscale) {\n var logr0 = utils.log10(yRange[0]);\n var logr1 = utils.log10(yRange[1]);\n pct = (logr1 - utils.log10(y)) / (logr1 - logr0);\n } else {\n // yRange[1] - y is unit distance from the bottom.\n // yRange[1] - yRange[0] is the scale of the range.\n // (yRange[1] - y) / (yRange[1] - yRange[0]) is the % from the bottom.\n pct = (yRange[1] - y) / (yRange[1] - yRange[0]);\n }\n return pct;\n};\n\n/**\n * Converts an x value to a percentage from the left to the right of\n * the drawing area.\n *\n * If the coordinate represents a value visible on the canvas, then\n * the value will be between 0 and 1, where 0 is the left of the canvas.\n * However, this method will return values outside the range, as\n * values can fall outside the canvas.\n *\n * If x is null, this returns null.\n * @param {number} x The data x-coordinate.\n * @return {number} A fraction in [0, 1] where 0 = the left edge.\n */\nDygraph.prototype.toPercentXCoord = function (x) {\n if (x === null) {\n return null;\n }\n\n var xRange = this.xAxisRange();\n var pct;\n var logscale = this.attributes_.getForAxis(\"logscale\", 'x');\n if (logscale === true) {\n // logscale can be null so we test for true explicitly.\n var logr0 = utils.log10(xRange[0]);\n var logr1 = utils.log10(xRange[1]);\n pct = (utils.log10(x) - logr0) / (logr1 - logr0);\n } else {\n // x - xRange[0] is unit distance from the left.\n // xRange[1] - xRange[0] is the scale of the range.\n // The full expression below is the % from the left.\n pct = (x - xRange[0]) / (xRange[1] - xRange[0]);\n }\n return pct;\n};\n\n/**\n * Returns the number of columns (including the independent variable).\n * @return {number} The number of columns.\n */\nDygraph.prototype.numColumns = function () {\n if (!this.rawData_) return 0;\n return this.rawData_[0] ? this.rawData_[0].length : this.attr_(\"labels\").length;\n};\n\n/**\n * Returns the number of rows (excluding any header/label row).\n * @return {number} The number of rows, less any header.\n */\nDygraph.prototype.numRows = function () {\n if (!this.rawData_) return 0;\n return this.rawData_.length;\n};\n\n/**\n * Returns the value in the given row and column. If the row and column exceed\n * the bounds on the data, returns null. Also returns null if the value is\n * missing.\n * @param {number} row The row number of the data (0-based). Row 0 is the\n * first row of data, not a header row.\n * @param {number} col The column number of the data (0-based)\n * @return {number} The value in the specified cell or null if the row/col\n * were out of range.\n */\nDygraph.prototype.getValue = function (row, col) {\n if (row < 0 || row > this.rawData_.length) return null;\n if (col < 0 || col > this.rawData_[row].length) return null;\n\n return this.rawData_[row][col];\n};\n\n/**\n * Generates interface elements for the Dygraph: a containing div, a div to\n * display the current point, and a textbox to adjust the rolling average\n * period. Also creates the Renderer/Layout elements.\n * @private\n */\nDygraph.prototype.createInterface_ = function () {\n // Create the all-enclosing graph div\n var enclosing = this.maindiv_;\n\n this.graphDiv = document.createElement(\"div\");\n\n // TODO(danvk): any other styles that are useful to set here?\n this.graphDiv.style.textAlign = 'left'; // This is a CSS \"reset\"\n this.graphDiv.style.position = 'relative';\n enclosing.appendChild(this.graphDiv);\n\n // Create the canvas for interactive parts of the chart.\n this.canvas_ = utils.createCanvas();\n this.canvas_.style.position = \"absolute\";\n\n // ... and for static parts of the chart.\n this.hidden_ = this.createPlotKitCanvas_(this.canvas_);\n\n this.canvas_ctx_ = utils.getContext(this.canvas_);\n this.hidden_ctx_ = utils.getContext(this.hidden_);\n\n this.resizeElements_();\n\n // The interactive parts of the graph are drawn on top of the chart.\n this.graphDiv.appendChild(this.hidden_);\n this.graphDiv.appendChild(this.canvas_);\n this.mouseEventElement_ = this.createMouseEventElement_();\n\n // Create the grapher\n this.layout_ = new _dygraphLayout2['default'](this);\n\n var dygraph = this;\n\n this.mouseMoveHandler_ = function (e) {\n dygraph.mouseMove_(e);\n };\n\n this.mouseOutHandler_ = function (e) {\n // The mouse has left the chart if:\n // 1. e.target is inside the chart\n // 2. e.relatedTarget is outside the chart\n var target = e.target || e.fromElement;\n var relatedTarget = e.relatedTarget || e.toElement;\n if (utils.isNodeContainedBy(target, dygraph.graphDiv) && !utils.isNodeContainedBy(relatedTarget, dygraph.graphDiv)) {\n dygraph.mouseOut_(e);\n }\n };\n\n this.addAndTrackEvent(window, 'mouseout', this.mouseOutHandler_);\n this.addAndTrackEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_);\n\n // Don't recreate and register the resize handler on subsequent calls.\n // This happens when the graph is resized.\n if (!this.resizeHandler_) {\n this.resizeHandler_ = function (e) {\n dygraph.resize();\n };\n\n // Update when the window is resized.\n // TODO(danvk): drop frames depending on complexity of the chart.\n this.addAndTrackEvent(window, 'resize', this.resizeHandler_);\n }\n};\n\nDygraph.prototype.resizeElements_ = function () {\n this.graphDiv.style.width = this.width_ + \"px\";\n this.graphDiv.style.height = this.height_ + \"px\";\n\n var pixelRatioOption = this.getNumericOption('pixelRatio');\n\n var canvasScale = pixelRatioOption || utils.getContextPixelRatio(this.canvas_ctx_);\n this.canvas_.width = this.width_ * canvasScale;\n this.canvas_.height = this.height_ * canvasScale;\n this.canvas_.style.width = this.width_ + \"px\"; // for IE\n this.canvas_.style.height = this.height_ + \"px\"; // for IE\n if (canvasScale !== 1) {\n this.canvas_ctx_.scale(canvasScale, canvasScale);\n }\n\n var hiddenScale = pixelRatioOption || utils.getContextPixelRatio(this.hidden_ctx_);\n this.hidden_.width = this.width_ * hiddenScale;\n this.hidden_.height = this.height_ * hiddenScale;\n this.hidden_.style.width = this.width_ + \"px\"; // for IE\n this.hidden_.style.height = this.height_ + \"px\"; // for IE\n if (hiddenScale !== 1) {\n this.hidden_ctx_.scale(hiddenScale, hiddenScale);\n }\n};\n\n/**\n * Detach DOM elements in the dygraph and null out all data references.\n * Calling this when you're done with a dygraph can dramatically reduce memory\n * usage. See, e.g., the tests/perf.html example.\n */\nDygraph.prototype.destroy = function () {\n this.canvas_ctx_.restore();\n this.hidden_ctx_.restore();\n\n // Destroy any plugins, in the reverse order that they were registered.\n for (var i = this.plugins_.length - 1; i >= 0; i--) {\n var p = this.plugins_.pop();\n if (p.plugin.destroy) p.plugin.destroy();\n }\n\n var removeRecursive = function removeRecursive(node) {\n while (node.hasChildNodes()) {\n removeRecursive(node.firstChild);\n node.removeChild(node.firstChild);\n }\n };\n\n this.removeTrackedEvents_();\n\n // remove mouse event handlers (This may not be necessary anymore)\n utils.removeEvent(window, 'mouseout', this.mouseOutHandler_);\n utils.removeEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_);\n\n // remove window handlers\n utils.removeEvent(window, 'resize', this.resizeHandler_);\n this.resizeHandler_ = null;\n\n removeRecursive(this.maindiv_);\n\n var nullOut = function nullOut(obj) {\n for (var n in obj) {\n if (typeof obj[n] === 'object') {\n obj[n] = null;\n }\n }\n };\n // These may not all be necessary, but it can't hurt...\n nullOut(this.layout_);\n nullOut(this.plotter_);\n nullOut(this);\n};\n\n/**\n * Creates the canvas on which the chart will be drawn. Only the Renderer ever\n * draws on this particular canvas. All Dygraph work (i.e. drawing hover dots\n * or the zoom rectangles) is done on this.canvas_.\n * @param {Object} canvas The Dygraph canvas over which to overlay the plot\n * @return {Object} The newly-created canvas\n * @private\n */\nDygraph.prototype.createPlotKitCanvas_ = function (canvas) {\n var h = utils.createCanvas();\n h.style.position = \"absolute\";\n // TODO(danvk): h should be offset from canvas. canvas needs to include\n // some extra area to make it easier to zoom in on the far left and far\n // right. h needs to be precisely the plot area, so that clipping occurs.\n h.style.top = canvas.style.top;\n h.style.left = canvas.style.left;\n h.width = this.width_;\n h.height = this.height_;\n h.style.width = this.width_ + \"px\"; // for IE\n h.style.height = this.height_ + \"px\"; // for IE\n return h;\n};\n\n/**\n * Creates an overlay element used to handle mouse events.\n * @return {Object} The mouse event element.\n * @private\n */\nDygraph.prototype.createMouseEventElement_ = function () {\n return this.canvas_;\n};\n\n/**\n * Generate a set of distinct colors for the data series. This is done with a\n * color wheel. Saturation/Value are customizable, and the hue is\n * equally-spaced around the color wheel. If a custom set of colors is\n * specified, that is used instead.\n * @private\n */\nDygraph.prototype.setColors_ = function () {\n var labels = this.getLabels();\n var num = labels.length - 1;\n this.colors_ = [];\n this.colorsMap_ = {};\n\n // These are used for when no custom colors are specified.\n var sat = this.getNumericOption('colorSaturation') || 1.0;\n var val = this.getNumericOption('colorValue') || 0.5;\n var half = Math.ceil(num / 2);\n\n var colors = this.getOption('colors');\n var visibility = this.visibility();\n for (var i = 0; i < num; i++) {\n if (!visibility[i]) {\n continue;\n }\n var label = labels[i + 1];\n var colorStr = this.attributes_.getForSeries('color', label);\n if (!colorStr) {\n if (colors) {\n colorStr = colors[i % colors.length];\n } else {\n // alternate colors for high contrast.\n var idx = i % 2 ? half + (i + 1) / 2 : Math.ceil((i + 1) / 2);\n var hue = 1.0 * idx / (1 + num);\n colorStr = utils.hsvToRGB(hue, sat, val);\n }\n }\n this.colors_.push(colorStr);\n this.colorsMap_[label] = colorStr;\n }\n};\n\n/**\n * Return the list of colors. This is either the list of colors passed in the\n * attributes or the autogenerated list of rgb(r,g,b) strings.\n * This does not return colors for invisible series.\n * @return {Array.} The list of colors.\n */\nDygraph.prototype.getColors = function () {\n return this.colors_;\n};\n\n/**\n * Returns a few attributes of a series, i.e. its color, its visibility, which\n * axis it's assigned to, and its column in the original data.\n * Returns null if the series does not exist.\n * Otherwise, returns an object with column, visibility, color and axis properties.\n * The \"axis\" property will be set to 1 for y1 and 2 for y2.\n * The \"column\" property can be fed back into getValue(row, column) to get\n * values for this series.\n */\nDygraph.prototype.getPropertiesForSeries = function (series_name) {\n var idx = -1;\n var labels = this.getLabels();\n for (var i = 1; i < labels.length; i++) {\n if (labels[i] == series_name) {\n idx = i;\n break;\n }\n }\n if (idx == -1) return null;\n\n return {\n name: series_name,\n column: idx,\n visible: this.visibility()[idx - 1],\n color: this.colorsMap_[series_name],\n axis: 1 + this.attributes_.axisForSeries(series_name)\n };\n};\n\n/**\n * Create the text box to adjust the averaging period\n * @private\n */\nDygraph.prototype.createRollInterface_ = function () {\n var _this = this;\n\n // Create a roller if one doesn't exist already.\n var roller = this.roller_;\n if (!roller) {\n this.roller_ = roller = document.createElement(\"input\");\n roller.type = \"text\";\n roller.style.display = \"none\";\n roller.className = 'dygraph-roller';\n this.graphDiv.appendChild(roller);\n }\n\n var display = this.getBooleanOption('showRoller') ? 'block' : 'none';\n\n var area = this.getArea();\n var textAttr = {\n \"top\": area.y + area.h - 25 + \"px\",\n \"left\": area.x + 1 + \"px\",\n \"display\": display\n };\n roller.size = \"2\";\n roller.value = this.rollPeriod_;\n utils.update(roller.style, textAttr);\n\n roller.onchange = function () {\n return _this.adjustRoll(roller.value);\n };\n};\n\n/**\n * Set up all the mouse handlers needed to capture dragging behavior for zoom\n * events.\n * @private\n */\nDygraph.prototype.createDragInterface_ = function () {\n var context = {\n // Tracks whether the mouse is down right now\n isZooming: false,\n isPanning: false, // is this drag part of a pan?\n is2DPan: false, // if so, is that pan 1- or 2-dimensional?\n dragStartX: null, // pixel coordinates\n dragStartY: null, // pixel coordinates\n dragEndX: null, // pixel coordinates\n dragEndY: null, // pixel coordinates\n dragDirection: null,\n prevEndX: null, // pixel coordinates\n prevEndY: null, // pixel coordinates\n prevDragDirection: null,\n cancelNextDblclick: false, // see comment in dygraph-interaction-model.js\n\n // The value on the left side of the graph when a pan operation starts.\n initialLeftmostDate: null,\n\n // The number of units each pixel spans. (This won't be valid for log\n // scales)\n xUnitsPerPixel: null,\n\n // TODO(danvk): update this comment\n // The range in second/value units that the viewport encompasses during a\n // panning operation.\n dateRange: null,\n\n // Top-left corner of the canvas, in DOM coords\n // TODO(konigsberg): Rename topLeftCanvasX, topLeftCanvasY.\n px: 0,\n py: 0,\n\n // Values for use with panEdgeFraction, which limit how far outside the\n // graph's data boundaries it can be panned.\n boundedDates: null, // [minDate, maxDate]\n boundedValues: null, // [[minValue, maxValue] ...]\n\n // We cover iframes during mouse interactions. See comments in\n // dygraph-utils.js for more info on why this is a good idea.\n tarp: new _iframeTarp2['default'](),\n\n // contextB is the same thing as this context object but renamed.\n initializeMouseDown: function initializeMouseDown(event, g, contextB) {\n // prevents mouse drags from selecting page text.\n if (event.preventDefault) {\n event.preventDefault(); // Firefox, Chrome, etc.\n } else {\n event.returnValue = false; // IE\n event.cancelBubble = true;\n }\n\n var canvasPos = utils.findPos(g.canvas_);\n contextB.px = canvasPos.x;\n contextB.py = canvasPos.y;\n contextB.dragStartX = utils.dragGetX_(event, contextB);\n contextB.dragStartY = utils.dragGetY_(event, contextB);\n contextB.cancelNextDblclick = false;\n contextB.tarp.cover();\n },\n destroy: function destroy() {\n var context = this;\n if (context.isZooming || context.isPanning) {\n context.isZooming = false;\n context.dragStartX = null;\n context.dragStartY = null;\n }\n\n if (context.isPanning) {\n context.isPanning = false;\n context.draggingDate = null;\n context.dateRange = null;\n for (var i = 0; i < self.axes_.length; i++) {\n delete self.axes_[i].draggingValue;\n delete self.axes_[i].dragValueRange;\n }\n }\n\n context.tarp.uncover();\n }\n };\n\n var interactionModel = this.getOption(\"interactionModel\");\n\n // Self is the graph.\n var self = this;\n\n // Function that binds the graph and context to the handler.\n var bindHandler = function bindHandler(handler) {\n return function (event) {\n handler(event, self, context);\n };\n };\n\n for (var eventName in interactionModel) {\n if (!interactionModel.hasOwnProperty(eventName)) continue;\n this.addAndTrackEvent(this.mouseEventElement_, eventName, bindHandler(interactionModel[eventName]));\n }\n\n // If the user releases the mouse button during a drag, but not over the\n // canvas, then it doesn't count as a zooming action.\n if (!interactionModel.willDestroyContextMyself) {\n var mouseUpHandler = function mouseUpHandler(event) {\n context.destroy();\n };\n\n this.addAndTrackEvent(document, 'mouseup', mouseUpHandler);\n }\n};\n\n/**\n * Draw a gray zoom rectangle over the desired area of the canvas. Also clears\n * up any previous zoom rectangles that were drawn. This could be optimized to\n * avoid extra redrawing, but it's tricky to avoid interactions with the status\n * dots.\n *\n * @param {number} direction the direction of the zoom rectangle. Acceptable\n * values are utils.HORIZONTAL and utils.VERTICAL.\n * @param {number} startX The X position where the drag started, in canvas\n * coordinates.\n * @param {number} endX The current X position of the drag, in canvas coords.\n * @param {number} startY The Y position where the drag started, in canvas\n * coordinates.\n * @param {number} endY The current Y position of the drag, in canvas coords.\n * @param {number} prevDirection the value of direction on the previous call to\n * this function. Used to avoid excess redrawing\n * @param {number} prevEndX The value of endX on the previous call to this\n * function. Used to avoid excess redrawing\n * @param {number} prevEndY The value of endY on the previous call to this\n * function. Used to avoid excess redrawing\n * @private\n */\nDygraph.prototype.drawZoomRect_ = function (direction, startX, endX, startY, endY, prevDirection, prevEndX, prevEndY) {\n var ctx = this.canvas_ctx_;\n\n // Clean up from the previous rect if necessary\n if (prevDirection == utils.HORIZONTAL) {\n ctx.clearRect(Math.min(startX, prevEndX), this.layout_.getPlotArea().y, Math.abs(startX - prevEndX), this.layout_.getPlotArea().h);\n } else if (prevDirection == utils.VERTICAL) {\n ctx.clearRect(this.layout_.getPlotArea().x, Math.min(startY, prevEndY), this.layout_.getPlotArea().w, Math.abs(startY - prevEndY));\n }\n\n // Draw a light-grey rectangle to show the new viewing area\n if (direction == utils.HORIZONTAL) {\n if (endX && startX) {\n ctx.fillStyle = \"rgba(128,128,128,0.33)\";\n ctx.fillRect(Math.min(startX, endX), this.layout_.getPlotArea().y, Math.abs(endX - startX), this.layout_.getPlotArea().h);\n }\n } else if (direction == utils.VERTICAL) {\n if (endY && startY) {\n ctx.fillStyle = \"rgba(128,128,128,0.33)\";\n ctx.fillRect(this.layout_.getPlotArea().x, Math.min(startY, endY), this.layout_.getPlotArea().w, Math.abs(endY - startY));\n }\n }\n};\n\n/**\n * Clear the zoom rectangle (and perform no zoom).\n * @private\n */\nDygraph.prototype.clearZoomRect_ = function () {\n this.currentZoomRectArgs_ = null;\n this.canvas_ctx_.clearRect(0, 0, this.width_, this.height_);\n};\n\n/**\n * Zoom to something containing [lowX, highX]. These are pixel coordinates in\n * the canvas. The exact zoom window may be slightly larger if there are no data\n * points near lowX or highX. Don't confuse this function with doZoomXDates,\n * which accepts dates that match the raw data. This function redraws the graph.\n *\n * @param {number} lowX The leftmost pixel value that should be visible.\n * @param {number} highX The rightmost pixel value that should be visible.\n * @private\n */\nDygraph.prototype.doZoomX_ = function (lowX, highX) {\n this.currentZoomRectArgs_ = null;\n // Find the earliest and latest dates contained in this canvasx range.\n // Convert the call to date ranges of the raw data.\n var minDate = this.toDataXCoord(lowX);\n var maxDate = this.toDataXCoord(highX);\n this.doZoomXDates_(minDate, maxDate);\n};\n\n/**\n * Zoom to something containing [minDate, maxDate] values. Don't confuse this\n * method with doZoomX which accepts pixel coordinates. This function redraws\n * the graph.\n *\n * @param {number} minDate The minimum date that should be visible.\n * @param {number} maxDate The maximum date that should be visible.\n * @private\n */\nDygraph.prototype.doZoomXDates_ = function (minDate, maxDate) {\n var _this2 = this;\n\n // TODO(danvk): when xAxisRange is null (i.e. \"fit to data\", the animation\n // can produce strange effects. Rather than the x-axis transitioning slowly\n // between values, it can jerk around.)\n var old_window = this.xAxisRange();\n var new_window = [minDate, maxDate];\n var zoomCallback = this.getFunctionOption('zoomCallback');\n this.doAnimatedZoom(old_window, new_window, null, null, function () {\n if (zoomCallback) {\n zoomCallback.call(_this2, minDate, maxDate, _this2.yAxisRanges());\n }\n });\n};\n\n/**\n * Zoom to something containing [lowY, highY]. These are pixel coordinates in\n * the canvas. This function redraws the graph.\n *\n * @param {number} lowY The topmost pixel value that should be visible.\n * @param {number} highY The lowest pixel value that should be visible.\n * @private\n */\nDygraph.prototype.doZoomY_ = function (lowY, highY) {\n var _this3 = this;\n\n this.currentZoomRectArgs_ = null;\n // Find the highest and lowest values in pixel range for each axis.\n // Note that lowY (in pixels) corresponds to the max Value (in data coords).\n // This is because pixels increase as you go down on the screen, whereas data\n // coordinates increase as you go up the screen.\n var oldValueRanges = this.yAxisRanges();\n var newValueRanges = [];\n for (var i = 0; i < this.axes_.length; i++) {\n var hi = this.toDataYCoord(lowY, i);\n var low = this.toDataYCoord(highY, i);\n newValueRanges.push([low, hi]);\n }\n\n var zoomCallback = this.getFunctionOption('zoomCallback');\n this.doAnimatedZoom(null, null, oldValueRanges, newValueRanges, function () {\n if (zoomCallback) {\n var _xAxisRange = _this3.xAxisRange();\n\n var _xAxisRange2 = _slicedToArray(_xAxisRange, 2);\n\n var minX = _xAxisRange2[0];\n var maxX = _xAxisRange2[1];\n\n zoomCallback.call(_this3, minX, maxX, _this3.yAxisRanges());\n }\n });\n};\n\n/**\n * Transition function to use in animations. Returns values between 0.0\n * (totally old values) and 1.0 (totally new values) for each frame.\n * @private\n */\nDygraph.zoomAnimationFunction = function (frame, numFrames) {\n var k = 1.5;\n return (1.0 - Math.pow(k, -frame)) / (1.0 - Math.pow(k, -numFrames));\n};\n\n/**\n * Reset the zoom to the original view coordinates. This is the same as\n * double-clicking on the graph.\n */\nDygraph.prototype.resetZoom = function () {\n var _this4 = this;\n\n var dirtyX = this.isZoomed('x');\n var dirtyY = this.isZoomed('y');\n var dirty = dirtyX || dirtyY;\n\n // Clear any selection, since it's likely to be drawn in the wrong place.\n this.clearSelection();\n\n if (!dirty) return;\n\n // Calculate extremes to avoid lack of padding on reset.\n\n var _xAxisExtremes = this.xAxisExtremes();\n\n var _xAxisExtremes2 = _slicedToArray(_xAxisExtremes, 2);\n\n var minDate = _xAxisExtremes2[0];\n var maxDate = _xAxisExtremes2[1];\n\n var animatedZooms = this.getBooleanOption('animatedZooms');\n var zoomCallback = this.getFunctionOption('zoomCallback');\n\n // TODO(danvk): merge this block w/ the code below.\n // TODO(danvk): factor out a generic, public zoomTo method.\n if (!animatedZooms) {\n this.dateWindow_ = null;\n this.axes_.forEach(function (axis) {\n if (axis.valueRange) delete axis.valueRange;\n });\n\n this.drawGraph_();\n if (zoomCallback) {\n zoomCallback.call(this, minDate, maxDate, this.yAxisRanges());\n }\n return;\n }\n\n var oldWindow = null,\n newWindow = null,\n oldValueRanges = null,\n newValueRanges = null;\n if (dirtyX) {\n oldWindow = this.xAxisRange();\n newWindow = [minDate, maxDate];\n }\n\n if (dirtyY) {\n oldValueRanges = this.yAxisRanges();\n newValueRanges = this.yAxisExtremes();\n }\n\n this.doAnimatedZoom(oldWindow, newWindow, oldValueRanges, newValueRanges, function () {\n _this4.dateWindow_ = null;\n _this4.axes_.forEach(function (axis) {\n if (axis.valueRange) delete axis.valueRange;\n });\n if (zoomCallback) {\n zoomCallback.call(_this4, minDate, maxDate, _this4.yAxisRanges());\n }\n });\n};\n\n/**\n * Combined animation logic for all zoom functions.\n * either the x parameters or y parameters may be null.\n * @private\n */\nDygraph.prototype.doAnimatedZoom = function (oldXRange, newXRange, oldYRanges, newYRanges, callback) {\n var _this5 = this;\n\n var steps = this.getBooleanOption(\"animatedZooms\") ? Dygraph.ANIMATION_STEPS : 1;\n\n var windows = [];\n var valueRanges = [];\n var step, frac;\n\n if (oldXRange !== null && newXRange !== null) {\n for (step = 1; step <= steps; step++) {\n frac = Dygraph.zoomAnimationFunction(step, steps);\n windows[step - 1] = [oldXRange[0] * (1 - frac) + frac * newXRange[0], oldXRange[1] * (1 - frac) + frac * newXRange[1]];\n }\n }\n\n if (oldYRanges !== null && newYRanges !== null) {\n for (step = 1; step <= steps; step++) {\n frac = Dygraph.zoomAnimationFunction(step, steps);\n var thisRange = [];\n for (var j = 0; j < this.axes_.length; j++) {\n thisRange.push([oldYRanges[j][0] * (1 - frac) + frac * newYRanges[j][0], oldYRanges[j][1] * (1 - frac) + frac * newYRanges[j][1]]);\n }\n valueRanges[step - 1] = thisRange;\n }\n }\n\n utils.repeatAndCleanup(function (step) {\n if (valueRanges.length) {\n for (var i = 0; i < _this5.axes_.length; i++) {\n var w = valueRanges[step][i];\n _this5.axes_[i].valueRange = [w[0], w[1]];\n }\n }\n if (windows.length) {\n _this5.dateWindow_ = windows[step];\n }\n _this5.drawGraph_();\n }, steps, Dygraph.ANIMATION_DURATION / steps, callback);\n};\n\n/**\n * Get the current graph's area object.\n *\n * Returns: {x, y, w, h}\n */\nDygraph.prototype.getArea = function () {\n return this.plotter_.area;\n};\n\n/**\n * Convert a mouse event to DOM coordinates relative to the graph origin.\n *\n * Returns a two-element array: [X, Y].\n */\nDygraph.prototype.eventToDomCoords = function (event) {\n if (event.offsetX && event.offsetY) {\n return [event.offsetX, event.offsetY];\n } else {\n var eventElementPos = utils.findPos(this.mouseEventElement_);\n var canvasx = utils.pageX(event) - eventElementPos.x;\n var canvasy = utils.pageY(event) - eventElementPos.y;\n return [canvasx, canvasy];\n }\n};\n\n/**\n * Given a canvas X coordinate, find the closest row.\n * @param {number} domX graph-relative DOM X coordinate\n * Returns {number} row number.\n * @private\n */\nDygraph.prototype.findClosestRow = function (domX) {\n var minDistX = Infinity;\n var closestRow = -1;\n var sets = this.layout_.points;\n for (var i = 0; i < sets.length; i++) {\n var points = sets[i];\n var len = points.length;\n for (var j = 0; j < len; j++) {\n var point = points[j];\n if (!utils.isValidPoint(point, true)) continue;\n var dist = Math.abs(point.canvasx - domX);\n if (dist < minDistX) {\n minDistX = dist;\n closestRow = point.idx;\n }\n }\n }\n\n return closestRow;\n};\n\n/**\n * Given canvas X,Y coordinates, find the closest point.\n *\n * This finds the individual data point across all visible series\n * that's closest to the supplied DOM coordinates using the standard\n * Euclidean X,Y distance.\n *\n * @param {number} domX graph-relative DOM X coordinate\n * @param {number} domY graph-relative DOM Y coordinate\n * Returns: {row, seriesName, point}\n * @private\n */\nDygraph.prototype.findClosestPoint = function (domX, domY) {\n var minDist = Infinity;\n var dist, dx, dy, point, closestPoint, closestSeries, closestRow;\n for (var setIdx = this.layout_.points.length - 1; setIdx >= 0; --setIdx) {\n var points = this.layout_.points[setIdx];\n for (var i = 0; i < points.length; ++i) {\n point = points[i];\n if (!utils.isValidPoint(point)) continue;\n dx = point.canvasx - domX;\n dy = point.canvasy - domY;\n dist = dx * dx + dy * dy;\n if (dist < minDist) {\n minDist = dist;\n closestPoint = point;\n closestSeries = setIdx;\n closestRow = point.idx;\n }\n }\n }\n var name = this.layout_.setNames[closestSeries];\n return {\n row: closestRow,\n seriesName: name,\n point: closestPoint\n };\n};\n\n/**\n * Given canvas X,Y coordinates, find the touched area in a stacked graph.\n *\n * This first finds the X data point closest to the supplied DOM X coordinate,\n * then finds the series which puts the Y coordinate on top of its filled area,\n * using linear interpolation between adjacent point pairs.\n *\n * @param {number} domX graph-relative DOM X coordinate\n * @param {number} domY graph-relative DOM Y coordinate\n * Returns: {row, seriesName, point}\n * @private\n */\nDygraph.prototype.findStackedPoint = function (domX, domY) {\n var row = this.findClosestRow(domX);\n var closestPoint, closestSeries;\n for (var setIdx = 0; setIdx < this.layout_.points.length; ++setIdx) {\n var boundary = this.getLeftBoundary_(setIdx);\n var rowIdx = row - boundary;\n var points = this.layout_.points[setIdx];\n if (rowIdx >= points.length) continue;\n var p1 = points[rowIdx];\n if (!utils.isValidPoint(p1)) continue;\n var py = p1.canvasy;\n if (domX > p1.canvasx && rowIdx + 1 < points.length) {\n // interpolate series Y value using next point\n var p2 = points[rowIdx + 1];\n if (utils.isValidPoint(p2)) {\n var dx = p2.canvasx - p1.canvasx;\n if (dx > 0) {\n var r = (domX - p1.canvasx) / dx;\n py += r * (p2.canvasy - p1.canvasy);\n }\n }\n } else if (domX < p1.canvasx && rowIdx > 0) {\n // interpolate series Y value using previous point\n var p0 = points[rowIdx - 1];\n if (utils.isValidPoint(p0)) {\n var dx = p1.canvasx - p0.canvasx;\n if (dx > 0) {\n var r = (p1.canvasx - domX) / dx;\n py += r * (p0.canvasy - p1.canvasy);\n }\n }\n }\n // Stop if the point (domX, py) is above this series' upper edge\n if (setIdx === 0 || py < domY) {\n closestPoint = p1;\n closestSeries = setIdx;\n }\n }\n var name = this.layout_.setNames[closestSeries];\n return {\n row: row,\n seriesName: name,\n point: closestPoint\n };\n};\n\n/**\n * When the mouse moves in the canvas, display information about a nearby data\n * point and draw dots over those points in the data series. This function\n * takes care of cleanup of previously-drawn dots.\n * @param {Object} event The mousemove event from the browser.\n * @private\n */\nDygraph.prototype.mouseMove_ = function (event) {\n // This prevents JS errors when mousing over the canvas before data loads.\n var points = this.layout_.points;\n if (points === undefined || points === null) return;\n\n var canvasCoords = this.eventToDomCoords(event);\n var canvasx = canvasCoords[0];\n var canvasy = canvasCoords[1];\n\n var highlightSeriesOpts = this.getOption(\"highlightSeriesOpts\");\n var selectionChanged = false;\n if (highlightSeriesOpts && !this.isSeriesLocked()) {\n var closest;\n if (this.getBooleanOption(\"stackedGraph\")) {\n closest = this.findStackedPoint(canvasx, canvasy);\n } else {\n closest = this.findClosestPoint(canvasx, canvasy);\n }\n selectionChanged = this.setSelection(closest.row, closest.seriesName);\n } else {\n var idx = this.findClosestRow(canvasx);\n selectionChanged = this.setSelection(idx);\n }\n\n var callback = this.getFunctionOption(\"highlightCallback\");\n if (callback && selectionChanged) {\n callback.call(this, event, this.lastx_, this.selPoints_, this.lastRow_, this.highlightSet_);\n }\n};\n\n/**\n * Fetch left offset from the specified set index or if not passed, the\n * first defined boundaryIds record (see bug #236).\n * @private\n */\nDygraph.prototype.getLeftBoundary_ = function (setIdx) {\n if (this.boundaryIds_[setIdx]) {\n return this.boundaryIds_[setIdx][0];\n } else {\n for (var i = 0; i < this.boundaryIds_.length; i++) {\n if (this.boundaryIds_[i] !== undefined) {\n return this.boundaryIds_[i][0];\n }\n }\n return 0;\n }\n};\n\nDygraph.prototype.animateSelection_ = function (direction) {\n var totalSteps = 10;\n var millis = 30;\n if (this.fadeLevel === undefined) this.fadeLevel = 0;\n if (this.animateId === undefined) this.animateId = 0;\n var start = this.fadeLevel;\n var steps = direction < 0 ? start : totalSteps - start;\n if (steps <= 0) {\n if (this.fadeLevel) {\n this.updateSelection_(1.0);\n }\n return;\n }\n\n var thisId = ++this.animateId;\n var that = this;\n var cleanupIfClearing = function cleanupIfClearing() {\n // if we haven't reached fadeLevel 0 in the max frame time,\n // ensure that the clear happens and just go to 0\n if (that.fadeLevel !== 0 && direction < 0) {\n that.fadeLevel = 0;\n that.clearSelection();\n }\n };\n utils.repeatAndCleanup(function (n) {\n // ignore simultaneous animations\n if (that.animateId != thisId) return;\n\n that.fadeLevel += direction;\n if (that.fadeLevel === 0) {\n that.clearSelection();\n } else {\n that.updateSelection_(that.fadeLevel / totalSteps);\n }\n }, steps, millis, cleanupIfClearing);\n};\n\n/**\n * Draw dots over the selectied points in the data series. This function\n * takes care of cleanup of previously-drawn dots.\n * @private\n */\nDygraph.prototype.updateSelection_ = function (opt_animFraction) {\n /*var defaultPrevented = */\n this.cascadeEvents_('select', {\n selectedRow: this.lastRow_ === -1 ? undefined : this.lastRow_,\n selectedX: this.lastx_ === -1 ? undefined : this.lastx_,\n selectedPoints: this.selPoints_\n });\n // TODO(danvk): use defaultPrevented here?\n\n // Clear the previously drawn vertical, if there is one\n var i;\n var ctx = this.canvas_ctx_;\n if (this.getOption('highlightSeriesOpts')) {\n ctx.clearRect(0, 0, this.width_, this.height_);\n var alpha = 1.0 - this.getNumericOption('highlightSeriesBackgroundAlpha');\n var backgroundColor = utils.toRGB_(this.getOption('highlightSeriesBackgroundColor'));\n\n if (alpha) {\n // Activating background fade includes an animation effect for a gradual\n // fade. TODO(klausw): make this independently configurable if it causes\n // issues? Use a shared preference to control animations?\n var animateBackgroundFade = true;\n if (animateBackgroundFade) {\n if (opt_animFraction === undefined) {\n // start a new animation\n this.animateSelection_(1);\n return;\n }\n alpha *= opt_animFraction;\n }\n ctx.fillStyle = 'rgba(' + backgroundColor.r + ',' + backgroundColor.g + ',' + backgroundColor.b + ',' + alpha + ')';\n ctx.fillRect(0, 0, this.width_, this.height_);\n }\n\n // Redraw only the highlighted series in the interactive canvas (not the\n // static plot canvas, which is where series are usually drawn).\n this.plotter_._renderLineChart(this.highlightSet_, ctx);\n } else if (this.previousVerticalX_ >= 0) {\n // Determine the maximum highlight circle size.\n var maxCircleSize = 0;\n var labels = this.attr_('labels');\n for (i = 1; i < labels.length; i++) {\n var r = this.getNumericOption('highlightCircleSize', labels[i]);\n if (r > maxCircleSize) maxCircleSize = r;\n }\n var px = this.previousVerticalX_;\n ctx.clearRect(px - maxCircleSize - 1, 0, 2 * maxCircleSize + 2, this.height_);\n }\n\n if (this.selPoints_.length > 0) {\n // Draw colored circles over the center of each selected point\n var canvasx = this.selPoints_[0].canvasx;\n ctx.save();\n for (i = 0; i < this.selPoints_.length; i++) {\n var pt = this.selPoints_[i];\n if (isNaN(pt.canvasy)) continue;\n\n var circleSize = this.getNumericOption('highlightCircleSize', pt.name);\n var callback = this.getFunctionOption(\"drawHighlightPointCallback\", pt.name);\n var color = this.plotter_.colors[pt.name];\n if (!callback) {\n callback = utils.Circles.DEFAULT;\n }\n ctx.lineWidth = this.getNumericOption('strokeWidth', pt.name);\n ctx.strokeStyle = color;\n ctx.fillStyle = color;\n callback.call(this, this, pt.name, ctx, canvasx, pt.canvasy, color, circleSize, pt.idx);\n }\n ctx.restore();\n\n this.previousVerticalX_ = canvasx;\n }\n};\n\n/**\n * Manually set the selected points and display information about them in the\n * legend. The selection can be cleared using clearSelection() and queried\n * using getSelection().\n *\n * To set a selected series but not a selected point, call setSelection with\n * row=false and the selected series name.\n *\n * @param {number} row Row number that should be highlighted (i.e. appear with\n * hover dots on the chart).\n * @param {seriesName} optional series name to highlight that series with the\n * the highlightSeriesOpts setting.\n * @param { locked } optional If true, keep seriesName selected when mousing\n * over the graph, disabling closest-series highlighting. Call clearSelection()\n * to unlock it.\n */\nDygraph.prototype.setSelection = function (row, opt_seriesName, opt_locked) {\n // Extract the points we've selected\n this.selPoints_ = [];\n\n var changed = false;\n if (row !== false && row >= 0) {\n if (row != this.lastRow_) changed = true;\n this.lastRow_ = row;\n for (var setIdx = 0; setIdx < this.layout_.points.length; ++setIdx) {\n var points = this.layout_.points[setIdx];\n // Check if the point at the appropriate index is the point we're looking\n // for. If it is, just use it, otherwise search the array for a point\n // in the proper place.\n var setRow = row - this.getLeftBoundary_(setIdx);\n if (setRow >= 0 && setRow < points.length && points[setRow].idx == row) {\n var point = points[setRow];\n if (point.yval !== null) this.selPoints_.push(point);\n } else {\n for (var pointIdx = 0; pointIdx < points.length; ++pointIdx) {\n var point = points[pointIdx];\n if (point.idx == row) {\n if (point.yval !== null) {\n this.selPoints_.push(point);\n }\n break;\n }\n }\n }\n }\n } else {\n if (this.lastRow_ >= 0) changed = true;\n this.lastRow_ = -1;\n }\n\n if (this.selPoints_.length) {\n this.lastx_ = this.selPoints_[0].xval;\n } else {\n this.lastx_ = -1;\n }\n\n if (opt_seriesName !== undefined) {\n if (this.highlightSet_ !== opt_seriesName) changed = true;\n this.highlightSet_ = opt_seriesName;\n }\n\n if (opt_locked !== undefined) {\n this.lockedSet_ = opt_locked;\n }\n\n if (changed) {\n this.updateSelection_(undefined);\n }\n return changed;\n};\n\n/**\n * The mouse has left the canvas. Clear out whatever artifacts remain\n * @param {Object} event the mouseout event from the browser.\n * @private\n */\nDygraph.prototype.mouseOut_ = function (event) {\n if (this.getFunctionOption(\"unhighlightCallback\")) {\n this.getFunctionOption(\"unhighlightCallback\").call(this, event);\n }\n\n if (this.getBooleanOption(\"hideOverlayOnMouseOut\") && !this.lockedSet_) {\n this.clearSelection();\n }\n};\n\n/**\n * Clears the current selection (i.e. points that were highlighted by moving\n * the mouse over the chart).\n */\nDygraph.prototype.clearSelection = function () {\n this.cascadeEvents_('deselect', {});\n\n this.lockedSet_ = false;\n // Get rid of the overlay data\n if (this.fadeLevel) {\n this.animateSelection_(-1);\n return;\n }\n this.canvas_ctx_.clearRect(0, 0, this.width_, this.height_);\n this.fadeLevel = 0;\n this.selPoints_ = [];\n this.lastx_ = -1;\n this.lastRow_ = -1;\n this.highlightSet_ = null;\n};\n\n/**\n * Returns the number of the currently selected row. To get data for this row,\n * you can use the getValue method.\n * @return {number} row number, or -1 if nothing is selected\n */\nDygraph.prototype.getSelection = function () {\n if (!this.selPoints_ || this.selPoints_.length < 1) {\n return -1;\n }\n\n for (var setIdx = 0; setIdx < this.layout_.points.length; setIdx++) {\n var points = this.layout_.points[setIdx];\n for (var row = 0; row < points.length; row++) {\n if (points[row].x == this.selPoints_[0].x) {\n return points[row].idx;\n }\n }\n }\n return -1;\n};\n\n/**\n * Returns the name of the currently-highlighted series.\n * Only available when the highlightSeriesOpts option is in use.\n */\nDygraph.prototype.getHighlightSeries = function () {\n return this.highlightSet_;\n};\n\n/**\n * Returns true if the currently-highlighted series was locked\n * via setSelection(..., seriesName, true).\n */\nDygraph.prototype.isSeriesLocked = function () {\n return this.lockedSet_;\n};\n\n/**\n * Fires when there's data available to be graphed.\n * @param {string} data Raw CSV data to be plotted\n * @private\n */\nDygraph.prototype.loadedEvent_ = function (data) {\n this.rawData_ = this.parseCSV_(data);\n this.cascadeDataDidUpdateEvent_();\n this.predraw_();\n};\n\n/**\n * Add ticks on the x-axis representing years, months, quarters, weeks, or days\n * @private\n */\nDygraph.prototype.addXTicks_ = function () {\n // Determine the correct ticks scale on the x-axis: quarterly, monthly, ...\n var range;\n if (this.dateWindow_) {\n range = [this.dateWindow_[0], this.dateWindow_[1]];\n } else {\n range = this.xAxisExtremes();\n }\n\n var xAxisOptionsView = this.optionsViewForAxis_('x');\n var xTicks = xAxisOptionsView('ticker')(range[0], range[1], this.plotter_.area.w, // TODO(danvk): should be area.width\n xAxisOptionsView, this);\n // var msg = 'ticker(' + range[0] + ', ' + range[1] + ', ' + this.width_ + ', ' + this.attr_('pixelsPerXLabel') + ') -> ' + JSON.stringify(xTicks);\n // console.log(msg);\n this.layout_.setXTicks(xTicks);\n};\n\n/**\n * Returns the correct handler class for the currently set options.\n * @private\n */\nDygraph.prototype.getHandlerClass_ = function () {\n var handlerClass;\n if (this.attr_('dataHandler')) {\n handlerClass = this.attr_('dataHandler');\n } else if (this.fractions_) {\n if (this.getBooleanOption('errorBars')) {\n handlerClass = _datahandlerBarsFractions2['default'];\n } else {\n handlerClass = _datahandlerDefaultFractions2['default'];\n }\n } else if (this.getBooleanOption('customBars')) {\n handlerClass = _datahandlerBarsCustom2['default'];\n } else if (this.getBooleanOption('errorBars')) {\n handlerClass = _datahandlerBarsError2['default'];\n } else {\n handlerClass = _datahandlerDefault2['default'];\n }\n return handlerClass;\n};\n\n/**\n * @private\n * This function is called once when the chart's data is changed or the options\n * dictionary is updated. It is _not_ called when the user pans or zooms. The\n * idea is that values derived from the chart's data can be computed here,\n * rather than every time the chart is drawn. This includes things like the\n * number of axes, rolling averages, etc.\n */\nDygraph.prototype.predraw_ = function () {\n var start = new Date();\n\n // Create the correct dataHandler\n this.dataHandler_ = new (this.getHandlerClass_())();\n\n this.layout_.computePlotArea();\n\n // TODO(danvk): move more computations out of drawGraph_ and into here.\n this.computeYAxes_();\n\n if (!this.is_initial_draw_) {\n this.canvas_ctx_.restore();\n this.hidden_ctx_.restore();\n }\n\n this.canvas_ctx_.save();\n this.hidden_ctx_.save();\n\n // Create a new plotter.\n this.plotter_ = new _dygraphCanvas2['default'](this, this.hidden_, this.hidden_ctx_, this.layout_);\n\n // The roller sits in the bottom left corner of the chart. We don't know where\n // this will be until the options are available, so it's positioned here.\n this.createRollInterface_();\n\n this.cascadeEvents_('predraw');\n\n // Convert the raw data (a 2D array) into the internal format and compute\n // rolling averages.\n this.rolledSeries_ = [null]; // x-axis is the first series and it's special\n for (var i = 1; i < this.numColumns(); i++) {\n // var logScale = this.attr_('logscale', i); // TODO(klausw): this looks wrong // konigsberg thinks so too.\n var series = this.dataHandler_.extractSeries(this.rawData_, i, this.attributes_);\n if (this.rollPeriod_ > 1) {\n series = this.dataHandler_.rollingAverage(series, this.rollPeriod_, this.attributes_);\n }\n\n this.rolledSeries_.push(series);\n }\n\n // If the data or options have changed, then we'd better redraw.\n this.drawGraph_();\n\n // This is used to determine whether to do various animations.\n var end = new Date();\n this.drawingTimeMs_ = end - start;\n};\n\n/**\n * Point structure.\n *\n * xval_* and yval_* are the original unscaled data values,\n * while x_* and y_* are scaled to the range (0.0-1.0) for plotting.\n * yval_stacked is the cumulative Y value used for stacking graphs,\n * and bottom/top/minus/plus are used for error bar graphs.\n *\n * @typedef {{\n * idx: number,\n * name: string,\n * x: ?number,\n * xval: ?number,\n * y_bottom: ?number,\n * y: ?number,\n * y_stacked: ?number,\n * y_top: ?number,\n * yval_minus: ?number,\n * yval: ?number,\n * yval_plus: ?number,\n * yval_stacked\n * }}\n */\nDygraph.PointType = undefined;\n\n/**\n * Calculates point stacking for stackedGraph=true.\n *\n * For stacking purposes, interpolate or extend neighboring data across\n * NaN values based on stackedGraphNaNFill settings. This is for display\n * only, the underlying data value as shown in the legend remains NaN.\n *\n * @param {Array.} points Point array for a single series.\n * Updates each Point's yval_stacked property.\n * @param {Array.} cumulativeYval Accumulated top-of-graph stacked Y\n * values for the series seen so far. Index is the row number. Updated\n * based on the current series's values.\n * @param {Array.} seriesExtremes Min and max values, updated\n * to reflect the stacked values.\n * @param {string} fillMethod Interpolation method, one of 'all', 'inside', or\n * 'none'.\n * @private\n */\nDygraph.stackPoints_ = function (points, cumulativeYval, seriesExtremes, fillMethod) {\n var lastXval = null;\n var prevPoint = null;\n var nextPoint = null;\n var nextPointIdx = -1;\n\n // Find the next stackable point starting from the given index.\n var updateNextPoint = function updateNextPoint(idx) {\n // If we've previously found a non-NaN point and haven't gone past it yet,\n // just use that.\n if (nextPointIdx >= idx) return;\n\n // We haven't found a non-NaN point yet or have moved past it,\n // look towards the right to find a non-NaN point.\n for (var j = idx; j < points.length; ++j) {\n // Clear out a previously-found point (if any) since it's no longer\n // valid, we shouldn't use it for interpolation anymore.\n nextPoint = null;\n if (!isNaN(points[j].yval) && points[j].yval !== null) {\n nextPointIdx = j;\n nextPoint = points[j];\n break;\n }\n }\n };\n\n for (var i = 0; i < points.length; ++i) {\n var point = points[i];\n var xval = point.xval;\n if (cumulativeYval[xval] === undefined) {\n cumulativeYval[xval] = 0;\n }\n\n var actualYval = point.yval;\n if (isNaN(actualYval) || actualYval === null) {\n if (fillMethod == 'none') {\n actualYval = 0;\n } else {\n // Interpolate/extend for stacking purposes if possible.\n updateNextPoint(i);\n if (prevPoint && nextPoint && fillMethod != 'none') {\n // Use linear interpolation between prevPoint and nextPoint.\n actualYval = prevPoint.yval + (nextPoint.yval - prevPoint.yval) * ((xval - prevPoint.xval) / (nextPoint.xval - prevPoint.xval));\n } else if (prevPoint && fillMethod == 'all') {\n actualYval = prevPoint.yval;\n } else if (nextPoint && fillMethod == 'all') {\n actualYval = nextPoint.yval;\n } else {\n actualYval = 0;\n }\n }\n } else {\n prevPoint = point;\n }\n\n var stackedYval = cumulativeYval[xval];\n if (lastXval != xval) {\n // If an x-value is repeated, we ignore the duplicates.\n stackedYval += actualYval;\n cumulativeYval[xval] = stackedYval;\n }\n lastXval = xval;\n\n point.yval_stacked = stackedYval;\n\n if (stackedYval > seriesExtremes[1]) {\n seriesExtremes[1] = stackedYval;\n }\n if (stackedYval < seriesExtremes[0]) {\n seriesExtremes[0] = stackedYval;\n }\n }\n};\n\n/**\n * Loop over all fields and create datasets, calculating extreme y-values for\n * each series and extreme x-indices as we go.\n *\n * dateWindow is passed in as an explicit parameter so that we can compute\n * extreme values \"speculatively\", i.e. without actually setting state on the\n * dygraph.\n *\n * @param {Array.)>>} rolledSeries, where\n * rolledSeries[seriesIndex][row] = raw point, where\n * seriesIndex is the column number starting with 1, and\n * rawPoint is [x,y] or [x, [y, err]] or [x, [y, yminus, yplus]].\n * @param {?Array.} dateWindow [xmin, xmax] pair, or null.\n * @return {{\n * points: Array.>,\n * seriesExtremes: Array.>,\n * boundaryIds: Array.}}\n * @private\n */\nDygraph.prototype.gatherDatasets_ = function (rolledSeries, dateWindow) {\n var boundaryIds = [];\n var points = [];\n var cumulativeYval = []; // For stacked series.\n var extremes = {}; // series name -> [low, high]\n var seriesIdx, sampleIdx;\n var firstIdx, lastIdx;\n var axisIdx;\n\n // Loop over the fields (series). Go from the last to the first,\n // because if they're stacked that's how we accumulate the values.\n var num_series = rolledSeries.length - 1;\n var series;\n for (seriesIdx = num_series; seriesIdx >= 1; seriesIdx--) {\n if (!this.visibility()[seriesIdx - 1]) continue;\n\n // Prune down to the desired range, if necessary (for zooming)\n // Because there can be lines going to points outside of the visible area,\n // we actually prune to visible points, plus one on either side.\n if (dateWindow) {\n series = rolledSeries[seriesIdx];\n var low = dateWindow[0];\n var high = dateWindow[1];\n\n // TODO(danvk): do binary search instead of linear search.\n // TODO(danvk): pass firstIdx and lastIdx directly to the renderer.\n firstIdx = null;\n lastIdx = null;\n for (sampleIdx = 0; sampleIdx < series.length; sampleIdx++) {\n if (series[sampleIdx][0] >= low && firstIdx === null) {\n firstIdx = sampleIdx;\n }\n if (series[sampleIdx][0] <= high) {\n lastIdx = sampleIdx;\n }\n }\n\n if (firstIdx === null) firstIdx = 0;\n var correctedFirstIdx = firstIdx;\n var isInvalidValue = true;\n while (isInvalidValue && correctedFirstIdx > 0) {\n correctedFirstIdx--;\n // check if the y value is null.\n isInvalidValue = series[correctedFirstIdx][1] === null;\n }\n\n if (lastIdx === null) lastIdx = series.length - 1;\n var correctedLastIdx = lastIdx;\n isInvalidValue = true;\n while (isInvalidValue && correctedLastIdx < series.length - 1) {\n correctedLastIdx++;\n isInvalidValue = series[correctedLastIdx][1] === null;\n }\n\n if (correctedFirstIdx !== firstIdx) {\n firstIdx = correctedFirstIdx;\n }\n if (correctedLastIdx !== lastIdx) {\n lastIdx = correctedLastIdx;\n }\n\n boundaryIds[seriesIdx - 1] = [firstIdx, lastIdx];\n\n // .slice's end is exclusive, we want to include lastIdx.\n series = series.slice(firstIdx, lastIdx + 1);\n } else {\n series = rolledSeries[seriesIdx];\n boundaryIds[seriesIdx - 1] = [0, series.length - 1];\n }\n\n var seriesName = this.attr_(\"labels\")[seriesIdx];\n var seriesExtremes = this.dataHandler_.getExtremeYValues(series, dateWindow, this.getBooleanOption(\"stepPlot\", seriesName));\n\n var seriesPoints = this.dataHandler_.seriesToPoints(series, seriesName, boundaryIds[seriesIdx - 1][0]);\n\n if (this.getBooleanOption(\"stackedGraph\")) {\n axisIdx = this.attributes_.axisForSeries(seriesName);\n if (cumulativeYval[axisIdx] === undefined) {\n cumulativeYval[axisIdx] = [];\n }\n Dygraph.stackPoints_(seriesPoints, cumulativeYval[axisIdx], seriesExtremes, this.getBooleanOption(\"stackedGraphNaNFill\"));\n }\n\n extremes[seriesName] = seriesExtremes;\n points[seriesIdx] = seriesPoints;\n }\n\n return { points: points, extremes: extremes, boundaryIds: boundaryIds };\n};\n\n/**\n * Update the graph with new data. This method is called when the viewing area\n * has changed. If the underlying data or options have changed, predraw_ will\n * be called before drawGraph_ is called.\n *\n * @private\n */\nDygraph.prototype.drawGraph_ = function () {\n var start = new Date();\n\n // This is used to set the second parameter to drawCallback, below.\n var is_initial_draw = this.is_initial_draw_;\n this.is_initial_draw_ = false;\n\n this.layout_.removeAllDatasets();\n this.setColors_();\n this.attrs_.pointSize = 0.5 * this.getNumericOption('highlightCircleSize');\n\n var packed = this.gatherDatasets_(this.rolledSeries_, this.dateWindow_);\n var points = packed.points;\n var extremes = packed.extremes;\n this.boundaryIds_ = packed.boundaryIds;\n\n this.setIndexByName_ = {};\n var labels = this.attr_(\"labels\");\n var dataIdx = 0;\n for (var i = 1; i < points.length; i++) {\n if (!this.visibility()[i - 1]) continue;\n this.layout_.addDataset(labels[i], points[i]);\n this.datasetIndex_[i] = dataIdx++;\n }\n for (var i = 0; i < labels.length; i++) {\n this.setIndexByName_[labels[i]] = i;\n }\n\n this.computeYAxisRanges_(extremes);\n this.layout_.setYAxes(this.axes_);\n\n this.addXTicks_();\n\n // Tell PlotKit to use this new data and render itself\n this.layout_.evaluate();\n this.renderGraph_(is_initial_draw);\n\n if (this.getStringOption(\"timingName\")) {\n var end = new Date();\n console.log(this.getStringOption(\"timingName\") + \" - drawGraph: \" + (end - start) + \"ms\");\n }\n};\n\n/**\n * This does the work of drawing the chart. It assumes that the layout and axis\n * scales have already been set (e.g. by predraw_).\n *\n * @private\n */\nDygraph.prototype.renderGraph_ = function (is_initial_draw) {\n this.cascadeEvents_('clearChart');\n this.plotter_.clear();\n\n var underlayCallback = this.getFunctionOption('underlayCallback');\n if (underlayCallback) {\n // NOTE: we pass the dygraph object to this callback twice to avoid breaking\n // users who expect a deprecated form of this callback.\n underlayCallback.call(this, this.hidden_ctx_, this.layout_.getPlotArea(), this, this);\n }\n\n var e = {\n canvas: this.hidden_,\n drawingContext: this.hidden_ctx_\n };\n this.cascadeEvents_('willDrawChart', e);\n this.plotter_.render();\n this.cascadeEvents_('didDrawChart', e);\n this.lastRow_ = -1; // because plugins/legend.js clears the legend\n\n // TODO(danvk): is this a performance bottleneck when panning?\n // The interaction canvas should already be empty in that situation.\n this.canvas_.getContext('2d').clearRect(0, 0, this.width_, this.height_);\n\n var drawCallback = this.getFunctionOption(\"drawCallback\");\n if (drawCallback !== null) {\n drawCallback.call(this, this, is_initial_draw);\n }\n if (is_initial_draw) {\n this.readyFired_ = true;\n while (this.readyFns_.length > 0) {\n var fn = this.readyFns_.pop();\n fn(this);\n }\n }\n};\n\n/**\n * @private\n * Determine properties of the y-axes which are independent of the data\n * currently being displayed. This includes things like the number of axes and\n * the style of the axes. It does not include the range of each axis and its\n * tick marks.\n * This fills in this.axes_.\n * axes_ = [ { options } ]\n * indices are into the axes_ array.\n */\nDygraph.prototype.computeYAxes_ = function () {\n var axis, index, opts, v;\n\n // this.axes_ doesn't match this.attributes_.axes_.options. It's used for\n // data computation as well as options storage.\n // Go through once and add all the axes.\n this.axes_ = [];\n\n for (axis = 0; axis < this.attributes_.numAxes(); axis++) {\n // Add a new axis, making a copy of its per-axis options.\n opts = { g: this };\n utils.update(opts, this.attributes_.axisOptions(axis));\n this.axes_[axis] = opts;\n }\n\n for (axis = 0; axis < this.axes_.length; axis++) {\n if (axis === 0) {\n opts = this.optionsViewForAxis_('y' + (axis ? '2' : ''));\n v = opts(\"valueRange\");\n if (v) this.axes_[axis].valueRange = v;\n } else {\n // To keep old behavior\n var axes = this.user_attrs_.axes;\n if (axes && axes.y2) {\n v = axes.y2.valueRange;\n if (v) this.axes_[axis].valueRange = v;\n }\n }\n }\n};\n\n/**\n * Returns the number of y-axes on the chart.\n * @return {number} the number of axes.\n */\nDygraph.prototype.numAxes = function () {\n return this.attributes_.numAxes();\n};\n\n/**\n * @private\n * Returns axis properties for the given series.\n * @param {string} setName The name of the series for which to get axis\n * properties, e.g. 'Y1'.\n * @return {Object} The axis properties.\n */\nDygraph.prototype.axisPropertiesForSeries = function (series) {\n // TODO(danvk): handle errors.\n return this.axes_[this.attributes_.axisForSeries(series)];\n};\n\n/**\n * @private\n * Determine the value range and tick marks for each axis.\n * @param {Object} extremes A mapping from seriesName -> [low, high]\n * This fills in the valueRange and ticks fields in each entry of this.axes_.\n */\nDygraph.prototype.computeYAxisRanges_ = function (extremes) {\n var isNullUndefinedOrNaN = function isNullUndefinedOrNaN(num) {\n return isNaN(parseFloat(num));\n };\n var numAxes = this.attributes_.numAxes();\n var ypadCompat, span, series, ypad;\n\n var p_axis;\n\n // Compute extreme values, a span and tick marks for each axis.\n for (var i = 0; i < numAxes; i++) {\n var axis = this.axes_[i];\n var logscale = this.attributes_.getForAxis(\"logscale\", i);\n var includeZero = this.attributes_.getForAxis(\"includeZero\", i);\n var independentTicks = this.attributes_.getForAxis(\"independentTicks\", i);\n series = this.attributes_.seriesForAxis(i);\n\n // Add some padding. This supports two Y padding operation modes:\n //\n // - backwards compatible (yRangePad not set):\n // 10% padding for automatic Y ranges, but not for user-supplied\n // ranges, and move a close-to-zero edge to zero, since drawing at the edge\n // results in invisible lines. Unfortunately lines drawn at the edge of a\n // user-supplied range will still be invisible. If logscale is\n // set, add a variable amount of padding at the top but\n // none at the bottom.\n //\n // - new-style (yRangePad set by the user):\n // always add the specified Y padding.\n //\n ypadCompat = true;\n ypad = 0.1; // add 10%\n var yRangePad = this.getNumericOption('yRangePad');\n if (yRangePad !== null) {\n ypadCompat = false;\n // Convert pixel padding to ratio\n ypad = yRangePad / this.plotter_.area.h;\n }\n\n if (series.length === 0) {\n // If no series are defined or visible then use a reasonable default\n axis.extremeRange = [0, 1];\n } else {\n // Calculate the extremes of extremes.\n var minY = Infinity; // extremes[series[0]][0];\n var maxY = -Infinity; // extremes[series[0]][1];\n var extremeMinY, extremeMaxY;\n\n for (var j = 0; j < series.length; j++) {\n // this skips invisible series\n if (!extremes.hasOwnProperty(series[j])) continue;\n\n // Only use valid extremes to stop null data series' from corrupting the scale.\n extremeMinY = extremes[series[j]][0];\n if (extremeMinY !== null) {\n minY = Math.min(extremeMinY, minY);\n }\n extremeMaxY = extremes[series[j]][1];\n if (extremeMaxY !== null) {\n maxY = Math.max(extremeMaxY, maxY);\n }\n }\n\n // Include zero if requested by the user.\n if (includeZero && !logscale) {\n if (minY > 0) minY = 0;\n if (maxY < 0) maxY = 0;\n }\n\n // Ensure we have a valid scale, otherwise default to [0, 1] for safety.\n if (minY == Infinity) minY = 0;\n if (maxY == -Infinity) maxY = 1;\n\n span = maxY - minY;\n // special case: if we have no sense of scale, center on the sole value.\n if (span === 0) {\n if (maxY !== 0) {\n span = Math.abs(maxY);\n } else {\n // ... and if the sole value is zero, use range 0-1.\n maxY = 1;\n span = 1;\n }\n }\n\n var maxAxisY = maxY,\n minAxisY = minY;\n if (ypadCompat) {\n if (logscale) {\n maxAxisY = maxY + ypad * span;\n minAxisY = minY;\n } else {\n maxAxisY = maxY + ypad * span;\n minAxisY = minY - ypad * span;\n\n // Backwards-compatible behavior: Move the span to start or end at zero if it's\n // close to zero.\n if (minAxisY < 0 && minY >= 0) minAxisY = 0;\n if (maxAxisY > 0 && maxY <= 0) maxAxisY = 0;\n }\n }\n axis.extremeRange = [minAxisY, maxAxisY];\n }\n if (axis.valueRange) {\n // This is a user-set value range for this axis.\n var y0 = isNullUndefinedOrNaN(axis.valueRange[0]) ? axis.extremeRange[0] : axis.valueRange[0];\n var y1 = isNullUndefinedOrNaN(axis.valueRange[1]) ? axis.extremeRange[1] : axis.valueRange[1];\n axis.computedValueRange = [y0, y1];\n } else {\n axis.computedValueRange = axis.extremeRange;\n }\n if (!ypadCompat) {\n // When using yRangePad, adjust the upper/lower bounds to add\n // padding unless the user has zoomed/panned the Y axis range.\n if (logscale) {\n y0 = axis.computedValueRange[0];\n y1 = axis.computedValueRange[1];\n var y0pct = ypad / (2 * ypad - 1);\n var y1pct = (ypad - 1) / (2 * ypad - 1);\n axis.computedValueRange[0] = utils.logRangeFraction(y0, y1, y0pct);\n axis.computedValueRange[1] = utils.logRangeFraction(y0, y1, y1pct);\n } else {\n y0 = axis.computedValueRange[0];\n y1 = axis.computedValueRange[1];\n span = y1 - y0;\n axis.computedValueRange[0] = y0 - span * ypad;\n axis.computedValueRange[1] = y1 + span * ypad;\n }\n }\n\n if (independentTicks) {\n axis.independentTicks = independentTicks;\n var opts = this.optionsViewForAxis_('y' + (i ? '2' : ''));\n var ticker = opts('ticker');\n axis.ticks = ticker(axis.computedValueRange[0], axis.computedValueRange[1], this.plotter_.area.h, opts, this);\n // Define the first independent axis as primary axis.\n if (!p_axis) p_axis = axis;\n }\n }\n if (p_axis === undefined) {\n throw \"Configuration Error: At least one axis has to have the \\\"independentTicks\\\" option activated.\";\n }\n // Add ticks. By default, all axes inherit the tick positions of the\n // primary axis. However, if an axis is specifically marked as having\n // independent ticks, then that is permissible as well.\n for (var i = 0; i < numAxes; i++) {\n var axis = this.axes_[i];\n\n if (!axis.independentTicks) {\n var opts = this.optionsViewForAxis_('y' + (i ? '2' : ''));\n var ticker = opts('ticker');\n var p_ticks = p_axis.ticks;\n var p_scale = p_axis.computedValueRange[1] - p_axis.computedValueRange[0];\n var scale = axis.computedValueRange[1] - axis.computedValueRange[0];\n var tick_values = [];\n for (var k = 0; k < p_ticks.length; k++) {\n var y_frac = (p_ticks[k].v - p_axis.computedValueRange[0]) / p_scale;\n var y_val = axis.computedValueRange[0] + y_frac * scale;\n tick_values.push(y_val);\n }\n\n axis.ticks = ticker(axis.computedValueRange[0], axis.computedValueRange[1], this.plotter_.area.h, opts, this, tick_values);\n }\n }\n};\n\n/**\n * Detects the type of the str (date or numeric) and sets the various\n * formatting attributes in this.attrs_ based on this type.\n * @param {string} str An x value.\n * @private\n */\nDygraph.prototype.detectTypeFromString_ = function (str) {\n var isDate = false;\n var dashPos = str.indexOf('-'); // could be 2006-01-01 _or_ 1.0e-2\n if (dashPos > 0 && str[dashPos - 1] != 'e' && str[dashPos - 1] != 'E' || str.indexOf('/') >= 0 || isNaN(parseFloat(str))) {\n isDate = true;\n } else if (str.length == 8 && str > '19700101' && str < '20371231') {\n // TODO(danvk): remove support for this format.\n isDate = true;\n }\n\n this.setXAxisOptions_(isDate);\n};\n\nDygraph.prototype.setXAxisOptions_ = function (isDate) {\n if (isDate) {\n this.attrs_.xValueParser = utils.dateParser;\n this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter;\n this.attrs_.axes.x.ticker = DygraphTickers.dateTicker;\n this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter;\n } else {\n /** @private (shut up, jsdoc!) */\n this.attrs_.xValueParser = function (x) {\n return parseFloat(x);\n };\n // TODO(danvk): use Dygraph.numberValueFormatter here?\n /** @private (shut up, jsdoc!) */\n this.attrs_.axes.x.valueFormatter = function (x) {\n return x;\n };\n this.attrs_.axes.x.ticker = DygraphTickers.numericTicks;\n this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter;\n }\n};\n\n/**\n * @private\n * Parses a string in a special csv format. We expect a csv file where each\n * line is a date point, and the first field in each line is the date string.\n * We also expect that all remaining fields represent series.\n * if the errorBars attribute is set, then interpret the fields as:\n * date, series1, stddev1, series2, stddev2, ...\n * @param {[Object]} data See above.\n *\n * @return [Object] An array with one entry for each row. These entries\n * are an array of cells in that row. The first entry is the parsed x-value for\n * the row. The second, third, etc. are the y-values. These can take on one of\n * three forms, depending on the CSV and constructor parameters:\n * 1. numeric value\n * 2. [ value, stddev ]\n * 3. [ low value, center value, high value ]\n */\nDygraph.prototype.parseCSV_ = function (data) {\n var ret = [];\n var line_delimiter = utils.detectLineDelimiter(data);\n var lines = data.split(line_delimiter || \"\\n\");\n var vals, j;\n\n // Use the default delimiter or fall back to a tab if that makes sense.\n var delim = this.getStringOption('delimiter');\n if (lines[0].indexOf(delim) == -1 && lines[0].indexOf('\\t') >= 0) {\n delim = '\\t';\n }\n\n var start = 0;\n if (!('labels' in this.user_attrs_)) {\n // User hasn't explicitly set labels, so they're (presumably) in the CSV.\n start = 1;\n this.attrs_.labels = lines[0].split(delim); // NOTE: _not_ user_attrs_.\n this.attributes_.reparseSeries();\n }\n var line_no = 0;\n\n var xParser;\n var defaultParserSet = false; // attempt to auto-detect x value type\n var expectedCols = this.attr_(\"labels\").length;\n var outOfOrder = false;\n for (var i = start; i < lines.length; i++) {\n var line = lines[i];\n line_no = i;\n if (line.length === 0) continue; // skip blank lines\n if (line[0] == '#') continue; // skip comment lines\n var inFields = line.split(delim);\n if (inFields.length < 2) continue;\n\n var fields = [];\n if (!defaultParserSet) {\n this.detectTypeFromString_(inFields[0]);\n xParser = this.getFunctionOption(\"xValueParser\");\n defaultParserSet = true;\n }\n fields[0] = xParser(inFields[0], this);\n\n // If fractions are expected, parse the numbers as \"A/B\"\n if (this.fractions_) {\n for (j = 1; j < inFields.length; j++) {\n // TODO(danvk): figure out an appropriate way to flag parse errors.\n vals = inFields[j].split(\"/\");\n if (vals.length != 2) {\n console.error('Expected fractional \"num/den\" values in CSV data ' + \"but found a value '\" + inFields[j] + \"' on line \" + (1 + i) + \" ('\" + line + \"') which is not of this form.\");\n fields[j] = [0, 0];\n } else {\n fields[j] = [utils.parseFloat_(vals[0], i, line), utils.parseFloat_(vals[1], i, line)];\n }\n }\n } else if (this.getBooleanOption(\"errorBars\")) {\n // If there are error bars, values are (value, stddev) pairs\n if (inFields.length % 2 != 1) {\n console.error('Expected alternating (value, stdev.) pairs in CSV data ' + 'but line ' + (1 + i) + ' has an odd number of values (' + (inFields.length - 1) + \"): '\" + line + \"'\");\n }\n for (j = 1; j < inFields.length; j += 2) {\n fields[(j + 1) / 2] = [utils.parseFloat_(inFields[j], i, line), utils.parseFloat_(inFields[j + 1], i, line)];\n }\n } else if (this.getBooleanOption(\"customBars\")) {\n // Bars are a low;center;high tuple\n for (j = 1; j < inFields.length; j++) {\n var val = inFields[j];\n if (/^ *$/.test(val)) {\n fields[j] = [null, null, null];\n } else {\n vals = val.split(\";\");\n if (vals.length == 3) {\n fields[j] = [utils.parseFloat_(vals[0], i, line), utils.parseFloat_(vals[1], i, line), utils.parseFloat_(vals[2], i, line)];\n } else {\n console.warn('When using customBars, values must be either blank ' + 'or \"low;center;high\" tuples (got \"' + val + '\" on line ' + (1 + i));\n }\n }\n }\n } else {\n // Values are just numbers\n for (j = 1; j < inFields.length; j++) {\n fields[j] = utils.parseFloat_(inFields[j], i, line);\n }\n }\n if (ret.length > 0 && fields[0] < ret[ret.length - 1][0]) {\n outOfOrder = true;\n }\n\n if (fields.length != expectedCols) {\n console.error(\"Number of columns in line \" + i + \" (\" + fields.length + \") does not agree with number of labels (\" + expectedCols + \") \" + line);\n }\n\n // If the user specified the 'labels' option and none of the cells of the\n // first row parsed correctly, then they probably double-specified the\n // labels. We go with the values set in the option, discard this row and\n // log a warning to the JS console.\n if (i === 0 && this.attr_('labels')) {\n var all_null = true;\n for (j = 0; all_null && j < fields.length; j++) {\n if (fields[j]) all_null = false;\n }\n if (all_null) {\n console.warn(\"The dygraphs 'labels' option is set, but the first row \" + \"of CSV data ('\" + line + \"') appears to also contain \" + \"labels. Will drop the CSV labels and use the option \" + \"labels.\");\n continue;\n }\n }\n ret.push(fields);\n }\n\n if (outOfOrder) {\n console.warn(\"CSV is out of order; order it correctly to speed loading.\");\n ret.sort(function (a, b) {\n return a[0] - b[0];\n });\n }\n\n return ret;\n};\n\n// In native format, all values must be dates or numbers.\n// This check isn't perfect but will catch most mistaken uses of strings.\nfunction validateNativeFormat(data) {\n var firstRow = data[0];\n var firstX = firstRow[0];\n if (typeof firstX !== 'number' && !utils.isDateLike(firstX)) {\n throw new Error('Expected number or date but got ' + typeof firstX + ': ' + firstX + '.');\n }\n for (var i = 1; i < firstRow.length; i++) {\n var val = firstRow[i];\n if (val === null || val === undefined) continue;\n if (typeof val === 'number') continue;\n if (utils.isArrayLike(val)) continue; // e.g. error bars or custom bars.\n throw new Error('Expected number or array but got ' + typeof val + ': ' + val + '.');\n }\n}\n\n/**\n * The user has provided their data as a pre-packaged JS array. If the x values\n * are numeric, this is the same as dygraphs' internal format. If the x values\n * are dates, we need to convert them from Date objects to ms since epoch.\n * @param {!Array} data\n * @return {Object} data with numeric x values.\n * @private\n */\nDygraph.prototype.parseArray_ = function (data) {\n // Peek at the first x value to see if it's numeric.\n if (data.length === 0) {\n console.error(\"Can't plot empty data set\");\n return null;\n }\n if (data[0].length === 0) {\n console.error(\"Data set cannot contain an empty row\");\n return null;\n }\n\n validateNativeFormat(data);\n\n var i;\n if (this.attr_(\"labels\") === null) {\n console.warn(\"Using default labels. Set labels explicitly via 'labels' \" + \"in the options parameter\");\n this.attrs_.labels = [\"X\"];\n for (i = 1; i < data[0].length; i++) {\n this.attrs_.labels.push(\"Y\" + i); // Not user_attrs_.\n }\n this.attributes_.reparseSeries();\n } else {\n var num_labels = this.attr_(\"labels\");\n if (num_labels.length != data[0].length) {\n console.error(\"Mismatch between number of labels (\" + num_labels + \")\" + \" and number of columns in array (\" + data[0].length + \")\");\n return null;\n }\n }\n\n if (utils.isDateLike(data[0][0])) {\n // Some intelligent defaults for a date x-axis.\n this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter;\n this.attrs_.axes.x.ticker = DygraphTickers.dateTicker;\n this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter;\n\n // Assume they're all dates.\n var parsedData = utils.clone(data);\n for (i = 0; i < data.length; i++) {\n if (parsedData[i].length === 0) {\n console.error(\"Row \" + (1 + i) + \" of data is empty\");\n return null;\n }\n if (parsedData[i][0] === null || typeof parsedData[i][0].getTime != 'function' || isNaN(parsedData[i][0].getTime())) {\n console.error(\"x value in row \" + (1 + i) + \" is not a Date\");\n return null;\n }\n parsedData[i][0] = parsedData[i][0].getTime();\n }\n return parsedData;\n } else {\n // Some intelligent defaults for a numeric x-axis.\n /** @private (shut up, jsdoc!) */\n this.attrs_.axes.x.valueFormatter = function (x) {\n return x;\n };\n this.attrs_.axes.x.ticker = DygraphTickers.numericTicks;\n this.attrs_.axes.x.axisLabelFormatter = utils.numberAxisLabelFormatter;\n return data;\n }\n};\n\n/**\n * Parses a DataTable object from gviz.\n * The data is expected to have a first column that is either a date or a\n * number. All subsequent columns must be numbers. If there is a clear mismatch\n * between this.xValueParser_ and the type of the first column, it will be\n * fixed. Fills out rawData_.\n * @param {!google.visualization.DataTable} data See above.\n * @private\n */\nDygraph.prototype.parseDataTable_ = function (data) {\n var shortTextForAnnotationNum = function shortTextForAnnotationNum(num) {\n // converts [0-9]+ [A-Z][a-z]*\n // example: 0=A, 1=B, 25=Z, 26=Aa, 27=Ab\n // and continues like.. Ba Bb .. Za .. Zz..Aaa...Zzz Aaaa Zzzz\n var shortText = String.fromCharCode(65 /* A */ + num % 26);\n num = Math.floor(num / 26);\n while (num > 0) {\n shortText = String.fromCharCode(65 /* A */ + (num - 1) % 26) + shortText.toLowerCase();\n num = Math.floor((num - 1) / 26);\n }\n return shortText;\n };\n\n var cols = data.getNumberOfColumns();\n var rows = data.getNumberOfRows();\n\n var indepType = data.getColumnType(0);\n if (indepType == 'date' || indepType == 'datetime') {\n this.attrs_.xValueParser = utils.dateParser;\n this.attrs_.axes.x.valueFormatter = utils.dateValueFormatter;\n this.attrs_.axes.x.ticker = DygraphTickers.dateTicker;\n this.attrs_.axes.x.axisLabelFormatter = utils.dateAxisLabelFormatter;\n } else if (indepType == 'number') {\n this.attrs_.xValueParser = function (x) {\n return parseFloat(x);\n };\n this.attrs_.axes.x.valueFormatter = function (x) {\n return x;\n };\n this.attrs_.axes.x.ticker = DygraphTickers.numericTicks;\n this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter;\n } else {\n throw new Error(\"only 'date', 'datetime' and 'number' types are supported \" + \"for column 1 of DataTable input (Got '\" + indepType + \"')\");\n }\n\n // Array of the column indices which contain data (and not annotations).\n var colIdx = [];\n var annotationCols = {}; // data index -> [annotation cols]\n var hasAnnotations = false;\n var i, j;\n for (i = 1; i < cols; i++) {\n var type = data.getColumnType(i);\n if (type == 'number') {\n colIdx.push(i);\n } else if (type == 'string' && this.getBooleanOption('displayAnnotations')) {\n // This is OK -- it's an annotation column.\n var dataIdx = colIdx[colIdx.length - 1];\n if (!annotationCols.hasOwnProperty(dataIdx)) {\n annotationCols[dataIdx] = [i];\n } else {\n annotationCols[dataIdx].push(i);\n }\n hasAnnotations = true;\n } else {\n throw new Error(\"Only 'number' is supported as a dependent type with Gviz.\" + \" 'string' is only supported if displayAnnotations is true\");\n }\n }\n\n // Read column labels\n // TODO(danvk): add support back for errorBars\n var labels = [data.getColumnLabel(0)];\n for (i = 0; i < colIdx.length; i++) {\n labels.push(data.getColumnLabel(colIdx[i]));\n if (this.getBooleanOption(\"errorBars\")) i += 1;\n }\n this.attrs_.labels = labels;\n cols = labels.length;\n\n var ret = [];\n var outOfOrder = false;\n var annotations = [];\n for (i = 0; i < rows; i++) {\n var row = [];\n if (typeof data.getValue(i, 0) === 'undefined' || data.getValue(i, 0) === null) {\n console.warn(\"Ignoring row \" + i + \" of DataTable because of undefined or null first column.\");\n continue;\n }\n\n if (indepType == 'date' || indepType == 'datetime') {\n row.push(data.getValue(i, 0).getTime());\n } else {\n row.push(data.getValue(i, 0));\n }\n if (!this.getBooleanOption(\"errorBars\")) {\n for (j = 0; j < colIdx.length; j++) {\n var col = colIdx[j];\n row.push(data.getValue(i, col));\n if (hasAnnotations && annotationCols.hasOwnProperty(col) && data.getValue(i, annotationCols[col][0]) !== null) {\n var ann = {};\n ann.series = data.getColumnLabel(col);\n ann.xval = row[0];\n ann.shortText = shortTextForAnnotationNum(annotations.length);\n ann.text = '';\n for (var k = 0; k < annotationCols[col].length; k++) {\n if (k) ann.text += \"\\n\";\n ann.text += data.getValue(i, annotationCols[col][k]);\n }\n annotations.push(ann);\n }\n }\n\n // Strip out infinities, which give dygraphs problems later on.\n for (j = 0; j < row.length; j++) {\n if (!isFinite(row[j])) row[j] = null;\n }\n } else {\n for (j = 0; j < cols - 1; j++) {\n row.push([data.getValue(i, 1 + 2 * j), data.getValue(i, 2 + 2 * j)]);\n }\n }\n if (ret.length > 0 && row[0] < ret[ret.length - 1][0]) {\n outOfOrder = true;\n }\n ret.push(row);\n }\n\n if (outOfOrder) {\n console.warn(\"DataTable is out of order; order it correctly to speed loading.\");\n ret.sort(function (a, b) {\n return a[0] - b[0];\n });\n }\n this.rawData_ = ret;\n\n if (annotations.length > 0) {\n this.setAnnotations(annotations, true);\n }\n this.attributes_.reparseSeries();\n};\n\n/**\n * Signals to plugins that the chart data has updated.\n * This happens after the data has updated but before the chart has redrawn.\n * @private\n */\nDygraph.prototype.cascadeDataDidUpdateEvent_ = function () {\n // TODO(danvk): there are some issues checking xAxisRange() and using\n // toDomCoords from handlers of this event. The visible range should be set\n // when the chart is drawn, not derived from the data.\n this.cascadeEvents_('dataDidUpdate', {});\n};\n\n/**\n * Get the CSV data. If it's in a function, call that function. If it's in a\n * file, do an XMLHttpRequest to get it.\n * @private\n */\nDygraph.prototype.start_ = function () {\n var data = this.file_;\n\n // Functions can return references of all other types.\n if (typeof data == 'function') {\n data = data();\n }\n\n if (utils.isArrayLike(data)) {\n this.rawData_ = this.parseArray_(data);\n this.cascadeDataDidUpdateEvent_();\n this.predraw_();\n } else if (typeof data == 'object' && typeof data.getColumnRange == 'function') {\n // must be a DataTable from gviz.\n this.parseDataTable_(data);\n this.cascadeDataDidUpdateEvent_();\n this.predraw_();\n } else if (typeof data == 'string') {\n // Heuristic: a newline means it's CSV data. Otherwise it's an URL.\n var line_delimiter = utils.detectLineDelimiter(data);\n if (line_delimiter) {\n this.loadedEvent_(data);\n } else {\n // REMOVE_FOR_IE\n var req;\n if (window.XMLHttpRequest) {\n // Firefox, Opera, IE7, and other browsers will use the native object\n req = new XMLHttpRequest();\n } else {\n // IE 5 and 6 will use the ActiveX control\n req = new ActiveXObject(\"Microsoft.XMLHTTP\");\n }\n\n var caller = this;\n req.onreadystatechange = function () {\n if (req.readyState == 4) {\n if (req.status === 200 || // Normal http\n req.status === 0) {\n // Chrome w/ --allow-file-access-from-files\n caller.loadedEvent_(req.responseText);\n }\n }\n };\n\n req.open(\"GET\", data, true);\n req.send(null);\n }\n } else {\n console.error(\"Unknown data format: \" + typeof data);\n }\n};\n\n/**\n * Changes various properties of the graph. These can include:\n *
    \n *
  • file: changes the source data for the graph
  • \n *
  • errorBars: changes whether the data contains stddev
  • \n *
\n *\n * There's a huge variety of options that can be passed to this method. For a\n * full list, see http://dygraphs.com/options.html.\n *\n * @param {Object} input_attrs The new properties and values\n * @param {boolean} block_redraw Usually the chart is redrawn after every\n * call to updateOptions(). If you know better, you can pass true to\n * explicitly block the redraw. This can be useful for chaining\n * updateOptions() calls, avoiding the occasional infinite loop and\n * preventing redraws when it's not necessary (e.g. when updating a\n * callback).\n */\nDygraph.prototype.updateOptions = function (input_attrs, block_redraw) {\n if (typeof block_redraw == 'undefined') block_redraw = false;\n\n // copyUserAttrs_ drops the \"file\" parameter as a convenience to us.\n var file = input_attrs.file;\n var attrs = Dygraph.copyUserAttrs_(input_attrs);\n\n // TODO(danvk): this is a mess. Move these options into attr_.\n if ('rollPeriod' in attrs) {\n this.rollPeriod_ = attrs.rollPeriod;\n }\n if ('dateWindow' in attrs) {\n this.dateWindow_ = attrs.dateWindow;\n }\n\n // TODO(danvk): validate per-series options.\n // Supported:\n // strokeWidth\n // pointSize\n // drawPoints\n // highlightCircleSize\n\n // Check if this set options will require new points.\n var requiresNewPoints = utils.isPixelChangingOptionList(this.attr_(\"labels\"), attrs);\n\n utils.updateDeep(this.user_attrs_, attrs);\n\n this.attributes_.reparseSeries();\n\n if (file) {\n // This event indicates that the data is about to change, but hasn't yet.\n // TODO(danvk): support cancellation of the update via this event.\n this.cascadeEvents_('dataWillUpdate', {});\n\n this.file_ = file;\n if (!block_redraw) this.start_();\n } else {\n if (!block_redraw) {\n if (requiresNewPoints) {\n this.predraw_();\n } else {\n this.renderGraph_(false);\n }\n }\n }\n};\n\n/**\n * Make a copy of input attributes, removing file as a convenience.\n * @private\n */\nDygraph.copyUserAttrs_ = function (attrs) {\n var my_attrs = {};\n for (var k in attrs) {\n if (!attrs.hasOwnProperty(k)) continue;\n if (k == 'file') continue;\n if (attrs.hasOwnProperty(k)) my_attrs[k] = attrs[k];\n }\n return my_attrs;\n};\n\n/**\n * Resizes the dygraph. If no parameters are specified, resizes to fill the\n * containing div (which has presumably changed size since the dygraph was\n * instantiated. If the width/height are specified, the div will be resized.\n *\n * This is far more efficient than destroying and re-instantiating a\n * Dygraph, since it doesn't have to reparse the underlying data.\n *\n * @param {number} width Width (in pixels)\n * @param {number} height Height (in pixels)\n */\nDygraph.prototype.resize = function (width, height) {\n if (this.resize_lock) {\n return;\n }\n this.resize_lock = true;\n\n if (width === null != (height === null)) {\n console.warn(\"Dygraph.resize() should be called with zero parameters or \" + \"two non-NULL parameters. Pretending it was zero.\");\n width = height = null;\n }\n\n var old_width = this.width_;\n var old_height = this.height_;\n\n if (width) {\n this.maindiv_.style.width = width + \"px\";\n this.maindiv_.style.height = height + \"px\";\n this.width_ = width;\n this.height_ = height;\n } else {\n this.width_ = this.maindiv_.clientWidth;\n this.height_ = this.maindiv_.clientHeight;\n }\n\n if (old_width != this.width_ || old_height != this.height_) {\n // Resizing a canvas erases it, even when the size doesn't change, so\n // any resize needs to be followed by a redraw.\n this.resizeElements_();\n this.predraw_();\n }\n\n this.resize_lock = false;\n};\n\n/**\n * Adjusts the number of points in the rolling average. Updates the graph to\n * reflect the new averaging period.\n * @param {number} length Number of points over which to average the data.\n */\nDygraph.prototype.adjustRoll = function (length) {\n this.rollPeriod_ = length;\n this.predraw_();\n};\n\n/**\n * Returns a boolean array of visibility statuses.\n */\nDygraph.prototype.visibility = function () {\n // Do lazy-initialization, so that this happens after we know the number of\n // data series.\n if (!this.getOption(\"visibility\")) {\n this.attrs_.visibility = [];\n }\n // TODO(danvk): it looks like this could go into an infinite loop w/ user_attrs.\n while (this.getOption(\"visibility\").length < this.numColumns() - 1) {\n this.attrs_.visibility.push(true);\n }\n return this.getOption(\"visibility\");\n};\n\n/**\n * Changes the visibility of one or more series.\n *\n * @param {number|number[]|object} num the series index or an array of series indices\n * or a boolean array of visibility states by index\n * or an object mapping series numbers, as keys, to\n * visibility state (boolean values)\n * @param {boolean} value the visibility state expressed as a boolean\n */\nDygraph.prototype.setVisibility = function (num, value) {\n var x = this.visibility();\n var numIsObject = false;\n\n if (!Array.isArray(num)) {\n if (num !== null && typeof num === 'object') {\n numIsObject = true;\n } else {\n num = [num];\n }\n }\n\n if (numIsObject) {\n for (var i in num) {\n if (num.hasOwnProperty(i)) {\n if (i < 0 || i >= x.length) {\n console.warn(\"Invalid series number in setVisibility: \" + i);\n } else {\n x[i] = num[i];\n }\n }\n }\n } else {\n for (var i = 0; i < num.length; i++) {\n if (typeof num[i] === 'boolean') {\n if (i >= x.length) {\n console.warn(\"Invalid series number in setVisibility: \" + i);\n } else {\n x[i] = num[i];\n }\n } else {\n if (num[i] < 0 || num[i] >= x.length) {\n console.warn(\"Invalid series number in setVisibility: \" + num[i]);\n } else {\n x[num[i]] = value;\n }\n }\n }\n }\n\n this.predraw_();\n};\n\n/**\n * How large of an area will the dygraph render itself in?\n * This is used for testing.\n * @return A {width: w, height: h} object.\n * @private\n */\nDygraph.prototype.size = function () {\n return { width: this.width_, height: this.height_ };\n};\n\n/**\n * Update the list of annotations and redraw the chart.\n * See dygraphs.com/annotations.html for more info on how to use annotations.\n * @param ann {Array} An array of annotation objects.\n * @param suppressDraw {Boolean} Set to \"true\" to block chart redraw (optional).\n */\nDygraph.prototype.setAnnotations = function (ann, suppressDraw) {\n // Only add the annotation CSS rule once we know it will be used.\n this.annotations_ = ann;\n if (!this.layout_) {\n console.warn(\"Tried to setAnnotations before dygraph was ready. \" + \"Try setting them in a ready() block. See \" + \"dygraphs.com/tests/annotation.html\");\n return;\n }\n\n this.layout_.setAnnotations(this.annotations_);\n if (!suppressDraw) {\n this.predraw_();\n }\n};\n\n/**\n * Return the list of annotations.\n */\nDygraph.prototype.annotations = function () {\n return this.annotations_;\n};\n\n/**\n * Get the list of label names for this graph. The first column is the\n * x-axis, so the data series names start at index 1.\n *\n * Returns null when labels have not yet been defined.\n */\nDygraph.prototype.getLabels = function () {\n var labels = this.attr_(\"labels\");\n return labels ? labels.slice() : null;\n};\n\n/**\n * Get the index of a series (column) given its name. The first column is the\n * x-axis, so the data series start with index 1.\n */\nDygraph.prototype.indexFromSetName = function (name) {\n return this.setIndexByName_[name];\n};\n\n/**\n * Find the row number corresponding to the given x-value.\n * Returns null if there is no such x-value in the data.\n * If there are multiple rows with the same x-value, this will return the\n * first one.\n * @param {number} xVal The x-value to look for (e.g. millis since epoch).\n * @return {?number} The row number, which you can pass to getValue(), or null.\n */\nDygraph.prototype.getRowForX = function (xVal) {\n var low = 0,\n high = this.numRows() - 1;\n\n while (low <= high) {\n var idx = high + low >> 1;\n var x = this.getValue(idx, 0);\n if (x < xVal) {\n low = idx + 1;\n } else if (x > xVal) {\n high = idx - 1;\n } else if (low != idx) {\n // equal, but there may be an earlier match.\n high = idx;\n } else {\n return idx;\n }\n }\n\n return null;\n};\n\n/**\n * Trigger a callback when the dygraph has drawn itself and is ready to be\n * manipulated. This is primarily useful when dygraphs has to do an XHR for the\n * data (i.e. a URL is passed as the data source) and the chart is drawn\n * asynchronously. If the chart has already drawn, the callback will fire\n * immediately.\n *\n * This is a good place to call setAnnotation().\n *\n * @param {function(!Dygraph)} callback The callback to trigger when the chart\n * is ready.\n */\nDygraph.prototype.ready = function (callback) {\n if (this.is_initial_draw_) {\n this.readyFns_.push(callback);\n } else {\n callback.call(this, this);\n }\n};\n\n/**\n * Add an event handler. This event handler is kept until the graph is\n * destroyed with a call to graph.destroy().\n *\n * @param {!Node} elem The element to add the event to.\n * @param {string} type The type of the event, e.g. 'click' or 'mousemove'.\n * @param {function(Event):(boolean|undefined)} fn The function to call\n * on the event. The function takes one parameter: the event object.\n * @private\n */\nDygraph.prototype.addAndTrackEvent = function (elem, type, fn) {\n utils.addEvent(elem, type, fn);\n this.registeredEvents_.push({ elem: elem, type: type, fn: fn });\n};\n\nDygraph.prototype.removeTrackedEvents_ = function () {\n if (this.registeredEvents_) {\n for (var idx = 0; idx < this.registeredEvents_.length; idx++) {\n var reg = this.registeredEvents_[idx];\n utils.removeEvent(reg.elem, reg.type, reg.fn);\n }\n }\n\n this.registeredEvents_ = [];\n};\n\n// Installed plugins, in order of precedence (most-general to most-specific).\nDygraph.PLUGINS = [_pluginsLegend2['default'], _pluginsAxes2['default'], _pluginsRangeSelector2['default'], // Has to be before ChartLabels so that its callbacks are called after ChartLabels' callbacks.\n_pluginsChartLabels2['default'], _pluginsAnnotations2['default'], _pluginsGrid2['default']];\n\n// There are many symbols which have historically been available through the\n// Dygraph class. These are exported here for backwards compatibility.\nDygraph.GVizChart = _dygraphGviz2['default'];\nDygraph.DASHED_LINE = utils.DASHED_LINE;\nDygraph.DOT_DASH_LINE = utils.DOT_DASH_LINE;\nDygraph.dateAxisLabelFormatter = utils.dateAxisLabelFormatter;\nDygraph.toRGB_ = utils.toRGB_;\nDygraph.findPos = utils.findPos;\nDygraph.pageX = utils.pageX;\nDygraph.pageY = utils.pageY;\nDygraph.dateString_ = utils.dateString_;\nDygraph.defaultInteractionModel = _dygraphInteractionModel2['default'].defaultModel;\nDygraph.nonInteractiveModel = Dygraph.nonInteractiveModel_ = _dygraphInteractionModel2['default'].nonInteractiveModel_;\nDygraph.Circles = utils.Circles;\n\nDygraph.Plugins = {\n Legend: _pluginsLegend2['default'],\n Axes: _pluginsAxes2['default'],\n Annotations: _pluginsAnnotations2['default'],\n ChartLabels: _pluginsChartLabels2['default'],\n Grid: _pluginsGrid2['default'],\n RangeSelector: _pluginsRangeSelector2['default']\n};\n\nDygraph.DataHandlers = {\n DefaultHandler: _datahandlerDefault2['default'],\n BarsHandler: _datahandlerBars2['default'],\n CustomBarsHandler: _datahandlerBarsCustom2['default'],\n DefaultFractionHandler: _datahandlerDefaultFractions2['default'],\n ErrorBarsHandler: _datahandlerBarsError2['default'],\n FractionsBarsHandler: _datahandlerBarsFractions2['default']\n};\n\nDygraph.startPan = _dygraphInteractionModel2['default'].startPan;\nDygraph.startZoom = _dygraphInteractionModel2['default'].startZoom;\nDygraph.movePan = _dygraphInteractionModel2['default'].movePan;\nDygraph.moveZoom = _dygraphInteractionModel2['default'].moveZoom;\nDygraph.endPan = _dygraphInteractionModel2['default'].endPan;\nDygraph.endZoom = _dygraphInteractionModel2['default'].endZoom;\n\nDygraph.numericLinearTicks = DygraphTickers.numericLinearTicks;\nDygraph.numericTicks = DygraphTickers.numericTicks;\nDygraph.dateTicker = DygraphTickers.dateTicker;\nDygraph.Granularity = DygraphTickers.Granularity;\nDygraph.getDateAxis = DygraphTickers.getDateAxis;\nDygraph.floatFormat = utils.floatFormat;\n\nexports['default'] = Dygraph;\nmodule.exports = exports['default'];","/**\n * @license\n * Copyright 2011 Dan Vanderkam (danvdk@gmail.com)\n * MIT-licensed (http://opensource.org/licenses/MIT)\n */\n\n/**\n * @fileoverview Description of this file.\n * @author danvk@google.com (Dan Vanderkam)\n *\n * A ticker is a function with the following interface:\n *\n * function(a, b, pixels, options_view, dygraph, forced_values);\n * -> [ { v: tick1_v, label: tick1_label[, label_v: label_v1] },\n * { v: tick2_v, label: tick2_label[, label_v: label_v2] },\n * ...\n * ]\n *\n * The returned value is called a \"tick list\".\n *\n * Arguments\n * ---------\n *\n * [a, b] is the range of the axis for which ticks are being generated. For a\n * numeric axis, these will simply be numbers. For a date axis, these will be\n * millis since epoch (convertable to Date objects using \"new Date(a)\" and \"new\n * Date(b)\").\n *\n * opts provides access to chart- and axis-specific options. It can be used to\n * access number/date formatting code/options, check for a log scale, etc.\n *\n * pixels is the length of the axis in pixels. opts('pixelsPerLabel') is the\n * minimum amount of space to be allotted to each label. For instance, if\n * pixels=400 and opts('pixelsPerLabel')=40 then the ticker should return\n * between zero and ten (400/40) ticks.\n *\n * dygraph is the Dygraph object for which an axis is being constructed.\n *\n * forced_values is used for secondary y-axes. The tick positions are typically\n * set by the primary y-axis, so the secondary y-axis has no choice in where to\n * put these. It simply has to generate labels for these data values.\n *\n * Tick lists\n * ----------\n * Typically a tick will have both a grid/tick line and a label at one end of\n * that line (at the bottom for an x-axis, at left or right for the y-axis).\n *\n * A tick may be missing one of these two components:\n * - If \"label_v\" is specified instead of \"v\", then there will be no tick or\n * gridline, just a label.\n * - Similarly, if \"label\" is not specified, then there will be a gridline\n * without a label.\n *\n * This flexibility is useful in a few situations:\n * - For log scales, some of the tick lines may be too close to all have labels.\n * - For date scales where years are being displayed, it is desirable to display\n * tick marks at the beginnings of years but labels (e.g. \"2006\") in the\n * middle of the years.\n */\n\n/*jshint sub:true */\n/*global Dygraph:false */\n\"use strict\";\n\nObject.defineProperty(exports, '__esModule', {\n value: true\n});\n\nfunction _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }\n\nvar _dygraphUtils = require('./dygraph-utils');\n\nvar utils = _interopRequireWildcard(_dygraphUtils);\n\n/** @typedef {Array.<{v:number, label:string, label_v:(string|undefined)}>} */\nvar TickList = undefined; // the ' = undefined' keeps jshint happy.\n\n/** @typedef {function(\n * number,\n * number,\n * number,\n * function(string):*,\n * Dygraph=,\n * Array.=\n * ): TickList}\n */\nvar Ticker = undefined; // the ' = undefined' keeps jshint happy.\n\n/** @type {Ticker} */\nvar numericLinearTicks = function numericLinearTicks(a, b, pixels, opts, dygraph, vals) {\n var nonLogscaleOpts = function nonLogscaleOpts(opt) {\n if (opt === 'logscale') return false;\n return opts(opt);\n };\n return numericTicks(a, b, pixels, nonLogscaleOpts, dygraph, vals);\n};\n\nexports.numericLinearTicks = numericLinearTicks;\n/** @type {Ticker} */\nvar numericTicks = function numericTicks(a, b, pixels, opts, dygraph, vals) {\n var pixels_per_tick = /** @type{number} */opts('pixelsPerLabel');\n var ticks = [];\n var i, j, tickV, nTicks;\n if (vals) {\n for (i = 0; i < vals.length; i++) {\n ticks.push({ v: vals[i] });\n }\n } else {\n // TODO(danvk): factor this log-scale block out into a separate function.\n if (opts(\"logscale\")) {\n nTicks = Math.floor(pixels / pixels_per_tick);\n var minIdx = utils.binarySearch(a, PREFERRED_LOG_TICK_VALUES, 1);\n var maxIdx = utils.binarySearch(b, PREFERRED_LOG_TICK_VALUES, -1);\n if (minIdx == -1) {\n minIdx = 0;\n }\n if (maxIdx == -1) {\n maxIdx = PREFERRED_LOG_TICK_VALUES.length - 1;\n }\n // Count the number of tick values would appear, if we can get at least\n // nTicks / 4 accept them.\n var lastDisplayed = null;\n if (maxIdx - minIdx >= nTicks / 4) {\n for (var idx = maxIdx; idx >= minIdx; idx--) {\n var tickValue = PREFERRED_LOG_TICK_VALUES[idx];\n var pixel_coord = Math.log(tickValue / a) / Math.log(b / a) * pixels;\n var tick = { v: tickValue };\n if (lastDisplayed === null) {\n lastDisplayed = {\n tickValue: tickValue,\n pixel_coord: pixel_coord\n };\n } else {\n if (Math.abs(pixel_coord - lastDisplayed.pixel_coord) >= pixels_per_tick) {\n lastDisplayed = {\n tickValue: tickValue,\n pixel_coord: pixel_coord\n };\n } else {\n tick.label = \"\";\n }\n }\n ticks.push(tick);\n }\n // Since we went in backwards order.\n ticks.reverse();\n }\n }\n\n // ticks.length won't be 0 if the log scale function finds values to insert.\n if (ticks.length === 0) {\n // Basic idea:\n // Try labels every 1, 2, 5, 10, 20, 50, 100, etc.\n // Calculate the resulting tick spacing (i.e. this.height_ / nTicks).\n // The first spacing greater than pixelsPerYLabel is what we use.\n // TODO(danvk): version that works on a log scale.\n var kmg2 = opts(\"labelsKMG2\");\n var mults, base;\n if (kmg2) {\n mults = [1, 2, 4, 8, 16, 32, 64, 128, 256];\n base = 16;\n } else {\n mults = [1, 2, 5, 10, 20, 50, 100];\n base = 10;\n }\n\n // Get the maximum number of permitted ticks based on the\n // graph's pixel size and pixels_per_tick setting.\n var max_ticks = Math.ceil(pixels / pixels_per_tick);\n\n // Now calculate the data unit equivalent of this tick spacing.\n // Use abs() since graphs may have a reversed Y axis.\n var units_per_tick = Math.abs(b - a) / max_ticks;\n\n // Based on this, get a starting scale which is the largest\n // integer power of the chosen base (10 or 16) that still remains\n // below the requested pixels_per_tick spacing.\n var base_power = Math.floor(Math.log(units_per_tick) / Math.log(base));\n var base_scale = Math.pow(base, base_power);\n\n // Now try multiples of the starting scale until we find one\n // that results in tick marks spaced sufficiently far apart.\n // The \"mults\" array should cover the range 1 .. base^2 to\n // adjust for rounding and edge effects.\n var scale, low_val, high_val, spacing;\n for (j = 0; j < mults.length; j++) {\n scale = base_scale * mults[j];\n low_val = Math.floor(a / scale) * scale;\n high_val = Math.ceil(b / scale) * scale;\n nTicks = Math.abs(high_val - low_val) / scale;\n spacing = pixels / nTicks;\n if (spacing > pixels_per_tick) break;\n }\n\n // Construct the set of ticks.\n // Allow reverse y-axis if it's explicitly requested.\n if (low_val > high_val) scale *= -1;\n for (i = 0; i <= nTicks; i++) {\n tickV = low_val + i * scale;\n ticks.push({ v: tickV });\n }\n }\n }\n\n var formatter = /**@type{AxisLabelFormatter}*/opts('axisLabelFormatter');\n\n // Add labels to the ticks.\n for (i = 0; i < ticks.length; i++) {\n if (ticks[i].label !== undefined) continue; // Use current label.\n // TODO(danvk): set granularity to something appropriate here.\n ticks[i].label = formatter.call(dygraph, ticks[i].v, 0, opts, dygraph);\n }\n\n return ticks;\n};\n\nexports.numericTicks = numericTicks;\n/** @type {Ticker} */\nvar dateTicker = function dateTicker(a, b, pixels, opts, dygraph, vals) {\n var chosen = pickDateTickGranularity(a, b, pixels, opts);\n\n if (chosen >= 0) {\n return getDateAxis(a, b, chosen, opts, dygraph);\n } else {\n // this can happen if self.width_ is zero.\n return [];\n }\n};\n\nexports.dateTicker = dateTicker;\n// Time granularity enumeration\nvar Granularity = {\n MILLISECONDLY: 0,\n TWO_MILLISECONDLY: 1,\n FIVE_MILLISECONDLY: 2,\n TEN_MILLISECONDLY: 3,\n FIFTY_MILLISECONDLY: 4,\n HUNDRED_MILLISECONDLY: 5,\n FIVE_HUNDRED_MILLISECONDLY: 6,\n SECONDLY: 7,\n TWO_SECONDLY: 8,\n FIVE_SECONDLY: 9,\n TEN_SECONDLY: 10,\n THIRTY_SECONDLY: 11,\n MINUTELY: 12,\n TWO_MINUTELY: 13,\n FIVE_MINUTELY: 14,\n TEN_MINUTELY: 15,\n THIRTY_MINUTELY: 16,\n HOURLY: 17,\n TWO_HOURLY: 18,\n SIX_HOURLY: 19,\n DAILY: 20,\n TWO_DAILY: 21,\n WEEKLY: 22,\n MONTHLY: 23,\n QUARTERLY: 24,\n BIANNUAL: 25,\n ANNUAL: 26,\n DECADAL: 27,\n CENTENNIAL: 28,\n NUM_GRANULARITIES: 29\n};\n\nexports.Granularity = Granularity;\n// Date components enumeration (in the order of the arguments in Date)\n// TODO: make this an @enum\nvar DateField = {\n DATEFIELD_Y: 0,\n DATEFIELD_M: 1,\n DATEFIELD_D: 2,\n DATEFIELD_HH: 3,\n DATEFIELD_MM: 4,\n DATEFIELD_SS: 5,\n DATEFIELD_MS: 6,\n NUM_DATEFIELDS: 7\n};\n\n/**\n * The value of datefield will start at an even multiple of \"step\", i.e.\n * if datefield=SS and step=5 then the first tick will be on a multiple of 5s.\n *\n * For granularities <= HOURLY, ticks are generated every `spacing` ms.\n *\n * At coarser granularities, ticks are generated by incrementing `datefield` by\n * `step`. In this case, the `spacing` value is only used to estimate the\n * number of ticks. It should roughly correspond to the spacing between\n * adjacent ticks.\n *\n * @type {Array.<{datefield:number, step:number, spacing:number}>}\n */\nvar TICK_PLACEMENT = [];\nTICK_PLACEMENT[Granularity.MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 1, spacing: 1 };\nTICK_PLACEMENT[Granularity.TWO_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 2, spacing: 2 };\nTICK_PLACEMENT[Granularity.FIVE_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 5, spacing: 5 };\nTICK_PLACEMENT[Granularity.TEN_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 10, spacing: 10 };\nTICK_PLACEMENT[Granularity.FIFTY_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 50, spacing: 50 };\nTICK_PLACEMENT[Granularity.HUNDRED_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 100, spacing: 100 };\nTICK_PLACEMENT[Granularity.FIVE_HUNDRED_MILLISECONDLY] = { datefield: DateField.DATEFIELD_MS, step: 500, spacing: 500 };\nTICK_PLACEMENT[Granularity.SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 1, spacing: 1000 * 1 };\nTICK_PLACEMENT[Granularity.TWO_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 2, spacing: 1000 * 2 };\nTICK_PLACEMENT[Granularity.FIVE_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 5, spacing: 1000 * 5 };\nTICK_PLACEMENT[Granularity.TEN_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 10, spacing: 1000 * 10 };\nTICK_PLACEMENT[Granularity.THIRTY_SECONDLY] = { datefield: DateField.DATEFIELD_SS, step: 30, spacing: 1000 * 30 };\nTICK_PLACEMENT[Granularity.MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 1, spacing: 1000 * 60 };\nTICK_PLACEMENT[Granularity.TWO_MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 2, spacing: 1000 * 60 * 2 };\nTICK_PLACEMENT[Granularity.FIVE_MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 5, spacing: 1000 * 60 * 5 };\nTICK_PLACEMENT[Granularity.TEN_MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 10, spacing: 1000 * 60 * 10 };\nTICK_PLACEMENT[Granularity.THIRTY_MINUTELY] = { datefield: DateField.DATEFIELD_MM, step: 30, spacing: 1000 * 60 * 30 };\nTICK_PLACEMENT[Granularity.HOURLY] = { datefield: DateField.DATEFIELD_HH, step: 1, spacing: 1000 * 3600 };\nTICK_PLACEMENT[Granularity.TWO_HOURLY] = { datefield: DateField.DATEFIELD_HH, step: 2, spacing: 1000 * 3600 * 2 };\nTICK_PLACEMENT[Granularity.SIX_HOURLY] = { datefield: DateField.DATEFIELD_HH, step: 6, spacing: 1000 * 3600 * 6 };\nTICK_PLACEMENT[Granularity.DAILY] = { datefield: DateField.DATEFIELD_D, step: 1, spacing: 1000 * 86400 };\nTICK_PLACEMENT[Granularity.TWO_DAILY] = { datefield: DateField.DATEFIELD_D, step: 2, spacing: 1000 * 86400 * 2 };\nTICK_PLACEMENT[Granularity.WEEKLY] = { datefield: DateField.DATEFIELD_D, step: 7, spacing: 1000 * 604800 };\nTICK_PLACEMENT[Granularity.MONTHLY] = { datefield: DateField.DATEFIELD_M, step: 1, spacing: 1000 * 7200 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 / 12\nTICK_PLACEMENT[Granularity.QUARTERLY] = { datefield: DateField.DATEFIELD_M, step: 3, spacing: 1000 * 21600 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 / 4\nTICK_PLACEMENT[Granularity.BIANNUAL] = { datefield: DateField.DATEFIELD_M, step: 6, spacing: 1000 * 43200 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 / 2\nTICK_PLACEMENT[Granularity.ANNUAL] = { datefield: DateField.DATEFIELD_Y, step: 1, spacing: 1000 * 86400 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 * 1\nTICK_PLACEMENT[Granularity.DECADAL] = { datefield: DateField.DATEFIELD_Y, step: 10, spacing: 1000 * 864000 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 * 10\nTICK_PLACEMENT[Granularity.CENTENNIAL] = { datefield: DateField.DATEFIELD_Y, step: 100, spacing: 1000 * 8640000 * 365.2524 }; // 1e3 * 60 * 60 * 24 * 365.2524 * 100\n\n/**\n * This is a list of human-friendly values at which to show tick marks on a log\n * scale. It is k * 10^n, where k=1..9 and n=-39..+39, so:\n * ..., 1, 2, 3, 4, 5, ..., 9, 10, 20, 30, ..., 90, 100, 200, 300, ...\n * NOTE: this assumes that utils.LOG_SCALE = 10.\n * @type {Array.}\n */\nvar PREFERRED_LOG_TICK_VALUES = (function () {\n var vals = [];\n for (var power = -39; power <= 39; power++) {\n var range = Math.pow(10, power);\n for (var mult = 1; mult <= 9; mult++) {\n var val = range * mult;\n vals.push(val);\n }\n }\n return vals;\n})();\n\n/**\n * Determine the correct granularity of ticks on a date axis.\n *\n * @param {number} a Left edge of the chart (ms)\n * @param {number} b Right edge of the chart (ms)\n * @param {number} pixels Size of the chart in the relevant dimension (width).\n * @param {function(string):*} opts Function mapping from option name -> value.\n * @return {number} The appropriate axis granularity for this chart. See the\n * enumeration of possible values in dygraph-tickers.js.\n */\nvar pickDateTickGranularity = function pickDateTickGranularity(a, b, pixels, opts) {\n var pixels_per_tick = /** @type{number} */opts('pixelsPerLabel');\n for (var i = 0; i < Granularity.NUM_GRANULARITIES; i++) {\n var num_ticks = numDateTicks(a, b, i);\n if (pixels / num_ticks >= pixels_per_tick) {\n return i;\n }\n }\n return -1;\n};\n\n/**\n * Compute the number of ticks on a date axis for a given granularity.\n * @param {number} start_time\n * @param {number} end_time\n * @param {number} granularity (one of the granularities enumerated above)\n * @return {number} (Approximate) number of ticks that would result.\n */\nvar numDateTicks = function numDateTicks(start_time, end_time, granularity) {\n var spacing = TICK_PLACEMENT[granularity].spacing;\n return Math.round(1.0 * (end_time - start_time) / spacing);\n};\n\n/**\n * Compute the positions and labels of ticks on a date axis for a given granularity.\n * @param {number} start_time\n * @param {number} end_time\n * @param {number} granularity (one of the granularities enumerated above)\n * @param {function(string):*} opts Function mapping from option name -> value.\n * @param {Dygraph=} dg\n * @return {!TickList}\n */\nvar getDateAxis = function getDateAxis(start_time, end_time, granularity, opts, dg) {\n var formatter = /** @type{AxisLabelFormatter} */opts(\"axisLabelFormatter\");\n var utc = opts(\"labelsUTC\");\n var accessors = utc ? utils.DateAccessorsUTC : utils.DateAccessorsLocal;\n\n var datefield = TICK_PLACEMENT[granularity].datefield;\n var step = TICK_PLACEMENT[granularity].step;\n var spacing = TICK_PLACEMENT[granularity].spacing;\n\n // Choose a nice tick position before the initial instant.\n // Currently, this code deals properly with the existent daily granularities:\n // DAILY (with step of 1) and WEEKLY (with step of 7 but specially handled).\n // Other daily granularities (say TWO_DAILY) should also be handled specially\n // by setting the start_date_offset to 0.\n var start_date = new Date(start_time);\n var date_array = [];\n date_array[DateField.DATEFIELD_Y] = accessors.getFullYear(start_date);\n date_array[DateField.DATEFIELD_M] = accessors.getMonth(start_date);\n date_array[DateField.DATEFIELD_D] = accessors.getDate(start_date);\n date_array[DateField.DATEFIELD_HH] = accessors.getHours(start_date);\n date_array[DateField.DATEFIELD_MM] = accessors.getMinutes(start_date);\n date_array[DateField.DATEFIELD_SS] = accessors.getSeconds(start_date);\n date_array[DateField.DATEFIELD_MS] = accessors.getMilliseconds(start_date);\n\n var start_date_offset = date_array[datefield] % step;\n if (granularity == Granularity.WEEKLY) {\n // This will put the ticks on Sundays.\n start_date_offset = accessors.getDay(start_date);\n }\n\n date_array[datefield] -= start_date_offset;\n for (var df = datefield + 1; df < DateField.NUM_DATEFIELDS; df++) {\n // The minimum value is 1 for the day of month, and 0 for all other fields.\n date_array[df] = df === DateField.DATEFIELD_D ? 1 : 0;\n }\n\n // Generate the ticks.\n // For granularities not coarser than HOURLY we use the fact that:\n // the number of milliseconds between ticks is constant\n // and equal to the defined spacing.\n // Otherwise we rely on the 'roll over' property of the Date functions:\n // when some date field is set to a value outside of its logical range,\n // the excess 'rolls over' the next (more significant) field.\n // However, when using local time with DST transitions,\n // there are dates that do not represent any time value at all\n // (those in the hour skipped at the 'spring forward'),\n // and the JavaScript engines usually return an equivalent value.\n // Hence we have to check that the date is properly increased at each step,\n // returning a date at a nice tick position.\n var ticks = [];\n var tick_date = accessors.makeDate.apply(null, date_array);\n var tick_time = tick_date.getTime();\n if (granularity <= Granularity.HOURLY) {\n if (tick_time < start_time) {\n tick_time += spacing;\n tick_date = new Date(tick_time);\n }\n while (tick_time <= end_time) {\n ticks.push({ v: tick_time,\n label: formatter.call(dg, tick_date, granularity, opts, dg)\n });\n tick_time += spacing;\n tick_date = new Date(tick_time);\n }\n } else {\n if (tick_time < start_time) {\n date_array[datefield] += step;\n tick_date = accessors.makeDate.apply(null, date_array);\n tick_time = tick_date.getTime();\n }\n while (tick_time <= end_time) {\n if (granularity >= Granularity.DAILY || accessors.getHours(tick_date) % step === 0) {\n ticks.push({ v: tick_time,\n label: formatter.call(dg, tick_date, granularity, opts, dg)\n });\n }\n date_array[datefield] += step;\n tick_date = accessors.makeDate.apply(null, date_array);\n tick_time = tick_date.getTime();\n }\n }\n return ticks;\n};\nexports.getDateAxis = getDateAxis;","/**\n * @license\n * Copyright 2011 Robert Konigsberg (konigsberg@google.com)\n * MIT-licensed (http://opensource.org/licenses/MIT)\n */\n\n/**\n * @fileoverview The default interaction model for Dygraphs. This is kept out\n * of dygraph.js for better navigability.\n * @author Robert Konigsberg (konigsberg@google.com)\n */\n\n/*global Dygraph:false */\n\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nfunction _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj[\"default\"] = obj; return newObj; } }\n\nvar _dygraphUtils = require('./dygraph-utils');\n\nvar utils = _interopRequireWildcard(_dygraphUtils);\n\n/**\n * You can drag this many pixels past the edge of the chart and still have it\n * be considered a zoom. This makes it easier to zoom to the exact edge of the\n * chart, a fairly common operation.\n */\nvar DRAG_EDGE_MARGIN = 100;\n\n/**\n * A collection of functions to facilitate build custom interaction models.\n * @class\n */\nvar DygraphInteraction = {};\n\n/**\n * Checks whether the beginning & ending of an event were close enough that it\n * should be considered a click. If it should, dispatch appropriate events.\n * Returns true if the event was treated as a click.\n *\n * @param {Event} event\n * @param {Dygraph} g\n * @param {Object} context\n */\nDygraphInteraction.maybeTreatMouseOpAsClick = function (event, g, context) {\n context.dragEndX = utils.dragGetX_(event, context);\n context.dragEndY = utils.dragGetY_(event, context);\n var regionWidth = Math.abs(context.dragEndX - context.dragStartX);\n var regionHeight = Math.abs(context.dragEndY - context.dragStartY);\n\n if (regionWidth < 2 && regionHeight < 2 && g.lastx_ !== undefined && g.lastx_ != -1) {\n DygraphInteraction.treatMouseOpAsClick(g, event, context);\n }\n\n context.regionWidth = regionWidth;\n context.regionHeight = regionHeight;\n};\n\n/**\n * Called in response to an interaction model operation that\n * should start the default panning behavior.\n *\n * It's used in the default callback for \"mousedown\" operations.\n * Custom interaction model builders can use it to provide the default\n * panning behavior.\n *\n * @param {Event} event the event object which led to the startPan call.\n * @param {Dygraph} g The dygraph on which to act.\n * @param {Object} context The dragging context object (with\n * dragStartX/dragStartY/etc. properties). This function modifies the\n * context.\n */\nDygraphInteraction.startPan = function (event, g, context) {\n var i, axis;\n context.isPanning = true;\n var xRange = g.xAxisRange();\n\n if (g.getOptionForAxis(\"logscale\", \"x\")) {\n context.initialLeftmostDate = utils.log10(xRange[0]);\n context.dateRange = utils.log10(xRange[1]) - utils.log10(xRange[0]);\n } else {\n context.initialLeftmostDate = xRange[0];\n context.dateRange = xRange[1] - xRange[0];\n }\n context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1);\n\n if (g.getNumericOption(\"panEdgeFraction\")) {\n var maxXPixelsToDraw = g.width_ * g.getNumericOption(\"panEdgeFraction\");\n var xExtremes = g.xAxisExtremes(); // I REALLY WANT TO CALL THIS xTremes!\n\n var boundedLeftX = g.toDomXCoord(xExtremes[0]) - maxXPixelsToDraw;\n var boundedRightX = g.toDomXCoord(xExtremes[1]) + maxXPixelsToDraw;\n\n var boundedLeftDate = g.toDataXCoord(boundedLeftX);\n var boundedRightDate = g.toDataXCoord(boundedRightX);\n context.boundedDates = [boundedLeftDate, boundedRightDate];\n\n var boundedValues = [];\n var maxYPixelsToDraw = g.height_ * g.getNumericOption(\"panEdgeFraction\");\n\n for (i = 0; i < g.axes_.length; i++) {\n axis = g.axes_[i];\n var yExtremes = axis.extremeRange;\n\n var boundedTopY = g.toDomYCoord(yExtremes[0], i) + maxYPixelsToDraw;\n var boundedBottomY = g.toDomYCoord(yExtremes[1], i) - maxYPixelsToDraw;\n\n var boundedTopValue = g.toDataYCoord(boundedTopY, i);\n var boundedBottomValue = g.toDataYCoord(boundedBottomY, i);\n\n boundedValues[i] = [boundedTopValue, boundedBottomValue];\n }\n context.boundedValues = boundedValues;\n }\n\n // Record the range of each y-axis at the start of the drag.\n // If any axis has a valueRange, then we want a 2D pan.\n // We can't store data directly in g.axes_, because it does not belong to us\n // and could change out from under us during a pan (say if there's a data\n // update).\n context.is2DPan = false;\n context.axes = [];\n for (i = 0; i < g.axes_.length; i++) {\n axis = g.axes_[i];\n var axis_data = {};\n var yRange = g.yAxisRange(i);\n // TODO(konigsberg): These values should be in |context|.\n // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale.\n var logscale = g.attributes_.getForAxis(\"logscale\", i);\n if (logscale) {\n axis_data.initialTopValue = utils.log10(yRange[1]);\n axis_data.dragValueRange = utils.log10(yRange[1]) - utils.log10(yRange[0]);\n } else {\n axis_data.initialTopValue = yRange[1];\n axis_data.dragValueRange = yRange[1] - yRange[0];\n }\n axis_data.unitsPerPixel = axis_data.dragValueRange / (g.plotter_.area.h - 1);\n context.axes.push(axis_data);\n\n // While calculating axes, set 2dpan.\n if (axis.valueRange) context.is2DPan = true;\n }\n};\n\n/**\n * Called in response to an interaction model operation that\n * responds to an event that pans the view.\n *\n * It's used in the default callback for \"mousemove\" operations.\n * Custom interaction model builders can use it to provide the default\n * panning behavior.\n *\n * @param {Event} event the event object which led to the movePan call.\n * @param {Dygraph} g The dygraph on which to act.\n * @param {Object} context The dragging context object (with\n * dragStartX/dragStartY/etc. properties). This function modifies the\n * context.\n */\nDygraphInteraction.movePan = function (event, g, context) {\n context.dragEndX = utils.dragGetX_(event, context);\n context.dragEndY = utils.dragGetY_(event, context);\n\n var minDate = context.initialLeftmostDate - (context.dragEndX - context.dragStartX) * context.xUnitsPerPixel;\n if (context.boundedDates) {\n minDate = Math.max(minDate, context.boundedDates[0]);\n }\n var maxDate = minDate + context.dateRange;\n if (context.boundedDates) {\n if (maxDate > context.boundedDates[1]) {\n // Adjust minDate, and recompute maxDate.\n minDate = minDate - (maxDate - context.boundedDates[1]);\n maxDate = minDate + context.dateRange;\n }\n }\n\n if (g.getOptionForAxis(\"logscale\", \"x\")) {\n g.dateWindow_ = [Math.pow(utils.LOG_SCALE, minDate), Math.pow(utils.LOG_SCALE, maxDate)];\n } else {\n g.dateWindow_ = [minDate, maxDate];\n }\n\n // y-axis scaling is automatic unless this is a full 2D pan.\n if (context.is2DPan) {\n\n var pixelsDragged = context.dragEndY - context.dragStartY;\n\n // Adjust each axis appropriately.\n for (var i = 0; i < g.axes_.length; i++) {\n var axis = g.axes_[i];\n var axis_data = context.axes[i];\n var unitsDragged = pixelsDragged * axis_data.unitsPerPixel;\n\n var boundedValue = context.boundedValues ? context.boundedValues[i] : null;\n\n // In log scale, maxValue and minValue are the logs of those values.\n var maxValue = axis_data.initialTopValue + unitsDragged;\n if (boundedValue) {\n maxValue = Math.min(maxValue, boundedValue[1]);\n }\n var minValue = maxValue - axis_data.dragValueRange;\n if (boundedValue) {\n if (minValue < boundedValue[0]) {\n // Adjust maxValue, and recompute minValue.\n maxValue = maxValue - (minValue - boundedValue[0]);\n minValue = maxValue - axis_data.dragValueRange;\n }\n }\n if (g.attributes_.getForAxis(\"logscale\", i)) {\n axis.valueRange = [Math.pow(utils.LOG_SCALE, minValue), Math.pow(utils.LOG_SCALE, maxValue)];\n } else {\n axis.valueRange = [minValue, maxValue];\n }\n }\n }\n\n g.drawGraph_(false);\n};\n\n/**\n * Called in response to an interaction model operation that\n * responds to an event that ends panning.\n *\n * It's used in the default callback for \"mouseup\" operations.\n * Custom interaction model builders can use it to provide the default\n * panning behavior.\n *\n * @param {Event} event the event object which led to the endPan call.\n * @param {Dygraph} g The dygraph on which to act.\n * @param {Object} context The dragging context object (with\n * dragStartX/dragStartY/etc. properties). This function modifies the\n * context.\n */\nDygraphInteraction.endPan = DygraphInteraction.maybeTreatMouseOpAsClick;\n\n/**\n * Called in response to an interaction model operation that\n * responds to an event that starts zooming.\n *\n * It's used in the default callback for \"mousedown\" operations.\n * Custom interaction model builders can use it to provide the default\n * zooming behavior.\n *\n * @param {Event} event the event object which led to the startZoom call.\n * @param {Dygraph} g The dygraph on which to act.\n * @param {Object} context The dragging context object (with\n * dragStartX/dragStartY/etc. properties). This function modifies the\n * context.\n */\nDygraphInteraction.startZoom = function (event, g, context) {\n context.isZooming = true;\n context.zoomMoved = false;\n};\n\n/**\n * Called in response to an interaction model operation that\n * responds to an event that defines zoom boundaries.\n *\n * It's used in the default callback for \"mousemove\" operations.\n * Custom interaction model builders can use it to provide the default\n * zooming behavior.\n *\n * @param {Event} event the event object which led to the moveZoom call.\n * @param {Dygraph} g The dygraph on which to act.\n * @param {Object} context The dragging context object (with\n * dragStartX/dragStartY/etc. properties). This function modifies the\n * context.\n */\nDygraphInteraction.moveZoom = function (event, g, context) {\n context.zoomMoved = true;\n context.dragEndX = utils.dragGetX_(event, context);\n context.dragEndY = utils.dragGetY_(event, context);\n\n var xDelta = Math.abs(context.dragStartX - context.dragEndX);\n var yDelta = Math.abs(context.dragStartY - context.dragEndY);\n\n // drag direction threshold for y axis is twice as large as x axis\n context.dragDirection = xDelta < yDelta / 2 ? utils.VERTICAL : utils.HORIZONTAL;\n\n g.drawZoomRect_(context.dragDirection, context.dragStartX, context.dragEndX, context.dragStartY, context.dragEndY, context.prevDragDirection, context.prevEndX, context.prevEndY);\n\n context.prevEndX = context.dragEndX;\n context.prevEndY = context.dragEndY;\n context.prevDragDirection = context.dragDirection;\n};\n\n/**\n * TODO(danvk): move this logic into dygraph.js\n * @param {Dygraph} g\n * @param {Event} event\n * @param {Object} context\n */\nDygraphInteraction.treatMouseOpAsClick = function (g, event, context) {\n var clickCallback = g.getFunctionOption('clickCallback');\n var pointClickCallback = g.getFunctionOption('pointClickCallback');\n\n var selectedPoint = null;\n\n // Find out if the click occurs on a point.\n var closestIdx = -1;\n var closestDistance = Number.MAX_VALUE;\n\n // check if the click was on a particular point.\n for (var i = 0; i < g.selPoints_.length; i++) {\n var p = g.selPoints_[i];\n var distance = Math.pow(p.canvasx - context.dragEndX, 2) + Math.pow(p.canvasy - context.dragEndY, 2);\n if (!isNaN(distance) && (closestIdx == -1 || distance < closestDistance)) {\n closestDistance = distance;\n closestIdx = i;\n }\n }\n\n // Allow any click within two pixels of the dot.\n var radius = g.getNumericOption('highlightCircleSize') + 2;\n if (closestDistance <= radius * radius) {\n selectedPoint = g.selPoints_[closestIdx];\n }\n\n if (selectedPoint) {\n var e = {\n cancelable: true,\n point: selectedPoint,\n canvasx: context.dragEndX,\n canvasy: context.dragEndY\n };\n var defaultPrevented = g.cascadeEvents_('pointClick', e);\n if (defaultPrevented) {\n // Note: this also prevents click / clickCallback from firing.\n return;\n }\n if (pointClickCallback) {\n pointClickCallback.call(g, event, selectedPoint);\n }\n }\n\n var e = {\n cancelable: true,\n xval: g.lastx_, // closest point by x value\n pts: g.selPoints_,\n canvasx: context.dragEndX,\n canvasy: context.dragEndY\n };\n if (!g.cascadeEvents_('click', e)) {\n if (clickCallback) {\n // TODO(danvk): pass along more info about the points, e.g. 'x'\n clickCallback.call(g, event, g.lastx_, g.selPoints_);\n }\n }\n};\n\n/**\n * Called in response to an interaction model operation that\n * responds to an event that performs a zoom based on previously defined\n * bounds..\n *\n * It's used in the default callback for \"mouseup\" operations.\n * Custom interaction model builders can use it to provide the default\n * zooming behavior.\n *\n * @param {Event} event the event object which led to the endZoom call.\n * @param {Dygraph} g The dygraph on which to end the zoom.\n * @param {Object} context The dragging context object (with\n * dragStartX/dragStartY/etc. properties). This function modifies the\n * context.\n */\nDygraphInteraction.endZoom = function (event, g, context) {\n g.clearZoomRect_();\n context.isZooming = false;\n DygraphInteraction.maybeTreatMouseOpAsClick(event, g, context);\n\n // The zoom rectangle is visibly clipped to the plot area, so its behavior\n // should be as well.\n // See http://code.google.com/p/dygraphs/issues/detail?id=280\n var plotArea = g.getArea();\n if (context.regionWidth >= 10 && context.dragDirection == utils.HORIZONTAL) {\n var left = Math.min(context.dragStartX, context.dragEndX),\n right = Math.max(context.dragStartX, context.dragEndX);\n left = Math.max(left, plotArea.x);\n right = Math.min(right, plotArea.x + plotArea.w);\n if (left < right) {\n g.doZoomX_(left, right);\n }\n context.cancelNextDblclick = true;\n } else if (context.regionHeight >= 10 && context.dragDirection == utils.VERTICAL) {\n var top = Math.min(context.dragStartY, context.dragEndY),\n bottom = Math.max(context.dragStartY, context.dragEndY);\n top = Math.max(top, plotArea.y);\n bottom = Math.min(bottom, plotArea.y + plotArea.h);\n if (top < bottom) {\n g.doZoomY_(top, bottom);\n }\n context.cancelNextDblclick = true;\n }\n context.dragStartX = null;\n context.dragStartY = null;\n};\n\n/**\n * @private\n */\nDygraphInteraction.startTouch = function (event, g, context) {\n event.preventDefault(); // touch browsers are all nice.\n if (event.touches.length > 1) {\n // If the user ever puts two fingers down, it's not a double tap.\n context.startTimeForDoubleTapMs = null;\n }\n\n var touches = [];\n for (var i = 0; i < event.touches.length; i++) {\n var t = event.touches[i];\n // we dispense with 'dragGetX_' because all touchBrowsers support pageX\n touches.push({\n pageX: t.pageX,\n pageY: t.pageY,\n dataX: g.toDataXCoord(t.pageX),\n dataY: g.toDataYCoord(t.pageY)\n // identifier: t.identifier\n });\n }\n context.initialTouches = touches;\n\n if (touches.length == 1) {\n // This is just a swipe.\n context.initialPinchCenter = touches[0];\n context.touchDirections = { x: true, y: true };\n } else if (touches.length >= 2) {\n // It's become a pinch!\n // In case there are 3+ touches, we ignore all but the \"first\" two.\n\n // only screen coordinates can be averaged (data coords could be log scale).\n context.initialPinchCenter = {\n pageX: 0.5 * (touches[0].pageX + touches[1].pageX),\n pageY: 0.5 * (touches[0].pageY + touches[1].pageY),\n\n // TODO(danvk): remove\n dataX: 0.5 * (touches[0].dataX + touches[1].dataX),\n dataY: 0.5 * (touches[0].dataY + touches[1].dataY)\n };\n\n // Make pinches in a 45-degree swath around either axis 1-dimensional zooms.\n var initialAngle = 180 / Math.PI * Math.atan2(context.initialPinchCenter.pageY - touches[0].pageY, touches[0].pageX - context.initialPinchCenter.pageX);\n\n // use symmetry to get it into the first quadrant.\n initialAngle = Math.abs(initialAngle);\n if (initialAngle > 90) initialAngle = 90 - initialAngle;\n\n context.touchDirections = {\n x: initialAngle < 90 - 45 / 2,\n y: initialAngle > 45 / 2\n };\n }\n\n // save the full x & y ranges.\n context.initialRange = {\n x: g.xAxisRange(),\n y: g.yAxisRange()\n };\n};\n\n/**\n * @private\n */\nDygraphInteraction.moveTouch = function (event, g, context) {\n // If the tap moves, then it's definitely not part of a double-tap.\n context.startTimeForDoubleTapMs = null;\n\n var i,\n touches = [];\n for (i = 0; i < event.touches.length; i++) {\n var t = event.touches[i];\n touches.push({\n pageX: t.pageX,\n pageY: t.pageY\n });\n }\n var initialTouches = context.initialTouches;\n\n var c_now;\n\n // old and new centers.\n var c_init = context.initialPinchCenter;\n if (touches.length == 1) {\n c_now = touches[0];\n } else {\n c_now = {\n pageX: 0.5 * (touches[0].pageX + touches[1].pageX),\n pageY: 0.5 * (touches[0].pageY + touches[1].pageY)\n };\n }\n\n // this is the \"swipe\" component\n // we toss it out for now, but could use it in the future.\n var swipe = {\n pageX: c_now.pageX - c_init.pageX,\n pageY: c_now.pageY - c_init.pageY\n };\n var dataWidth = context.initialRange.x[1] - context.initialRange.x[0];\n var dataHeight = context.initialRange.y[0] - context.initialRange.y[1];\n swipe.dataX = swipe.pageX / g.plotter_.area.w * dataWidth;\n swipe.dataY = swipe.pageY / g.plotter_.area.h * dataHeight;\n var xScale, yScale;\n\n // The residual bits are usually split into scale & rotate bits, but we split\n // them into x-scale and y-scale bits.\n if (touches.length == 1) {\n xScale = 1.0;\n yScale = 1.0;\n } else if (touches.length >= 2) {\n var initHalfWidth = initialTouches[1].pageX - c_init.pageX;\n xScale = (touches[1].pageX - c_now.pageX) / initHalfWidth;\n\n var initHalfHeight = initialTouches[1].pageY - c_init.pageY;\n yScale = (touches[1].pageY - c_now.pageY) / initHalfHeight;\n }\n\n // Clip scaling to [1/8, 8] to prevent too much blowup.\n xScale = Math.min(8, Math.max(0.125, xScale));\n yScale = Math.min(8, Math.max(0.125, yScale));\n\n var didZoom = false;\n if (context.touchDirections.x) {\n g.dateWindow_ = [c_init.dataX - swipe.dataX + (context.initialRange.x[0] - c_init.dataX) / xScale, c_init.dataX - swipe.dataX + (context.initialRange.x[1] - c_init.dataX) / xScale];\n didZoom = true;\n }\n\n if (context.touchDirections.y) {\n for (i = 0; i < 1 /*g.axes_.length*/; i++) {\n var axis = g.axes_[i];\n var logscale = g.attributes_.getForAxis(\"logscale\", i);\n if (logscale) {\n // TODO(danvk): implement\n } else {\n axis.valueRange = [c_init.dataY - swipe.dataY + (context.initialRange.y[0] - c_init.dataY) / yScale, c_init.dataY - swipe.dataY + (context.initialRange.y[1] - c_init.dataY) / yScale];\n didZoom = true;\n }\n }\n }\n\n g.drawGraph_(false);\n\n // We only call zoomCallback on zooms, not pans, to mirror desktop behavior.\n if (didZoom && touches.length > 1 && g.getFunctionOption('zoomCallback')) {\n var viewWindow = g.xAxisRange();\n g.getFunctionOption(\"zoomCallback\").call(g, viewWindow[0], viewWindow[1], g.yAxisRanges());\n }\n};\n\n/**\n * @private\n */\nDygraphInteraction.endTouch = function (event, g, context) {\n if (event.touches.length !== 0) {\n // this is effectively a \"reset\"\n DygraphInteraction.startTouch(event, g, context);\n } else if (event.changedTouches.length == 1) {\n // Could be part of a \"double tap\"\n // The heuristic here is that it's a double-tap if the two touchend events\n // occur within 500ms and within a 50x50 pixel box.\n var now = new Date().getTime();\n var t = event.changedTouches[0];\n if (context.startTimeForDoubleTapMs && now - context.startTimeForDoubleTapMs < 500 && context.doubleTapX && Math.abs(context.doubleTapX - t.screenX) < 50 && context.doubleTapY && Math.abs(context.doubleTapY - t.screenY) < 50) {\n g.resetZoom();\n } else {\n context.startTimeForDoubleTapMs = now;\n context.doubleTapX = t.screenX;\n context.doubleTapY = t.screenY;\n }\n }\n};\n\n// Determine the distance from x to [left, right].\nvar distanceFromInterval = function distanceFromInterval(x, left, right) {\n if (x < left) {\n return left - x;\n } else if (x > right) {\n return x - right;\n } else {\n return 0;\n }\n};\n\n/**\n * Returns the number of pixels by which the event happens from the nearest\n * edge of the chart. For events in the interior of the chart, this returns zero.\n */\nvar distanceFromChart = function distanceFromChart(event, g) {\n var chartPos = utils.findPos(g.canvas_);\n var box = {\n left: chartPos.x,\n right: chartPos.x + g.canvas_.offsetWidth,\n top: chartPos.y,\n bottom: chartPos.y + g.canvas_.offsetHeight\n };\n\n var pt = {\n x: utils.pageX(event),\n y: utils.pageY(event)\n };\n\n var dx = distanceFromInterval(pt.x, box.left, box.right),\n dy = distanceFromInterval(pt.y, box.top, box.bottom);\n return Math.max(dx, dy);\n};\n\n/**\n * Default interation model for dygraphs. You can refer to specific elements of\n * this when constructing your own interaction model, e.g.:\n * g.updateOptions( {\n * interactionModel: {\n * mousedown: DygraphInteraction.defaultInteractionModel.mousedown\n * }\n * } );\n */\nDygraphInteraction.defaultModel = {\n // Track the beginning of drag events\n mousedown: function mousedown(event, g, context) {\n // Right-click should not initiate a zoom.\n if (event.button && event.button == 2) return;\n\n context.initializeMouseDown(event, g, context);\n\n if (event.altKey || event.shiftKey) {\n DygraphInteraction.startPan(event, g, context);\n } else {\n DygraphInteraction.startZoom(event, g, context);\n }\n\n // Note: we register mousemove/mouseup on document to allow some leeway for\n // events to move outside of the chart. Interaction model events get\n // registered on the canvas, which is too small to allow this.\n var mousemove = function mousemove(event) {\n if (context.isZooming) {\n // When the mouse moves >200px from the chart edge, cancel the zoom.\n var d = distanceFromChart(event, g);\n if (d < DRAG_EDGE_MARGIN) {\n DygraphInteraction.moveZoom(event, g, context);\n } else {\n if (context.dragEndX !== null) {\n context.dragEndX = null;\n context.dragEndY = null;\n g.clearZoomRect_();\n }\n }\n } else if (context.isPanning) {\n DygraphInteraction.movePan(event, g, context);\n }\n };\n var mouseup = function mouseup(event) {\n if (context.isZooming) {\n if (context.dragEndX !== null) {\n DygraphInteraction.endZoom(event, g, context);\n } else {\n DygraphInteraction.maybeTreatMouseOpAsClick(event, g, context);\n }\n } else if (context.isPanning) {\n DygraphInteraction.endPan(event, g, context);\n }\n\n utils.removeEvent(document, 'mousemove', mousemove);\n utils.removeEvent(document, 'mouseup', mouseup);\n context.destroy();\n };\n\n g.addAndTrackEvent(document, 'mousemove', mousemove);\n g.addAndTrackEvent(document, 'mouseup', mouseup);\n },\n willDestroyContextMyself: true,\n\n touchstart: function touchstart(event, g, context) {\n DygraphInteraction.startTouch(event, g, context);\n },\n touchmove: function touchmove(event, g, context) {\n DygraphInteraction.moveTouch(event, g, context);\n },\n touchend: function touchend(event, g, context) {\n DygraphInteraction.endTouch(event, g, context);\n },\n\n // Disable zooming out if panning.\n dblclick: function dblclick(event, g, context) {\n if (context.cancelNextDblclick) {\n context.cancelNextDblclick = false;\n return;\n }\n\n // Give plugins a chance to grab this event.\n var e = {\n canvasx: context.dragEndX,\n canvasy: context.dragEndY,\n cancelable: true\n };\n if (g.cascadeEvents_('dblclick', e)) {\n return;\n }\n\n if (event.altKey || event.shiftKey) {\n return;\n }\n g.resetZoom();\n }\n};\n\n/*\nDygraph.DEFAULT_ATTRS.interactionModel = DygraphInteraction.defaultModel;\n\n// old ways of accessing these methods/properties\nDygraph.defaultInteractionModel = DygraphInteraction.defaultModel;\nDygraph.endZoom = DygraphInteraction.endZoom;\nDygraph.moveZoom = DygraphInteraction.moveZoom;\nDygraph.startZoom = DygraphInteraction.startZoom;\nDygraph.endPan = DygraphInteraction.endPan;\nDygraph.movePan = DygraphInteraction.movePan;\nDygraph.startPan = DygraphInteraction.startPan;\n*/\n\nDygraphInteraction.nonInteractiveModel_ = {\n mousedown: function mousedown(event, g, context) {\n context.initializeMouseDown(event, g, context);\n },\n mouseup: DygraphInteraction.maybeTreatMouseOpAsClick\n};\n\n// Default interaction model when using the range selector.\nDygraphInteraction.dragIsPanInteractionModel = {\n mousedown: function mousedown(event, g, context) {\n context.initializeMouseDown(event, g, context);\n DygraphInteraction.startPan(event, g, context);\n },\n mousemove: function mousemove(event, g, context) {\n if (context.isPanning) {\n DygraphInteraction.movePan(event, g, context);\n }\n },\n mouseup: function mouseup(event, g, context) {\n if (context.isPanning) {\n DygraphInteraction.endPan(event, g, context);\n }\n }\n};\n\nexports[\"default\"] = DygraphInteraction;\nmodule.exports = exports[\"default\"];","/**\n * @license\n * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)\n * MIT-licensed (http://opensource.org/licenses/MIT)\n */\n\n/**\n * @fileoverview This file contains the managment of data handlers\n * @author David Eberlein (david.eberlein@ch.sauter-bc.com)\n *\n * The idea is to define a common, generic data format that works for all data\n * structures supported by dygraphs. To make this possible, the DataHandler\n * interface is introduced. This makes it possible, that dygraph itself can work\n * with the same logic for every data type independent of the actual format and\n * the DataHandler takes care of the data format specific jobs.\n * DataHandlers are implemented for all data types supported by Dygraphs and\n * return Dygraphs compliant formats.\n * By default the correct DataHandler is chosen based on the options set.\n * Optionally the user may use his own DataHandler (similar to the plugin\n * system).\n *\n *\n * The unified data format returend by each handler is defined as so:\n * series[n][point] = [x,y,(extras)]\n *\n * This format contains the common basis that is needed to draw a simple line\n * series extended by optional extras for more complex graphing types. It\n * contains a primitive x value as first array entry, a primitive y value as\n * second array entry and an optional extras object for additional data needed.\n *\n * x must always be a number.\n * y must always be a number, NaN of type number or null.\n * extras is optional and must be interpreted by the DataHandler. It may be of\n * any type.\n *\n * In practice this might look something like this:\n * default: [x, yVal]\n * errorBar / customBar: [x, yVal, [yTopVariance, yBottomVariance] ]\n *\n */\n/*global Dygraph:false */\n/*global DygraphLayout:false */\n\n\"use strict\";\n\n/**\n *\n * The data handler is responsible for all data specific operations. All of the\n * series data it receives and returns is always in the unified data format.\n * Initially the unified data is created by the extractSeries method\n * @constructor\n */\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nvar DygraphDataHandler = function DygraphDataHandler() {};\n\nvar handler = DygraphDataHandler;\n\n/**\n * X-value array index constant for unified data samples.\n * @const\n * @type {number}\n */\nhandler.X = 0;\n\n/**\n * Y-value array index constant for unified data samples.\n * @const\n * @type {number}\n */\nhandler.Y = 1;\n\n/**\n * Extras-value array index constant for unified data samples.\n * @const\n * @type {number}\n */\nhandler.EXTRAS = 2;\n\n/**\n * Extracts one series from the raw data (a 2D array) into an array of the\n * unified data format.\n * This is where undesirable points (i.e. negative values on log scales and\n * missing values through which we wish to connect lines) are dropped.\n * TODO(danvk): the \"missing values\" bit above doesn't seem right.\n *\n * @param {!Array.} rawData The raw data passed into dygraphs where\n * rawData[i] = [x,ySeries1,...,ySeriesN].\n * @param {!number} seriesIndex Index of the series to extract. All other\n * series should be ignored.\n * @param {!DygraphOptions} options Dygraph options.\n * @return {Array.<[!number,?number,?]>} The series in the unified data format\n * where series[i] = [x,y,{extras}].\n */\nhandler.prototype.extractSeries = function (rawData, seriesIndex, options) {};\n\n/**\n * Converts a series to a Point array. The resulting point array must be\n * returned in increasing order of idx property.\n *\n * @param {!Array.<[!number,?number,?]>} series The series in the unified\n * data format where series[i] = [x,y,{extras}].\n * @param {!string} setName Name of the series.\n * @param {!number} boundaryIdStart Index offset of the first point, equal to the\n * number of skipped points left of the date window minimum (if any).\n * @return {!Array.} List of points for this series.\n */\nhandler.prototype.seriesToPoints = function (series, setName, boundaryIdStart) {\n // TODO(bhs): these loops are a hot-spot for high-point-count charts. In\n // fact,\n // on chrome+linux, they are 6 times more expensive than iterating through\n // the\n // points and drawing the lines. The brunt of the cost comes from allocating\n // the |point| structures.\n var points = [];\n for (var i = 0; i < series.length; ++i) {\n var item = series[i];\n var yraw = item[1];\n var yval = yraw === null ? null : handler.parseFloat(yraw);\n var point = {\n x: NaN,\n y: NaN,\n xval: handler.parseFloat(item[0]),\n yval: yval,\n name: setName, // TODO(danvk): is this really necessary?\n idx: i + boundaryIdStart\n };\n points.push(point);\n }\n this.onPointsCreated_(series, points);\n return points;\n};\n\n/**\n * Callback called for each series after the series points have been generated\n * which will later be used by the plotters to draw the graph.\n * Here data may be added to the seriesPoints which is needed by the plotters.\n * The indexes of series and points are in sync meaning the original data\n * sample for series[i] is points[i].\n *\n * @param {!Array.<[!number,?number,?]>} series The series in the unified\n * data format where series[i] = [x,y,{extras}].\n * @param {!Array.} points The corresponding points passed\n * to the plotter.\n * @protected\n */\nhandler.prototype.onPointsCreated_ = function (series, points) {};\n\n/**\n * Calculates the rolling average of a data set.\n *\n * @param {!Array.<[!number,?number,?]>} series The series in the unified\n * data format where series[i] = [x,y,{extras}].\n * @param {!number} rollPeriod The number of points over which to average the data\n * @param {!DygraphOptions} options The dygraph options.\n * @return {!Array.<[!number,?number,?]>} the rolled series.\n */\nhandler.prototype.rollingAverage = function (series, rollPeriod, options) {};\n\n/**\n * Computes the range of the data series (including confidence intervals).\n *\n * @param {!Array.<[!number,?number,?]>} series The series in the unified\n * data format where series[i] = [x, y, {extras}].\n * @param {!Array.} dateWindow The x-value range to display with\n * the format: [min, max].\n * @param {!DygraphOptions} options The dygraph options.\n * @return {Array.} The low and high extremes of the series in the\n * given window with the format: [low, high].\n */\nhandler.prototype.getExtremeYValues = function (series, dateWindow, options) {};\n\n/**\n * Callback called for each series after the layouting data has been\n * calculated before the series is drawn. Here normalized positioning data\n * should be calculated for the extras of each point.\n *\n * @param {!Array.} points The points passed to\n * the plotter.\n * @param {!Object} axis The axis on which the series will be plotted.\n * @param {!boolean} logscale Weather or not to use a logscale.\n */\nhandler.prototype.onLineEvaluated = function (points, axis, logscale) {};\n\n/**\n * Optimized replacement for parseFloat, which was way too slow when almost\n * all values were type number, with few edge cases, none of which were strings.\n * @param {?number} val\n * @return {number}\n * @protected\n */\nhandler.parseFloat = function (val) {\n // parseFloat(null) is NaN\n if (val === null) {\n return NaN;\n }\n\n // Assume it's a number or NaN. If it's something else, I'll be shocked.\n return val;\n};\n\nexports[\"default\"] = DygraphDataHandler;\nmodule.exports = exports[\"default\"];","'use strict';\n\nvar reactIs = require('react-is');\n\n/**\n * Copyright 2015, Yahoo! Inc.\n * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.\n */\nvar REACT_STATICS = {\n childContextTypes: true,\n contextType: true,\n contextTypes: true,\n defaultProps: true,\n displayName: true,\n getDefaultProps: true,\n getDerivedStateFromError: true,\n getDerivedStateFromProps: true,\n mixins: true,\n propTypes: true,\n type: true\n};\nvar KNOWN_STATICS = {\n name: true,\n length: true,\n prototype: true,\n caller: true,\n callee: true,\n arguments: true,\n arity: true\n};\nvar FORWARD_REF_STATICS = {\n '$$typeof': true,\n render: true,\n defaultProps: true,\n displayName: true,\n propTypes: true\n};\nvar MEMO_STATICS = {\n '$$typeof': true,\n compare: true,\n defaultProps: true,\n displayName: true,\n propTypes: true,\n type: true\n};\nvar TYPE_STATICS = {};\nTYPE_STATICS[reactIs.ForwardRef] = FORWARD_REF_STATICS;\nTYPE_STATICS[reactIs.Memo] = MEMO_STATICS;\n\nfunction getStatics(component) {\n // React v16.11 and below\n if (reactIs.isMemo(component)) {\n return MEMO_STATICS;\n } // React v16.12 and above\n\n\n return TYPE_STATICS[component['$$typeof']] || REACT_STATICS;\n}\n\nvar defineProperty = Object.defineProperty;\nvar getOwnPropertyNames = Object.getOwnPropertyNames;\nvar getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\nvar getPrototypeOf = Object.getPrototypeOf;\nvar objectPrototype = Object.prototype;\nfunction hoistNonReactStatics(targetComponent, sourceComponent, blacklist) {\n if (typeof sourceComponent !== 'string') {\n // don't hoist over string (html) components\n if (objectPrototype) {\n var inheritedComponent = getPrototypeOf(sourceComponent);\n\n if (inheritedComponent && inheritedComponent !== objectPrototype) {\n hoistNonReactStatics(targetComponent, inheritedComponent, blacklist);\n }\n }\n\n var keys = getOwnPropertyNames(sourceComponent);\n\n if (getOwnPropertySymbols) {\n keys = keys.concat(getOwnPropertySymbols(sourceComponent));\n }\n\n var targetStatics = getStatics(targetComponent);\n var sourceStatics = getStatics(sourceComponent);\n\n for (var i = 0; i < keys.length; ++i) {\n var key = keys[i];\n\n if (!KNOWN_STATICS[key] && !(blacklist && blacklist[key]) && !(sourceStatics && sourceStatics[key]) && !(targetStatics && targetStatics[key])) {\n var descriptor = getOwnPropertyDescriptor(sourceComponent, key);\n\n try {\n // Avoid failures from read-only properties\n defineProperty(targetComponent, key, descriptor);\n } catch (e) {}\n }\n }\n }\n\n return targetComponent;\n}\n\nmodule.exports = hoistNonReactStatics;\n","var __assign = (this && this.__assign) || function () {\n __assign = Object.assign || function(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))\n t[p] = s[p];\n }\n return t;\n };\n return __assign.apply(this, arguments);\n};\nvar __rest = (this && this.__rest) || function (s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n};\nvar __read = (this && this.__read) || function (o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n};\nvar __spread = (this && this.__spread) || function () {\n for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));\n return ar;\n};\nimport * as React from 'react';\nimport classNamesFunc from 'classnames';\nimport { parseThemeOptions } from './with-theme';\nimport { handleDeprecations } from './utils/deprecation';\n// ALL OF THESE FUNCTIONS MUTATE THE COPY OF PROPS\n// this is intentional and done for speed and memory\nvar handleClassNames = function (props, classNames, className, theme) {\n var finalClassNames = classNamesFunc.apply(void 0, __spread([className], (!!theme ? parseThemeOptions(theme) : []), (typeof classNames === 'function' ? classNames(props) : classNames)));\n props.className = finalClassNames;\n};\nvar handleTag = function (props, defaultTag, tag) {\n // Handle the case where we are extending a component but passing\n // a string as a tag. For instance, extending an Icon but rendering a span\n if (typeof defaultTag !== 'string') {\n props.tag = tag;\n return defaultTag;\n }\n return tag || defaultTag;\n};\nvar handleConsumeProps = function (props, consumeProps) {\n consumeProps.forEach(function (p) {\n delete props[p];\n });\n};\nexport var componentFactory = function (_a) {\n var displayName = _a.displayName, _b = _a.classNames, classNames = _b === void 0 ? [] : _b, _c = _a.tag, defaultTag = _c === void 0 ? 'div' : _c, deprecate = _a.deprecate, defaultProps = _a.defaultProps, _d = _a.consumeProps, consumeProps = _d === void 0 ? [] : _d, render = _a.render;\n var Component = React.forwardRef(function (props, ref) {\n var className = props.className, theme = props.theme, tag = props.tag, rest = __rest(props, [\"className\", \"theme\", \"tag\"]);\n var newProps = rest;\n handleClassNames(newProps, classNames, className, theme);\n var Tag = handleTag(newProps, defaultTag, tag);\n if (deprecate) {\n newProps = handleDeprecations(newProps, deprecate, displayName);\n }\n handleConsumeProps(newProps, consumeProps);\n var finalProps = newProps;\n // @ts-ignore\n return render ? (render(finalProps, ref, Tag)) : (React.createElement(Tag, __assign({}, finalProps, { ref: ref })));\n });\n Component.displayName = displayName;\n Component.defaultProps = defaultProps;\n return Component;\n};\n","import toDate from \"../toDate/index.js\";\nimport requiredArgs from \"../_lib/requiredArgs/index.js\";\n/**\n * @name isValid\n * @category Common Helpers\n * @summary Is the given date valid?\n *\n * @description\n * Returns false if argument is Invalid Date and true otherwise.\n * Argument is converted to Date using `toDate`. See [toDate]{@link https://date-fns.org/docs/toDate}\n * Invalid Date is a Date, whose time value is NaN.\n *\n * Time value of Date: http://es5.github.io/#x15.9.1.1\n *\n * ### v2.0.0 breaking changes:\n *\n * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes).\n *\n * - Now `isValid` doesn't throw an exception\n * if the first argument is not an instance of Date.\n * Instead, argument is converted beforehand using `toDate`.\n *\n * Examples:\n *\n * | `isValid` argument | Before v2.0.0 | v2.0.0 onward |\n * |---------------------------|---------------|---------------|\n * | `new Date()` | `true` | `true` |\n * | `new Date('2016-01-01')` | `true` | `true` |\n * | `new Date('')` | `false` | `false` |\n * | `new Date(1488370835081)` | `true` | `true` |\n * | `new Date(NaN)` | `false` | `false` |\n * | `'2016-01-01'` | `TypeError` | `false` |\n * | `''` | `TypeError` | `false` |\n * | `1488370835081` | `TypeError` | `true` |\n * | `NaN` | `TypeError` | `false` |\n *\n * We introduce this change to make *date-fns* consistent with ECMAScript behavior\n * that try to coerce arguments to the expected type\n * (which is also the case with other *date-fns* functions).\n *\n * @param {*} date - the date to check\n * @returns {Boolean} the date is valid\n * @throws {TypeError} 1 argument required\n *\n * @example\n * // For the valid date:\n * var result = isValid(new Date(2014, 1, 31))\n * //=> true\n *\n * @example\n * // For the value, convertable into a date:\n * var result = isValid(1393804800000)\n * //=> true\n *\n * @example\n * // For the invalid date:\n * var result = isValid(new Date(''))\n * //=> false\n */\n\nexport default function isValid(dirtyDate) {\n requiredArgs(1, arguments);\n var date = toDate(dirtyDate);\n return !isNaN(date);\n}","import toInteger from \"../_lib/toInteger/index.js\";\nimport addMilliseconds from \"../addMilliseconds/index.js\";\nimport requiredArgs from \"../_lib/requiredArgs/index.js\";\nvar MILLISECONDS_IN_MINUTE = 60000;\n/**\n * @name addMinutes\n * @category Minute Helpers\n * @summary Add the specified number of minutes to the given date.\n *\n * @description\n * Add the specified number of minutes to the given date.\n *\n * ### v2.0.0 breaking changes:\n *\n * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes).\n *\n * @param {Date|Number} date - the date to be changed\n * @param {Number} amount - the amount of minutes to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.\n * @returns {Date} the new date with the minutes added\n * @throws {TypeError} 2 arguments required\n *\n * @example\n * // Add 30 minutes to 10 July 2014 12:00:00:\n * const result = addMinutes(new Date(2014, 6, 10, 12, 0), 30)\n * //=> Thu Jul 10 2014 12:30:00\n */\n\nexport default function addMinutes(dirtyDate, dirtyAmount) {\n requiredArgs(2, arguments);\n var amount = toInteger(dirtyAmount);\n return addMilliseconds(dirtyDate, amount * MILLISECONDS_IN_MINUTE);\n}","import toInteger from \"../_lib/toInteger/index.js\";\nimport addMilliseconds from \"../addMilliseconds/index.js\";\nimport requiredArgs from \"../_lib/requiredArgs/index.js\";\nvar MILLISECONDS_IN_HOUR = 3600000;\n/**\n * @name addHours\n * @category Hour Helpers\n * @summary Add the specified number of hours to the given date.\n *\n * @description\n * Add the specified number of hours to the given date.\n *\n * ### v2.0.0 breaking changes:\n *\n * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes).\n *\n * @param {Date|Number} date - the date to be changed\n * @param {Number} amount - the amount of hours to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.\n * @returns {Date} the new date with the hours added\n * @throws {TypeError} 2 arguments required\n *\n * @example\n * // Add 2 hours to 10 July 2014 23:00:00:\n * const result = addHours(new Date(2014, 6, 10, 23, 0), 2)\n * //=> Fri Jul 11 2014 01:00:00\n */\n\nexport default function addHours(dirtyDate, dirtyAmount) {\n requiredArgs(2, arguments);\n var amount = toInteger(dirtyAmount);\n return addMilliseconds(dirtyDate, amount * MILLISECONDS_IN_HOUR);\n}","import toInteger from \"../_lib/toInteger/index.js\";\nimport addDays from \"../addDays/index.js\";\nimport requiredArgs from \"../_lib/requiredArgs/index.js\";\n/**\n * @name addWeeks\n * @category Week Helpers\n * @summary Add the specified number of weeks to the given date.\n *\n * @description\n * Add the specified number of week to the given date.\n *\n * ### v2.0.0 breaking changes:\n *\n * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes).\n *\n * @param {Date|Number} date - the date to be changed\n * @param {Number} amount - the amount of weeks to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.\n * @returns {Date} the new date with the weeks added\n * @throws {TypeError} 2 arguments required\n *\n * @example\n * // Add 4 weeks to 1 September 2014:\n * const result = addWeeks(new Date(2014, 8, 1), 4)\n * //=> Mon Sep 29 2014 00:00:00\n */\n\nexport default function addWeeks(dirtyDate, dirtyAmount) {\n requiredArgs(2, arguments);\n var amount = toInteger(dirtyAmount);\n var days = amount * 7;\n return addDays(dirtyDate, days);\n}","import toInteger from \"../_lib/toInteger/index.js\";\nimport addMonths from \"../addMonths/index.js\";\nimport requiredArgs from \"../_lib/requiredArgs/index.js\";\n/**\n * @name addYears\n * @category Year Helpers\n * @summary Add the specified number of years to the given date.\n *\n * @description\n * Add the specified number of years to the given date.\n *\n * ### v2.0.0 breaking changes:\n *\n * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes).\n *\n * @param {Date|Number} date - the date to be changed\n * @param {Number} amount - the amount of years to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.\n * @returns {Date} the new date with the years added\n * @throws {TypeError} 2 arguments required\n *\n * @example\n * // Add 5 years to 1 September 2014:\n * const result = addYears(new Date(2014, 8, 1), 5)\n * //=> Sun Sep 01 2019 00:00:00\n */\n\nexport default function addYears(dirtyDate, dirtyAmount) {\n requiredArgs(2, arguments);\n var amount = toInteger(dirtyAmount);\n return addMonths(dirtyDate, amount * 12);\n}","import toInteger from \"../_lib/toInteger/index.js\";\nimport toDate from \"../toDate/index.js\";\nimport getDaysInMonth from \"../getDaysInMonth/index.js\";\nimport requiredArgs from \"../_lib/requiredArgs/index.js\";\n/**\n * @name setMonth\n * @category Month Helpers\n * @summary Set the month to the given date.\n *\n * @description\n * Set the month to the given date.\n *\n * ### v2.0.0 breaking changes:\n *\n * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes).\n *\n * @param {Date|Number} date - the date to be changed\n * @param {Number} month - the month of the new date\n * @returns {Date} the new date with the month set\n * @throws {TypeError} 2 arguments required\n *\n * @example\n * // Set February to 1 September 2014:\n * const result = setMonth(new Date(2014, 8, 1), 1)\n * //=> Sat Feb 01 2014 00:00:00\n */\n\nexport default function setMonth(dirtyDate, dirtyMonth) {\n requiredArgs(2, arguments);\n var date = toDate(dirtyDate);\n var month = toInteger(dirtyMonth);\n var year = date.getFullYear();\n var day = date.getDate();\n var dateWithDesiredMonth = new Date(0);\n dateWithDesiredMonth.setFullYear(year, month, 15);\n dateWithDesiredMonth.setHours(0, 0, 0, 0);\n var daysInMonth = getDaysInMonth(dateWithDesiredMonth); // Set the last day of the new month\n // if the original date was the last day of the longer month\n\n date.setMonth(month, Math.min(day, daysInMonth));\n return date;\n}","import toDate from \"../toDate/index.js\";\nimport requiredArgs from \"../_lib/requiredArgs/index.js\";\n/**\n * @name getDaysInMonth\n * @category Month Helpers\n * @summary Get the number of days in a month of the given date.\n *\n * @description\n * Get the number of days in a month of the given date.\n *\n * ### v2.0.0 breaking changes:\n *\n * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes).\n *\n * @param {Date|Number} date - the given date\n * @returns {Number} the number of days in a month\n * @throws {TypeError} 1 argument required\n *\n * @example\n * // How many days are in February 2000?\n * const result = getDaysInMonth(new Date(2000, 1))\n * //=> 29\n */\n\nexport default function getDaysInMonth(dirtyDate) {\n requiredArgs(1, arguments);\n var date = toDate(dirtyDate);\n var year = date.getFullYear();\n var monthIndex = date.getMonth();\n var lastDayOfMonth = new Date(0);\n lastDayOfMonth.setFullYear(year, monthIndex + 1, 0);\n lastDayOfMonth.setHours(0, 0, 0, 0);\n return lastDayOfMonth.getDate();\n}","/* global window */\nimport ponyfill from './ponyfill.js';\n\nvar root;\n\nif (typeof self !== 'undefined') {\n root = self;\n} else if (typeof window !== 'undefined') {\n root = window;\n} else if (typeof global !== 'undefined') {\n root = global;\n} else if (typeof module !== 'undefined') {\n root = module;\n} else {\n root = Function('return this')();\n}\n\nvar result = ponyfill(root);\nexport default result;\n","import _curry2 from './internal/_curry2.js';\n\n/**\n * Retrieve the value at a given path.\n *\n * @func\n * @memberOf R\n * @since v0.2.0\n * @category Object\n * @typedefn Idx = String | Int\n * @sig [Idx] -> {a} -> a | Undefined\n * @param {Array} path The path to use.\n * @param {Object} obj The object to retrieve the nested property from.\n * @return {*} The data at `path`.\n * @see R.prop\n * @example\n *\n * R.path(['a', 'b'], {a: {b: 2}}); //=> 2\n * R.path(['a', 'b'], {c: {b: 2}}); //=> undefined\n */\nvar path = /*#__PURE__*/_curry2(function path(paths, obj) {\n var val = obj;\n var idx = 0;\n while (idx < paths.length) {\n if (val == null) {\n return;\n }\n val = val[paths[idx]];\n idx += 1;\n }\n return val;\n});\nexport default path;","'use strict';\n\nvar stringify = require('./stringify');\nvar parse = require('./parse');\nvar formats = require('./formats');\n\nmodule.exports = {\n formats: formats,\n parse: parse,\n stringify: stringify\n};\n","'use strict';\n\nvar reactIs = require('react-is');\n\n/**\n * Copyright 2015, Yahoo! Inc.\n * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.\n */\nvar REACT_STATICS = {\n childContextTypes: true,\n contextType: true,\n contextTypes: true,\n defaultProps: true,\n displayName: true,\n getDefaultProps: true,\n getDerivedStateFromError: true,\n getDerivedStateFromProps: true,\n mixins: true,\n propTypes: true,\n type: true\n};\nvar KNOWN_STATICS = {\n name: true,\n length: true,\n prototype: true,\n caller: true,\n callee: true,\n arguments: true,\n arity: true\n};\nvar FORWARD_REF_STATICS = {\n '$$typeof': true,\n render: true,\n defaultProps: true,\n displayName: true,\n propTypes: true\n};\nvar MEMO_STATICS = {\n '$$typeof': true,\n compare: true,\n defaultProps: true,\n displayName: true,\n propTypes: true,\n type: true\n};\nvar TYPE_STATICS = {};\nTYPE_STATICS[reactIs.ForwardRef] = FORWARD_REF_STATICS;\nTYPE_STATICS[reactIs.Memo] = MEMO_STATICS;\n\nfunction getStatics(component) {\n // React v16.11 and below\n if (reactIs.isMemo(component)) {\n return MEMO_STATICS;\n } // React v16.12 and above\n\n\n return TYPE_STATICS[component['$$typeof']] || REACT_STATICS;\n}\n\nvar defineProperty = Object.defineProperty;\nvar getOwnPropertyNames = Object.getOwnPropertyNames;\nvar getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\nvar getPrototypeOf = Object.getPrototypeOf;\nvar objectPrototype = Object.prototype;\nfunction hoistNonReactStatics(targetComponent, sourceComponent, blacklist) {\n if (typeof sourceComponent !== 'string') {\n // don't hoist over string (html) components\n if (objectPrototype) {\n var inheritedComponent = getPrototypeOf(sourceComponent);\n\n if (inheritedComponent && inheritedComponent !== objectPrototype) {\n hoistNonReactStatics(targetComponent, inheritedComponent, blacklist);\n }\n }\n\n var keys = getOwnPropertyNames(sourceComponent);\n\n if (getOwnPropertySymbols) {\n keys = keys.concat(getOwnPropertySymbols(sourceComponent));\n }\n\n var targetStatics = getStatics(targetComponent);\n var sourceStatics = getStatics(sourceComponent);\n\n for (var i = 0; i < keys.length; ++i) {\n var key = keys[i];\n\n if (!KNOWN_STATICS[key] && !(blacklist && blacklist[key]) && !(sourceStatics && sourceStatics[key]) && !(targetStatics && targetStatics[key])) {\n var descriptor = getOwnPropertyDescriptor(sourceComponent, key);\n\n try {\n // Avoid failures from read-only properties\n defineProperty(targetComponent, key, descriptor);\n } catch (e) {}\n }\n }\n }\n\n return targetComponent;\n}\n\nmodule.exports = hoistNonReactStatics;\n","'use strict';\n\nvar colorString = require('color-string');\nvar convert = require('color-convert');\n\nvar _slice = [].slice;\n\nvar skippedModels = [\n\t// to be honest, I don't really feel like keyword belongs in color convert, but eh.\n\t'keyword',\n\n\t// gray conflicts with some method names, and has its own method defined.\n\t'gray',\n\n\t// shouldn't really be in color-convert either...\n\t'hex'\n];\n\nvar hashedModelKeys = {};\nObject.keys(convert).forEach(function (model) {\n\thashedModelKeys[_slice.call(convert[model].labels).sort().join('')] = model;\n});\n\nvar limiters = {};\n\nfunction Color(obj, model) {\n\tif (!(this instanceof Color)) {\n\t\treturn new Color(obj, model);\n\t}\n\n\tif (model && model in skippedModels) {\n\t\tmodel = null;\n\t}\n\n\tif (model && !(model in convert)) {\n\t\tthrow new Error('Unknown model: ' + model);\n\t}\n\n\tvar i;\n\tvar channels;\n\n\tif (obj == null) { // eslint-disable-line no-eq-null,eqeqeq\n\t\tthis.model = 'rgb';\n\t\tthis.color = [0, 0, 0];\n\t\tthis.valpha = 1;\n\t} else if (obj instanceof Color) {\n\t\tthis.model = obj.model;\n\t\tthis.color = obj.color.slice();\n\t\tthis.valpha = obj.valpha;\n\t} else if (typeof obj === 'string') {\n\t\tvar result = colorString.get(obj);\n\t\tif (result === null) {\n\t\t\tthrow new Error('Unable to parse color from string: ' + obj);\n\t\t}\n\n\t\tthis.model = result.model;\n\t\tchannels = convert[this.model].channels;\n\t\tthis.color = result.value.slice(0, channels);\n\t\tthis.valpha = typeof result.value[channels] === 'number' ? result.value[channels] : 1;\n\t} else if (obj.length) {\n\t\tthis.model = model || 'rgb';\n\t\tchannels = convert[this.model].channels;\n\t\tvar newArr = _slice.call(obj, 0, channels);\n\t\tthis.color = zeroArray(newArr, channels);\n\t\tthis.valpha = typeof obj[channels] === 'number' ? obj[channels] : 1;\n\t} else if (typeof obj === 'number') {\n\t\t// this is always RGB - can be converted later on.\n\t\tobj &= 0xFFFFFF;\n\t\tthis.model = 'rgb';\n\t\tthis.color = [\n\t\t\t(obj >> 16) & 0xFF,\n\t\t\t(obj >> 8) & 0xFF,\n\t\t\tobj & 0xFF\n\t\t];\n\t\tthis.valpha = 1;\n\t} else {\n\t\tthis.valpha = 1;\n\n\t\tvar keys = Object.keys(obj);\n\t\tif ('alpha' in obj) {\n\t\t\tkeys.splice(keys.indexOf('alpha'), 1);\n\t\t\tthis.valpha = typeof obj.alpha === 'number' ? obj.alpha : 0;\n\t\t}\n\n\t\tvar hashedKeys = keys.sort().join('');\n\t\tif (!(hashedKeys in hashedModelKeys)) {\n\t\t\tthrow new Error('Unable to parse color from object: ' + JSON.stringify(obj));\n\t\t}\n\n\t\tthis.model = hashedModelKeys[hashedKeys];\n\n\t\tvar labels = convert[this.model].labels;\n\t\tvar color = [];\n\t\tfor (i = 0; i < labels.length; i++) {\n\t\t\tcolor.push(obj[labels[i]]);\n\t\t}\n\n\t\tthis.color = zeroArray(color);\n\t}\n\n\t// perform limitations (clamping, etc.)\n\tif (limiters[this.model]) {\n\t\tchannels = convert[this.model].channels;\n\t\tfor (i = 0; i < channels; i++) {\n\t\t\tvar limit = limiters[this.model][i];\n\t\t\tif (limit) {\n\t\t\t\tthis.color[i] = limit(this.color[i]);\n\t\t\t}\n\t\t}\n\t}\n\n\tthis.valpha = Math.max(0, Math.min(1, this.valpha));\n\n\tif (Object.freeze) {\n\t\tObject.freeze(this);\n\t}\n}\n\nColor.prototype = {\n\ttoString: function () {\n\t\treturn this.string();\n\t},\n\n\ttoJSON: function () {\n\t\treturn this[this.model]();\n\t},\n\n\tstring: function (places) {\n\t\tvar self = this.model in colorString.to ? this : this.rgb();\n\t\tself = self.round(typeof places === 'number' ? places : 1);\n\t\tvar args = self.valpha === 1 ? self.color : self.color.concat(this.valpha);\n\t\treturn colorString.to[self.model](args);\n\t},\n\n\tpercentString: function (places) {\n\t\tvar self = this.rgb().round(typeof places === 'number' ? places : 1);\n\t\tvar args = self.valpha === 1 ? self.color : self.color.concat(this.valpha);\n\t\treturn colorString.to.rgb.percent(args);\n\t},\n\n\tarray: function () {\n\t\treturn this.valpha === 1 ? this.color.slice() : this.color.concat(this.valpha);\n\t},\n\n\tobject: function () {\n\t\tvar result = {};\n\t\tvar channels = convert[this.model].channels;\n\t\tvar labels = convert[this.model].labels;\n\n\t\tfor (var i = 0; i < channels; i++) {\n\t\t\tresult[labels[i]] = this.color[i];\n\t\t}\n\n\t\tif (this.valpha !== 1) {\n\t\t\tresult.alpha = this.valpha;\n\t\t}\n\n\t\treturn result;\n\t},\n\n\tunitArray: function () {\n\t\tvar rgb = this.rgb().color;\n\t\trgb[0] /= 255;\n\t\trgb[1] /= 255;\n\t\trgb[2] /= 255;\n\n\t\tif (this.valpha !== 1) {\n\t\t\trgb.push(this.valpha);\n\t\t}\n\n\t\treturn rgb;\n\t},\n\n\tunitObject: function () {\n\t\tvar rgb = this.rgb().object();\n\t\trgb.r /= 255;\n\t\trgb.g /= 255;\n\t\trgb.b /= 255;\n\n\t\tif (this.valpha !== 1) {\n\t\t\trgb.alpha = this.valpha;\n\t\t}\n\n\t\treturn rgb;\n\t},\n\n\tround: function (places) {\n\t\tplaces = Math.max(places || 0, 0);\n\t\treturn new Color(this.color.map(roundToPlace(places)).concat(this.valpha), this.model);\n\t},\n\n\talpha: function (val) {\n\t\tif (arguments.length) {\n\t\t\treturn new Color(this.color.concat(Math.max(0, Math.min(1, val))), this.model);\n\t\t}\n\n\t\treturn this.valpha;\n\t},\n\n\t// rgb\n\tred: getset('rgb', 0, maxfn(255)),\n\tgreen: getset('rgb', 1, maxfn(255)),\n\tblue: getset('rgb', 2, maxfn(255)),\n\n\thue: getset(['hsl', 'hsv', 'hsl', 'hwb', 'hcg'], 0, function (val) { return ((val % 360) + 360) % 360; }), // eslint-disable-line brace-style\n\n\tsaturationl: getset('hsl', 1, maxfn(100)),\n\tlightness: getset('hsl', 2, maxfn(100)),\n\n\tsaturationv: getset('hsv', 1, maxfn(100)),\n\tvalue: getset('hsv', 2, maxfn(100)),\n\n\tchroma: getset('hcg', 1, maxfn(100)),\n\tgray: getset('hcg', 2, maxfn(100)),\n\n\twhite: getset('hwb', 1, maxfn(100)),\n\twblack: getset('hwb', 2, maxfn(100)),\n\n\tcyan: getset('cmyk', 0, maxfn(100)),\n\tmagenta: getset('cmyk', 1, maxfn(100)),\n\tyellow: getset('cmyk', 2, maxfn(100)),\n\tblack: getset('cmyk', 3, maxfn(100)),\n\n\tx: getset('xyz', 0, maxfn(100)),\n\ty: getset('xyz', 1, maxfn(100)),\n\tz: getset('xyz', 2, maxfn(100)),\n\n\tl: getset('lab', 0, maxfn(100)),\n\ta: getset('lab', 1),\n\tb: getset('lab', 2),\n\n\tkeyword: function (val) {\n\t\tif (arguments.length) {\n\t\t\treturn new Color(val);\n\t\t}\n\n\t\treturn convert[this.model].keyword(this.color);\n\t},\n\n\thex: function (val) {\n\t\tif (arguments.length) {\n\t\t\treturn new Color(val);\n\t\t}\n\n\t\treturn colorString.to.hex(this.rgb().round().color);\n\t},\n\n\trgbNumber: function () {\n\t\tvar rgb = this.rgb().color;\n\t\treturn ((rgb[0] & 0xFF) << 16) | ((rgb[1] & 0xFF) << 8) | (rgb[2] & 0xFF);\n\t},\n\n\tluminosity: function () {\n\t\t// http://www.w3.org/TR/WCAG20/#relativeluminancedef\n\t\tvar rgb = this.rgb().color;\n\n\t\tvar lum = [];\n\t\tfor (var i = 0; i < rgb.length; i++) {\n\t\t\tvar chan = rgb[i] / 255;\n\t\t\tlum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);\n\t\t}\n\n\t\treturn 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];\n\t},\n\n\tcontrast: function (color2) {\n\t\t// http://www.w3.org/TR/WCAG20/#contrast-ratiodef\n\t\tvar lum1 = this.luminosity();\n\t\tvar lum2 = color2.luminosity();\n\n\t\tif (lum1 > lum2) {\n\t\t\treturn (lum1 + 0.05) / (lum2 + 0.05);\n\t\t}\n\n\t\treturn (lum2 + 0.05) / (lum1 + 0.05);\n\t},\n\n\tlevel: function (color2) {\n\t\tvar contrastRatio = this.contrast(color2);\n\t\tif (contrastRatio >= 7.1) {\n\t\t\treturn 'AAA';\n\t\t}\n\n\t\treturn (contrastRatio >= 4.5) ? 'AA' : '';\n\t},\n\n\tisDark: function () {\n\t\t// YIQ equation from http://24ways.org/2010/calculating-color-contrast\n\t\tvar rgb = this.rgb().color;\n\t\tvar yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;\n\t\treturn yiq < 128;\n\t},\n\n\tisLight: function () {\n\t\treturn !this.isDark();\n\t},\n\n\tnegate: function () {\n\t\tvar rgb = this.rgb();\n\t\tfor (var i = 0; i < 3; i++) {\n\t\t\trgb.color[i] = 255 - rgb.color[i];\n\t\t}\n\t\treturn rgb;\n\t},\n\n\tlighten: function (ratio) {\n\t\tvar hsl = this.hsl();\n\t\thsl.color[2] += hsl.color[2] * ratio;\n\t\treturn hsl;\n\t},\n\n\tdarken: function (ratio) {\n\t\tvar hsl = this.hsl();\n\t\thsl.color[2] -= hsl.color[2] * ratio;\n\t\treturn hsl;\n\t},\n\n\tsaturate: function (ratio) {\n\t\tvar hsl = this.hsl();\n\t\thsl.color[1] += hsl.color[1] * ratio;\n\t\treturn hsl;\n\t},\n\n\tdesaturate: function (ratio) {\n\t\tvar hsl = this.hsl();\n\t\thsl.color[1] -= hsl.color[1] * ratio;\n\t\treturn hsl;\n\t},\n\n\twhiten: function (ratio) {\n\t\tvar hwb = this.hwb();\n\t\thwb.color[1] += hwb.color[1] * ratio;\n\t\treturn hwb;\n\t},\n\n\tblacken: function (ratio) {\n\t\tvar hwb = this.hwb();\n\t\thwb.color[2] += hwb.color[2] * ratio;\n\t\treturn hwb;\n\t},\n\n\tgrayscale: function () {\n\t\t// http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale\n\t\tvar rgb = this.rgb().color;\n\t\tvar val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;\n\t\treturn Color.rgb(val, val, val);\n\t},\n\n\tfade: function (ratio) {\n\t\treturn this.alpha(this.valpha - (this.valpha * ratio));\n\t},\n\n\topaquer: function (ratio) {\n\t\treturn this.alpha(this.valpha + (this.valpha * ratio));\n\t},\n\n\trotate: function (degrees) {\n\t\tvar hsl = this.hsl();\n\t\tvar hue = hsl.color[0];\n\t\thue = (hue + degrees) % 360;\n\t\thue = hue < 0 ? 360 + hue : hue;\n\t\thsl.color[0] = hue;\n\t\treturn hsl;\n\t},\n\n\tmix: function (mixinColor, weight) {\n\t\t// ported from sass implementation in C\n\t\t// https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209\n\t\tif (!mixinColor || !mixinColor.rgb) {\n\t\t\tthrow new Error('Argument to \"mix\" was not a Color instance, but rather an instance of ' + typeof mixinColor);\n\t\t}\n\t\tvar color1 = mixinColor.rgb();\n\t\tvar color2 = this.rgb();\n\t\tvar p = weight === undefined ? 0.5 : weight;\n\n\t\tvar w = 2 * p - 1;\n\t\tvar a = color1.alpha() - color2.alpha();\n\n\t\tvar w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;\n\t\tvar w2 = 1 - w1;\n\n\t\treturn Color.rgb(\n\t\t\t\tw1 * color1.red() + w2 * color2.red(),\n\t\t\t\tw1 * color1.green() + w2 * color2.green(),\n\t\t\t\tw1 * color1.blue() + w2 * color2.blue(),\n\t\t\t\tcolor1.alpha() * p + color2.alpha() * (1 - p));\n\t}\n};\n\n// model conversion methods and static constructors\nObject.keys(convert).forEach(function (model) {\n\tif (skippedModels.indexOf(model) !== -1) {\n\t\treturn;\n\t}\n\n\tvar channels = convert[model].channels;\n\n\t// conversion methods\n\tColor.prototype[model] = function () {\n\t\tif (this.model === model) {\n\t\t\treturn new Color(this);\n\t\t}\n\n\t\tif (arguments.length) {\n\t\t\treturn new Color(arguments, model);\n\t\t}\n\n\t\tvar newAlpha = typeof arguments[channels] === 'number' ? channels : this.valpha;\n\t\treturn new Color(assertArray(convert[this.model][model].raw(this.color)).concat(newAlpha), model);\n\t};\n\n\t// 'static' construction methods\n\tColor[model] = function (color) {\n\t\tif (typeof color === 'number') {\n\t\t\tcolor = zeroArray(_slice.call(arguments), channels);\n\t\t}\n\t\treturn new Color(color, model);\n\t};\n});\n\nfunction roundTo(num, places) {\n\treturn Number(num.toFixed(places));\n}\n\nfunction roundToPlace(places) {\n\treturn function (num) {\n\t\treturn roundTo(num, places);\n\t};\n}\n\nfunction getset(model, channel, modifier) {\n\tmodel = Array.isArray(model) ? model : [model];\n\n\tmodel.forEach(function (m) {\n\t\t(limiters[m] || (limiters[m] = []))[channel] = modifier;\n\t});\n\n\tmodel = model[0];\n\n\treturn function (val) {\n\t\tvar result;\n\n\t\tif (arguments.length) {\n\t\t\tif (modifier) {\n\t\t\t\tval = modifier(val);\n\t\t\t}\n\n\t\t\tresult = this[model]();\n\t\t\tresult.color[channel] = val;\n\t\t\treturn result;\n\t\t}\n\n\t\tresult = this[model]().color[channel];\n\t\tif (modifier) {\n\t\t\tresult = modifier(result);\n\t\t}\n\n\t\treturn result;\n\t};\n}\n\nfunction maxfn(max) {\n\treturn function (v) {\n\t\treturn Math.max(0, Math.min(max, v));\n\t};\n}\n\nfunction assertArray(val) {\n\treturn Array.isArray(val) ? val : [val];\n}\n\nfunction zeroArray(arr, length) {\n\tfor (var i = 0; i < length; i++) {\n\t\tif (typeof arr[i] !== 'number') {\n\t\t\tarr[i] = 0;\n\t\t}\n\t}\n\n\treturn arr;\n}\n\nmodule.exports = Color;\n","function memoize(fn) {\n var cache = Object.create(null);\n return function (arg) {\n if (cache[arg] === undefined) cache[arg] = fn(arg);\n return cache[arg];\n };\n}\n\nexport default memoize;\n","import memoize from '@emotion/memoize';\n\nvar reactPropsRegex = /^((children|dangerouslySetInnerHTML|key|ref|autoFocus|defaultValue|defaultChecked|innerHTML|suppressContentEditableWarning|suppressHydrationWarning|valueLink|abbr|accept|acceptCharset|accessKey|action|allow|allowUserMedia|allowPaymentRequest|allowFullScreen|allowTransparency|alt|async|autoComplete|autoPlay|capture|cellPadding|cellSpacing|challenge|charSet|checked|cite|classID|className|cols|colSpan|content|contentEditable|contextMenu|controls|controlsList|coords|crossOrigin|data|dateTime|decoding|default|defer|dir|disabled|disablePictureInPicture|download|draggable|encType|enterKeyHint|form|formAction|formEncType|formMethod|formNoValidate|formTarget|frameBorder|headers|height|hidden|high|href|hrefLang|htmlFor|httpEquiv|id|inputMode|integrity|is|keyParams|keyType|kind|label|lang|list|loading|loop|low|marginHeight|marginWidth|max|maxLength|media|mediaGroup|method|min|minLength|multiple|muted|name|nonce|noValidate|open|optimum|pattern|placeholder|playsInline|poster|preload|profile|radioGroup|readOnly|referrerPolicy|rel|required|reversed|role|rows|rowSpan|sandbox|scope|scoped|scrolling|seamless|selected|shape|size|sizes|slot|span|spellCheck|src|srcDoc|srcLang|srcSet|start|step|style|summary|tabIndex|target|title|translate|type|useMap|value|width|wmode|wrap|about|datatype|inlist|prefix|property|resource|typeof|vocab|autoCapitalize|autoCorrect|autoSave|color|incremental|fallback|inert|itemProp|itemScope|itemType|itemID|itemRef|on|option|results|security|unselectable|accentHeight|accumulate|additive|alignmentBaseline|allowReorder|alphabetic|amplitude|arabicForm|ascent|attributeName|attributeType|autoReverse|azimuth|baseFrequency|baselineShift|baseProfile|bbox|begin|bias|by|calcMode|capHeight|clip|clipPathUnits|clipPath|clipRule|colorInterpolation|colorInterpolationFilters|colorProfile|colorRendering|contentScriptType|contentStyleType|cursor|cx|cy|d|decelerate|descent|diffuseConstant|direction|display|divisor|dominantBaseline|dur|dx|dy|edgeMode|elevation|enableBackground|end|exponent|externalResourcesRequired|fill|fillOpacity|fillRule|filter|filterRes|filterUnits|floodColor|floodOpacity|focusable|fontFamily|fontSize|fontSizeAdjust|fontStretch|fontStyle|fontVariant|fontWeight|format|from|fr|fx|fy|g1|g2|glyphName|glyphOrientationHorizontal|glyphOrientationVertical|glyphRef|gradientTransform|gradientUnits|hanging|horizAdvX|horizOriginX|ideographic|imageRendering|in|in2|intercept|k|k1|k2|k3|k4|kernelMatrix|kernelUnitLength|kerning|keyPoints|keySplines|keyTimes|lengthAdjust|letterSpacing|lightingColor|limitingConeAngle|local|markerEnd|markerMid|markerStart|markerHeight|markerUnits|markerWidth|mask|maskContentUnits|maskUnits|mathematical|mode|numOctaves|offset|opacity|operator|order|orient|orientation|origin|overflow|overlinePosition|overlineThickness|panose1|paintOrder|pathLength|patternContentUnits|patternTransform|patternUnits|pointerEvents|points|pointsAtX|pointsAtY|pointsAtZ|preserveAlpha|preserveAspectRatio|primitiveUnits|r|radius|refX|refY|renderingIntent|repeatCount|repeatDur|requiredExtensions|requiredFeatures|restart|result|rotate|rx|ry|scale|seed|shapeRendering|slope|spacing|specularConstant|specularExponent|speed|spreadMethod|startOffset|stdDeviation|stemh|stemv|stitchTiles|stopColor|stopOpacity|strikethroughPosition|strikethroughThickness|string|stroke|strokeDasharray|strokeDashoffset|strokeLinecap|strokeLinejoin|strokeMiterlimit|strokeOpacity|strokeWidth|surfaceScale|systemLanguage|tableValues|targetX|targetY|textAnchor|textDecoration|textRendering|textLength|to|transform|u1|u2|underlinePosition|underlineThickness|unicode|unicodeBidi|unicodeRange|unitsPerEm|vAlphabetic|vHanging|vIdeographic|vMathematical|values|vectorEffect|version|vertAdvY|vertOriginX|vertOriginY|viewBox|viewTarget|visibility|widths|wordSpacing|writingMode|x|xHeight|x1|x2|xChannelSelector|xlinkActuate|xlinkArcrole|xlinkHref|xlinkRole|xlinkShow|xlinkTitle|xlinkType|xmlBase|xmlns|xmlnsXlink|xmlLang|xmlSpace|y|y1|y2|yChannelSelector|z|zoomAndPan|for|class|autofocus)|(([Dd][Aa][Tt][Aa]|[Aa][Rr][Ii][Aa]|x)-.*))$/; // https://esbench.com/bench/5bfee68a4cd7e6009ef61d23\n\nvar isPropValid = /* #__PURE__ */memoize(function (prop) {\n return reactPropsRegex.test(prop) || prop.charCodeAt(0) === 111\n /* o */\n && prop.charCodeAt(1) === 110\n /* n */\n && prop.charCodeAt(2) < 91;\n}\n/* Z+1 */\n);\n\nexport default isPropValid;\n","import _objectAssign from './internal/_objectAssign.js';\nimport _curry1 from './internal/_curry1.js';\n\n/**\n * Merges a list of objects together into one object.\n *\n * @func\n * @memberOf R\n * @since v0.10.0\n * @category List\n * @sig [{k: v}] -> {k: v}\n * @param {Array} list An array of objects\n * @return {Object} A merged object.\n * @see R.reduce\n * @example\n *\n * R.mergeAll([{foo:1},{bar:2},{baz:3}]); //=> {foo:1,bar:2,baz:3}\n * R.mergeAll([{foo:1},{foo:2},{bar:2}]); //=> {foo:2,bar:2}\n * @symb R.mergeAll([{ x: 1 }, { y: 2 }, { z: 3 }]) = { x: 1, y: 2, z: 3 }\n */\nvar mergeAll = /*#__PURE__*/_curry1(function mergeAll(list) {\n return _objectAssign.apply(null, [{}].concat(list));\n});\nexport default mergeAll;","import nth from './nth.js';\n\n/**\n * Returns the last element of the given list or string.\n *\n * @func\n * @memberOf R\n * @since v0.1.4\n * @category List\n * @sig [a] -> a | Undefined\n * @sig String -> String\n * @param {*} list\n * @return {*}\n * @see R.init, R.head, R.tail\n * @example\n *\n * R.last(['fi', 'fo', 'fum']); //=> 'fum'\n * R.last([]); //=> undefined\n *\n * R.last('abc'); //=> 'c'\n * R.last(''); //=> ''\n */\nvar last = /*#__PURE__*/nth(-1);\nexport default last;","import _curry2 from './internal/_curry2.js';\nimport _isString from './internal/_isString.js';\n\n/**\n * Returns the nth element of the given list or string. If n is negative the\n * element at index length + n is returned.\n *\n * @func\n * @memberOf R\n * @since v0.1.0\n * @category List\n * @sig Number -> [a] -> a | Undefined\n * @sig Number -> String -> String\n * @param {Number} offset\n * @param {*} list\n * @return {*}\n * @example\n *\n * const list = ['foo', 'bar', 'baz', 'quux'];\n * R.nth(1, list); //=> 'bar'\n * R.nth(-1, list); //=> 'quux'\n * R.nth(-99, list); //=> undefined\n *\n * R.nth(2, 'abc'); //=> 'c'\n * R.nth(3, 'abc'); //=> ''\n * @symb R.nth(-1, [a, b, c]) = c\n * @symb R.nth(0, [a, b, c]) = a\n * @symb R.nth(1, [a, b, c]) = b\n */\nvar nth = /*#__PURE__*/_curry2(function nth(offset, list) {\n var idx = offset < 0 ? list.length + offset : offset;\n return _isString(list) ? list.charAt(idx) : list[idx];\n});\nexport default nth;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.add = add;\nexports.remove = remove;\nexports.has = has;\nexports.check = check;\nexports.all = all;\nexports.clear = clear;\nexports.enableChecking = enableChecking;\nexports.disableChecking = disableChecking;\nvar types = {};\nvar config = {\n checkExisting: true\n};\n\nfunction add(name) {\n types[name] = true;\n}\n\nfunction remove(name) {\n types[name] = false;\n}\n\nfunction has(name) {\n return !!types[name];\n}\n\nfunction check(name) {\n if (config.checkExisting && has(name)) {\n throw new TypeError(\"Duplicate action type: \".concat(name));\n }\n}\n\nfunction all() {\n return Object.keys(types).filter(has);\n}\n\nfunction clear() {\n all().forEach(remove);\n}\n\nfunction enableChecking() {\n config.checkExisting = true;\n}\n\nfunction disableChecking() {\n config.checkExisting = false;\n}","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = createAction;\n\nvar _types = require(\"./types\");\n\nvar id = 0;\n\nvar identity = function identity(arg) {\n return arg;\n};\n\nvar normalize = function normalize(dispatchOrStore) {\n if (dispatchOrStore && typeof dispatchOrStore.dispatch === 'function') {\n return dispatchOrStore.dispatch;\n } else {\n return dispatchOrStore;\n }\n};\n\nvar normalizeAll = function normalizeAll(dispatchOrStores) {\n if (Array.isArray(dispatchOrStores)) {\n return dispatchOrStores.map(normalize);\n } else {\n return normalize(dispatchOrStores);\n }\n};\n\nfunction createAction(description, payloadReducer, metaReducer) {\n if (typeof description === 'function') {\n metaReducer = payloadReducer;\n payloadReducer = description;\n description = undefined;\n }\n\n if (typeof payloadReducer !== 'function') {\n payloadReducer = identity;\n }\n\n if (typeof metaReducer !== 'function') {\n metaReducer = undefined;\n }\n\n var isSerializable = typeof description === 'string' && /^[0-9A-Z_]+$/.test(description);\n\n if (isSerializable) {\n (0, _types.check)(description);\n (0, _types.add)(description);\n } else {\n ++id;\n }\n\n var type = isSerializable ? description : \"[\".concat(id, \"]\").concat(description ? ' ' + description : '');\n var dispatchFunctions = undefined;\n\n function makeAction() {\n var payload = payloadReducer.apply(void 0, arguments);\n\n if (metaReducer) {\n return {\n type: type,\n payload: payload,\n error: payload instanceof Error,\n meta: metaReducer.apply(void 0, arguments)\n };\n }\n\n return {\n type: type,\n payload: payload,\n error: payload instanceof Error\n };\n }\n\n var makeAndDispatch = function makeAndDispatch(dispatchs, isError) {\n return function () {\n var payloadedAction = makeAction.apply(void 0, arguments);\n\n if (!payloadedAction.error) {\n payloadedAction.error = isError;\n }\n\n if (Array.isArray(dispatchs)) {\n return dispatchs.map(function (dispatch) {\n return dispatch(payloadedAction);\n });\n } else if (dispatchs) {\n return dispatchs(payloadedAction);\n } else {\n return payloadedAction;\n }\n };\n };\n\n function actionCreator() {\n return makeAndDispatch(dispatchFunctions, false).apply(void 0, arguments);\n }\n\n actionCreator.asError = function () {\n return makeAndDispatch(dispatchFunctions, true).apply(void 0, arguments);\n };\n\n actionCreator.getType = function () {\n return type;\n };\n\n actionCreator.toString = function () {\n return type;\n };\n\n actionCreator.raw = makeAction;\n\n actionCreator.assignTo = function (dispatchOrStores) {\n dispatchFunctions = normalizeAll(dispatchOrStores);\n return actionCreator;\n };\n\n actionCreator.assigned = function () {\n return !!dispatchFunctions;\n };\n\n actionCreator.bound = function () {\n return false;\n };\n\n actionCreator.dispatched = actionCreator.assigned;\n\n actionCreator.bindTo = function (dispatchOrStores) {\n var boundActionCreator = makeAndDispatch(normalizeAll(dispatchOrStores, false));\n boundActionCreator.asError = makeAndDispatch(normalizeAll(dispatchOrStores, true));\n boundActionCreator.raw = makeAction;\n boundActionCreator.getType = actionCreator.getType;\n boundActionCreator.toString = actionCreator.toString;\n\n boundActionCreator.assignTo = function () {\n return boundActionCreator;\n };\n\n boundActionCreator.bindTo = function () {\n return boundActionCreator;\n };\n\n boundActionCreator.assigned = function () {\n return false;\n };\n\n boundActionCreator.bound = function () {\n return true;\n };\n\n boundActionCreator.dispatched = boundActionCreator.bound;\n return boundActionCreator;\n };\n\n return actionCreator;\n}\n\n;","'use strict';\n\nmodule.exports = function bind(fn, thisArg) {\n return function wrap() {\n var args = new Array(arguments.length);\n for (var i = 0; i < args.length; i++) {\n args[i] = arguments[i];\n }\n return fn.apply(thisArg, args);\n };\n};\n","'use strict';\n\nvar utils = require('./../utils');\n\nfunction encode(val) {\n return encodeURIComponent(val).\n replace(/%3A/gi, ':').\n replace(/%24/g, '$').\n replace(/%2C/gi, ',').\n replace(/%20/g, '+').\n replace(/%5B/gi, '[').\n replace(/%5D/gi, ']');\n}\n\n/**\n * Build a URL by appending params to the end\n *\n * @param {string} url The base of the url (e.g., http://www.google.com)\n * @param {object} [params] The params to be appended\n * @returns {string} The formatted url\n */\nmodule.exports = function buildURL(url, params, paramsSerializer) {\n /*eslint no-param-reassign:0*/\n if (!params) {\n return url;\n }\n\n var serializedParams;\n if (paramsSerializer) {\n serializedParams = paramsSerializer(params);\n } else if (utils.isURLSearchParams(params)) {\n serializedParams = params.toString();\n } else {\n var parts = [];\n\n utils.forEach(params, function serialize(val, key) {\n if (val === null || typeof val === 'undefined') {\n return;\n }\n\n if (utils.isArray(val)) {\n key = key + '[]';\n } else {\n val = [val];\n }\n\n utils.forEach(val, function parseValue(v) {\n if (utils.isDate(v)) {\n v = v.toISOString();\n } else if (utils.isObject(v)) {\n v = JSON.stringify(v);\n }\n parts.push(encode(key) + '=' + encode(v));\n });\n });\n\n serializedParams = parts.join('&');\n }\n\n if (serializedParams) {\n var hashmarkIndex = url.indexOf('#');\n if (hashmarkIndex !== -1) {\n url = url.slice(0, hashmarkIndex);\n }\n\n url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;\n }\n\n return url;\n};\n","'use strict';\n\n/**\n * Update an Error with the specified config, error code, and response.\n *\n * @param {Error} error The error to update.\n * @param {Object} config The config.\n * @param {string} [code] The error code (for example, 'ECONNABORTED').\n * @param {Object} [request] The request.\n * @param {Object} [response] The response.\n * @returns {Error} The error.\n */\nmodule.exports = function enhanceError(error, config, code, request, response) {\n error.config = config;\n if (code) {\n error.code = code;\n }\n\n error.request = request;\n error.response = response;\n error.isAxiosError = true;\n\n error.toJSON = function toJSON() {\n return {\n // Standard\n message: this.message,\n name: this.name,\n // Microsoft\n description: this.description,\n number: this.number,\n // Mozilla\n fileName: this.fileName,\n lineNumber: this.lineNumber,\n columnNumber: this.columnNumber,\n stack: this.stack,\n // Axios\n config: this.config,\n code: this.code\n };\n };\n return error;\n};\n","'use strict';\n\nvar utils = require('./../utils');\nvar settle = require('./../core/settle');\nvar cookies = require('./../helpers/cookies');\nvar buildURL = require('./../helpers/buildURL');\nvar buildFullPath = require('../core/buildFullPath');\nvar parseHeaders = require('./../helpers/parseHeaders');\nvar isURLSameOrigin = require('./../helpers/isURLSameOrigin');\nvar createError = require('../core/createError');\n\nmodule.exports = function xhrAdapter(config) {\n return new Promise(function dispatchXhrRequest(resolve, reject) {\n var requestData = config.data;\n var requestHeaders = config.headers;\n var responseType = config.responseType;\n\n if (utils.isFormData(requestData)) {\n delete requestHeaders['Content-Type']; // Let the browser set it\n }\n\n var request = new XMLHttpRequest();\n\n // HTTP basic authentication\n if (config.auth) {\n var username = config.auth.username || '';\n var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';\n requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);\n }\n\n var fullPath = buildFullPath(config.baseURL, config.url);\n request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);\n\n // Set the request timeout in MS\n request.timeout = config.timeout;\n\n function onloadend() {\n if (!request) {\n return;\n }\n // Prepare the response\n var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;\n var responseData = !responseType || responseType === 'text' || responseType === 'json' ?\n request.responseText : request.response;\n var response = {\n data: responseData,\n status: request.status,\n statusText: request.statusText,\n headers: responseHeaders,\n config: config,\n request: request\n };\n\n settle(resolve, reject, response);\n\n // Clean up request\n request = null;\n }\n\n if ('onloadend' in request) {\n // Use onloadend if available\n request.onloadend = onloadend;\n } else {\n // Listen for ready state to emulate onloadend\n request.onreadystatechange = function handleLoad() {\n if (!request || request.readyState !== 4) {\n return;\n }\n\n // The request errored out and we didn't get a response, this will be\n // handled by onerror instead\n // With one exception: request that using file: protocol, most browsers\n // will return status as 0 even though it's a successful request\n if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {\n return;\n }\n // readystate handler is calling before onerror or ontimeout handlers,\n // so we should call onloadend on the next 'tick'\n setTimeout(onloadend);\n };\n }\n\n // Handle browser request cancellation (as opposed to a manual cancellation)\n request.onabort = function handleAbort() {\n if (!request) {\n return;\n }\n\n reject(createError('Request aborted', config, 'ECONNABORTED', request));\n\n // Clean up request\n request = null;\n };\n\n // Handle low level network errors\n request.onerror = function handleError() {\n // Real errors are hidden from us by the browser\n // onerror should only fire if it's a network error\n reject(createError('Network Error', config, null, request));\n\n // Clean up request\n request = null;\n };\n\n // Handle timeout\n request.ontimeout = function handleTimeout() {\n var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';\n if (config.timeoutErrorMessage) {\n timeoutErrorMessage = config.timeoutErrorMessage;\n }\n reject(createError(\n timeoutErrorMessage,\n config,\n config.transitional && config.transitional.clarifyTimeoutError ? 'ETIMEDOUT' : 'ECONNABORTED',\n request));\n\n // Clean up request\n request = null;\n };\n\n // Add xsrf header\n // This is only done if running in a standard browser environment.\n // Specifically not if we're in a web worker, or react-native.\n if (utils.isStandardBrowserEnv()) {\n // Add xsrf header\n var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?\n cookies.read(config.xsrfCookieName) :\n undefined;\n\n if (xsrfValue) {\n requestHeaders[config.xsrfHeaderName] = xsrfValue;\n }\n }\n\n // Add headers to the request\n if ('setRequestHeader' in request) {\n utils.forEach(requestHeaders, function setRequestHeader(val, key) {\n if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {\n // Remove Content-Type if data is undefined\n delete requestHeaders[key];\n } else {\n // Otherwise add header to the request\n request.setRequestHeader(key, val);\n }\n });\n }\n\n // Add withCredentials to request if needed\n if (!utils.isUndefined(config.withCredentials)) {\n request.withCredentials = !!config.withCredentials;\n }\n\n // Add responseType to request if needed\n if (responseType && responseType !== 'json') {\n request.responseType = config.responseType;\n }\n\n // Handle progress if needed\n if (typeof config.onDownloadProgress === 'function') {\n request.addEventListener('progress', config.onDownloadProgress);\n }\n\n // Not all browsers support upload events\n if (typeof config.onUploadProgress === 'function' && request.upload) {\n request.upload.addEventListener('progress', config.onUploadProgress);\n }\n\n if (config.cancelToken) {\n // Handle cancellation\n config.cancelToken.promise.then(function onCanceled(cancel) {\n if (!request) {\n return;\n }\n\n request.abort();\n reject(cancel);\n // Clean up request\n request = null;\n });\n }\n\n if (!requestData) {\n requestData = null;\n }\n\n // Send the request\n request.send(requestData);\n });\n};\n","'use strict';\n\nvar enhanceError = require('./enhanceError');\n\n/**\n * Create an Error with the specified message, config, error code, request and response.\n *\n * @param {string} message The error message.\n * @param {Object} config The config.\n * @param {string} [code] The error code (for example, 'ECONNABORTED').\n * @param {Object} [request] The request.\n * @param {Object} [response] The response.\n * @returns {Error} The created error.\n */\nmodule.exports = function createError(message, config, code, request, response) {\n var error = new Error(message);\n return enhanceError(error, config, code, request, response);\n};\n","'use strict';\n\nmodule.exports = function isCancel(value) {\n return !!(value && value.__CANCEL__);\n};\n","'use strict';\n\nvar utils = require('../utils');\n\n/**\n * Config-specific merge-function which creates a new config-object\n * by merging two configuration objects together.\n *\n * @param {Object} config1\n * @param {Object} config2\n * @returns {Object} New object resulting from merging config2 to config1\n */\nmodule.exports = function mergeConfig(config1, config2) {\n // eslint-disable-next-line no-param-reassign\n config2 = config2 || {};\n var config = {};\n\n var valueFromConfig2Keys = ['url', 'method', 'data'];\n var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy', 'params'];\n var defaultToConfig2Keys = [\n 'baseURL', 'transformRequest', 'transformResponse', 'paramsSerializer',\n 'timeout', 'timeoutMessage', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName',\n 'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'decompress',\n 'maxContentLength', 'maxBodyLength', 'maxRedirects', 'transport', 'httpAgent',\n 'httpsAgent', 'cancelToken', 'socketPath', 'responseEncoding'\n ];\n var directMergeKeys = ['validateStatus'];\n\n function getMergedValue(target, source) {\n if (utils.isPlainObject(target) && utils.isPlainObject(source)) {\n return utils.merge(target, source);\n } else if (utils.isPlainObject(source)) {\n return utils.merge({}, source);\n } else if (utils.isArray(source)) {\n return source.slice();\n }\n return source;\n }\n\n function mergeDeepProperties(prop) {\n if (!utils.isUndefined(config2[prop])) {\n config[prop] = getMergedValue(config1[prop], config2[prop]);\n } else if (!utils.isUndefined(config1[prop])) {\n config[prop] = getMergedValue(undefined, config1[prop]);\n }\n }\n\n utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) {\n if (!utils.isUndefined(config2[prop])) {\n config[prop] = getMergedValue(undefined, config2[prop]);\n }\n });\n\n utils.forEach(mergeDeepPropertiesKeys, mergeDeepProperties);\n\n utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) {\n if (!utils.isUndefined(config2[prop])) {\n config[prop] = getMergedValue(undefined, config2[prop]);\n } else if (!utils.isUndefined(config1[prop])) {\n config[prop] = getMergedValue(undefined, config1[prop]);\n }\n });\n\n utils.forEach(directMergeKeys, function merge(prop) {\n if (prop in config2) {\n config[prop] = getMergedValue(config1[prop], config2[prop]);\n } else if (prop in config1) {\n config[prop] = getMergedValue(undefined, config1[prop]);\n }\n });\n\n var axiosKeys = valueFromConfig2Keys\n .concat(mergeDeepPropertiesKeys)\n .concat(defaultToConfig2Keys)\n .concat(directMergeKeys);\n\n var otherKeys = Object\n .keys(config1)\n .concat(Object.keys(config2))\n .filter(function filterAxiosKeys(key) {\n return axiosKeys.indexOf(key) === -1;\n });\n\n utils.forEach(otherKeys, mergeDeepProperties);\n\n return config;\n};\n","'use strict';\n\n/**\n * A `Cancel` is an object that is thrown when an operation is canceled.\n *\n * @class\n * @param {string=} message The message.\n */\nfunction Cancel(message) {\n this.message = message;\n}\n\nCancel.prototype.toString = function toString() {\n return 'Cancel' + (this.message ? ': ' + this.message : '');\n};\n\nCancel.prototype.__CANCEL__ = true;\n\nmodule.exports = Cancel;\n","/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n var desc = Object.getOwnPropertyDescriptor(m, k);\r\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\r\n desc = { enumerable: true, get: function() { return m[k]; } };\r\n }\r\n Object.defineProperty(o, k2, desc);\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n\r\nexport function __classPrivateFieldIn(state, receiver) {\r\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\r\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\r\n}\r\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.useFirstMountState = void 0;\nvar react_1 = require(\"react\");\nfunction useFirstMountState() {\n var isFirst = react_1.useRef(true);\n if (isFirst.current) {\n isFirst.current = false;\n return true;\n }\n return isFirst.current;\n}\nexports.useFirstMountState = useFirstMountState;\n","'use strict';\n\nmodule.exports = function bind(fn, thisArg) {\n return function wrap() {\n var args = new Array(arguments.length);\n for (var i = 0; i < args.length; i++) {\n args[i] = arguments[i];\n }\n return fn.apply(thisArg, args);\n };\n};\n","'use strict';\n\nvar utils = require('./../utils');\n\nfunction encode(val) {\n return encodeURIComponent(val).\n replace(/%3A/gi, ':').\n replace(/%24/g, '$').\n replace(/%2C/gi, ',').\n replace(/%20/g, '+').\n replace(/%5B/gi, '[').\n replace(/%5D/gi, ']');\n}\n\n/**\n * Build a URL by appending params to the end\n *\n * @param {string} url The base of the url (e.g., http://www.google.com)\n * @param {object} [params] The params to be appended\n * @returns {string} The formatted url\n */\nmodule.exports = function buildURL(url, params, paramsSerializer) {\n /*eslint no-param-reassign:0*/\n if (!params) {\n return url;\n }\n\n var serializedParams;\n if (paramsSerializer) {\n serializedParams = paramsSerializer(params);\n } else if (utils.isURLSearchParams(params)) {\n serializedParams = params.toString();\n } else {\n var parts = [];\n\n utils.forEach(params, function serialize(val, key) {\n if (val === null || typeof val === 'undefined') {\n return;\n }\n\n if (utils.isArray(val)) {\n key = key + '[]';\n } else {\n val = [val];\n }\n\n utils.forEach(val, function parseValue(v) {\n if (utils.isDate(v)) {\n v = v.toISOString();\n } else if (utils.isObject(v)) {\n v = JSON.stringify(v);\n }\n parts.push(encode(key) + '=' + encode(v));\n });\n });\n\n serializedParams = parts.join('&');\n }\n\n if (serializedParams) {\n var hashmarkIndex = url.indexOf('#');\n if (hashmarkIndex !== -1) {\n url = url.slice(0, hashmarkIndex);\n }\n\n url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;\n }\n\n return url;\n};\n","'use strict';\n\nmodule.exports = {\n silentJSONParsing: true,\n forcedJSONParsing: true,\n clarifyTimeoutError: false\n};\n","'use strict';\n\nvar utils = require('../utils');\n\n/**\n * Convert a data object to FormData\n * @param {Object} obj\n * @param {?Object} [formData]\n * @returns {Object}\n **/\n\nfunction toFormData(obj, formData) {\n // eslint-disable-next-line no-param-reassign\n formData = formData || new FormData();\n\n var stack = [];\n\n function convertValue(value) {\n if (value === null) return '';\n\n if (utils.isDate(value)) {\n return value.toISOString();\n }\n\n if (utils.isArrayBuffer(value) || utils.isTypedArray(value)) {\n return typeof Blob === 'function' ? new Blob([value]) : Buffer.from(value);\n }\n\n return value;\n }\n\n function build(data, parentKey) {\n if (utils.isPlainObject(data) || utils.isArray(data)) {\n if (stack.indexOf(data) !== -1) {\n throw Error('Circular reference detected in ' + parentKey);\n }\n\n stack.push(data);\n\n utils.forEach(data, function each(value, key) {\n if (utils.isUndefined(value)) return;\n var fullKey = parentKey ? parentKey + '.' + key : key;\n var arr;\n\n if (value && !parentKey && typeof value === 'object') {\n if (utils.endsWith(key, '{}')) {\n // eslint-disable-next-line no-param-reassign\n value = JSON.stringify(value);\n } else if (utils.endsWith(key, '[]') && (arr = utils.toArray(value))) {\n // eslint-disable-next-line func-names\n arr.forEach(function(el) {\n !utils.isUndefined(el) && formData.append(fullKey, convertValue(el));\n });\n return;\n }\n }\n\n build(value, fullKey);\n });\n\n stack.pop();\n } else {\n formData.append(parentKey, convertValue(data));\n }\n }\n\n build(obj);\n\n return formData;\n}\n\nmodule.exports = toFormData;\n","'use strict';\n\nvar utils = require('./../utils');\nvar settle = require('./../core/settle');\nvar cookies = require('./../helpers/cookies');\nvar buildURL = require('./../helpers/buildURL');\nvar buildFullPath = require('../core/buildFullPath');\nvar parseHeaders = require('./../helpers/parseHeaders');\nvar isURLSameOrigin = require('./../helpers/isURLSameOrigin');\nvar transitionalDefaults = require('../defaults/transitional');\nvar AxiosError = require('../core/AxiosError');\nvar CanceledError = require('../cancel/CanceledError');\nvar parseProtocol = require('../helpers/parseProtocol');\n\nmodule.exports = function xhrAdapter(config) {\n return new Promise(function dispatchXhrRequest(resolve, reject) {\n var requestData = config.data;\n var requestHeaders = config.headers;\n var responseType = config.responseType;\n var onCanceled;\n function done() {\n if (config.cancelToken) {\n config.cancelToken.unsubscribe(onCanceled);\n }\n\n if (config.signal) {\n config.signal.removeEventListener('abort', onCanceled);\n }\n }\n\n if (utils.isFormData(requestData) && utils.isStandardBrowserEnv()) {\n delete requestHeaders['Content-Type']; // Let the browser set it\n }\n\n var request = new XMLHttpRequest();\n\n // HTTP basic authentication\n if (config.auth) {\n var username = config.auth.username || '';\n var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';\n requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);\n }\n\n var fullPath = buildFullPath(config.baseURL, config.url);\n\n request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);\n\n // Set the request timeout in MS\n request.timeout = config.timeout;\n\n function onloadend() {\n if (!request) {\n return;\n }\n // Prepare the response\n var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;\n var responseData = !responseType || responseType === 'text' || responseType === 'json' ?\n request.responseText : request.response;\n var response = {\n data: responseData,\n status: request.status,\n statusText: request.statusText,\n headers: responseHeaders,\n config: config,\n request: request\n };\n\n settle(function _resolve(value) {\n resolve(value);\n done();\n }, function _reject(err) {\n reject(err);\n done();\n }, response);\n\n // Clean up request\n request = null;\n }\n\n if ('onloadend' in request) {\n // Use onloadend if available\n request.onloadend = onloadend;\n } else {\n // Listen for ready state to emulate onloadend\n request.onreadystatechange = function handleLoad() {\n if (!request || request.readyState !== 4) {\n return;\n }\n\n // The request errored out and we didn't get a response, this will be\n // handled by onerror instead\n // With one exception: request that using file: protocol, most browsers\n // will return status as 0 even though it's a successful request\n if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {\n return;\n }\n // readystate handler is calling before onerror or ontimeout handlers,\n // so we should call onloadend on the next 'tick'\n setTimeout(onloadend);\n };\n }\n\n // Handle browser request cancellation (as opposed to a manual cancellation)\n request.onabort = function handleAbort() {\n if (!request) {\n return;\n }\n\n reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));\n\n // Clean up request\n request = null;\n };\n\n // Handle low level network errors\n request.onerror = function handleError() {\n // Real errors are hidden from us by the browser\n // onerror should only fire if it's a network error\n reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request, request));\n\n // Clean up request\n request = null;\n };\n\n // Handle timeout\n request.ontimeout = function handleTimeout() {\n var timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';\n var transitional = config.transitional || transitionalDefaults;\n if (config.timeoutErrorMessage) {\n timeoutErrorMessage = config.timeoutErrorMessage;\n }\n reject(new AxiosError(\n timeoutErrorMessage,\n transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,\n config,\n request));\n\n // Clean up request\n request = null;\n };\n\n // Add xsrf header\n // This is only done if running in a standard browser environment.\n // Specifically not if we're in a web worker, or react-native.\n if (utils.isStandardBrowserEnv()) {\n // Add xsrf header\n var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?\n cookies.read(config.xsrfCookieName) :\n undefined;\n\n if (xsrfValue) {\n requestHeaders[config.xsrfHeaderName] = xsrfValue;\n }\n }\n\n // Add headers to the request\n if ('setRequestHeader' in request) {\n utils.forEach(requestHeaders, function setRequestHeader(val, key) {\n if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {\n // Remove Content-Type if data is undefined\n delete requestHeaders[key];\n } else {\n // Otherwise add header to the request\n request.setRequestHeader(key, val);\n }\n });\n }\n\n // Add withCredentials to request if needed\n if (!utils.isUndefined(config.withCredentials)) {\n request.withCredentials = !!config.withCredentials;\n }\n\n // Add responseType to request if needed\n if (responseType && responseType !== 'json') {\n request.responseType = config.responseType;\n }\n\n // Handle progress if needed\n if (typeof config.onDownloadProgress === 'function') {\n request.addEventListener('progress', config.onDownloadProgress);\n }\n\n // Not all browsers support upload events\n if (typeof config.onUploadProgress === 'function' && request.upload) {\n request.upload.addEventListener('progress', config.onUploadProgress);\n }\n\n if (config.cancelToken || config.signal) {\n // Handle cancellation\n // eslint-disable-next-line func-names\n onCanceled = function(cancel) {\n if (!request) {\n return;\n }\n reject(!cancel || (cancel && cancel.type) ? new CanceledError() : cancel);\n request.abort();\n request = null;\n };\n\n config.cancelToken && config.cancelToken.subscribe(onCanceled);\n if (config.signal) {\n config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);\n }\n }\n\n if (!requestData) {\n requestData = null;\n }\n\n var protocol = parseProtocol(fullPath);\n\n if (protocol && [ 'http', 'https', 'file' ].indexOf(protocol) === -1) {\n reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));\n return;\n }\n\n\n // Send the request\n request.send(requestData);\n });\n};\n","'use strict';\n\nvar isAbsoluteURL = require('../helpers/isAbsoluteURL');\nvar combineURLs = require('../helpers/combineURLs');\n\n/**\n * Creates a new URL by combining the baseURL with the requestedURL,\n * only when the requestedURL is not already an absolute URL.\n * If the requestURL is absolute, this function returns the requestedURL untouched.\n *\n * @param {string} baseURL The base URL\n * @param {string} requestedURL Absolute or relative URL to combine\n * @returns {string} The combined full path\n */\nmodule.exports = function buildFullPath(baseURL, requestedURL) {\n if (baseURL && !isAbsoluteURL(requestedURL)) {\n return combineURLs(baseURL, requestedURL);\n }\n return requestedURL;\n};\n","'use strict';\n\nmodule.exports = function isCancel(value) {\n return !!(value && value.__CANCEL__);\n};\n","'use strict';\n\nvar utils = require('../utils');\n\n/**\n * Config-specific merge-function which creates a new config-object\n * by merging two configuration objects together.\n *\n * @param {Object} config1\n * @param {Object} config2\n * @returns {Object} New object resulting from merging config2 to config1\n */\nmodule.exports = function mergeConfig(config1, config2) {\n // eslint-disable-next-line no-param-reassign\n config2 = config2 || {};\n var config = {};\n\n function getMergedValue(target, source) {\n if (utils.isPlainObject(target) && utils.isPlainObject(source)) {\n return utils.merge(target, source);\n } else if (utils.isPlainObject(source)) {\n return utils.merge({}, source);\n } else if (utils.isArray(source)) {\n return source.slice();\n }\n return source;\n }\n\n // eslint-disable-next-line consistent-return\n function mergeDeepProperties(prop) {\n if (!utils.isUndefined(config2[prop])) {\n return getMergedValue(config1[prop], config2[prop]);\n } else if (!utils.isUndefined(config1[prop])) {\n return getMergedValue(undefined, config1[prop]);\n }\n }\n\n // eslint-disable-next-line consistent-return\n function valueFromConfig2(prop) {\n if (!utils.isUndefined(config2[prop])) {\n return getMergedValue(undefined, config2[prop]);\n }\n }\n\n // eslint-disable-next-line consistent-return\n function defaultToConfig2(prop) {\n if (!utils.isUndefined(config2[prop])) {\n return getMergedValue(undefined, config2[prop]);\n } else if (!utils.isUndefined(config1[prop])) {\n return getMergedValue(undefined, config1[prop]);\n }\n }\n\n // eslint-disable-next-line consistent-return\n function mergeDirectKeys(prop) {\n if (prop in config2) {\n return getMergedValue(config1[prop], config2[prop]);\n } else if (prop in config1) {\n return getMergedValue(undefined, config1[prop]);\n }\n }\n\n var mergeMap = {\n 'url': valueFromConfig2,\n 'method': valueFromConfig2,\n 'data': valueFromConfig2,\n 'baseURL': defaultToConfig2,\n 'transformRequest': defaultToConfig2,\n 'transformResponse': defaultToConfig2,\n 'paramsSerializer': defaultToConfig2,\n 'timeout': defaultToConfig2,\n 'timeoutMessage': defaultToConfig2,\n 'withCredentials': defaultToConfig2,\n 'adapter': defaultToConfig2,\n 'responseType': defaultToConfig2,\n 'xsrfCookieName': defaultToConfig2,\n 'xsrfHeaderName': defaultToConfig2,\n 'onUploadProgress': defaultToConfig2,\n 'onDownloadProgress': defaultToConfig2,\n 'decompress': defaultToConfig2,\n 'maxContentLength': defaultToConfig2,\n 'maxBodyLength': defaultToConfig2,\n 'beforeRedirect': defaultToConfig2,\n 'transport': defaultToConfig2,\n 'httpAgent': defaultToConfig2,\n 'httpsAgent': defaultToConfig2,\n 'cancelToken': defaultToConfig2,\n 'socketPath': defaultToConfig2,\n 'responseEncoding': defaultToConfig2,\n 'validateStatus': mergeDirectKeys\n };\n\n utils.forEach(Object.keys(config1).concat(Object.keys(config2)), function computeConfigValue(prop) {\n var merge = mergeMap[prop] || mergeDeepProperties;\n var configValue = merge(prop);\n (utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue);\n });\n\n return config;\n};\n","module.exports = {\n \"version\": \"0.27.2\"\n};","'use strict';\n\nvar slice = Array.prototype.slice;\nvar isArgs = require('./isArguments');\n\nvar origKeys = Object.keys;\nvar keysShim = origKeys ? function keys(o) { return origKeys(o); } : require('./implementation');\n\nvar originalKeys = Object.keys;\n\nkeysShim.shim = function shimObjectKeys() {\n\tif (Object.keys) {\n\t\tvar keysWorksWithArguments = (function () {\n\t\t\t// Safari 5.0 bug\n\t\t\tvar args = Object.keys(arguments);\n\t\t\treturn args && args.length === arguments.length;\n\t\t}(1, 2));\n\t\tif (!keysWorksWithArguments) {\n\t\t\tObject.keys = function keys(object) { // eslint-disable-line func-name-matching\n\t\t\t\tif (isArgs(object)) {\n\t\t\t\t\treturn originalKeys(slice.call(object));\n\t\t\t\t}\n\t\t\t\treturn originalKeys(object);\n\t\t\t};\n\t\t}\n\t} else {\n\t\tObject.keys = keysShim;\n\t}\n\treturn Object.keys || keysShim;\n};\n\nmodule.exports = keysShim;\n","'use strict';\n\nvar toStr = Object.prototype.toString;\n\nmodule.exports = function isArguments(value) {\n\tvar str = toStr.call(value);\n\tvar isArgs = str === '[object Arguments]';\n\tif (!isArgs) {\n\t\tisArgs = str !== '[object Array]' &&\n\t\t\tvalue !== null &&\n\t\t\ttypeof value === 'object' &&\n\t\t\ttypeof value.length === 'number' &&\n\t\t\tvalue.length >= 0 &&\n\t\t\ttoStr.call(value.callee) === '[object Function]';\n\t}\n\treturn isArgs;\n};\n","'use strict';\n\nvar bind = require('function-bind');\n\nmodule.exports = bind.call(Function.call, Object.prototype.hasOwnProperty);\n","'use strict';\n\nvar toObject = Object;\nvar TypeErr = TypeError;\n\nmodule.exports = function flags() {\n\tif (this != null && this !== toObject(this)) {\n\t\tthrow new TypeErr('RegExp.prototype.flags getter called on non-object');\n\t}\n\tvar result = '';\n\tif (this.global) {\n\t\tresult += 'g';\n\t}\n\tif (this.ignoreCase) {\n\t\tresult += 'i';\n\t}\n\tif (this.multiline) {\n\t\tresult += 'm';\n\t}\n\tif (this.dotAll) {\n\t\tresult += 's';\n\t}\n\tif (this.unicode) {\n\t\tresult += 'u';\n\t}\n\tif (this.sticky) {\n\t\tresult += 'y';\n\t}\n\treturn result;\n};\n","'use strict';\n\nvar implementation = require('./implementation');\n\nvar supportsDescriptors = require('define-properties').supportsDescriptors;\nvar gOPD = Object.getOwnPropertyDescriptor;\nvar TypeErr = TypeError;\n\nmodule.exports = function getPolyfill() {\n\tif (!supportsDescriptors) {\n\t\tthrow new TypeErr('RegExp.prototype.flags requires a true ES5 environment that supports property descriptors');\n\t}\n\tif (/a/mig.flags === 'gim') {\n\t\tvar descriptor = gOPD(RegExp.prototype, 'flags');\n\t\tif (descriptor && typeof descriptor.get === 'function' && typeof (/a/).dotAll === 'boolean') {\n\t\t\treturn descriptor.get;\n\t\t}\n\t}\n\treturn implementation;\n};\n","'use strict';\n\nvar formats = require('./formats');\n\nvar has = Object.prototype.hasOwnProperty;\nvar isArray = Array.isArray;\n\nvar hexTable = (function () {\n var array = [];\n for (var i = 0; i < 256; ++i) {\n array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase());\n }\n\n return array;\n}());\n\nvar compactQueue = function compactQueue(queue) {\n while (queue.length > 1) {\n var item = queue.pop();\n var obj = item.obj[item.prop];\n\n if (isArray(obj)) {\n var compacted = [];\n\n for (var j = 0; j < obj.length; ++j) {\n if (typeof obj[j] !== 'undefined') {\n compacted.push(obj[j]);\n }\n }\n\n item.obj[item.prop] = compacted;\n }\n }\n};\n\nvar arrayToObject = function arrayToObject(source, options) {\n var obj = options && options.plainObjects ? Object.create(null) : {};\n for (var i = 0; i < source.length; ++i) {\n if (typeof source[i] !== 'undefined') {\n obj[i] = source[i];\n }\n }\n\n return obj;\n};\n\nvar merge = function merge(target, source, options) {\n /* eslint no-param-reassign: 0 */\n if (!source) {\n return target;\n }\n\n if (typeof source !== 'object') {\n if (isArray(target)) {\n target.push(source);\n } else if (target && typeof target === 'object') {\n if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {\n target[source] = true;\n }\n } else {\n return [target, source];\n }\n\n return target;\n }\n\n if (!target || typeof target !== 'object') {\n return [target].concat(source);\n }\n\n var mergeTarget = target;\n if (isArray(target) && !isArray(source)) {\n mergeTarget = arrayToObject(target, options);\n }\n\n if (isArray(target) && isArray(source)) {\n source.forEach(function (item, i) {\n if (has.call(target, i)) {\n var targetItem = target[i];\n if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {\n target[i] = merge(targetItem, item, options);\n } else {\n target.push(item);\n }\n } else {\n target[i] = item;\n }\n });\n return target;\n }\n\n return Object.keys(source).reduce(function (acc, key) {\n var value = source[key];\n\n if (has.call(acc, key)) {\n acc[key] = merge(acc[key], value, options);\n } else {\n acc[key] = value;\n }\n return acc;\n }, mergeTarget);\n};\n\nvar assign = function assignSingleSource(target, source) {\n return Object.keys(source).reduce(function (acc, key) {\n acc[key] = source[key];\n return acc;\n }, target);\n};\n\nvar decode = function (str, decoder, charset) {\n var strWithoutPlus = str.replace(/\\+/g, ' ');\n if (charset === 'iso-8859-1') {\n // unescape never throws, no try...catch needed:\n return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape);\n }\n // utf-8\n try {\n return decodeURIComponent(strWithoutPlus);\n } catch (e) {\n return strWithoutPlus;\n }\n};\n\nvar encode = function encode(str, defaultEncoder, charset, kind, format) {\n // This code was originally written by Brian White (mscdex) for the io.js core querystring library.\n // It has been adapted here for stricter adherence to RFC 3986\n if (str.length === 0) {\n return str;\n }\n\n var string = str;\n if (typeof str === 'symbol') {\n string = Symbol.prototype.toString.call(str);\n } else if (typeof str !== 'string') {\n string = String(str);\n }\n\n if (charset === 'iso-8859-1') {\n return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) {\n return '%26%23' + parseInt($0.slice(2), 16) + '%3B';\n });\n }\n\n var out = '';\n for (var i = 0; i < string.length; ++i) {\n var c = string.charCodeAt(i);\n\n if (\n c === 0x2D // -\n || c === 0x2E // .\n || c === 0x5F // _\n || c === 0x7E // ~\n || (c >= 0x30 && c <= 0x39) // 0-9\n || (c >= 0x41 && c <= 0x5A) // a-z\n || (c >= 0x61 && c <= 0x7A) // A-Z\n || (format === formats.RFC1738 && (c === 0x28 || c === 0x29)) // ( )\n ) {\n out += string.charAt(i);\n continue;\n }\n\n if (c < 0x80) {\n out = out + hexTable[c];\n continue;\n }\n\n if (c < 0x800) {\n out = out + (hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)]);\n continue;\n }\n\n if (c < 0xD800 || c >= 0xE000) {\n out = out + (hexTable[0xE0 | (c >> 12)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]);\n continue;\n }\n\n i += 1;\n c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));\n /* eslint operator-linebreak: [2, \"before\"] */\n out += hexTable[0xF0 | (c >> 18)]\n + hexTable[0x80 | ((c >> 12) & 0x3F)]\n + hexTable[0x80 | ((c >> 6) & 0x3F)]\n + hexTable[0x80 | (c & 0x3F)];\n }\n\n return out;\n};\n\nvar compact = function compact(value) {\n var queue = [{ obj: { o: value }, prop: 'o' }];\n var refs = [];\n\n for (var i = 0; i < queue.length; ++i) {\n var item = queue[i];\n var obj = item.obj[item.prop];\n\n var keys = Object.keys(obj);\n for (var j = 0; j < keys.length; ++j) {\n var key = keys[j];\n var val = obj[key];\n if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {\n queue.push({ obj: obj, prop: key });\n refs.push(val);\n }\n }\n }\n\n compactQueue(queue);\n\n return value;\n};\n\nvar isRegExp = function isRegExp(obj) {\n return Object.prototype.toString.call(obj) === '[object RegExp]';\n};\n\nvar isBuffer = function isBuffer(obj) {\n if (!obj || typeof obj !== 'object') {\n return false;\n }\n\n return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj));\n};\n\nvar combine = function combine(a, b) {\n return [].concat(a, b);\n};\n\nvar maybeMap = function maybeMap(val, fn) {\n if (isArray(val)) {\n var mapped = [];\n for (var i = 0; i < val.length; i += 1) {\n mapped.push(fn(val[i]));\n }\n return mapped;\n }\n return fn(val);\n};\n\nmodule.exports = {\n arrayToObject: arrayToObject,\n assign: assign,\n combine: combine,\n compact: compact,\n decode: decode,\n encode: encode,\n isBuffer: isBuffer,\n isRegExp: isRegExp,\n maybeMap: maybeMap,\n merge: merge\n};\n","var arrayLikeToArray = require(\"./arrayLikeToArray.js\");\n\nfunction _unsupportedIterableToArray(o, minLen) {\n if (!o) return;\n if (typeof o === \"string\") return arrayLikeToArray(o, minLen);\n var n = Object.prototype.toString.call(o).slice(8, -1);\n if (n === \"Object\" && o.constructor) n = o.constructor.name;\n if (n === \"Map\" || n === \"Set\") return Array.from(o);\n if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return arrayLikeToArray(o, minLen);\n}\n\nmodule.exports = _unsupportedIterableToArray, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","function _arrayLikeToArray(arr, len) {\n if (len == null || len > arr.length) len = arr.length;\n\n for (var i = 0, arr2 = new Array(len); i < len; i++) {\n arr2[i] = arr[i];\n }\n\n return arr2;\n}\n\nmodule.exports = _arrayLikeToArray, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.DateTimeFormat = void 0;\nvar tslib_1 = require(\"tslib\");\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\nvar get_internal_slots_1 = tslib_1.__importDefault(require(\"./get_internal_slots\"));\nvar links_1 = tslib_1.__importDefault(require(\"./data/links\"));\nvar packer_1 = require(\"./packer\");\nvar FormatDateTime_1 = require(\"./abstract/FormatDateTime\");\nvar InitializeDateTimeFormat_1 = require(\"./abstract/InitializeDateTimeFormat\");\nvar utils_1 = require(\"./abstract/utils\");\nvar FormatDateTimeToParts_1 = require(\"./abstract/FormatDateTimeToParts\");\nvar FormatDateTimeRangeToParts_1 = require(\"./abstract/FormatDateTimeRangeToParts\");\nvar FormatDateTimeRange_1 = require(\"./abstract/FormatDateTimeRange\");\nvar skeleton_1 = require(\"./abstract/skeleton\");\nvar UPPERCASED_LINKS = Object.keys(links_1.default).reduce(function (all, l) {\n all[l.toUpperCase()] = links_1.default[l];\n return all;\n}, {});\nvar RESOLVED_OPTIONS_KEYS = [\n 'locale',\n 'calendar',\n 'numberingSystem',\n 'dateStyle',\n 'timeStyle',\n 'timeZone',\n 'hourCycle',\n 'weekday',\n 'era',\n 'year',\n 'month',\n 'day',\n 'hour',\n 'minute',\n 'second',\n 'timeZoneName',\n];\nvar formatDescriptor = {\n enumerable: false,\n configurable: true,\n get: function () {\n if (typeof this !== 'object' ||\n !ecma402_abstract_1.OrdinaryHasInstance(exports.DateTimeFormat, this)) {\n throw TypeError('Intl.DateTimeFormat format property accessor called on incompatible receiver');\n }\n var internalSlots = get_internal_slots_1.default(this);\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n var dtf = this;\n var boundFormat = internalSlots.boundFormat;\n if (boundFormat === undefined) {\n // https://tc39.es/proposal-unified-intl-numberformat/section11/numberformat_diff_out.html#sec-number-format-functions\n boundFormat = function (date) {\n var x;\n if (date === undefined) {\n x = Date.now();\n }\n else {\n x = Number(date);\n }\n return FormatDateTime_1.FormatDateTime(dtf, x, {\n getInternalSlots: get_internal_slots_1.default,\n localeData: exports.DateTimeFormat.localeData,\n tzData: exports.DateTimeFormat.tzData,\n getDefaultTimeZone: exports.DateTimeFormat.getDefaultTimeZone,\n });\n };\n try {\n // https://github.com/tc39/test262/blob/master/test/intl402/NumberFormat/prototype/format/format-function-name.js\n Object.defineProperty(boundFormat, 'name', {\n configurable: true,\n enumerable: false,\n writable: false,\n value: '',\n });\n }\n catch (e) {\n // In older browser (e.g Chrome 36 like polyfill.io)\n // TypeError: Cannot redefine property: name\n }\n internalSlots.boundFormat = boundFormat;\n }\n return boundFormat;\n },\n};\ntry {\n // https://github.com/tc39/test262/blob/master/test/intl402/NumberFormat/prototype/format/name.js\n Object.defineProperty(formatDescriptor.get, 'name', {\n configurable: true,\n enumerable: false,\n writable: false,\n value: 'get format',\n });\n}\ncatch (e) {\n // In older browser (e.g Chrome 36 like polyfill.io)\n // TypeError: Cannot redefine property: name\n}\nexports.DateTimeFormat = function (locales, options) {\n // Cannot use `new.target` bc of IE11 & TS transpiles it to something else\n if (!this || !ecma402_abstract_1.OrdinaryHasInstance(exports.DateTimeFormat, this)) {\n return new exports.DateTimeFormat(locales, options);\n }\n InitializeDateTimeFormat_1.InitializeDateTimeFormat(this, locales, options, {\n tzData: exports.DateTimeFormat.tzData,\n uppercaseLinks: UPPERCASED_LINKS,\n availableLocales: exports.DateTimeFormat.availableLocales,\n relevantExtensionKeys: exports.DateTimeFormat.relevantExtensionKeys,\n getDefaultLocale: exports.DateTimeFormat.getDefaultLocale,\n getDefaultTimeZone: exports.DateTimeFormat.getDefaultTimeZone,\n getInternalSlots: get_internal_slots_1.default,\n localeData: exports.DateTimeFormat.localeData,\n });\n /** IMPL START */\n var internalSlots = get_internal_slots_1.default(this);\n var dataLocale = internalSlots.dataLocale;\n var dataLocaleData = exports.DateTimeFormat.localeData[dataLocale];\n ecma402_abstract_1.invariant(dataLocaleData !== undefined, \"Cannot load locale-dependent data for \" + dataLocale + \".\");\n /** IMPL END */\n};\n// Static properties\necma402_abstract_1.defineProperty(exports.DateTimeFormat, 'supportedLocalesOf', {\n value: function supportedLocalesOf(locales, options) {\n return ecma402_abstract_1.SupportedLocales(exports.DateTimeFormat.availableLocales, ecma402_abstract_1.CanonicalizeLocaleList(locales), options);\n },\n});\necma402_abstract_1.defineProperty(exports.DateTimeFormat.prototype, 'resolvedOptions', {\n value: function resolvedOptions() {\n if (typeof this !== 'object' ||\n !ecma402_abstract_1.OrdinaryHasInstance(exports.DateTimeFormat, this)) {\n throw TypeError('Method Intl.DateTimeFormat.prototype.resolvedOptions called on incompatible receiver');\n }\n var internalSlots = get_internal_slots_1.default(this);\n var ro = {};\n for (var _i = 0, RESOLVED_OPTIONS_KEYS_1 = RESOLVED_OPTIONS_KEYS; _i < RESOLVED_OPTIONS_KEYS_1.length; _i++) {\n var key = RESOLVED_OPTIONS_KEYS_1[_i];\n var value = internalSlots[key];\n if (key === 'hourCycle') {\n var hour12 = value === 'h11' || value === 'h12'\n ? true\n : value === 'h23' || value === 'h24'\n ? false\n : undefined;\n if (hour12 !== undefined) {\n ro.hour12 = hour12;\n }\n }\n if (utils_1.DATE_TIME_PROPS.indexOf(key) > -1) {\n if (internalSlots.dateStyle !== undefined ||\n internalSlots.timeStyle !== undefined) {\n value = undefined;\n }\n }\n if (value !== undefined) {\n ro[key] = value;\n }\n }\n return ro;\n },\n});\necma402_abstract_1.defineProperty(exports.DateTimeFormat.prototype, 'formatToParts', {\n value: function formatToParts(date) {\n if (date === undefined) {\n date = Date.now();\n }\n else {\n date = ecma402_abstract_1.ToNumber(date);\n }\n return FormatDateTimeToParts_1.FormatDateTimeToParts(this, date, {\n getInternalSlots: get_internal_slots_1.default,\n localeData: exports.DateTimeFormat.localeData,\n tzData: exports.DateTimeFormat.tzData,\n getDefaultTimeZone: exports.DateTimeFormat.getDefaultTimeZone,\n });\n },\n});\necma402_abstract_1.defineProperty(exports.DateTimeFormat.prototype, 'formatRangeToParts', {\n value: function formatRangeToParts(startDate, endDate) {\n var dtf = this;\n if (typeof dtf !== 'object') {\n throw new TypeError();\n }\n if (startDate === undefined || endDate === undefined) {\n throw new TypeError('startDate/endDate cannot be undefined');\n }\n var x = ecma402_abstract_1.ToNumber(startDate);\n var y = ecma402_abstract_1.ToNumber(endDate);\n return FormatDateTimeRangeToParts_1.FormatDateTimeRangeToParts(dtf, x, y, {\n getInternalSlots: get_internal_slots_1.default,\n localeData: exports.DateTimeFormat.localeData,\n tzData: exports.DateTimeFormat.tzData,\n getDefaultTimeZone: exports.DateTimeFormat.getDefaultTimeZone,\n });\n },\n});\necma402_abstract_1.defineProperty(exports.DateTimeFormat.prototype, 'formatRange', {\n value: function formatRange(startDate, endDate) {\n var dtf = this;\n if (typeof dtf !== 'object') {\n throw new TypeError();\n }\n if (startDate === undefined || endDate === undefined) {\n throw new TypeError('startDate/endDate cannot be undefined');\n }\n var x = ecma402_abstract_1.ToNumber(startDate);\n var y = ecma402_abstract_1.ToNumber(endDate);\n return FormatDateTimeRange_1.FormatDateTimeRange(dtf, x, y, {\n getInternalSlots: get_internal_slots_1.default,\n localeData: exports.DateTimeFormat.localeData,\n tzData: exports.DateTimeFormat.tzData,\n getDefaultTimeZone: exports.DateTimeFormat.getDefaultTimeZone,\n });\n },\n});\nvar DEFAULT_TIMEZONE = 'UTC';\nexports.DateTimeFormat.__setDefaultTimeZone = function (timeZone) {\n if (timeZone !== undefined) {\n timeZone = String(timeZone);\n if (!ecma402_abstract_1.IsValidTimeZoneName(timeZone, {\n tzData: exports.DateTimeFormat.tzData,\n uppercaseLinks: UPPERCASED_LINKS,\n })) {\n throw new RangeError('Invalid timeZoneName');\n }\n timeZone = ecma402_abstract_1.CanonicalizeTimeZoneName(timeZone, {\n tzData: exports.DateTimeFormat.tzData,\n uppercaseLinks: UPPERCASED_LINKS,\n });\n }\n else {\n timeZone = DEFAULT_TIMEZONE;\n }\n exports.DateTimeFormat.__defaultTimeZone = timeZone;\n};\nexports.DateTimeFormat.relevantExtensionKeys = ['nu', 'ca', 'hc'];\nexports.DateTimeFormat.__defaultTimeZone = DEFAULT_TIMEZONE;\nexports.DateTimeFormat.getDefaultTimeZone = function () { return exports.DateTimeFormat.__defaultTimeZone; };\nexports.DateTimeFormat.__addLocaleData = function __addLocaleData() {\n var data = [];\n for (var _i = 0; _i < arguments.length; _i++) {\n data[_i] = arguments[_i];\n }\n var _loop_1 = function (d, locale) {\n var dateFormat = d.dateFormat, timeFormat = d.timeFormat, dateTimeFormat = d.dateTimeFormat, formats = d.formats, intervalFormats = d.intervalFormats, rawData = tslib_1.__rest(d, [\"dateFormat\", \"timeFormat\", \"dateTimeFormat\", \"formats\", \"intervalFormats\"]);\n var processedData = tslib_1.__assign(tslib_1.__assign({}, rawData), { dateFormat: {\n full: skeleton_1.parseDateTimeSkeleton(dateFormat.full),\n long: skeleton_1.parseDateTimeSkeleton(dateFormat.long),\n medium: skeleton_1.parseDateTimeSkeleton(dateFormat.medium),\n short: skeleton_1.parseDateTimeSkeleton(dateFormat.short),\n }, timeFormat: {\n full: skeleton_1.parseDateTimeSkeleton(timeFormat.full),\n long: skeleton_1.parseDateTimeSkeleton(timeFormat.long),\n medium: skeleton_1.parseDateTimeSkeleton(timeFormat.medium),\n short: skeleton_1.parseDateTimeSkeleton(timeFormat.short),\n }, dateTimeFormat: {\n full: skeleton_1.parseDateTimeSkeleton(dateTimeFormat.full).pattern,\n long: skeleton_1.parseDateTimeSkeleton(dateTimeFormat.long).pattern,\n medium: skeleton_1.parseDateTimeSkeleton(dateTimeFormat.medium).pattern,\n short: skeleton_1.parseDateTimeSkeleton(dateTimeFormat.short).pattern,\n }, formats: {} });\n var _loop_2 = function (calendar) {\n processedData.formats[calendar] = Object.keys(formats[calendar]).map(function (skeleton) {\n return skeleton_1.parseDateTimeSkeleton(skeleton, formats[calendar][skeleton], intervalFormats[skeleton], intervalFormats.intervalFormatFallback);\n });\n };\n for (var calendar in formats) {\n _loop_2(calendar);\n }\n var minimizedLocale = new Intl.Locale(locale)\n .minimize()\n .toString();\n exports.DateTimeFormat.localeData[locale] = exports.DateTimeFormat.localeData[minimizedLocale] = processedData;\n exports.DateTimeFormat.availableLocales.add(locale);\n exports.DateTimeFormat.availableLocales.add(minimizedLocale);\n if (!exports.DateTimeFormat.__defaultLocale) {\n exports.DateTimeFormat.__defaultLocale = minimizedLocale;\n }\n };\n for (var _a = 0, data_1 = data; _a < data_1.length; _a++) {\n var _b = data_1[_a], d = _b.data, locale = _b.locale;\n _loop_1(d, locale);\n }\n};\nObject.defineProperty(exports.DateTimeFormat.prototype, 'format', formatDescriptor);\nexports.DateTimeFormat.__defaultLocale = '';\nexports.DateTimeFormat.localeData = {};\nexports.DateTimeFormat.availableLocales = new Set();\nexports.DateTimeFormat.getDefaultLocale = function () {\n return exports.DateTimeFormat.__defaultLocale;\n};\nexports.DateTimeFormat.polyfilled = true;\nexports.DateTimeFormat.tzData = {};\nexports.DateTimeFormat.__addTZData = function (d) {\n exports.DateTimeFormat.tzData = packer_1.unpack(d);\n};\ntry {\n if (typeof Symbol !== 'undefined') {\n Object.defineProperty(exports.DateTimeFormat.prototype, Symbol.toStringTag, {\n value: 'Intl.DateTimeFormat',\n writable: false,\n enumerable: false,\n configurable: true,\n });\n }\n Object.defineProperty(exports.DateTimeFormat.prototype.constructor, 'length', {\n value: 1,\n writable: false,\n enumerable: false,\n configurable: true,\n });\n}\ncatch (e) {\n // Meta fix so we're test262-compliant, not important\n}\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.ResolveLocale = void 0;\nvar LookupMatcher_1 = require(\"./LookupMatcher\");\nvar BestFitMatcher_1 = require(\"./BestFitMatcher\");\nvar utils_1 = require(\"./utils\");\nvar UnicodeExtensionValue_1 = require(\"./UnicodeExtensionValue\");\n/**\n * https://tc39.es/ecma402/#sec-resolvelocale\n */\nfunction ResolveLocale(availableLocales, requestedLocales, options, relevantExtensionKeys, localeData, getDefaultLocale) {\n var matcher = options.localeMatcher;\n var r;\n if (matcher === 'lookup') {\n r = LookupMatcher_1.LookupMatcher(availableLocales, requestedLocales, getDefaultLocale);\n }\n else {\n r = BestFitMatcher_1.BestFitMatcher(availableLocales, requestedLocales, getDefaultLocale);\n }\n var foundLocale = r.locale;\n var result = { locale: '', dataLocale: foundLocale };\n var supportedExtension = '-u';\n for (var _i = 0, relevantExtensionKeys_1 = relevantExtensionKeys; _i < relevantExtensionKeys_1.length; _i++) {\n var key = relevantExtensionKeys_1[_i];\n utils_1.invariant(foundLocale in localeData, \"Missing locale data for \" + foundLocale);\n var foundLocaleData = localeData[foundLocale];\n utils_1.invariant(typeof foundLocaleData === 'object' && foundLocaleData !== null, \"locale data \" + key + \" must be an object\");\n var keyLocaleData = foundLocaleData[key];\n utils_1.invariant(Array.isArray(keyLocaleData), \"keyLocaleData for \" + key + \" must be an array\");\n var value = keyLocaleData[0];\n utils_1.invariant(typeof value === 'string' || value === null, \"value must be string or null but got \" + typeof value + \" in key \" + key);\n var supportedExtensionAddition = '';\n if (r.extension) {\n var requestedValue = UnicodeExtensionValue_1.UnicodeExtensionValue(r.extension, key);\n if (requestedValue !== undefined) {\n if (requestedValue !== '') {\n if (~keyLocaleData.indexOf(requestedValue)) {\n value = requestedValue;\n supportedExtensionAddition = \"-\" + key + \"-\" + value;\n }\n }\n else if (~requestedValue.indexOf('true')) {\n value = 'true';\n supportedExtensionAddition = \"-\" + key;\n }\n }\n }\n if (key in options) {\n var optionsValue = options[key];\n utils_1.invariant(typeof optionsValue === 'string' ||\n typeof optionsValue === 'undefined' ||\n optionsValue === null, 'optionsValue must be String, Undefined or Null');\n if (~keyLocaleData.indexOf(optionsValue)) {\n if (optionsValue !== value) {\n value = optionsValue;\n supportedExtensionAddition = '';\n }\n }\n }\n result[key] = value;\n supportedExtension += supportedExtensionAddition;\n }\n if (supportedExtension.length > 2) {\n var privateIndex = foundLocale.indexOf('-x-');\n if (privateIndex === -1) {\n foundLocale = foundLocale + supportedExtension;\n }\n else {\n var preExtension = foundLocale.slice(0, privateIndex);\n var postExtension = foundLocale.slice(privateIndex, foundLocale.length);\n foundLocale = preExtension + supportedExtension + postExtension;\n }\n foundLocale = Intl.getCanonicalLocales(foundLocale)[0];\n }\n result.locale = foundLocale;\n return result;\n}\nexports.ResolveLocale = ResolveLocale;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.PartitionDateTimePattern = void 0;\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\nvar FormatDateTimePattern_1 = require(\"./FormatDateTimePattern\");\n/**\n * https://tc39.es/ecma402/#sec-partitiondatetimepattern\n * @param dtf\n * @param x\n */\nfunction PartitionDateTimePattern(dtf, x, implDetails) {\n x = ecma402_abstract_1.TimeClip(x);\n if (isNaN(x)) {\n throw new RangeError('invalid time');\n }\n /** IMPL START */\n var getInternalSlots = implDetails.getInternalSlots;\n var internalSlots = getInternalSlots(dtf);\n /** IMPL END */\n var pattern = internalSlots.pattern;\n return FormatDateTimePattern_1.FormatDateTimePattern(dtf, ecma402_abstract_1.PartitionPattern(pattern), x, implDetails);\n}\nexports.PartitionDateTimePattern = PartitionDateTimePattern;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.FormatDateTimePattern = void 0;\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\nvar utils_1 = require(\"./utils\");\nvar ToLocalTime_1 = require(\"./ToLocalTime\");\nfunction pad(n) {\n if (n < 10) {\n return \"0\" + n;\n }\n return String(n);\n}\nfunction offsetToGmtString(gmtFormat, hourFormat, offsetInMs, style) {\n var offsetInMinutes = Math.floor(offsetInMs / 60000);\n var mins = Math.abs(offsetInMinutes) % 60;\n var hours = Math.floor(Math.abs(offsetInMinutes) / 60);\n var _a = hourFormat.split(';'), positivePattern = _a[0], negativePattern = _a[1];\n var offsetStr = '';\n var pattern = offsetInMs < 0 ? negativePattern : positivePattern;\n if (style === 'long') {\n offsetStr = pattern\n .replace('HH', pad(hours))\n .replace('H', String(hours))\n .replace('mm', pad(mins))\n .replace('m', String(mins));\n }\n else if (mins || hours) {\n if (!mins) {\n pattern = pattern.replace(/:?m+/, '');\n }\n offsetStr = pattern.replace(/H+/, String(hours)).replace(/m+/, String(mins));\n }\n return gmtFormat.replace('{0}', offsetStr);\n}\n/**\n * https://tc39.es/ecma402/#sec-partitiondatetimepattern\n * @param dtf\n * @param x\n */\nfunction FormatDateTimePattern(dtf, patternParts, x, _a) {\n var getInternalSlots = _a.getInternalSlots, localeData = _a.localeData, getDefaultTimeZone = _a.getDefaultTimeZone, tzData = _a.tzData;\n x = ecma402_abstract_1.TimeClip(x);\n /** IMPL START */\n var internalSlots = getInternalSlots(dtf);\n var dataLocale = internalSlots.dataLocale;\n var dataLocaleData = localeData[dataLocale];\n /** IMPL END */\n var locale = internalSlots.locale;\n var nfOptions = Object.create(null);\n nfOptions.useGrouping = false;\n var nf = new Intl.NumberFormat(locale, nfOptions);\n var nf2Options = Object.create(null);\n nf2Options.minimumIntegerDigits = 2;\n nf2Options.useGrouping = false;\n var nf2 = new Intl.NumberFormat(locale, nf2Options);\n var fractionalSecondDigits = internalSlots.fractionalSecondDigits;\n var nf3;\n if (fractionalSecondDigits !== undefined) {\n var nf3Options = Object.create(null);\n nf3Options.minimumIntegerDigits = fractionalSecondDigits;\n nf3Options.useGrouping = false;\n nf3 = new Intl.NumberFormat(locale, nf3Options);\n }\n var tm = ToLocalTime_1.ToLocalTime(x, \n // @ts-ignore\n internalSlots.calendar, internalSlots.timeZone, { tzData: tzData });\n var result = [];\n for (var _i = 0, patternParts_1 = patternParts; _i < patternParts_1.length; _i++) {\n var patternPart = patternParts_1[_i];\n var p = patternPart.type;\n if (p === 'literal') {\n result.push({\n type: 'literal',\n value: patternPart.value,\n });\n }\n else if (p === 'fractionalSecondDigits') {\n var v = Math.floor(tm.millisecond * Math.pow(10, ((fractionalSecondDigits || 0) - 3)));\n result.push({\n // @ts-expect-error Spec is not there yet\n type: 'fractionalSecond',\n value: nf3.format(v),\n });\n }\n else if (p === 'dayPeriod') {\n // TODO\n }\n else if (utils_1.DATE_TIME_PROPS.indexOf(p) > -1) {\n var fv = '';\n var f = internalSlots[p];\n // @ts-ignore\n var v = tm[p];\n if (p === 'year' && v <= 0) {\n v = 1 - v;\n }\n if (p === 'month') {\n v++;\n }\n var hourCycle = internalSlots.hourCycle;\n if (p === 'hour' && (hourCycle === 'h11' || hourCycle === 'h12')) {\n v = v % 12;\n if (v === 0 && hourCycle === 'h12') {\n v = 12;\n }\n }\n if (p === 'hour' && hourCycle === 'h24') {\n if (v === 0) {\n v = 24;\n }\n }\n if (f === 'numeric') {\n fv = nf.format(v);\n }\n else if (f === '2-digit') {\n fv = nf2.format(v);\n if (fv.length > 2) {\n fv = fv.slice(fv.length - 2, fv.length);\n }\n }\n else if (f === 'narrow' || f === 'short' || f === 'long') {\n if (p === 'era') {\n fv = dataLocaleData[p][f][v];\n }\n else if (p === 'timeZoneName') {\n var timeZoneName = dataLocaleData.timeZoneName, gmtFormat = dataLocaleData.gmtFormat, hourFormat = dataLocaleData.hourFormat;\n var timeZone = internalSlots.timeZone || getDefaultTimeZone();\n var timeZoneData = timeZoneName[timeZone];\n if (timeZoneData && timeZoneData[f]) {\n fv = timeZoneData[f][+tm.inDST];\n }\n else {\n // Fallback to gmtFormat\n fv = offsetToGmtString(gmtFormat, hourFormat, tm.timeZoneOffset, f);\n }\n }\n else if (p === 'month') {\n fv = dataLocaleData.month[f][v - 1];\n }\n else {\n fv = dataLocaleData[p][f][v];\n }\n }\n result.push({\n type: p,\n value: fv,\n });\n }\n else if (p === 'ampm') {\n var v = tm.hour;\n var fv = void 0;\n if (v > 11) {\n fv = dataLocaleData.pm;\n }\n else {\n fv = dataLocaleData.am;\n }\n result.push({\n type: 'dayPeriod',\n value: fv,\n });\n }\n else if (p === 'relatedYear') {\n var v = tm.relatedYear;\n // @ts-ignore\n var fv = nf.format(v);\n result.push({\n // @ts-ignore TODO: Fix TS type\n type: 'relatedYear',\n value: fv,\n });\n }\n else if (p === 'yearName') {\n var v = tm.yearName;\n // @ts-ignore\n var fv = nf.format(v);\n result.push({\n // @ts-ignore TODO: Fix TS type\n type: 'yearName',\n value: fv,\n });\n }\n }\n return result;\n}\nexports.FormatDateTimePattern = FormatDateTimePattern;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.ToLocalTime = void 0;\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\nfunction getApplicableZoneData(t, timeZone, tzData) {\n var _a;\n var zoneData = tzData[timeZone];\n // We don't have data for this so just say it's UTC\n if (!zoneData) {\n return [0, false];\n }\n var i = 0;\n var offset = 0;\n var dst = false;\n for (; i <= zoneData.length; i++) {\n if (i === zoneData.length || zoneData[i][0] * 1e3 > t) {\n ;\n _a = zoneData[i - 1], offset = _a[2], dst = _a[3];\n break;\n }\n }\n return [offset * 1e3, dst];\n}\n/**\n * https://tc39.es/ecma402/#sec-tolocaltime\n * @param t\n * @param calendar\n * @param timeZone\n */\nfunction ToLocalTime(t, calendar, timeZone, _a) {\n var tzData = _a.tzData;\n ecma402_abstract_1.invariant(ecma402_abstract_1.Type(t) === 'Number', 'invalid time');\n ecma402_abstract_1.invariant(calendar === 'gregory', 'We only support Gregory calendar right now');\n var _b = getApplicableZoneData(t, timeZone, tzData), timeZoneOffset = _b[0], inDST = _b[1];\n var tz = t + timeZoneOffset;\n var year = ecma402_abstract_1.YearFromTime(tz);\n return {\n weekday: ecma402_abstract_1.WeekDay(tz),\n era: year < 0 ? 'BC' : 'AD',\n year: year,\n relatedYear: undefined,\n yearName: undefined,\n month: ecma402_abstract_1.MonthFromTime(tz),\n day: ecma402_abstract_1.DateFromTime(tz),\n hour: ecma402_abstract_1.HourFromTime(tz),\n minute: ecma402_abstract_1.MinFromTime(tz),\n second: ecma402_abstract_1.SecFromTime(tz),\n millisecond: ecma402_abstract_1.msFromTime(tz),\n inDST: inDST,\n // IMPORTANT: Not in spec\n timeZoneOffset: timeZoneOffset,\n };\n}\nexports.ToLocalTime = ToLocalTime;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.splitRangePattern = exports.splitFallbackRangePattern = exports.parseDateTimeSkeleton = exports.processDateTimePattern = void 0;\nvar tslib_1 = require(\"tslib\");\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\n/**\n * https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table\n * Credit: https://github.com/caridy/intl-datetimeformat-pattern/blob/master/index.js\n * with some tweaks\n */\nvar DATE_TIME_REGEX = /(?:[Eec]{1,6}|G{1,5}|[Qq]{1,5}|(?:[yYur]+|U{1,5})|[ML]{1,5}|d{1,2}|D{1,3}|F{1}|[abB]{1,5}|[hkHK]{1,2}|w{1,2}|W{1}|m{1,2}|s{1,2}|[zZOvVxX]{1,4})(?=([^']*'[^']*')*[^']*$)/g;\n// trim patterns after transformations\nvar expPatternTrimmer = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;\nfunction matchSkeletonPattern(match, result) {\n var len = match.length;\n switch (match[0]) {\n // Era\n case 'G':\n result.era = len === 4 ? 'long' : len === 5 ? 'narrow' : 'short';\n return '{era}';\n // Year\n case 'y':\n case 'Y':\n case 'u':\n case 'U':\n case 'r':\n result.year = len === 2 ? '2-digit' : 'numeric';\n return '{year}';\n // Quarter\n case 'q':\n case 'Q':\n throw new RangeError('`w/Q` (quarter) patterns are not supported');\n // Month\n case 'M':\n case 'L':\n result.month = ['numeric', '2-digit', 'short', 'long', 'narrow'][len - 1];\n return '{month}';\n // Week\n case 'w':\n case 'W':\n throw new RangeError('`w/W` (week of year) patterns are not supported');\n case 'd':\n result.day = ['numeric', '2-digit'][len - 1];\n return '{day}';\n case 'D':\n case 'F':\n case 'g':\n result.day = 'numeric';\n return '{day}';\n // Weekday\n case 'E':\n result.weekday = len === 4 ? 'long' : len === 5 ? 'narrow' : 'short';\n return '{weekday}';\n case 'e':\n result.weekday = [\n undefined,\n undefined,\n 'short',\n 'long',\n 'narrow',\n 'short',\n ][len - 1];\n return '{weekday}';\n case 'c':\n result.weekday = [\n undefined,\n undefined,\n 'short',\n 'long',\n 'narrow',\n 'short',\n ][len - 1];\n return '{weekday}';\n // Period\n case 'a': // AM, PM\n case 'b': // am, pm, noon, midnight\n case 'B': // flexible day periods\n result.hour12 = true;\n return '{ampm}';\n // Hour\n case 'h':\n result.hour = ['numeric', '2-digit'][len - 1];\n result.hour12 = true;\n return '{hour}';\n case 'H':\n result.hour = ['numeric', '2-digit'][len - 1];\n return '{hour}';\n case 'K':\n result.hour = ['numeric', '2-digit'][len - 1];\n result.hour12 = true;\n return '{hour}';\n case 'k':\n result.hour = ['numeric', '2-digit'][len - 1];\n return '{hour}';\n case 'j':\n case 'J':\n case 'C':\n throw new RangeError('`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead');\n // Minute\n case 'm':\n result.minute = ['numeric', '2-digit'][len - 1];\n return '{minute}';\n // Second\n case 's':\n result.second = ['numeric', '2-digit'][len - 1];\n return '{second}';\n case 'S':\n case 'A':\n result.second = 'numeric';\n return '{second}';\n // Zone\n case 'z': // 1..3, 4: specific non-location format\n case 'Z': // 1..3, 4, 5: The ISO8601 varios formats\n case 'O': // 1, 4: miliseconds in day short, long\n case 'v': // 1, 4: generic non-location format\n case 'V': // 1, 2, 3, 4: time zone ID or city\n case 'X': // 1, 2, 3, 4: The ISO8601 varios formats\n case 'x': // 1, 2, 3, 4: The ISO8601 varios formats\n result.timeZoneName = len < 4 ? 'short' : 'long';\n return '{timeZoneName}';\n }\n return '';\n}\nfunction skeletonTokenToTable2(c) {\n switch (c) {\n // Era\n case 'G':\n return 'era';\n // Year\n case 'y':\n case 'Y':\n case 'u':\n case 'U':\n case 'r':\n return 'year';\n // Month\n case 'M':\n case 'L':\n return 'month';\n // Day\n case 'd':\n case 'D':\n case 'F':\n case 'g':\n return 'day';\n // Period\n case 'a': // AM, PM\n case 'b': // am, pm, noon, midnight\n case 'B': // flexible day periods\n return 'ampm';\n // Hour\n case 'h':\n case 'H':\n case 'K':\n case 'k':\n return 'hour';\n // Minute\n case 'm':\n return 'minute';\n // Second\n case 's':\n case 'S':\n case 'A':\n return 'second';\n default:\n throw new RangeError('Invalid range pattern token');\n }\n}\nfunction processDateTimePattern(pattern, result) {\n var literals = [];\n // Use skeleton to populate result, but use mapped pattern to populate pattern\n var pattern12 = pattern\n // Double apostrophe\n .replace(/'{2}/g, '{apostrophe}')\n // Apostrophe-escaped\n .replace(/'(.*?)'/g, function (_, literal) {\n literals.push(literal);\n return \"$$\" + (literals.length - 1) + \"$$\";\n })\n .replace(DATE_TIME_REGEX, function (m) { return matchSkeletonPattern(m, result || {}); });\n //Restore literals\n if (literals.length) {\n pattern12 = pattern12\n .replace(/\\$\\$(\\d+)\\$\\$/g, function (_, i) {\n return literals[+i];\n })\n .replace(/\\{apostrophe\\}/g, \"'\");\n }\n // Handle apostrophe-escaped things\n return [\n pattern12\n .replace(/([\\s\\uFEFF\\xA0])\\{ampm\\}([\\s\\uFEFF\\xA0])/, '$1')\n .replace('{ampm}', '')\n .replace(expPatternTrimmer, ''),\n pattern12,\n ];\n}\nexports.processDateTimePattern = processDateTimePattern;\n/**\n * Parse Date time skeleton into Intl.DateTimeFormatOptions\n * Ref: https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table\n * @public\n * @param skeleton skeleton string\n */\nfunction parseDateTimeSkeleton(skeleton, rawPattern, rangePatterns, intervalFormatFallback) {\n if (rawPattern === void 0) { rawPattern = skeleton; }\n var result = {\n pattern: '',\n pattern12: '',\n skeleton: skeleton,\n rawPattern: rawPattern,\n rangePatterns: {},\n rangePatterns12: {},\n };\n if (rangePatterns) {\n for (var k in rangePatterns) {\n var key = skeletonTokenToTable2(k);\n var rawPattern_1 = rangePatterns[k];\n var intervalResult = {\n patternParts: [],\n };\n var _a = processDateTimePattern(rawPattern_1, intervalResult), pattern_1 = _a[0], pattern12_1 = _a[1];\n result.rangePatterns[key] = tslib_1.__assign(tslib_1.__assign({}, intervalResult), { patternParts: splitRangePattern(pattern_1) });\n result.rangePatterns12[key] = tslib_1.__assign(tslib_1.__assign({}, intervalResult), { patternParts: splitRangePattern(pattern12_1) });\n }\n }\n if (intervalFormatFallback) {\n var patternParts = splitFallbackRangePattern(intervalFormatFallback);\n result.rangePatterns.default = {\n patternParts: patternParts,\n };\n result.rangePatterns12.default = {\n patternParts: patternParts,\n };\n }\n // Process skeleton\n skeleton.replace(DATE_TIME_REGEX, function (m) { return matchSkeletonPattern(m, result); });\n var _b = processDateTimePattern(rawPattern), pattern = _b[0], pattern12 = _b[1];\n result.pattern = pattern;\n result.pattern12 = pattern12;\n return result;\n}\nexports.parseDateTimeSkeleton = parseDateTimeSkeleton;\nfunction splitFallbackRangePattern(pattern) {\n var parts = pattern.split(/(\\{[0|1]\\})/g).filter(Boolean);\n return parts.map(function (pattern) {\n switch (pattern) {\n case '{0}':\n return {\n source: ecma402_abstract_1.RangePatternType.startRange,\n pattern: pattern,\n };\n case '{1}':\n return {\n source: ecma402_abstract_1.RangePatternType.endRange,\n pattern: pattern,\n };\n default:\n return {\n source: ecma402_abstract_1.RangePatternType.shared,\n pattern: pattern,\n };\n }\n });\n}\nexports.splitFallbackRangePattern = splitFallbackRangePattern;\nfunction splitRangePattern(pattern) {\n var PART_REGEX = /\\{(.*?)\\}/g;\n // Map of part and index within the string\n var parts = {};\n var match;\n var splitIndex = 0;\n while ((match = PART_REGEX.exec(pattern))) {\n if (!(match[0] in parts)) {\n parts[match[0]] = match.index;\n }\n else {\n splitIndex = match.index;\n break;\n }\n }\n if (!splitIndex) {\n return [\n {\n source: ecma402_abstract_1.RangePatternType.startRange,\n pattern: pattern,\n },\n ];\n }\n return [\n {\n source: ecma402_abstract_1.RangePatternType.startRange,\n pattern: pattern.slice(0, splitIndex),\n },\n {\n source: ecma402_abstract_1.RangePatternType.endRange,\n pattern: pattern.slice(splitIndex),\n },\n ];\n}\nexports.splitRangePattern = splitRangePattern;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.ToDateTimeOptions = void 0;\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\n/**\n * https://tc39.es/ecma402/#sec-todatetimeoptions\n * @param options\n * @param required\n * @param defaults\n */\nfunction ToDateTimeOptions(options, required, defaults) {\n if (options === undefined) {\n options = null;\n }\n else {\n options = ecma402_abstract_1.ToObject(options);\n }\n options = Object.create(options);\n var needDefaults = true;\n if (required === 'date' || required === 'any') {\n for (var _i = 0, _a = ['weekday', 'year', 'month', 'day']; _i < _a.length; _i++) {\n var prop = _a[_i];\n var value = options[prop];\n if (value !== undefined) {\n needDefaults = false;\n }\n }\n }\n if (required === 'time' || required === 'any') {\n for (var _b = 0, _c = [\n 'dayPeriod',\n 'hour',\n 'minute',\n 'second',\n 'fractionalSecondDigits',\n ]; _b < _c.length; _b++) {\n var prop = _c[_b];\n var value = options[prop];\n if (value !== undefined) {\n needDefaults = false;\n }\n }\n }\n if (options.dateStyle !== undefined || options.timeStyle !== undefined) {\n needDefaults = false;\n }\n if (required === 'date' && options.timeStyle) {\n throw new TypeError('Intl.DateTimeFormat date was required but timeStyle was included');\n }\n if (required === 'time' && options.dateStyle) {\n throw new TypeError('Intl.DateTimeFormat time was required but dateStyle was included');\n }\n if (needDefaults && (defaults === 'date' || defaults === 'all')) {\n for (var _d = 0, _e = ['year', 'month', 'day']; _d < _e.length; _d++) {\n var prop = _e[_d];\n options[prop] = 'numeric';\n }\n }\n if (needDefaults && (defaults === 'time' || defaults === 'all')) {\n for (var _f = 0, _g = ['hour', 'minute', 'second']; _f < _g.length; _f++) {\n var prop = _g[_f];\n options[prop] = 'numeric';\n }\n }\n return options;\n}\nexports.ToDateTimeOptions = ToDateTimeOptions;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.PartitionDateTimeRangePattern = void 0;\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\nvar ToLocalTime_1 = require(\"./ToLocalTime\");\nvar FormatDateTimePattern_1 = require(\"./FormatDateTimePattern\");\nvar TABLE_2_FIELDS = [\n 'era',\n 'year',\n 'month',\n 'day',\n 'dayPeriod',\n 'ampm',\n 'hour',\n 'minute',\n 'second',\n 'fractionalSecondDigits',\n];\nfunction PartitionDateTimeRangePattern(dtf, x, y, implDetails) {\n x = ecma402_abstract_1.TimeClip(x);\n if (isNaN(x)) {\n throw new RangeError('Invalid start time');\n }\n y = ecma402_abstract_1.TimeClip(y);\n if (isNaN(y)) {\n throw new RangeError('Invalid end time');\n }\n /** IMPL START */\n var getInternalSlots = implDetails.getInternalSlots, tzData = implDetails.tzData;\n var internalSlots = getInternalSlots(dtf);\n /** IMPL END */\n var tm1 = ToLocalTime_1.ToLocalTime(x, \n // @ts-ignore\n internalSlots.calendar, internalSlots.timeZone, { tzData: tzData });\n var tm2 = ToLocalTime_1.ToLocalTime(y, \n // @ts-ignore\n internalSlots.calendar, internalSlots.timeZone, { tzData: tzData });\n var pattern = internalSlots.pattern, rangePatterns = internalSlots.rangePatterns;\n var rangePattern;\n var dateFieldsPracticallyEqual = true;\n var patternContainsLargerDateField = false;\n for (var _i = 0, TABLE_2_FIELDS_1 = TABLE_2_FIELDS; _i < TABLE_2_FIELDS_1.length; _i++) {\n var fieldName = TABLE_2_FIELDS_1[_i];\n if (dateFieldsPracticallyEqual && !patternContainsLargerDateField) {\n var rp = fieldName in rangePatterns ? rangePatterns[fieldName] : undefined;\n if (rangePattern !== undefined && rp === undefined) {\n patternContainsLargerDateField = true;\n }\n else {\n rangePattern = rp;\n if (fieldName === 'ampm') {\n var v1 = tm1.hour;\n var v2 = tm2.hour;\n if ((v1 > 11 && v2 < 11) || (v1 < 11 && v2 > 11)) {\n dateFieldsPracticallyEqual = false;\n }\n }\n else if (fieldName === 'dayPeriod') {\n // TODO\n }\n else if (fieldName === 'fractionalSecondDigits') {\n var fractionalSecondDigits = internalSlots.fractionalSecondDigits;\n if (fractionalSecondDigits === undefined) {\n fractionalSecondDigits = 3;\n }\n var v1 = Math.floor(tm1.millisecond * Math.pow(10, (fractionalSecondDigits - 3)));\n var v2 = Math.floor(tm2.millisecond * Math.pow(10, (fractionalSecondDigits - 3)));\n if (!ecma402_abstract_1.SameValue(v1, v2)) {\n dateFieldsPracticallyEqual = false;\n }\n }\n else {\n var v1 = tm1[fieldName];\n var v2 = tm2[fieldName];\n if (!ecma402_abstract_1.SameValue(v1, v2)) {\n dateFieldsPracticallyEqual = false;\n }\n }\n }\n }\n }\n if (dateFieldsPracticallyEqual) {\n var result_2 = FormatDateTimePattern_1.FormatDateTimePattern(dtf, ecma402_abstract_1.PartitionPattern(pattern), x, implDetails);\n for (var _a = 0, result_1 = result_2; _a < result_1.length; _a++) {\n var r = result_1[_a];\n r.source = ecma402_abstract_1.RangePatternType.shared;\n }\n return result_2;\n }\n var result = [];\n if (rangePattern === undefined) {\n rangePattern = rangePatterns.default;\n /** IMPL DETAILS */\n // Now we have to replace {0} & {1} with actual pattern\n for (var _b = 0, _c = rangePattern.patternParts; _b < _c.length; _b++) {\n var patternPart = _c[_b];\n if (patternPart.pattern === '{0}' || patternPart.pattern === '{1}') {\n patternPart.pattern = pattern;\n }\n }\n }\n for (var _d = 0, _e = rangePattern.patternParts; _d < _e.length; _d++) {\n var rangePatternPart = _e[_d];\n var source = rangePatternPart.source, pattern_1 = rangePatternPart.pattern;\n var z = void 0;\n if (source === ecma402_abstract_1.RangePatternType.startRange ||\n source === ecma402_abstract_1.RangePatternType.shared) {\n z = x;\n }\n else {\n z = y;\n }\n var patternParts = ecma402_abstract_1.PartitionPattern(pattern_1);\n var partResult = FormatDateTimePattern_1.FormatDateTimePattern(dtf, patternParts, z, implDetails);\n for (var _f = 0, partResult_1 = partResult; _f < partResult_1.length; _f++) {\n var r = partResult_1[_f];\n r.source = source;\n }\n result = result.concat(partResult);\n }\n return result;\n}\nexports.PartitionDateTimeRangePattern = PartitionDateTimeRangePattern;\n","/**\n * @license\n * Copyright 2011 Dan Vanderkam (danvdk@gmail.com)\n * MIT-licensed (http://opensource.org/licenses/MIT)\n */\n\n/**\n * @fileoverview Based on PlotKitLayout, but modified to meet the needs of\n * dygraphs.\n */\n\n/*global Dygraph:false */\n\"use strict\";\n\nObject.defineProperty(exports, '__esModule', {\n value: true\n});\n\nfunction _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }\n\nvar _dygraphUtils = require('./dygraph-utils');\n\nvar utils = _interopRequireWildcard(_dygraphUtils);\n\n/**\n * Creates a new DygraphLayout object.\n *\n * This class contains all the data to be charted.\n * It uses data coordinates, but also records the chart range (in data\n * coordinates) and hence is able to calculate percentage positions ('In this\n * view, Point A lies 25% down the x-axis.')\n *\n * Two things that it does not do are:\n * 1. Record pixel coordinates for anything.\n * 2. (oddly) determine anything about the layout of chart elements.\n *\n * The naming is a vestige of Dygraph's original PlotKit roots.\n *\n * @constructor\n */\nvar DygraphLayout = function DygraphLayout(dygraph) {\n this.dygraph_ = dygraph;\n /**\n * Array of points for each series.\n *\n * [series index][row index in series] = |Point| structure,\n * where series index refers to visible series only, and the\n * point index is for the reduced set of points for the current\n * zoom region (including one point just outside the window).\n * All points in the same row index share the same X value.\n *\n * @type {Array.>}\n */\n this.points = [];\n this.setNames = [];\n this.annotations = [];\n this.yAxes_ = null;\n\n // TODO(danvk): it's odd that xTicks_ and yTicks_ are inputs, but xticks and\n // yticks are outputs. Clean this up.\n this.xTicks_ = null;\n this.yTicks_ = null;\n};\n\n/**\n * Add points for a single series.\n *\n * @param {string} setname Name of the series.\n * @param {Array.} set_xy Points for the series.\n */\nDygraphLayout.prototype.addDataset = function (setname, set_xy) {\n this.points.push(set_xy);\n this.setNames.push(setname);\n};\n\n/**\n * Returns the box which the chart should be drawn in. This is the canvas's\n * box, less space needed for the axis and chart labels.\n *\n * @return {{x: number, y: number, w: number, h: number}}\n */\nDygraphLayout.prototype.getPlotArea = function () {\n return this.area_;\n};\n\n// Compute the box which the chart should be drawn in. This is the canvas's\n// box, less space needed for axis, chart labels, and other plug-ins.\n// NOTE: This should only be called by Dygraph.predraw_().\nDygraphLayout.prototype.computePlotArea = function () {\n var area = {\n // TODO(danvk): per-axis setting.\n x: 0,\n y: 0\n };\n\n area.w = this.dygraph_.width_ - area.x - this.dygraph_.getOption('rightGap');\n area.h = this.dygraph_.height_;\n\n // Let plugins reserve space.\n var e = {\n chart_div: this.dygraph_.graphDiv,\n reserveSpaceLeft: function reserveSpaceLeft(px) {\n var r = {\n x: area.x,\n y: area.y,\n w: px,\n h: area.h\n };\n area.x += px;\n area.w -= px;\n return r;\n },\n reserveSpaceRight: function reserveSpaceRight(px) {\n var r = {\n x: area.x + area.w - px,\n y: area.y,\n w: px,\n h: area.h\n };\n area.w -= px;\n return r;\n },\n reserveSpaceTop: function reserveSpaceTop(px) {\n var r = {\n x: area.x,\n y: area.y,\n w: area.w,\n h: px\n };\n area.y += px;\n area.h -= px;\n return r;\n },\n reserveSpaceBottom: function reserveSpaceBottom(px) {\n var r = {\n x: area.x,\n y: area.y + area.h - px,\n w: area.w,\n h: px\n };\n area.h -= px;\n return r;\n },\n chartRect: function chartRect() {\n return { x: area.x, y: area.y, w: area.w, h: area.h };\n }\n };\n this.dygraph_.cascadeEvents_('layout', e);\n\n this.area_ = area;\n};\n\nDygraphLayout.prototype.setAnnotations = function (ann) {\n // The Dygraph object's annotations aren't parsed. We parse them here and\n // save a copy. If there is no parser, then the user must be using raw format.\n this.annotations = [];\n var parse = this.dygraph_.getOption('xValueParser') || function (x) {\n return x;\n };\n for (var i = 0; i < ann.length; i++) {\n var a = {};\n if (!ann[i].xval && ann[i].x === undefined) {\n console.error(\"Annotations must have an 'x' property\");\n return;\n }\n if (ann[i].icon && !(ann[i].hasOwnProperty('width') && ann[i].hasOwnProperty('height'))) {\n console.error(\"Must set width and height when setting \" + \"annotation.icon property\");\n return;\n }\n utils.update(a, ann[i]);\n if (!a.xval) a.xval = parse(a.x);\n this.annotations.push(a);\n }\n};\n\nDygraphLayout.prototype.setXTicks = function (xTicks) {\n this.xTicks_ = xTicks;\n};\n\n// TODO(danvk): add this to the Dygraph object's API or move it into Layout.\nDygraphLayout.prototype.setYAxes = function (yAxes) {\n this.yAxes_ = yAxes;\n};\n\nDygraphLayout.prototype.evaluate = function () {\n this._xAxis = {};\n this._evaluateLimits();\n this._evaluateLineCharts();\n this._evaluateLineTicks();\n this._evaluateAnnotations();\n};\n\nDygraphLayout.prototype._evaluateLimits = function () {\n var xlimits = this.dygraph_.xAxisRange();\n this._xAxis.minval = xlimits[0];\n this._xAxis.maxval = xlimits[1];\n var xrange = xlimits[1] - xlimits[0];\n this._xAxis.scale = xrange !== 0 ? 1 / xrange : 1.0;\n\n if (this.dygraph_.getOptionForAxis(\"logscale\", 'x')) {\n this._xAxis.xlogrange = utils.log10(this._xAxis.maxval) - utils.log10(this._xAxis.minval);\n this._xAxis.xlogscale = this._xAxis.xlogrange !== 0 ? 1.0 / this._xAxis.xlogrange : 1.0;\n }\n for (var i = 0; i < this.yAxes_.length; i++) {\n var axis = this.yAxes_[i];\n axis.minyval = axis.computedValueRange[0];\n axis.maxyval = axis.computedValueRange[1];\n axis.yrange = axis.maxyval - axis.minyval;\n axis.yscale = axis.yrange !== 0 ? 1.0 / axis.yrange : 1.0;\n\n if (this.dygraph_.getOption(\"logscale\")) {\n axis.ylogrange = utils.log10(axis.maxyval) - utils.log10(axis.minyval);\n axis.ylogscale = axis.ylogrange !== 0 ? 1.0 / axis.ylogrange : 1.0;\n if (!isFinite(axis.ylogrange) || isNaN(axis.ylogrange)) {\n console.error('axis ' + i + ' of graph at ' + axis.g + ' can\\'t be displayed in log scale for range [' + axis.minyval + ' - ' + axis.maxyval + ']');\n }\n }\n }\n};\n\nDygraphLayout.calcXNormal_ = function (value, xAxis, logscale) {\n if (logscale) {\n return (utils.log10(value) - utils.log10(xAxis.minval)) * xAxis.xlogscale;\n } else {\n return (value - xAxis.minval) * xAxis.scale;\n }\n};\n\n/**\n * @param {DygraphAxisType} axis\n * @param {number} value\n * @param {boolean} logscale\n * @return {number}\n */\nDygraphLayout.calcYNormal_ = function (axis, value, logscale) {\n if (logscale) {\n var x = 1.0 - (utils.log10(value) - utils.log10(axis.minyval)) * axis.ylogscale;\n return isFinite(x) ? x : NaN; // shim for v8 issue; see pull request 276\n } else {\n return 1.0 - (value - axis.minyval) * axis.yscale;\n }\n};\n\nDygraphLayout.prototype._evaluateLineCharts = function () {\n var isStacked = this.dygraph_.getOption(\"stackedGraph\");\n var isLogscaleForX = this.dygraph_.getOptionForAxis(\"logscale\", 'x');\n\n for (var setIdx = 0; setIdx < this.points.length; setIdx++) {\n var points = this.points[setIdx];\n var setName = this.setNames[setIdx];\n var connectSeparated = this.dygraph_.getOption('connectSeparatedPoints', setName);\n var axis = this.dygraph_.axisPropertiesForSeries(setName);\n // TODO (konigsberg): use optionsForAxis instead.\n var logscale = this.dygraph_.attributes_.getForSeries(\"logscale\", setName);\n\n for (var j = 0; j < points.length; j++) {\n var point = points[j];\n\n // Range from 0-1 where 0 represents left and 1 represents right.\n point.x = DygraphLayout.calcXNormal_(point.xval, this._xAxis, isLogscaleForX);\n // Range from 0-1 where 0 represents top and 1 represents bottom\n var yval = point.yval;\n if (isStacked) {\n point.y_stacked = DygraphLayout.calcYNormal_(axis, point.yval_stacked, logscale);\n if (yval !== null && !isNaN(yval)) {\n yval = point.yval_stacked;\n }\n }\n if (yval === null) {\n yval = NaN;\n if (!connectSeparated) {\n point.yval = NaN;\n }\n }\n point.y = DygraphLayout.calcYNormal_(axis, yval, logscale);\n }\n\n this.dygraph_.dataHandler_.onLineEvaluated(points, axis, logscale);\n }\n};\n\nDygraphLayout.prototype._evaluateLineTicks = function () {\n var i, tick, label, pos, v, has_tick;\n this.xticks = [];\n for (i = 0; i < this.xTicks_.length; i++) {\n tick = this.xTicks_[i];\n label = tick.label;\n has_tick = !('label_v' in tick);\n v = has_tick ? tick.v : tick.label_v;\n pos = this.dygraph_.toPercentXCoord(v);\n if (pos >= 0.0 && pos < 1.0) {\n this.xticks.push({ pos: pos, label: label, has_tick: has_tick });\n }\n }\n\n this.yticks = [];\n for (i = 0; i < this.yAxes_.length; i++) {\n var axis = this.yAxes_[i];\n for (var j = 0; j < axis.ticks.length; j++) {\n tick = axis.ticks[j];\n label = tick.label;\n has_tick = !('label_v' in tick);\n v = has_tick ? tick.v : tick.label_v;\n pos = this.dygraph_.toPercentYCoord(v, i);\n if (pos > 0.0 && pos <= 1.0) {\n this.yticks.push({ axis: i, pos: pos, label: label, has_tick: has_tick });\n }\n }\n }\n};\n\nDygraphLayout.prototype._evaluateAnnotations = function () {\n // Add the annotations to the point to which they belong.\n // Make a map from (setName, xval) to annotation for quick lookups.\n var i;\n var annotations = {};\n for (i = 0; i < this.annotations.length; i++) {\n var a = this.annotations[i];\n annotations[a.xval + \",\" + a.series] = a;\n }\n\n this.annotated_points = [];\n\n // Exit the function early if there are no annotations.\n if (!this.annotations || !this.annotations.length) {\n return;\n }\n\n // TODO(antrob): loop through annotations not points.\n for (var setIdx = 0; setIdx < this.points.length; setIdx++) {\n var points = this.points[setIdx];\n for (i = 0; i < points.length; i++) {\n var p = points[i];\n var k = p.xval + \",\" + p.name;\n if (k in annotations) {\n p.annotation = annotations[k];\n this.annotated_points.push(p);\n }\n }\n }\n};\n\n/**\n * Convenience function to remove all the data sets from a graph\n */\nDygraphLayout.prototype.removeAllDatasets = function () {\n delete this.points;\n delete this.setNames;\n delete this.setPointsLengths;\n delete this.setPointsOffsets;\n this.points = [];\n this.setNames = [];\n this.setPointsLengths = [];\n this.setPointsOffsets = [];\n};\n\nexports['default'] = DygraphLayout;\nmodule.exports = exports['default'];","/**\n * @license\n * Copyright 2006 Dan Vanderkam (danvdk@gmail.com)\n * MIT-licensed (http://opensource.org/licenses/MIT)\n */\n\n/**\n * @fileoverview Based on PlotKit.CanvasRenderer, but modified to meet the\n * needs of dygraphs.\n *\n * In particular, support for:\n * - grid overlays\n * - error bars\n * - dygraphs attribute system\n */\n\n/**\n * The DygraphCanvasRenderer class does the actual rendering of the chart onto\n * a canvas. It's based on PlotKit.CanvasRenderer.\n * @param {Object} element The canvas to attach to\n * @param {Object} elementContext The 2d context of the canvas (injected so it\n * can be mocked for testing.)\n * @param {Layout} layout The DygraphLayout object for this graph.\n * @constructor\n */\n\n/*global Dygraph:false */\n\"use strict\";\n\nObject.defineProperty(exports, '__esModule', {\n value: true\n});\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nfunction _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }\n\nvar _dygraphUtils = require('./dygraph-utils');\n\nvar utils = _interopRequireWildcard(_dygraphUtils);\n\nvar _dygraph = require('./dygraph');\n\nvar _dygraph2 = _interopRequireDefault(_dygraph);\n\n/**\n * @constructor\n *\n * This gets called when there are \"new points\" to chart. This is generally the\n * case when the underlying data being charted has changed. It is _not_ called\n * in the common case that the user has zoomed or is panning the view.\n *\n * The chart canvas has already been created by the Dygraph object. The\n * renderer simply gets a drawing context.\n *\n * @param {Dygraph} dygraph The chart to which this renderer belongs.\n * @param {HTMLCanvasElement} element The <canvas> DOM element on which to draw.\n * @param {CanvasRenderingContext2D} elementContext The drawing context.\n * @param {DygraphLayout} layout The chart's DygraphLayout object.\n *\n * TODO(danvk): remove the elementContext property.\n */\nvar DygraphCanvasRenderer = function DygraphCanvasRenderer(dygraph, element, elementContext, layout) {\n this.dygraph_ = dygraph;\n\n this.layout = layout;\n this.element = element;\n this.elementContext = elementContext;\n\n this.height = dygraph.height_;\n this.width = dygraph.width_;\n\n // --- check whether everything is ok before we return\n if (!utils.isCanvasSupported(this.element)) {\n throw \"Canvas is not supported.\";\n }\n\n // internal state\n this.area = layout.getPlotArea();\n\n // Set up a clipping area for the canvas (and the interaction canvas).\n // This ensures that we don't overdraw.\n var ctx = this.dygraph_.canvas_ctx_;\n ctx.beginPath();\n ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h);\n ctx.clip();\n\n ctx = this.dygraph_.hidden_ctx_;\n ctx.beginPath();\n ctx.rect(this.area.x, this.area.y, this.area.w, this.area.h);\n ctx.clip();\n};\n\n/**\n * Clears out all chart content and DOM elements.\n * This is called immediately before render() on every frame, including\n * during zooms and pans.\n * @private\n */\nDygraphCanvasRenderer.prototype.clear = function () {\n this.elementContext.clearRect(0, 0, this.width, this.height);\n};\n\n/**\n * This method is responsible for drawing everything on the chart, including\n * lines, error bars, fills and axes.\n * It is called immediately after clear() on every frame, including during pans\n * and zooms.\n * @private\n */\nDygraphCanvasRenderer.prototype.render = function () {\n // attaches point.canvas{x,y}\n this._updatePoints();\n\n // actually draws the chart.\n this._renderLineChart();\n};\n\n/**\n * Returns a predicate to be used with an iterator, which will\n * iterate over points appropriately, depending on whether\n * connectSeparatedPoints is true. When it's false, the predicate will\n * skip over points with missing yVals.\n */\nDygraphCanvasRenderer._getIteratorPredicate = function (connectSeparatedPoints) {\n return connectSeparatedPoints ? DygraphCanvasRenderer._predicateThatSkipsEmptyPoints : null;\n};\n\nDygraphCanvasRenderer._predicateThatSkipsEmptyPoints = function (array, idx) {\n return array[idx].yval !== null;\n};\n\n/**\n * Draws a line with the styles passed in and calls all the drawPointCallbacks.\n * @param {Object} e The dictionary passed to the plotter function.\n * @private\n */\nDygraphCanvasRenderer._drawStyledLine = function (e, color, strokeWidth, strokePattern, drawPoints, drawPointCallback, pointSize) {\n var g = e.dygraph;\n // TODO(konigsberg): Compute attributes outside this method call.\n var stepPlot = g.getBooleanOption(\"stepPlot\", e.setName);\n\n if (!utils.isArrayLike(strokePattern)) {\n strokePattern = null;\n }\n\n var drawGapPoints = g.getBooleanOption('drawGapEdgePoints', e.setName);\n\n var points = e.points;\n var setName = e.setName;\n var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate(g.getBooleanOption(\"connectSeparatedPoints\", setName)));\n\n var stroking = strokePattern && strokePattern.length >= 2;\n\n var ctx = e.drawingContext;\n ctx.save();\n if (stroking) {\n if (ctx.setLineDash) ctx.setLineDash(strokePattern);\n }\n\n var pointsOnLine = DygraphCanvasRenderer._drawSeries(e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color);\n DygraphCanvasRenderer._drawPointsOnLine(e, pointsOnLine, drawPointCallback, color, pointSize);\n\n if (stroking) {\n if (ctx.setLineDash) ctx.setLineDash([]);\n }\n\n ctx.restore();\n};\n\n/**\n * This does the actual drawing of lines on the canvas, for just one series.\n * Returns a list of [canvasx, canvasy] pairs for points for which a\n * drawPointCallback should be fired. These include isolated points, or all\n * points if drawPoints=true.\n * @param {Object} e The dictionary passed to the plotter function.\n * @private\n */\nDygraphCanvasRenderer._drawSeries = function (e, iter, strokeWidth, pointSize, drawPoints, drawGapPoints, stepPlot, color) {\n\n var prevCanvasX = null;\n var prevCanvasY = null;\n var nextCanvasY = null;\n var isIsolated; // true if this point is isolated (no line segments)\n var point; // the point being processed in the while loop\n var pointsOnLine = []; // Array of [canvasx, canvasy] pairs.\n var first = true; // the first cycle through the while loop\n\n var ctx = e.drawingContext;\n ctx.beginPath();\n ctx.strokeStyle = color;\n ctx.lineWidth = strokeWidth;\n\n // NOTE: we break the iterator's encapsulation here for about a 25% speedup.\n var arr = iter.array_;\n var limit = iter.end_;\n var predicate = iter.predicate_;\n\n for (var i = iter.start_; i < limit; i++) {\n point = arr[i];\n if (predicate) {\n while (i < limit && !predicate(arr, i)) {\n i++;\n }\n if (i == limit) break;\n point = arr[i];\n }\n\n // FIXME: The 'canvasy != canvasy' test here catches NaN values but the test\n // doesn't catch Infinity values. Could change this to\n // !isFinite(point.canvasy), but I assume it avoids isNaN for performance?\n if (point.canvasy === null || point.canvasy != point.canvasy) {\n if (stepPlot && prevCanvasX !== null) {\n // Draw a horizontal line to the start of the missing data\n ctx.moveTo(prevCanvasX, prevCanvasY);\n ctx.lineTo(point.canvasx, prevCanvasY);\n }\n prevCanvasX = prevCanvasY = null;\n } else {\n isIsolated = false;\n if (drawGapPoints || prevCanvasX === null) {\n iter.nextIdx_ = i;\n iter.next();\n nextCanvasY = iter.hasNext ? iter.peek.canvasy : null;\n\n var isNextCanvasYNullOrNaN = nextCanvasY === null || nextCanvasY != nextCanvasY;\n isIsolated = prevCanvasX === null && isNextCanvasYNullOrNaN;\n if (drawGapPoints) {\n // Also consider a point to be \"isolated\" if it's adjacent to a\n // null point, excluding the graph edges.\n if (!first && prevCanvasX === null || iter.hasNext && isNextCanvasYNullOrNaN) {\n isIsolated = true;\n }\n }\n }\n\n if (prevCanvasX !== null) {\n if (strokeWidth) {\n if (stepPlot) {\n ctx.moveTo(prevCanvasX, prevCanvasY);\n ctx.lineTo(point.canvasx, prevCanvasY);\n }\n\n ctx.lineTo(point.canvasx, point.canvasy);\n }\n } else {\n ctx.moveTo(point.canvasx, point.canvasy);\n }\n if (drawPoints || isIsolated) {\n pointsOnLine.push([point.canvasx, point.canvasy, point.idx]);\n }\n prevCanvasX = point.canvasx;\n prevCanvasY = point.canvasy;\n }\n first = false;\n }\n ctx.stroke();\n return pointsOnLine;\n};\n\n/**\n * This fires the drawPointCallback functions, which draw dots on the points by\n * default. This gets used when the \"drawPoints\" option is set, or when there\n * are isolated points.\n * @param {Object} e The dictionary passed to the plotter function.\n * @private\n */\nDygraphCanvasRenderer._drawPointsOnLine = function (e, pointsOnLine, drawPointCallback, color, pointSize) {\n var ctx = e.drawingContext;\n for (var idx = 0; idx < pointsOnLine.length; idx++) {\n var cb = pointsOnLine[idx];\n ctx.save();\n drawPointCallback.call(e.dygraph, e.dygraph, e.setName, ctx, cb[0], cb[1], color, pointSize, cb[2]);\n ctx.restore();\n }\n};\n\n/**\n * Attaches canvas coordinates to the points array.\n * @private\n */\nDygraphCanvasRenderer.prototype._updatePoints = function () {\n // Update Points\n // TODO(danvk): here\n //\n // TODO(bhs): this loop is a hot-spot for high-point-count charts. These\n // transformations can be pushed into the canvas via linear transformation\n // matrices.\n // NOTE(danvk): this is trickier than it sounds at first. The transformation\n // needs to be done before the .moveTo() and .lineTo() calls, but must be\n // undone before the .stroke() call to ensure that the stroke width is\n // unaffected. An alternative is to reduce the stroke width in the\n // transformed coordinate space, but you can't specify different values for\n // each dimension (as you can with .scale()). The speedup here is ~12%.\n var sets = this.layout.points;\n for (var i = sets.length; i--;) {\n var points = sets[i];\n for (var j = points.length; j--;) {\n var point = points[j];\n point.canvasx = this.area.w * point.x + this.area.x;\n point.canvasy = this.area.h * point.y + this.area.y;\n }\n }\n};\n\n/**\n * Add canvas Actually draw the lines chart, including error bars.\n *\n * This function can only be called if DygraphLayout's points array has been\n * updated with canvas{x,y} attributes, i.e. by\n * DygraphCanvasRenderer._updatePoints.\n *\n * @param {string=} opt_seriesName when specified, only that series will\n * be drawn. (This is used for expedited redrawing with highlightSeriesOpts)\n * @param {CanvasRenderingContext2D} opt_ctx when specified, the drawing\n * context. However, lines are typically drawn on the object's\n * elementContext.\n * @private\n */\nDygraphCanvasRenderer.prototype._renderLineChart = function (opt_seriesName, opt_ctx) {\n var ctx = opt_ctx || this.elementContext;\n var i;\n\n var sets = this.layout.points;\n var setNames = this.layout.setNames;\n var setName;\n\n this.colors = this.dygraph_.colorsMap_;\n\n // Determine which series have specialized plotters.\n var plotter_attr = this.dygraph_.getOption(\"plotter\");\n var plotters = plotter_attr;\n if (!utils.isArrayLike(plotters)) {\n plotters = [plotters];\n }\n\n var setPlotters = {}; // series name -> plotter fn.\n for (i = 0; i < setNames.length; i++) {\n setName = setNames[i];\n var setPlotter = this.dygraph_.getOption(\"plotter\", setName);\n if (setPlotter == plotter_attr) continue; // not specialized.\n\n setPlotters[setName] = setPlotter;\n }\n\n for (i = 0; i < plotters.length; i++) {\n var plotter = plotters[i];\n var is_last = i == plotters.length - 1;\n\n for (var j = 0; j < sets.length; j++) {\n setName = setNames[j];\n if (opt_seriesName && setName != opt_seriesName) continue;\n\n var points = sets[j];\n\n // Only throw in the specialized plotters on the last iteration.\n var p = plotter;\n if (setName in setPlotters) {\n if (is_last) {\n p = setPlotters[setName];\n } else {\n // Don't use the standard plotters in this case.\n continue;\n }\n }\n\n var color = this.colors[setName];\n var strokeWidth = this.dygraph_.getOption(\"strokeWidth\", setName);\n\n ctx.save();\n ctx.strokeStyle = color;\n ctx.lineWidth = strokeWidth;\n p({\n points: points,\n setName: setName,\n drawingContext: ctx,\n color: color,\n strokeWidth: strokeWidth,\n dygraph: this.dygraph_,\n axis: this.dygraph_.axisPropertiesForSeries(setName),\n plotArea: this.area,\n seriesIndex: j,\n seriesCount: sets.length,\n singleSeriesName: opt_seriesName,\n allSeriesPoints: sets\n });\n ctx.restore();\n }\n }\n};\n\n/**\n * Standard plotters. These may be used by clients via Dygraph.Plotters.\n * See comments there for more details.\n */\nDygraphCanvasRenderer._Plotters = {\n linePlotter: function linePlotter(e) {\n DygraphCanvasRenderer._linePlotter(e);\n },\n\n fillPlotter: function fillPlotter(e) {\n DygraphCanvasRenderer._fillPlotter(e);\n },\n\n errorPlotter: function errorPlotter(e) {\n DygraphCanvasRenderer._errorPlotter(e);\n }\n};\n\n/**\n * Plotter which draws the central lines for a series.\n * @private\n */\nDygraphCanvasRenderer._linePlotter = function (e) {\n var g = e.dygraph;\n var setName = e.setName;\n var strokeWidth = e.strokeWidth;\n\n // TODO(danvk): Check if there's any performance impact of just calling\n // getOption() inside of _drawStyledLine. Passing in so many parameters makes\n // this code a bit nasty.\n var borderWidth = g.getNumericOption(\"strokeBorderWidth\", setName);\n var drawPointCallback = g.getOption(\"drawPointCallback\", setName) || utils.Circles.DEFAULT;\n var strokePattern = g.getOption(\"strokePattern\", setName);\n var drawPoints = g.getBooleanOption(\"drawPoints\", setName);\n var pointSize = g.getNumericOption(\"pointSize\", setName);\n\n if (borderWidth && strokeWidth) {\n DygraphCanvasRenderer._drawStyledLine(e, g.getOption(\"strokeBorderColor\", setName), strokeWidth + 2 * borderWidth, strokePattern, drawPoints, drawPointCallback, pointSize);\n }\n\n DygraphCanvasRenderer._drawStyledLine(e, e.color, strokeWidth, strokePattern, drawPoints, drawPointCallback, pointSize);\n};\n\n/**\n * Draws the shaded error bars/confidence intervals for each series.\n * This happens before the center lines are drawn, since the center lines\n * need to be drawn on top of the error bars for all series.\n * @private\n */\nDygraphCanvasRenderer._errorPlotter = function (e) {\n var g = e.dygraph;\n var setName = e.setName;\n var errorBars = g.getBooleanOption(\"errorBars\") || g.getBooleanOption(\"customBars\");\n if (!errorBars) return;\n\n var fillGraph = g.getBooleanOption(\"fillGraph\", setName);\n if (fillGraph) {\n console.warn(\"Can't use fillGraph option with error bars\");\n }\n\n var ctx = e.drawingContext;\n var color = e.color;\n var fillAlpha = g.getNumericOption('fillAlpha', setName);\n var stepPlot = g.getBooleanOption(\"stepPlot\", setName);\n var points = e.points;\n\n var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate(g.getBooleanOption(\"connectSeparatedPoints\", setName)));\n\n var newYs;\n\n // setup graphics context\n var prevX = NaN;\n var prevY = NaN;\n var prevYs = [-1, -1];\n // should be same color as the lines but only 15% opaque.\n var rgb = utils.toRGB_(color);\n var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')';\n ctx.fillStyle = err_color;\n ctx.beginPath();\n\n var isNullUndefinedOrNaN = function isNullUndefinedOrNaN(x) {\n return x === null || x === undefined || isNaN(x);\n };\n\n while (iter.hasNext) {\n var point = iter.next();\n if (!stepPlot && isNullUndefinedOrNaN(point.y) || stepPlot && !isNaN(prevY) && isNullUndefinedOrNaN(prevY)) {\n prevX = NaN;\n continue;\n }\n\n newYs = [point.y_bottom, point.y_top];\n if (stepPlot) {\n prevY = point.y;\n }\n\n // The documentation specifically disallows nulls inside the point arrays,\n // but in case it happens we should do something sensible.\n if (isNaN(newYs[0])) newYs[0] = point.y;\n if (isNaN(newYs[1])) newYs[1] = point.y;\n\n newYs[0] = e.plotArea.h * newYs[0] + e.plotArea.y;\n newYs[1] = e.plotArea.h * newYs[1] + e.plotArea.y;\n if (!isNaN(prevX)) {\n if (stepPlot) {\n ctx.moveTo(prevX, prevYs[0]);\n ctx.lineTo(point.canvasx, prevYs[0]);\n ctx.lineTo(point.canvasx, prevYs[1]);\n } else {\n ctx.moveTo(prevX, prevYs[0]);\n ctx.lineTo(point.canvasx, newYs[0]);\n ctx.lineTo(point.canvasx, newYs[1]);\n }\n ctx.lineTo(prevX, prevYs[1]);\n ctx.closePath();\n }\n prevYs = newYs;\n prevX = point.canvasx;\n }\n ctx.fill();\n};\n\n/**\n * Proxy for CanvasRenderingContext2D which drops moveTo/lineTo calls which are\n * superfluous. It accumulates all movements which haven't changed the x-value\n * and only applies the two with the most extreme y-values.\n *\n * Calls to lineTo/moveTo must have non-decreasing x-values.\n */\nDygraphCanvasRenderer._fastCanvasProxy = function (context) {\n var pendingActions = []; // array of [type, x, y] tuples\n var lastRoundedX = null;\n var lastFlushedX = null;\n\n var LINE_TO = 1,\n MOVE_TO = 2;\n\n var actionCount = 0; // number of moveTos and lineTos passed to context.\n\n // Drop superfluous motions\n // Assumes all pendingActions have the same (rounded) x-value.\n var compressActions = function compressActions(opt_losslessOnly) {\n if (pendingActions.length <= 1) return;\n\n // Lossless compression: drop inconsequential moveTos.\n for (var i = pendingActions.length - 1; i > 0; i--) {\n var action = pendingActions[i];\n if (action[0] == MOVE_TO) {\n var prevAction = pendingActions[i - 1];\n if (prevAction[1] == action[1] && prevAction[2] == action[2]) {\n pendingActions.splice(i, 1);\n }\n }\n }\n\n // Lossless compression: ... drop consecutive moveTos ...\n for (var i = 0; i < pendingActions.length - 1;) /* incremented internally */{\n var action = pendingActions[i];\n if (action[0] == MOVE_TO && pendingActions[i + 1][0] == MOVE_TO) {\n pendingActions.splice(i, 1);\n } else {\n i++;\n }\n }\n\n // Lossy compression: ... drop all but the extreme y-values ...\n if (pendingActions.length > 2 && !opt_losslessOnly) {\n // keep an initial moveTo, but drop all others.\n var startIdx = 0;\n if (pendingActions[0][0] == MOVE_TO) startIdx++;\n var minIdx = null,\n maxIdx = null;\n for (var i = startIdx; i < pendingActions.length; i++) {\n var action = pendingActions[i];\n if (action[0] != LINE_TO) continue;\n if (minIdx === null && maxIdx === null) {\n minIdx = i;\n maxIdx = i;\n } else {\n var y = action[2];\n if (y < pendingActions[minIdx][2]) {\n minIdx = i;\n } else if (y > pendingActions[maxIdx][2]) {\n maxIdx = i;\n }\n }\n }\n var minAction = pendingActions[minIdx],\n maxAction = pendingActions[maxIdx];\n pendingActions.splice(startIdx, pendingActions.length - startIdx);\n if (minIdx < maxIdx) {\n pendingActions.push(minAction);\n pendingActions.push(maxAction);\n } else if (minIdx > maxIdx) {\n pendingActions.push(maxAction);\n pendingActions.push(minAction);\n } else {\n pendingActions.push(minAction);\n }\n }\n };\n\n var flushActions = function flushActions(opt_noLossyCompression) {\n compressActions(opt_noLossyCompression);\n for (var i = 0, len = pendingActions.length; i < len; i++) {\n var action = pendingActions[i];\n if (action[0] == LINE_TO) {\n context.lineTo(action[1], action[2]);\n } else if (action[0] == MOVE_TO) {\n context.moveTo(action[1], action[2]);\n }\n }\n if (pendingActions.length) {\n lastFlushedX = pendingActions[pendingActions.length - 1][1];\n }\n actionCount += pendingActions.length;\n pendingActions = [];\n };\n\n var addAction = function addAction(action, x, y) {\n var rx = Math.round(x);\n if (lastRoundedX === null || rx != lastRoundedX) {\n // if there are large gaps on the x-axis, it's essential to keep the\n // first and last point as well.\n var hasGapOnLeft = lastRoundedX - lastFlushedX > 1,\n hasGapOnRight = rx - lastRoundedX > 1,\n hasGap = hasGapOnLeft || hasGapOnRight;\n flushActions(hasGap);\n lastRoundedX = rx;\n }\n pendingActions.push([action, x, y]);\n };\n\n return {\n moveTo: function moveTo(x, y) {\n addAction(MOVE_TO, x, y);\n },\n lineTo: function lineTo(x, y) {\n addAction(LINE_TO, x, y);\n },\n\n // for major operations like stroke/fill, we skip compression to ensure\n // that there are no artifacts at the right edge.\n stroke: function stroke() {\n flushActions(true);context.stroke();\n },\n fill: function fill() {\n flushActions(true);context.fill();\n },\n beginPath: function beginPath() {\n flushActions(true);context.beginPath();\n },\n closePath: function closePath() {\n flushActions(true);context.closePath();\n },\n\n _count: function _count() {\n return actionCount;\n }\n };\n};\n\n/**\n * Draws the shaded regions when \"fillGraph\" is set. Not to be confused with\n * error bars.\n *\n * For stacked charts, it's more convenient to handle all the series\n * simultaneously. So this plotter plots all the points on the first series\n * it's asked to draw, then ignores all the other series.\n *\n * @private\n */\nDygraphCanvasRenderer._fillPlotter = function (e) {\n // Skip if we're drawing a single series for interactive highlight overlay.\n if (e.singleSeriesName) return;\n\n // We'll handle all the series at once, not one-by-one.\n if (e.seriesIndex !== 0) return;\n\n var g = e.dygraph;\n var setNames = g.getLabels().slice(1); // remove x-axis\n\n // getLabels() includes names for invisible series, which are not included in\n // allSeriesPoints. We remove those to make the two match.\n // TODO(danvk): provide a simpler way to get this information.\n for (var i = setNames.length; i >= 0; i--) {\n if (!g.visibility()[i]) setNames.splice(i, 1);\n }\n\n var anySeriesFilled = (function () {\n for (var i = 0; i < setNames.length; i++) {\n if (g.getBooleanOption(\"fillGraph\", setNames[i])) return true;\n }\n return false;\n })();\n\n if (!anySeriesFilled) return;\n\n var area = e.plotArea;\n var sets = e.allSeriesPoints;\n var setCount = sets.length;\n\n var stackedGraph = g.getBooleanOption(\"stackedGraph\");\n var colors = g.getColors();\n\n // For stacked graphs, track the baseline for filling.\n //\n // The filled areas below graph lines are trapezoids with two\n // vertical edges. The top edge is the line segment being drawn, and\n // the baseline is the bottom edge. Each baseline corresponds to the\n // top line segment from the previous stacked line. In the case of\n // step plots, the trapezoids are rectangles.\n var baseline = {};\n var currBaseline;\n var prevStepPlot; // for different line drawing modes (line/step) per series\n\n // Helper function to trace a line back along the baseline.\n var traceBackPath = function traceBackPath(ctx, baselineX, baselineY, pathBack) {\n ctx.lineTo(baselineX, baselineY);\n if (stackedGraph) {\n for (var i = pathBack.length - 1; i >= 0; i--) {\n var pt = pathBack[i];\n ctx.lineTo(pt[0], pt[1]);\n }\n }\n };\n\n // process sets in reverse order (needed for stacked graphs)\n for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) {\n var ctx = e.drawingContext;\n var setName = setNames[setIdx];\n if (!g.getBooleanOption('fillGraph', setName)) continue;\n\n var fillAlpha = g.getNumericOption('fillAlpha', setName);\n var stepPlot = g.getBooleanOption('stepPlot', setName);\n var color = colors[setIdx];\n var axis = g.axisPropertiesForSeries(setName);\n var axisY = 1.0 + axis.minyval * axis.yscale;\n if (axisY < 0.0) axisY = 0.0;else if (axisY > 1.0) axisY = 1.0;\n axisY = area.h * axisY + area.y;\n\n var points = sets[setIdx];\n var iter = utils.createIterator(points, 0, points.length, DygraphCanvasRenderer._getIteratorPredicate(g.getBooleanOption(\"connectSeparatedPoints\", setName)));\n\n // setup graphics context\n var prevX = NaN;\n var prevYs = [-1, -1];\n var newYs;\n // should be same color as the lines but only 15% opaque.\n var rgb = utils.toRGB_(color);\n var err_color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + fillAlpha + ')';\n ctx.fillStyle = err_color;\n ctx.beginPath();\n var last_x,\n is_first = true;\n\n // If the point density is high enough, dropping segments on their way to\n // the canvas justifies the overhead of doing so.\n if (points.length > 2 * g.width_ || _dygraph2['default'].FORCE_FAST_PROXY) {\n ctx = DygraphCanvasRenderer._fastCanvasProxy(ctx);\n }\n\n // For filled charts, we draw points from left to right, then back along\n // the x-axis to complete a shape for filling.\n // For stacked plots, this \"back path\" is a more complex shape. This array\n // stores the [x, y] values needed to trace that shape.\n var pathBack = [];\n\n // TODO(danvk): there are a lot of options at play in this loop.\n // The logic would be much clearer if some (e.g. stackGraph and\n // stepPlot) were split off into separate sub-plotters.\n var point;\n while (iter.hasNext) {\n point = iter.next();\n if (!utils.isOK(point.y) && !stepPlot) {\n traceBackPath(ctx, prevX, prevYs[1], pathBack);\n pathBack = [];\n prevX = NaN;\n if (point.y_stacked !== null && !isNaN(point.y_stacked)) {\n baseline[point.canvasx] = area.h * point.y_stacked + area.y;\n }\n continue;\n }\n if (stackedGraph) {\n if (!is_first && last_x == point.xval) {\n continue;\n } else {\n is_first = false;\n last_x = point.xval;\n }\n\n currBaseline = baseline[point.canvasx];\n var lastY;\n if (currBaseline === undefined) {\n lastY = axisY;\n } else {\n if (prevStepPlot) {\n lastY = currBaseline[0];\n } else {\n lastY = currBaseline;\n }\n }\n newYs = [point.canvasy, lastY];\n\n if (stepPlot) {\n // Step plots must keep track of the top and bottom of\n // the baseline at each point.\n if (prevYs[0] === -1) {\n baseline[point.canvasx] = [point.canvasy, axisY];\n } else {\n baseline[point.canvasx] = [point.canvasy, prevYs[0]];\n }\n } else {\n baseline[point.canvasx] = point.canvasy;\n }\n } else {\n if (isNaN(point.canvasy) && stepPlot) {\n newYs = [area.y + area.h, axisY];\n } else {\n newYs = [point.canvasy, axisY];\n }\n }\n if (!isNaN(prevX)) {\n // Move to top fill point\n if (stepPlot) {\n ctx.lineTo(point.canvasx, prevYs[0]);\n ctx.lineTo(point.canvasx, newYs[0]);\n } else {\n ctx.lineTo(point.canvasx, newYs[0]);\n }\n\n // Record the baseline for the reverse path.\n if (stackedGraph) {\n pathBack.push([prevX, prevYs[1]]);\n if (prevStepPlot && currBaseline) {\n // Draw to the bottom of the baseline\n pathBack.push([point.canvasx, currBaseline[1]]);\n } else {\n pathBack.push([point.canvasx, newYs[1]]);\n }\n }\n } else {\n ctx.moveTo(point.canvasx, newYs[1]);\n ctx.lineTo(point.canvasx, newYs[0]);\n }\n prevYs = newYs;\n prevX = point.canvasx;\n }\n prevStepPlot = stepPlot;\n if (newYs && point) {\n traceBackPath(ctx, point.canvasx, newYs[1], pathBack);\n pathBack = [];\n }\n ctx.fill();\n }\n};\n\nexports['default'] = DygraphCanvasRenderer;\nmodule.exports = exports['default'];","'use strict';\n\nObject.defineProperty(exports, '__esModule', {\n value: true\n});\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nfunction _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }\n\nvar _dygraphTickers = require('./dygraph-tickers');\n\nvar DygraphTickers = _interopRequireWildcard(_dygraphTickers);\n\nvar _dygraphInteractionModel = require('./dygraph-interaction-model');\n\nvar _dygraphInteractionModel2 = _interopRequireDefault(_dygraphInteractionModel);\n\nvar _dygraphCanvas = require('./dygraph-canvas');\n\nvar _dygraphCanvas2 = _interopRequireDefault(_dygraphCanvas);\n\nvar _dygraphUtils = require('./dygraph-utils');\n\nvar utils = _interopRequireWildcard(_dygraphUtils);\n\n// Default attribute values.\nvar DEFAULT_ATTRS = {\n highlightCircleSize: 3,\n highlightSeriesOpts: null,\n highlightSeriesBackgroundAlpha: 0.5,\n highlightSeriesBackgroundColor: 'rgb(255, 255, 255)',\n\n labelsSeparateLines: false,\n labelsShowZeroValues: true,\n labelsKMB: false,\n labelsKMG2: false,\n showLabelsOnHighlight: true,\n\n digitsAfterDecimal: 2,\n maxNumberWidth: 6,\n sigFigs: null,\n\n strokeWidth: 1.0,\n strokeBorderWidth: 0,\n strokeBorderColor: \"white\",\n\n axisTickSize: 3,\n axisLabelFontSize: 14,\n rightGap: 5,\n\n showRoller: false,\n xValueParser: undefined,\n\n delimiter: ',',\n\n sigma: 2.0,\n errorBars: false,\n fractions: false,\n wilsonInterval: true, // only relevant if fractions is true\n customBars: false,\n fillGraph: false,\n fillAlpha: 0.15,\n connectSeparatedPoints: false,\n\n stackedGraph: false,\n stackedGraphNaNFill: 'all',\n hideOverlayOnMouseOut: true,\n\n legend: 'onmouseover',\n stepPlot: false,\n xRangePad: 0,\n yRangePad: null,\n drawAxesAtZero: false,\n\n // Sizes of the various chart labels.\n titleHeight: 28,\n xLabelHeight: 18,\n yLabelWidth: 18,\n\n axisLineColor: \"black\",\n axisLineWidth: 0.3,\n gridLineWidth: 0.3,\n axisLabelWidth: 50,\n gridLineColor: \"rgb(128,128,128)\",\n\n interactionModel: _dygraphInteractionModel2['default'].defaultModel,\n animatedZooms: false, // (for now)\n\n // Range selector options\n showRangeSelector: false,\n rangeSelectorHeight: 40,\n rangeSelectorPlotStrokeColor: \"#808FAB\",\n rangeSelectorPlotFillGradientColor: \"white\",\n rangeSelectorPlotFillColor: \"#A7B1C4\",\n rangeSelectorBackgroundStrokeColor: \"gray\",\n rangeSelectorBackgroundLineWidth: 1,\n rangeSelectorPlotLineWidth: 1.5,\n rangeSelectorForegroundStrokeColor: \"black\",\n rangeSelectorForegroundLineWidth: 1,\n rangeSelectorAlpha: 0.6,\n showInRangeSelector: null,\n\n // The ordering here ensures that central lines always appear above any\n // fill bars/error bars.\n plotter: [_dygraphCanvas2['default']._fillPlotter, _dygraphCanvas2['default']._errorPlotter, _dygraphCanvas2['default']._linePlotter],\n\n plugins: [],\n\n // per-axis options\n axes: {\n x: {\n pixelsPerLabel: 70,\n axisLabelWidth: 60,\n axisLabelFormatter: utils.dateAxisLabelFormatter,\n valueFormatter: utils.dateValueFormatter,\n drawGrid: true,\n drawAxis: true,\n independentTicks: true,\n ticker: DygraphTickers.dateTicker\n },\n y: {\n axisLabelWidth: 50,\n pixelsPerLabel: 30,\n valueFormatter: utils.numberValueFormatter,\n axisLabelFormatter: utils.numberAxisLabelFormatter,\n drawGrid: true,\n drawAxis: true,\n independentTicks: true,\n ticker: DygraphTickers.numericTicks\n },\n y2: {\n axisLabelWidth: 50,\n pixelsPerLabel: 30,\n valueFormatter: utils.numberValueFormatter,\n axisLabelFormatter: utils.numberAxisLabelFormatter,\n drawAxis: true, // only applies when there are two axes of data.\n drawGrid: false,\n independentTicks: false,\n ticker: DygraphTickers.numericTicks\n }\n }\n};\n\nexports['default'] = DEFAULT_ATTRS;\nmodule.exports = exports['default'];","/**\n * @license\n * Copyright 2011 Dan Vanderkam (danvdk@gmail.com)\n * MIT-licensed (http://opensource.org/licenses/MIT)\n */\n\n\"use strict\";\n\nObject.defineProperty(exports, '__esModule', {\n value: true\n});\nvar OPTIONS_REFERENCE = null;\n\n// For \"production\" code, this gets removed by uglifyjs.\nif (typeof process !== 'undefined') {\n if (process.env.NODE_ENV != 'production') {\n\n // NOTE: in addition to parsing as JS, this snippet is expected to be valid\n // JSON. This assumption cannot be checked in JS, but it will be checked when\n // documentation is generated by the generate-documentation.py script. For the\n // most part, this just means that you should always use double quotes.\n OPTIONS_REFERENCE = // \n {\n \"xValueParser\": {\n \"default\": \"parseFloat() or Date.parse()*\",\n \"labels\": [\"CSV parsing\"],\n \"type\": \"function(str) -> number\",\n \"description\": \"A function which parses x-values (i.e. the dependent series). Must return a number, even when the values are dates. In this case, millis since epoch are used. This is used primarily for parsing CSV data. *=Dygraphs is slightly more accepting in the dates which it will parse. See code for details.\"\n },\n \"stackedGraph\": {\n \"default\": \"false\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"boolean\",\n \"description\": \"If set, stack series on top of one another rather than drawing them independently. The first series specified in the input data will wind up on top of the chart and the last will be on bottom. NaN values are drawn as white areas without a line on top, see stackedGraphNaNFill for details.\"\n },\n \"stackedGraphNaNFill\": {\n \"default\": \"all\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"string\",\n \"description\": \"Controls handling of NaN values inside a stacked graph. NaN values are interpolated/extended for stacking purposes, but the actual point value remains NaN in the legend display. Valid option values are \\\"all\\\" (interpolate internally, repeat leftmost and rightmost value as needed), \\\"inside\\\" (interpolate internally only, use zero outside leftmost and rightmost value), and \\\"none\\\" (treat NaN as zero everywhere).\"\n },\n \"pointSize\": {\n \"default\": \"1\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"integer\",\n \"description\": \"The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is \\\"isolated\\\", i.e. there is a missing point on either side of it. This also controls the size of those dots.\"\n },\n \"drawPoints\": {\n \"default\": \"false\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"boolean\",\n \"description\": \"Draw a small dot at each point, in addition to a line going through the point. This makes the individual data points easier to see, but can increase visual clutter in the chart. The small dot can be replaced with a custom rendering by supplying a drawPointCallback.\"\n },\n \"drawGapEdgePoints\": {\n \"default\": \"false\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"boolean\",\n \"description\": \"Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.\"\n },\n \"drawPointCallback\": {\n \"default\": \"null\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"function(g, seriesName, canvasContext, cx, cy, color, pointSize)\",\n \"parameters\": [[\"g\", \"the reference graph\"], [\"seriesName\", \"the name of the series\"], [\"canvasContext\", \"the canvas to draw on\"], [\"cx\", \"center x coordinate\"], [\"cy\", \"center y coordinate\"], [\"color\", \"series color\"], [\"pointSize\", \"the radius of the image.\"], [\"idx\", \"the row-index of the point in the data.\"]],\n \"description\": \"Draw a custom item when drawPoints is enabled. Default is a small dot matching the series color. This method should constrain drawing to within pointSize pixels from (cx, cy). Also see drawHighlightPointCallback\"\n },\n \"height\": {\n \"default\": \"320\",\n \"labels\": [\"Overall display\"],\n \"type\": \"integer\",\n \"description\": \"Height, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored.\"\n },\n \"zoomCallback\": {\n \"default\": \"null\",\n \"labels\": [\"Callbacks\"],\n \"type\": \"function(minDate, maxDate, yRanges)\",\n \"parameters\": [[\"minDate\", \"milliseconds since epoch\"], [\"maxDate\", \"milliseconds since epoch.\"], [\"yRanges\", \"is an array of [bottom, top] pairs, one for each y-axis.\"]],\n \"description\": \"A function to call when the zoom window is changed (either by zooming in or out). When animatedZooms is set, zoomCallback is called once at the end of the transition (it will not be called for intermediate frames).\"\n },\n \"pointClickCallback\": {\n \"snippet\": \"function(e, point){
  alert(point);
}\",\n \"default\": \"null\",\n \"labels\": [\"Callbacks\", \"Interactive Elements\"],\n \"type\": \"function(e, point)\",\n \"parameters\": [[\"e\", \"the event object for the click\"], [\"point\", \"the point that was clicked See Point properties for details\"]],\n \"description\": \"A function to call when a data point is clicked. and the point that was clicked.\"\n },\n \"color\": {\n \"default\": \"(see description)\",\n \"labels\": [\"Data Series Colors\"],\n \"type\": \"string\",\n \"example\": \"red\",\n \"description\": \"A per-series color definition. Used in conjunction with, and overrides, the colors option.\"\n },\n \"colors\": {\n \"default\": \"(see description)\",\n \"labels\": [\"Data Series Colors\"],\n \"type\": \"array\",\n \"example\": \"['red', '#00FF00']\",\n \"description\": \"List of colors for the data series. These can be of the form \\\"#AABBCC\\\" or \\\"rgb(255,100,200)\\\" or \\\"yellow\\\", etc. If not specified, equally-spaced points around a color wheel are used. Overridden by the 'color' option.\"\n },\n \"connectSeparatedPoints\": {\n \"default\": \"false\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"boolean\",\n \"description\": \"Usually, when Dygraphs encounters a missing value in a data series, it interprets this as a gap and draws it as such. If, instead, the missing values represents an x-value for which only a different series has data, then you'll want to connect the dots by setting this to true. To explicitly include a gap with this option set, use a value of NaN.\"\n },\n \"highlightCallback\": {\n \"default\": \"null\",\n \"labels\": [\"Callbacks\"],\n \"type\": \"function(event, x, points, row, seriesName)\",\n \"description\": \"When set, this callback gets called every time a new point is highlighted.\",\n \"parameters\": [[\"event\", \"the JavaScript mousemove event\"], [\"x\", \"the x-coordinate of the highlighted points\"], [\"points\", \"an array of highlighted points: [ {name: 'series', yval: y-value}, … ]\"], [\"row\", \"integer index of the highlighted row in the data table, starting from 0\"], [\"seriesName\", \"name of the highlighted series, only present if highlightSeriesOpts is set.\"]]\n },\n \"drawHighlightPointCallback\": {\n \"default\": \"null\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"function(g, seriesName, canvasContext, cx, cy, color, pointSize)\",\n \"parameters\": [[\"g\", \"the reference graph\"], [\"seriesName\", \"the name of the series\"], [\"canvasContext\", \"the canvas to draw on\"], [\"cx\", \"center x coordinate\"], [\"cy\", \"center y coordinate\"], [\"color\", \"series color\"], [\"pointSize\", \"the radius of the image.\"], [\"idx\", \"the row-index of the point in the data.\"]],\n \"description\": \"Draw a custom item when a point is highlighted. Default is a small dot matching the series color. This method should constrain drawing to within pointSize pixels from (cx, cy) Also see drawPointCallback\"\n },\n \"highlightSeriesOpts\": {\n \"default\": \"null\",\n \"labels\": [\"Interactive Elements\"],\n \"type\": \"Object\",\n \"description\": \"When set, the options from this object are applied to the timeseries closest to the mouse pointer for interactive highlighting. See also 'highlightCallback'. Example: highlightSeriesOpts: { strokeWidth: 3 }.\"\n },\n \"highlightSeriesBackgroundAlpha\": {\n \"default\": \"0.5\",\n \"labels\": [\"Interactive Elements\"],\n \"type\": \"float\",\n \"description\": \"Fade the background while highlighting series. 1=fully visible background (disable fading), 0=hiddden background (show highlighted series only).\"\n },\n \"highlightSeriesBackgroundColor\": {\n \"default\": \"rgb(255, 255, 255)\",\n \"labels\": [\"Interactive Elements\"],\n \"type\": \"string\",\n \"description\": \"Sets the background color used to fade out the series in conjunction with 'highlightSeriesBackgroundAlpha'.\"\n },\n \"includeZero\": {\n \"default\": \"false\",\n \"labels\": [\"Axis display\"],\n \"type\": \"boolean\",\n \"description\": \"Usually, dygraphs will use the range of the data plus some padding to set the range of the y-axis. If this option is set, the y-axis will always include zero, typically as the lowest value. This can be used to avoid exaggerating the variance in the data\"\n },\n \"rollPeriod\": {\n \"default\": \"1\",\n \"labels\": [\"Error Bars\", \"Rolling Averages\"],\n \"type\": \"integer >= 1\",\n \"description\": \"Number of days over which to average data. Discussed extensively above.\"\n },\n \"unhighlightCallback\": {\n \"default\": \"null\",\n \"labels\": [\"Callbacks\"],\n \"type\": \"function(event)\",\n \"parameters\": [[\"event\", \"the mouse event\"]],\n \"description\": \"When set, this callback gets called every time the user stops highlighting any point by mousing out of the graph.\"\n },\n \"axisTickSize\": {\n \"default\": \"3.0\",\n \"labels\": [\"Axis display\"],\n \"type\": \"number\",\n \"description\": \"The size of the line to display next to each tick mark on x- or y-axes.\"\n },\n \"labelsSeparateLines\": {\n \"default\": \"false\",\n \"labels\": [\"Legend\"],\n \"type\": \"boolean\",\n \"description\": \"Put <br/> between lines in the label string. Often used in conjunction with labelsDiv.\"\n },\n \"valueFormatter\": {\n \"default\": \"Depends on the type of your data.\",\n \"labels\": [\"Legend\", \"Value display/formatting\"],\n \"type\": \"function(num or millis, opts, seriesName, dygraph, row, col)\",\n \"description\": \"Function to provide a custom display format for the values displayed on mouseover. This does not affect the values that appear on tick marks next to the axes. To format those, see axisLabelFormatter. This is usually set on a per-axis basis. .\",\n \"parameters\": [[\"num_or_millis\", \"The value to be formatted. This is always a number. For date axes, it's millis since epoch. You can call new Date(millis) to get a Date object.\"], [\"opts\", \"This is a function you can call to access various options (e.g. opts('labelsKMB')). It returns per-axis values for the option when available.\"], [\"seriesName\", \"The name of the series from which the point came, e.g. 'X', 'Y', 'A', etc.\"], [\"dygraph\", \"The dygraph object for which the formatting is being done\"], [\"row\", \"The row of the data from which this point comes. g.getValue(row, 0) will return the x-value for this point.\"], [\"col\", \"The column of the data from which this point comes. g.getValue(row, col) will return the original y-value for this point. This can be used to get the full confidence interval for the point, or access un-rolled values for the point.\"]]\n },\n \"annotationMouseOverHandler\": {\n \"default\": \"null\",\n \"labels\": [\"Annotations\"],\n \"type\": \"function(annotation, point, dygraph, event)\",\n \"description\": \"If provided, this function is called whenever the user mouses over an annotation.\"\n },\n \"annotationMouseOutHandler\": {\n \"default\": \"null\",\n \"labels\": [\"Annotations\"],\n \"type\": \"function(annotation, point, dygraph, event)\",\n \"parameters\": [[\"annotation\", \"the annotation left\"], [\"point\", \"the point associated with the annotation\"], [\"dygraph\", \"the reference graph\"], [\"event\", \"the mouse event\"]],\n \"description\": \"If provided, this function is called whenever the user mouses out of an annotation.\"\n },\n \"annotationClickHandler\": {\n \"default\": \"null\",\n \"labels\": [\"Annotations\"],\n \"type\": \"function(annotation, point, dygraph, event)\",\n \"parameters\": [[\"annotation\", \"the annotation left\"], [\"point\", \"the point associated with the annotation\"], [\"dygraph\", \"the reference graph\"], [\"event\", \"the mouse event\"]],\n \"description\": \"If provided, this function is called whenever the user clicks on an annotation.\"\n },\n \"annotationDblClickHandler\": {\n \"default\": \"null\",\n \"labels\": [\"Annotations\"],\n \"type\": \"function(annotation, point, dygraph, event)\",\n \"parameters\": [[\"annotation\", \"the annotation left\"], [\"point\", \"the point associated with the annotation\"], [\"dygraph\", \"the reference graph\"], [\"event\", \"the mouse event\"]],\n \"description\": \"If provided, this function is called whenever the user double-clicks on an annotation.\"\n },\n \"drawCallback\": {\n \"default\": \"null\",\n \"labels\": [\"Callbacks\"],\n \"type\": \"function(dygraph, is_initial)\",\n \"parameters\": [[\"dygraph\", \"The graph being drawn\"], [\"is_initial\", \"True if this is the initial draw, false for subsequent draws.\"]],\n \"description\": \"When set, this callback gets called every time the dygraph is drawn. This includes the initial draw, after zooming and repeatedly while panning.\"\n },\n \"labelsKMG2\": {\n \"default\": \"false\",\n \"labels\": [\"Value display/formatting\"],\n \"type\": \"boolean\",\n \"description\": \"Show k/M/G for kilo/Mega/Giga on y-axis. This is different than labelsKMB in that it uses base 2, not 10.\"\n },\n \"delimiter\": {\n \"default\": \",\",\n \"labels\": [\"CSV parsing\"],\n \"type\": \"string\",\n \"description\": \"The delimiter to look for when separating fields of a CSV file. Setting this to a tab is not usually necessary, since tab-delimited data is auto-detected.\"\n },\n \"axisLabelFontSize\": {\n \"default\": \"14\",\n \"labels\": [\"Axis display\"],\n \"type\": \"integer\",\n \"description\": \"Size of the font (in pixels) to use in the axis labels, both x- and y-axis.\"\n },\n \"underlayCallback\": {\n \"default\": \"null\",\n \"labels\": [\"Callbacks\"],\n \"type\": \"function(context, area, dygraph)\",\n \"parameters\": [[\"context\", \"the canvas drawing context on which to draw\"], [\"area\", \"An object with {x,y,w,h} properties describing the drawing area.\"], [\"dygraph\", \"the reference graph\"]],\n \"description\": \"When set, this callback gets called before the chart is drawn. It details on how to use this.\"\n },\n \"width\": {\n \"default\": \"480\",\n \"labels\": [\"Overall display\"],\n \"type\": \"integer\",\n \"description\": \"Width, in pixels, of the chart. If the container div has been explicitly sized, this will be ignored.\"\n },\n \"pixelRatio\": {\n \"default\": \"(devicePixelRatio / context.backingStoreRatio)\",\n \"labels\": [\"Overall display\"],\n \"type\": \"float\",\n \"description\": \"Overrides the pixel ratio scaling factor for the canvas's 2d context. Ordinarily, this is set to the devicePixelRatio / (context.backingStoreRatio || 1), so on mobile devices, where the devicePixelRatio can be somewhere around 3, performance can be improved by overriding this value to something less precise, like 1, at the expense of resolution.\"\n },\n \"interactionModel\": {\n \"default\": \"...\",\n \"labels\": [\"Interactive Elements\"],\n \"type\": \"Object\",\n \"description\": \"TODO(konigsberg): document this\"\n },\n \"ticker\": {\n \"default\": \"Dygraph.dateTicker or Dygraph.numericTicks\",\n \"labels\": [\"Axis display\"],\n \"type\": \"function(min, max, pixels, opts, dygraph, vals) -> [{v: ..., label: ...}, ...]\",\n \"parameters\": [[\"min\", \"\"], [\"max\", \"\"], [\"pixels\", \"\"], [\"opts\", \"\"], [\"dygraph\", \"the reference graph\"], [\"vals\", \"\"]],\n \"description\": \"This lets you specify an arbitrary function to generate tick marks on an axis. The tick marks are an array of (value, label) pairs. The built-in functions go to great lengths to choose good tick marks so, if you set this option, you'll most likely want to call one of them and modify the result. See dygraph-tickers.js for an extensive discussion. This is set on a per-axis basis.\"\n },\n \"xAxisHeight\": {\n \"default\": \"(null)\",\n \"labels\": [\"Axis display\"],\n \"type\": \"integer\",\n \"description\": \"Height, in pixels, of the x-axis. If not set explicitly, this is computed based on axisLabelFontSize and axisTickSize.\"\n },\n \"showLabelsOnHighlight\": {\n \"default\": \"true\",\n \"labels\": [\"Interactive Elements\", \"Legend\"],\n \"type\": \"boolean\",\n \"description\": \"Whether to show the legend upon mouseover.\"\n },\n \"axis\": {\n \"default\": \"(none)\",\n \"labels\": [\"Axis display\"],\n \"type\": \"string\",\n \"description\": \"Set to either 'y1' or 'y2' to assign a series to a y-axis (primary or secondary). Must be set per-series.\"\n },\n \"pixelsPerLabel\": {\n \"default\": \"70 (x-axis) or 30 (y-axes)\",\n \"labels\": [\"Axis display\", \"Grid\"],\n \"type\": \"integer\",\n \"description\": \"Number of pixels to require between each x- and y-label. Larger values will yield a sparser axis with fewer ticks. This is set on a per-axis basis.\"\n },\n \"labelsDiv\": {\n \"default\": \"null\",\n \"labels\": [\"Legend\"],\n \"type\": \"DOM element or string\",\n \"example\": \"document.getElementById('foo')or'foo'\",\n \"description\": \"Show data labels in an external div, rather than on the graph. This value can either be a div element or a div id.\"\n },\n \"fractions\": {\n \"default\": \"false\",\n \"labels\": [\"CSV parsing\", \"Error Bars\"],\n \"type\": \"boolean\",\n \"description\": \"When set, attempt to parse each cell in the CSV file as \\\"a/b\\\", where a and b are integers. The ratio will be plotted. This allows computation of Wilson confidence intervals (see below).\"\n },\n \"logscale\": {\n \"default\": \"false\",\n \"labels\": [\"Axis display\"],\n \"type\": \"boolean\",\n \"description\": \"When set for the y-axis or x-axis, the graph shows that axis in log scale. Any values less than or equal to zero are not displayed. Showing log scale with ranges that go below zero will result in an unviewable graph.\\n\\n Not compatible with showZero. connectSeparatedPoints is ignored. This is ignored for date-based x-axes.\"\n },\n \"strokeWidth\": {\n \"default\": \"1.0\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"float\",\n \"example\": \"0.5, 2.0\",\n \"description\": \"The width of the lines connecting data points. This can be used to increase the contrast or some graphs.\"\n },\n \"strokePattern\": {\n \"default\": \"null\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"array\",\n \"example\": \"[10, 2, 5, 2]\",\n \"description\": \"A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array. This is used to create dashed lines.\"\n },\n \"strokeBorderWidth\": {\n \"default\": \"null\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"float\",\n \"example\": \"1.0\",\n \"description\": \"Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.\"\n },\n \"strokeBorderColor\": {\n \"default\": \"white\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"string\",\n \"example\": \"red, #ccffdd\",\n \"description\": \"Color for the line border used if strokeBorderWidth is set.\"\n },\n \"wilsonInterval\": {\n \"default\": \"true\",\n \"labels\": [\"Error Bars\"],\n \"type\": \"boolean\",\n \"description\": \"Use in conjunction with the \\\"fractions\\\" option. Instead of plotting +/- N standard deviations, dygraphs will compute a Wilson confidence interval and plot that. This has more reasonable behavior for ratios close to 0 or 1.\"\n },\n \"fillGraph\": {\n \"default\": \"false\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"boolean\",\n \"description\": \"Should the area underneath the graph be filled? This option is not compatible with error bars. This may be set on a per-series basis.\"\n },\n \"highlightCircleSize\": {\n \"default\": \"3\",\n \"labels\": [\"Interactive Elements\"],\n \"type\": \"integer\",\n \"description\": \"The size in pixels of the dot drawn over highlighted points.\"\n },\n \"gridLineColor\": {\n \"default\": \"rgb(128,128,128)\",\n \"labels\": [\"Grid\"],\n \"type\": \"red, blue\",\n \"description\": \"The color of the gridlines. This may be set on a per-axis basis to define each axis' grid separately.\"\n },\n \"gridLinePattern\": {\n \"default\": \"null\",\n \"labels\": [\"Grid\"],\n \"type\": \"array\",\n \"example\": \"[10, 2, 5, 2]\",\n \"description\": \"A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array. This is used to create dashed gridlines.\"\n },\n \"visibility\": {\n \"default\": \"[true, true, ...]\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"Array of booleans\",\n \"description\": \"Which series should initially be visible? Once the Dygraph has been constructed, you can access and modify the visibility of each series using the visibility and setVisibility methods.\"\n },\n \"valueRange\": {\n \"default\": \"Full range of the input is shown\",\n \"labels\": [\"Axis display\"],\n \"type\": \"Array of two numbers\",\n \"example\": \"[10, 110]\",\n \"description\": \"Explicitly set the vertical range of the graph to [low, high]. This may be set on a per-axis basis to define each y-axis separately. If either limit is unspecified, it will be calculated automatically (e.g. [null, 30] to automatically calculate just the lower bound)\"\n },\n \"colorSaturation\": {\n \"default\": \"1.0\",\n \"labels\": [\"Data Series Colors\"],\n \"type\": \"float (0.0 - 1.0)\",\n \"description\": \"If colors is not specified, saturation of the automatically-generated data series colors.\"\n },\n \"hideOverlayOnMouseOut\": {\n \"default\": \"true\",\n \"labels\": [\"Interactive Elements\", \"Legend\"],\n \"type\": \"boolean\",\n \"description\": \"Whether to hide the legend when the mouse leaves the chart area.\"\n },\n \"legend\": {\n \"default\": \"onmouseover\",\n \"labels\": [\"Legend\"],\n \"type\": \"string\",\n \"description\": \"When to display the legend. By default, it only appears when a user mouses over the chart. Set it to \\\"always\\\" to always display a legend of some sort. When set to \\\"follow\\\", legend follows highlighted points.\"\n },\n \"legendFormatter\": {\n \"default\": \"null\",\n \"labels\": [\"Legend\"],\n \"type\": \"function(data): string\",\n \"params\": [[\"data\", \"An object containing information about the selection (or lack of a selection). This includes formatted values and series information. See here for sample values.\"]],\n \"description\": \"Set this to supply a custom formatter for the legend. See this comment and the legendFormatter demo for usage.\"\n },\n \"labelsShowZeroValues\": {\n \"default\": \"true\",\n \"labels\": [\"Legend\"],\n \"type\": \"boolean\",\n \"description\": \"Show zero value labels in the labelsDiv.\"\n },\n \"stepPlot\": {\n \"default\": \"false\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"boolean\",\n \"description\": \"When set, display the graph as a step plot instead of a line plot. This option may either be set for the whole graph or for single series.\"\n },\n \"labelsUTC\": {\n \"default\": \"false\",\n \"labels\": [\"Value display/formatting\", \"Axis display\"],\n \"type\": \"boolean\",\n \"description\": \"Show date/time labels according to UTC (instead of local time).\"\n },\n \"labelsKMB\": {\n \"default\": \"false\",\n \"labels\": [\"Value display/formatting\"],\n \"type\": \"boolean\",\n \"description\": \"Show K/M/B for thousands/millions/billions on y-axis.\"\n },\n \"rightGap\": {\n \"default\": \"5\",\n \"labels\": [\"Overall display\"],\n \"type\": \"integer\",\n \"description\": \"Number of pixels to leave blank at the right edge of the Dygraph. This makes it easier to highlight the right-most data point.\"\n },\n \"drawAxesAtZero\": {\n \"default\": \"false\",\n \"labels\": [\"Axis display\"],\n \"type\": \"boolean\",\n \"description\": \"When set, draw the X axis at the Y=0 position and the Y axis at the X=0 position if those positions are inside the graph's visible area. Otherwise, draw the axes at the bottom or left graph edge as usual.\"\n },\n \"xRangePad\": {\n \"default\": \"0\",\n \"labels\": [\"Axis display\"],\n \"type\": \"float\",\n \"description\": \"Add the specified amount of extra space (in pixels) around the X-axis value range to ensure points at the edges remain visible.\"\n },\n \"yRangePad\": {\n \"default\": \"null\",\n \"labels\": [\"Axis display\"],\n \"type\": \"float\",\n \"description\": \"If set, add the specified amount of extra space (in pixels) around the Y-axis value range to ensure points at the edges remain visible. If unset, use the traditional Y padding algorithm.\"\n },\n \"axisLabelFormatter\": {\n \"default\": \"Depends on the data type\",\n \"labels\": [\"Axis display\"],\n \"type\": \"function(number or Date, granularity, opts, dygraph)\",\n \"parameters\": [[\"number or date\", \"Either a number (for a numeric axis) or a Date object (for a date axis)\"], [\"granularity\", \"specifies how fine-grained the axis is. For date axes, this is a reference to the time granularity enumeration, defined in dygraph-tickers.js, e.g. Dygraph.WEEKLY.\"], [\"opts\", \"a function which provides access to various options on the dygraph, e.g. opts('labelsKMB').\"], [\"dygraph\", \"the referenced graph\"]],\n \"description\": \"Function to call to format the tick values that appear along an axis. This is usually set on a per-axis basis.\"\n },\n \"clickCallback\": {\n \"snippet\": \"function(e, date_millis){
  alert(new Date(date_millis));
}\",\n \"default\": \"null\",\n \"labels\": [\"Callbacks\"],\n \"type\": \"function(e, x, points)\",\n \"parameters\": [[\"e\", \"The event object for the click\"], [\"x\", \"The x value that was clicked (for dates, this is milliseconds since epoch)\"], [\"points\", \"The closest points along that date. See Point properties for details.\"]],\n \"description\": \"A function to call when the canvas is clicked.\"\n },\n \"labels\": {\n \"default\": \"[\\\"X\\\", \\\"Y1\\\", \\\"Y2\\\", ...]*\",\n \"labels\": [\"Legend\"],\n \"type\": \"array\",\n \"description\": \"A name for each data series, including the independent (X) series. For CSV files and DataTable objections, this is determined by context. For raw data, this must be specified. If it is not, default values are supplied and a warning is logged.\"\n },\n \"dateWindow\": {\n \"default\": \"Full range of the input is shown\",\n \"labels\": [\"Axis display\"],\n \"type\": \"Array of two numbers\",\n \"example\": \"[
  Date.parse('2006-01-01'),
  (new Date()).valueOf()
]\",\n \"description\": \"Initially zoom in on a section of the graph. Is of the form [earliest, latest], where earliest/latest are milliseconds since epoch. If the data for the x-axis is numeric, the values in dateWindow must also be numbers.\"\n },\n \"showRoller\": {\n \"default\": \"false\",\n \"labels\": [\"Interactive Elements\", \"Rolling Averages\"],\n \"type\": \"boolean\",\n \"description\": \"If the rolling average period text box should be shown.\"\n },\n \"sigma\": {\n \"default\": \"2.0\",\n \"labels\": [\"Error Bars\"],\n \"type\": \"float\",\n \"description\": \"When errorBars is set, shade this many standard deviations above/below each point.\"\n },\n \"customBars\": {\n \"default\": \"false\",\n \"labels\": [\"CSV parsing\", \"Error Bars\"],\n \"type\": \"boolean\",\n \"description\": \"When set, parse each CSV cell as \\\"low;middle;high\\\". Error bars will be drawn for each point between low and high, with the series itself going through middle.\"\n },\n \"colorValue\": {\n \"default\": \"1.0\",\n \"labels\": [\"Data Series Colors\"],\n \"type\": \"float (0.0 - 1.0)\",\n \"description\": \"If colors is not specified, value of the data series colors, as in hue/saturation/value. (0.0-1.0, default 0.5)\"\n },\n \"errorBars\": {\n \"default\": \"false\",\n \"labels\": [\"CSV parsing\", \"Error Bars\"],\n \"type\": \"boolean\",\n \"description\": \"Does the data contain standard deviations? Setting this to true alters the input format (see above).\"\n },\n \"displayAnnotations\": {\n \"default\": \"false\",\n \"labels\": [\"Annotations\"],\n \"type\": \"boolean\",\n \"description\": \"Only applies when Dygraphs is used as a GViz chart. Causes string columns following a data series to be interpreted as annotations on points in that series. This is the same format used by Google's AnnotatedTimeLine chart.\"\n },\n \"panEdgeFraction\": {\n \"default\": \"null\",\n \"labels\": [\"Axis display\", \"Interactive Elements\"],\n \"type\": \"float\",\n \"description\": \"A value representing the farthest a graph may be panned, in percent of the display. For example, a value of 0.1 means that the graph can only be panned 10% passed the edges of the displayed values. null means no bounds.\"\n },\n \"title\": {\n \"labels\": [\"Chart labels\"],\n \"type\": \"string\",\n \"default\": \"null\",\n \"description\": \"Text to display above the chart. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-title' classes.\"\n },\n \"titleHeight\": {\n \"default\": \"18\",\n \"labels\": [\"Chart labels\"],\n \"type\": \"integer\",\n \"description\": \"Height of the chart title, in pixels. This also controls the default font size of the title. If you style the title on your own, this controls how much space is set aside above the chart for the title's div.\"\n },\n \"xlabel\": {\n \"labels\": [\"Chart labels\"],\n \"type\": \"string\",\n \"default\": \"null\",\n \"description\": \"Text to display below the chart's x-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-xlabel' classes.\"\n },\n \"xLabelHeight\": {\n \"labels\": [\"Chart labels\"],\n \"type\": \"integer\",\n \"default\": \"18\",\n \"description\": \"Height of the x-axis label, in pixels. This also controls the default font size of the x-axis label. If you style the label on your own, this controls how much space is set aside below the chart for the x-axis label's div.\"\n },\n \"ylabel\": {\n \"labels\": [\"Chart labels\"],\n \"type\": \"string\",\n \"default\": \"null\",\n \"description\": \"Text to display to the left of the chart's y-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-ylabel' classes. The text will be rotated 90 degrees by default, so CSS rules may behave in unintuitive ways. No additional space is set aside for a y-axis label. If you need more space, increase the width of the y-axis tick labels using the yAxisLabelWidth option. If you need a wider div for the y-axis label, either style it that way with CSS (but remember that it's rotated, so width is controlled by the 'height' property) or set the yLabelWidth option.\"\n },\n \"y2label\": {\n \"labels\": [\"Chart labels\"],\n \"type\": \"string\",\n \"default\": \"null\",\n \"description\": \"Text to display to the right of the chart's secondary y-axis. This label is only displayed if a secondary y-axis is present. See this test for an example of how to do this. The comments for the 'ylabel' option generally apply here as well. This label gets a 'dygraph-y2label' instead of a 'dygraph-ylabel' class.\"\n },\n \"yLabelWidth\": {\n \"labels\": [\"Chart labels\"],\n \"type\": \"integer\",\n \"default\": \"18\",\n \"description\": \"Width of the div which contains the y-axis label. Since the y-axis label appears rotated 90 degrees, this actually affects the height of its div.\"\n },\n \"drawGrid\": {\n \"default\": \"true for x and y, false for y2\",\n \"labels\": [\"Grid\"],\n \"type\": \"boolean\",\n \"description\": \"Whether to display gridlines in the chart. This may be set on a per-axis basis to define the visibility of each axis' grid separately.\"\n },\n \"independentTicks\": {\n \"default\": \"true for y, false for y2\",\n \"labels\": [\"Axis display\", \"Grid\"],\n \"type\": \"boolean\",\n \"description\": \"Only valid for y and y2, has no effect on x: This option defines whether the y axes should align their ticks or if they should be independent. Possible combinations: 1.) y=true, y2=false (default): y is the primary axis and the y2 ticks are aligned to the the ones of y. (only 1 grid) 2.) y=false, y2=true: y2 is the primary axis and the y ticks are aligned to the the ones of y2. (only 1 grid) 3.) y=true, y2=true: Both axis are independent and have their own ticks. (2 grids) 4.) y=false, y2=false: Invalid configuration causes an error.\"\n },\n \"drawAxis\": {\n \"default\": \"true for x and y, false for y2\",\n \"labels\": [\"Axis display\"],\n \"type\": \"boolean\",\n \"description\": \"Whether to draw the specified axis. This may be set on a per-axis basis to define the visibility of each axis separately. Setting this to false also prevents axis ticks from being drawn and reclaims the space for the chart grid/lines.\"\n },\n \"gridLineWidth\": {\n \"default\": \"0.3\",\n \"labels\": [\"Grid\"],\n \"type\": \"float\",\n \"description\": \"Thickness (in pixels) of the gridlines drawn under the chart. The vertical/horizontal gridlines can be turned off entirely by using the drawGrid option. This may be set on a per-axis basis to define each axis' grid separately.\"\n },\n \"axisLineWidth\": {\n \"default\": \"0.3\",\n \"labels\": [\"Axis display\"],\n \"type\": \"float\",\n \"description\": \"Thickness (in pixels) of the x- and y-axis lines.\"\n },\n \"axisLineColor\": {\n \"default\": \"black\",\n \"labels\": [\"Axis display\"],\n \"type\": \"string\",\n \"description\": \"Color of the x- and y-axis lines. Accepts any value which the HTML canvas strokeStyle attribute understands, e.g. 'black' or 'rgb(0, 100, 255)'.\"\n },\n \"fillAlpha\": {\n \"default\": \"0.15\",\n \"labels\": [\"Error Bars\", \"Data Series Colors\"],\n \"type\": \"float (0.0 - 1.0)\",\n \"description\": \"Error bars (or custom bars) for each series are drawn in the same color as the series, but with partial transparency. This sets the transparency. A value of 0.0 means that the error bars will not be drawn, whereas a value of 1.0 means that the error bars will be as dark as the line for the series itself. This can be used to produce chart lines whose thickness varies at each point.\"\n },\n \"axisLabelWidth\": {\n \"default\": \"50 (y-axis), 60 (x-axis)\",\n \"labels\": [\"Axis display\", \"Chart labels\"],\n \"type\": \"integer\",\n \"description\": \"Width (in pixels) of the containing divs for x- and y-axis labels. For the y-axis, this also controls the width of the y-axis. Note that for the x-axis, this is independent from pixelsPerLabel, which controls the spacing between labels.\"\n },\n \"sigFigs\": {\n \"default\": \"null\",\n \"labels\": [\"Value display/formatting\"],\n \"type\": \"integer\",\n \"description\": \"By default, dygraphs displays numbers with a fixed number of digits after the decimal point. If you'd prefer to have a fixed number of significant figures, set this option to that number of sig figs. A value of 2, for instance, would cause 1 to be display as 1.0 and 1234 to be displayed as 1.23e+3.\"\n },\n \"digitsAfterDecimal\": {\n \"default\": \"2\",\n \"labels\": [\"Value display/formatting\"],\n \"type\": \"integer\",\n \"description\": \"Unless it's run in scientific mode (see the sigFigs option), dygraphs displays numbers with digitsAfterDecimal digits after the decimal point. Trailing zeros are not displayed, so with a value of 2 you'll get '0', '0.1', '0.12', '123.45' but not '123.456' (it will be rounded to '123.46'). Numbers with absolute value less than 0.1^digitsAfterDecimal (i.e. those which would show up as '0.00') will be displayed in scientific notation.\"\n },\n \"maxNumberWidth\": {\n \"default\": \"6\",\n \"labels\": [\"Value display/formatting\"],\n \"type\": \"integer\",\n \"description\": \"When displaying numbers in normal (not scientific) mode, large numbers will be displayed with many trailing zeros (e.g. 100000000 instead of 1e9). This can lead to unwieldy y-axis labels. If there are more than maxNumberWidth digits to the left of the decimal in a number, dygraphs will switch to scientific notation, even when not operating in scientific mode. If you'd like to see all those digits, set this to something large, like 20 or 30.\"\n },\n \"file\": {\n \"default\": \"(set when constructed)\",\n \"labels\": [\"Data\"],\n \"type\": \"string (URL of CSV or CSV), GViz DataTable or 2D Array\",\n \"description\": \"Sets the data being displayed in the chart. This can only be set when calling updateOptions; it cannot be set from the constructor. For a full description of valid data formats, see the Data Formats page.\"\n },\n \"timingName\": {\n \"default\": \"null\",\n \"labels\": [\"Debugging\", \"Deprecated\"],\n \"type\": \"string\",\n \"description\": \"Set this option to log timing information. The value of the option will be logged along with the timimg, so that you can distinguish multiple dygraphs on the same page.\"\n },\n \"showRangeSelector\": {\n \"default\": \"false\",\n \"labels\": [\"Range Selector\"],\n \"type\": \"boolean\",\n \"description\": \"Show or hide the range selector widget.\"\n },\n \"rangeSelectorHeight\": {\n \"default\": \"40\",\n \"labels\": [\"Range Selector\"],\n \"type\": \"integer\",\n \"description\": \"Height, in pixels, of the range selector widget. This option can only be specified at Dygraph creation time.\"\n },\n \"rangeSelectorPlotStrokeColor\": {\n \"default\": \"#808FAB\",\n \"labels\": [\"Range Selector\"],\n \"type\": \"string\",\n \"description\": \"The range selector mini plot stroke color. This can be of the form \\\"#AABBCC\\\" or \\\"rgb(255,100,200)\\\" or \\\"yellow\\\". You can also specify null or \\\"\\\" to turn off stroke.\"\n },\n \"rangeSelectorPlotFillColor\": {\n \"default\": \"#A7B1C4\",\n \"labels\": [\"Range Selector\"],\n \"type\": \"string\",\n \"description\": \"The range selector mini plot fill color. This can be of the form \\\"#AABBCC\\\" or \\\"rgb(255,100,200)\\\" or \\\"yellow\\\". You can also specify null or \\\"\\\" to turn off fill.\"\n },\n \"rangeSelectorPlotFillGradientColor\": {\n \"default\": \"white\",\n \"labels\": [\"Range Selector\"],\n \"type\": \"string\",\n \"description\": \"The top color for the range selector mini plot fill color gradient. This can be of the form \\\"#AABBCC\\\" or \\\"rgb(255,100,200)\\\" or \\\"rgba(255,100,200,42)\\\" or \\\"yellow\\\". You can also specify null or \\\"\\\" to disable the gradient and fill with one single color.\"\n },\n \"rangeSelectorBackgroundStrokeColor\": {\n \"default\": \"gray\",\n \"labels\": [\"Range Selector\"],\n \"type\": \"string\",\n \"description\": \"The color of the lines below and on both sides of the range selector mini plot. This can be of the form \\\"#AABBCC\\\" or \\\"rgb(255,100,200)\\\" or \\\"yellow\\\".\"\n },\n \"rangeSelectorBackgroundLineWidth\": {\n \"default\": \"1\",\n \"labels\": [\"Range Selector\"],\n \"type\": \"float\",\n \"description\": \"The width of the lines below and on both sides of the range selector mini plot.\"\n },\n \"rangeSelectorPlotLineWidth\": {\n \"default\": \"1.5\",\n \"labels\": [\"Range Selector\"],\n \"type\": \"float\",\n \"description\": \"The width of the range selector mini plot line.\"\n },\n \"rangeSelectorForegroundStrokeColor\": {\n \"default\": \"black\",\n \"labels\": [\"Range Selector\"],\n \"type\": \"string\",\n \"description\": \"The color of the lines in the interactive layer of the range selector. This can be of the form \\\"#AABBCC\\\" or \\\"rgb(255,100,200)\\\" or \\\"yellow\\\".\"\n },\n \"rangeSelectorForegroundLineWidth\": {\n \"default\": \"1\",\n \"labels\": [\"Range Selector\"],\n \"type\": \"float\",\n \"description\": \"The width the lines in the interactive layer of the range selector.\"\n },\n \"rangeSelectorAlpha\": {\n \"default\": \"0.6\",\n \"labels\": [\"Range Selector\"],\n \"type\": \"float (0.0 - 1.0)\",\n \"description\": \"The transparency of the veil that is drawn over the unselected portions of the range selector mini plot. A value of 0 represents full transparency and the unselected portions of the mini plot will appear as normal. A value of 1 represents full opacity and the unselected portions of the mini plot will be hidden.\"\n },\n \"showInRangeSelector\": {\n \"default\": \"null\",\n \"labels\": [\"Range Selector\"],\n \"type\": \"boolean\",\n \"description\": \"Mark this series for inclusion in the range selector. The mini plot curve will be an average of all such series. If this is not specified for any series, the default behavior is to average all the visible series. Setting it for one series will result in that series being charted alone in the range selector. Once it's set for a single series, it needs to be set for all series which should be included (regardless of visibility).\"\n },\n \"animatedZooms\": {\n \"default\": \"false\",\n \"labels\": [\"Interactive Elements\"],\n \"type\": \"boolean\",\n \"description\": \"Set this option to animate the transition between zoom windows. Applies to programmatic and interactive zooms. Note that if you also set a drawCallback, it will be called several times on each zoom. If you set a zoomCallback, it will only be called after the animation is complete.\"\n },\n \"plotter\": {\n \"default\": \"[DygraphCanvasRenderer.Plotters.fillPlotter, DygraphCanvasRenderer.Plotters.errorPlotter, DygraphCanvasRenderer.Plotters.linePlotter]\",\n \"labels\": [\"Data Line display\"],\n \"type\": \"array or function\",\n \"description\": \"A function (or array of functions) which plot each data series on the chart. TODO(danvk): more details! May be set per-series.\"\n },\n \"axes\": {\n \"default\": \"null\",\n \"labels\": [\"Configuration\"],\n \"type\": \"Object\",\n \"description\": \"Defines per-axis options. Valid keys are 'x', 'y' and 'y2'. Only some options may be set on a per-axis basis. If an option may be set in this way, it will be noted on this page. See also documentation on per-series and per-axis options.\"\n },\n \"series\": {\n \"default\": \"null\",\n \"labels\": [\"Series\"],\n \"type\": \"Object\",\n \"description\": \"Defines per-series options. Its keys match the y-axis label names, and the values are dictionaries themselves that contain options specific to that series.\"\n },\n \"plugins\": {\n \"default\": \"[]\",\n \"labels\": [\"Configuration\"],\n \"type\": \"Array\",\n \"description\": \"Defines per-graph plugins. Useful for per-graph customization\"\n },\n \"dataHandler\": {\n \"default\": \"(depends on data)\",\n \"labels\": [\"Data\"],\n \"type\": \"Dygraph.DataHandler\",\n \"description\": \"Custom DataHandler. This is an advanced customization. See http://bit.ly/151E7Aq.\"\n }\n }; //
\n // NOTE: in addition to parsing as JS, this snippet is expected to be valid\n // JSON. This assumption cannot be checked in JS, but it will be checked when\n // documentation is generated by the generate-documentation.py script. For the\n // most part, this just means that you should always use double quotes.\n\n // Do a quick sanity check on the options reference.\n var warn = function warn(msg) {\n if (window.console) window.console.warn(msg);\n };\n var flds = ['type', 'default', 'description'];\n var valid_cats = ['Annotations', 'Axis display', 'Chart labels', 'CSV parsing', 'Callbacks', 'Data', 'Data Line display', 'Data Series Colors', 'Error Bars', 'Grid', 'Interactive Elements', 'Range Selector', 'Legend', 'Overall display', 'Rolling Averages', 'Series', 'Value display/formatting', 'Zooming', 'Debugging', 'Configuration', 'Deprecated'];\n var i;\n var cats = {};\n for (i = 0; i < valid_cats.length; i++) cats[valid_cats[i]] = true;\n\n for (var k in OPTIONS_REFERENCE) {\n if (!OPTIONS_REFERENCE.hasOwnProperty(k)) continue;\n var op = OPTIONS_REFERENCE[k];\n for (i = 0; i < flds.length; i++) {\n if (!op.hasOwnProperty(flds[i])) {\n warn('Option ' + k + ' missing \"' + flds[i] + '\" property');\n } else if (typeof op[flds[i]] != 'string') {\n warn(k + '.' + flds[i] + ' must be of type string');\n }\n }\n var labels = op.labels;\n if (typeof labels !== 'object') {\n warn('Option \"' + k + '\" is missing a \"labels\": [...] option');\n } else {\n for (i = 0; i < labels.length; i++) {\n if (!cats.hasOwnProperty(labels[i])) {\n warn('Option \"' + k + '\" has label \"' + labels[i] + '\", which is invalid.');\n }\n }\n }\n }\n }\n}\n\nexports['default'] = OPTIONS_REFERENCE;\nmodule.exports = exports['default'];","/**\n * To create a \"drag\" interaction, you typically register a mousedown event\n * handler on the element where the drag begins. In that handler, you register a\n * mouseup handler on the window to determine when the mouse is released,\n * wherever that release happens. This works well, except when the user releases\n * the mouse over an off-domain iframe. In that case, the mouseup event is\n * handled by the iframe and never bubbles up to the window handler.\n *\n * To deal with this issue, we cover iframes with high z-index divs to make sure\n * they don't capture mouseup.\n *\n * Usage:\n * element.addEventListener('mousedown', function() {\n * var tarper = new IFrameTarp();\n * tarper.cover();\n * var mouseUpHandler = function() {\n * ...\n * window.removeEventListener(mouseUpHandler);\n * tarper.uncover();\n * };\n * window.addEventListener('mouseup', mouseUpHandler);\n * };\n *\n * @constructor\n */\n\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nfunction _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj[\"default\"] = obj; return newObj; } }\n\nvar _dygraphUtils = require('./dygraph-utils');\n\nvar utils = _interopRequireWildcard(_dygraphUtils);\n\nfunction IFrameTarp() {\n /** @type {Array.} */\n this.tarps = [];\n};\n\n/**\n * Find all the iframes in the document and cover them with high z-index\n * transparent divs.\n */\nIFrameTarp.prototype.cover = function () {\n var iframes = document.getElementsByTagName(\"iframe\");\n for (var i = 0; i < iframes.length; i++) {\n var iframe = iframes[i];\n var pos = utils.findPos(iframe),\n x = pos.x,\n y = pos.y,\n width = iframe.offsetWidth,\n height = iframe.offsetHeight;\n\n var div = document.createElement(\"div\");\n div.style.position = \"absolute\";\n div.style.left = x + 'px';\n div.style.top = y + 'px';\n div.style.width = width + 'px';\n div.style.height = height + 'px';\n div.style.zIndex = 999;\n document.body.appendChild(div);\n this.tarps.push(div);\n }\n};\n\n/**\n * Remove all the iframe covers. You should call this in a mouseup handler.\n */\nIFrameTarp.prototype.uncover = function () {\n for (var i = 0; i < this.tarps.length; i++) {\n this.tarps[i].parentNode.removeChild(this.tarps[i]);\n }\n this.tarps = [];\n};\n\nexports[\"default\"] = IFrameTarp;\nmodule.exports = exports[\"default\"];","/**\n * @license\n * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)\n * MIT-licensed (http://opensource.org/licenses/MIT)\n */\n\n/**\n * @fileoverview DataHandler default implementation used for simple line charts.\n * @author David Eberlein (david.eberlein@ch.sauter-bc.com)\n */\n\n/*global Dygraph:false */\n\"use strict\";\n\nObject.defineProperty(exports, '__esModule', {\n value: true\n});\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }\n\nvar _datahandler = require('./datahandler');\n\nvar _datahandler2 = _interopRequireDefault(_datahandler);\n\n/**\n * @constructor\n * @extends Dygraph.DataHandler\n */\nvar DefaultHandler = function DefaultHandler() {};\n\nDefaultHandler.prototype = new _datahandler2['default']();\n\n/** @inheritDoc */\nDefaultHandler.prototype.extractSeries = function (rawData, i, options) {\n // TODO(danvk): pre-allocate series here.\n var series = [];\n var logScale = options.get('logscale');\n for (var j = 0; j < rawData.length; j++) {\n var x = rawData[j][0];\n var point = rawData[j][i];\n if (logScale) {\n // On the log scale, points less than zero do not exist.\n // This will create a gap in the chart.\n if (point <= 0) {\n point = null;\n }\n }\n series.push([x, point]);\n }\n return series;\n};\n\n/** @inheritDoc */\nDefaultHandler.prototype.rollingAverage = function (originalData, rollPeriod, options) {\n rollPeriod = Math.min(rollPeriod, originalData.length);\n var rollingData = [];\n\n var i, j, y, sum, num_ok;\n // Calculate the rolling average for the first rollPeriod - 1 points\n // where\n // there is not enough data to roll over the full number of points\n if (rollPeriod == 1) {\n return originalData;\n }\n for (i = 0; i < originalData.length; i++) {\n sum = 0;\n num_ok = 0;\n for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {\n y = originalData[j][1];\n if (y === null || isNaN(y)) continue;\n num_ok++;\n sum += originalData[j][1];\n }\n if (num_ok) {\n rollingData[i] = [originalData[i][0], sum / num_ok];\n } else {\n rollingData[i] = [originalData[i][0], null];\n }\n }\n\n return rollingData;\n};\n\n/** @inheritDoc */\nDefaultHandler.prototype.getExtremeYValues = function (series, dateWindow, options) {\n var minY = null,\n maxY = null,\n y;\n var firstIdx = 0,\n lastIdx = series.length - 1;\n\n for (var j = firstIdx; j <= lastIdx; j++) {\n y = series[j][1];\n if (y === null || isNaN(y)) continue;\n if (maxY === null || y > maxY) {\n maxY = y;\n }\n if (minY === null || y < minY) {\n minY = y;\n }\n }\n return [minY, maxY];\n};\n\nexports['default'] = DefaultHandler;\nmodule.exports = exports['default'];","'use strict'\r\n\r\nmodule.exports = {\r\n\t\"aliceblue\": [240, 248, 255],\r\n\t\"antiquewhite\": [250, 235, 215],\r\n\t\"aqua\": [0, 255, 255],\r\n\t\"aquamarine\": [127, 255, 212],\r\n\t\"azure\": [240, 255, 255],\r\n\t\"beige\": [245, 245, 220],\r\n\t\"bisque\": [255, 228, 196],\r\n\t\"black\": [0, 0, 0],\r\n\t\"blanchedalmond\": [255, 235, 205],\r\n\t\"blue\": [0, 0, 255],\r\n\t\"blueviolet\": [138, 43, 226],\r\n\t\"brown\": [165, 42, 42],\r\n\t\"burlywood\": [222, 184, 135],\r\n\t\"cadetblue\": [95, 158, 160],\r\n\t\"chartreuse\": [127, 255, 0],\r\n\t\"chocolate\": [210, 105, 30],\r\n\t\"coral\": [255, 127, 80],\r\n\t\"cornflowerblue\": [100, 149, 237],\r\n\t\"cornsilk\": [255, 248, 220],\r\n\t\"crimson\": [220, 20, 60],\r\n\t\"cyan\": [0, 255, 255],\r\n\t\"darkblue\": [0, 0, 139],\r\n\t\"darkcyan\": [0, 139, 139],\r\n\t\"darkgoldenrod\": [184, 134, 11],\r\n\t\"darkgray\": [169, 169, 169],\r\n\t\"darkgreen\": [0, 100, 0],\r\n\t\"darkgrey\": [169, 169, 169],\r\n\t\"darkkhaki\": [189, 183, 107],\r\n\t\"darkmagenta\": [139, 0, 139],\r\n\t\"darkolivegreen\": [85, 107, 47],\r\n\t\"darkorange\": [255, 140, 0],\r\n\t\"darkorchid\": [153, 50, 204],\r\n\t\"darkred\": [139, 0, 0],\r\n\t\"darksalmon\": [233, 150, 122],\r\n\t\"darkseagreen\": [143, 188, 143],\r\n\t\"darkslateblue\": [72, 61, 139],\r\n\t\"darkslategray\": [47, 79, 79],\r\n\t\"darkslategrey\": [47, 79, 79],\r\n\t\"darkturquoise\": [0, 206, 209],\r\n\t\"darkviolet\": [148, 0, 211],\r\n\t\"deeppink\": [255, 20, 147],\r\n\t\"deepskyblue\": [0, 191, 255],\r\n\t\"dimgray\": [105, 105, 105],\r\n\t\"dimgrey\": [105, 105, 105],\r\n\t\"dodgerblue\": [30, 144, 255],\r\n\t\"firebrick\": [178, 34, 34],\r\n\t\"floralwhite\": [255, 250, 240],\r\n\t\"forestgreen\": [34, 139, 34],\r\n\t\"fuchsia\": [255, 0, 255],\r\n\t\"gainsboro\": [220, 220, 220],\r\n\t\"ghostwhite\": [248, 248, 255],\r\n\t\"gold\": [255, 215, 0],\r\n\t\"goldenrod\": [218, 165, 32],\r\n\t\"gray\": [128, 128, 128],\r\n\t\"green\": [0, 128, 0],\r\n\t\"greenyellow\": [173, 255, 47],\r\n\t\"grey\": [128, 128, 128],\r\n\t\"honeydew\": [240, 255, 240],\r\n\t\"hotpink\": [255, 105, 180],\r\n\t\"indianred\": [205, 92, 92],\r\n\t\"indigo\": [75, 0, 130],\r\n\t\"ivory\": [255, 255, 240],\r\n\t\"khaki\": [240, 230, 140],\r\n\t\"lavender\": [230, 230, 250],\r\n\t\"lavenderblush\": [255, 240, 245],\r\n\t\"lawngreen\": [124, 252, 0],\r\n\t\"lemonchiffon\": [255, 250, 205],\r\n\t\"lightblue\": [173, 216, 230],\r\n\t\"lightcoral\": [240, 128, 128],\r\n\t\"lightcyan\": [224, 255, 255],\r\n\t\"lightgoldenrodyellow\": [250, 250, 210],\r\n\t\"lightgray\": [211, 211, 211],\r\n\t\"lightgreen\": [144, 238, 144],\r\n\t\"lightgrey\": [211, 211, 211],\r\n\t\"lightpink\": [255, 182, 193],\r\n\t\"lightsalmon\": [255, 160, 122],\r\n\t\"lightseagreen\": [32, 178, 170],\r\n\t\"lightskyblue\": [135, 206, 250],\r\n\t\"lightslategray\": [119, 136, 153],\r\n\t\"lightslategrey\": [119, 136, 153],\r\n\t\"lightsteelblue\": [176, 196, 222],\r\n\t\"lightyellow\": [255, 255, 224],\r\n\t\"lime\": [0, 255, 0],\r\n\t\"limegreen\": [50, 205, 50],\r\n\t\"linen\": [250, 240, 230],\r\n\t\"magenta\": [255, 0, 255],\r\n\t\"maroon\": [128, 0, 0],\r\n\t\"mediumaquamarine\": [102, 205, 170],\r\n\t\"mediumblue\": [0, 0, 205],\r\n\t\"mediumorchid\": [186, 85, 211],\r\n\t\"mediumpurple\": [147, 112, 219],\r\n\t\"mediumseagreen\": [60, 179, 113],\r\n\t\"mediumslateblue\": [123, 104, 238],\r\n\t\"mediumspringgreen\": [0, 250, 154],\r\n\t\"mediumturquoise\": [72, 209, 204],\r\n\t\"mediumvioletred\": [199, 21, 133],\r\n\t\"midnightblue\": [25, 25, 112],\r\n\t\"mintcream\": [245, 255, 250],\r\n\t\"mistyrose\": [255, 228, 225],\r\n\t\"moccasin\": [255, 228, 181],\r\n\t\"navajowhite\": [255, 222, 173],\r\n\t\"navy\": [0, 0, 128],\r\n\t\"oldlace\": [253, 245, 230],\r\n\t\"olive\": [128, 128, 0],\r\n\t\"olivedrab\": [107, 142, 35],\r\n\t\"orange\": [255, 165, 0],\r\n\t\"orangered\": [255, 69, 0],\r\n\t\"orchid\": [218, 112, 214],\r\n\t\"palegoldenrod\": [238, 232, 170],\r\n\t\"palegreen\": [152, 251, 152],\r\n\t\"paleturquoise\": [175, 238, 238],\r\n\t\"palevioletred\": [219, 112, 147],\r\n\t\"papayawhip\": [255, 239, 213],\r\n\t\"peachpuff\": [255, 218, 185],\r\n\t\"peru\": [205, 133, 63],\r\n\t\"pink\": [255, 192, 203],\r\n\t\"plum\": [221, 160, 221],\r\n\t\"powderblue\": [176, 224, 230],\r\n\t\"purple\": [128, 0, 128],\r\n\t\"rebeccapurple\": [102, 51, 153],\r\n\t\"red\": [255, 0, 0],\r\n\t\"rosybrown\": [188, 143, 143],\r\n\t\"royalblue\": [65, 105, 225],\r\n\t\"saddlebrown\": [139, 69, 19],\r\n\t\"salmon\": [250, 128, 114],\r\n\t\"sandybrown\": [244, 164, 96],\r\n\t\"seagreen\": [46, 139, 87],\r\n\t\"seashell\": [255, 245, 238],\r\n\t\"sienna\": [160, 82, 45],\r\n\t\"silver\": [192, 192, 192],\r\n\t\"skyblue\": [135, 206, 235],\r\n\t\"slateblue\": [106, 90, 205],\r\n\t\"slategray\": [112, 128, 144],\r\n\t\"slategrey\": [112, 128, 144],\r\n\t\"snow\": [255, 250, 250],\r\n\t\"springgreen\": [0, 255, 127],\r\n\t\"steelblue\": [70, 130, 180],\r\n\t\"tan\": [210, 180, 140],\r\n\t\"teal\": [0, 128, 128],\r\n\t\"thistle\": [216, 191, 216],\r\n\t\"tomato\": [255, 99, 71],\r\n\t\"turquoise\": [64, 224, 208],\r\n\t\"violet\": [238, 130, 238],\r\n\t\"wheat\": [245, 222, 179],\r\n\t\"white\": [255, 255, 255],\r\n\t\"whitesmoke\": [245, 245, 245],\r\n\t\"yellow\": [255, 255, 0],\r\n\t\"yellowgreen\": [154, 205, 50]\r\n};\r\n","/* MIT license */\nvar cssKeywords = require('color-name');\n\n// NOTE: conversions should only return primitive values (i.e. arrays, or\n// values that give correct `typeof` results).\n// do not use box values types (i.e. Number(), String(), etc.)\n\nvar reverseKeywords = {};\nfor (var key in cssKeywords) {\n\tif (cssKeywords.hasOwnProperty(key)) {\n\t\treverseKeywords[cssKeywords[key]] = key;\n\t}\n}\n\nvar convert = module.exports = {\n\trgb: {channels: 3, labels: 'rgb'},\n\thsl: {channels: 3, labels: 'hsl'},\n\thsv: {channels: 3, labels: 'hsv'},\n\thwb: {channels: 3, labels: 'hwb'},\n\tcmyk: {channels: 4, labels: 'cmyk'},\n\txyz: {channels: 3, labels: 'xyz'},\n\tlab: {channels: 3, labels: 'lab'},\n\tlch: {channels: 3, labels: 'lch'},\n\thex: {channels: 1, labels: ['hex']},\n\tkeyword: {channels: 1, labels: ['keyword']},\n\tansi16: {channels: 1, labels: ['ansi16']},\n\tansi256: {channels: 1, labels: ['ansi256']},\n\thcg: {channels: 3, labels: ['h', 'c', 'g']},\n\tapple: {channels: 3, labels: ['r16', 'g16', 'b16']},\n\tgray: {channels: 1, labels: ['gray']}\n};\n\n// hide .channels and .labels properties\nfor (var model in convert) {\n\tif (convert.hasOwnProperty(model)) {\n\t\tif (!('channels' in convert[model])) {\n\t\t\tthrow new Error('missing channels property: ' + model);\n\t\t}\n\n\t\tif (!('labels' in convert[model])) {\n\t\t\tthrow new Error('missing channel labels property: ' + model);\n\t\t}\n\n\t\tif (convert[model].labels.length !== convert[model].channels) {\n\t\t\tthrow new Error('channel and label counts mismatch: ' + model);\n\t\t}\n\n\t\tvar channels = convert[model].channels;\n\t\tvar labels = convert[model].labels;\n\t\tdelete convert[model].channels;\n\t\tdelete convert[model].labels;\n\t\tObject.defineProperty(convert[model], 'channels', {value: channels});\n\t\tObject.defineProperty(convert[model], 'labels', {value: labels});\n\t}\n}\n\nconvert.rgb.hsl = function (rgb) {\n\tvar r = rgb[0] / 255;\n\tvar g = rgb[1] / 255;\n\tvar b = rgb[2] / 255;\n\tvar min = Math.min(r, g, b);\n\tvar max = Math.max(r, g, b);\n\tvar delta = max - min;\n\tvar h;\n\tvar s;\n\tvar l;\n\n\tif (max === min) {\n\t\th = 0;\n\t} else if (r === max) {\n\t\th = (g - b) / delta;\n\t} else if (g === max) {\n\t\th = 2 + (b - r) / delta;\n\t} else if (b === max) {\n\t\th = 4 + (r - g) / delta;\n\t}\n\n\th = Math.min(h * 60, 360);\n\n\tif (h < 0) {\n\t\th += 360;\n\t}\n\n\tl = (min + max) / 2;\n\n\tif (max === min) {\n\t\ts = 0;\n\t} else if (l <= 0.5) {\n\t\ts = delta / (max + min);\n\t} else {\n\t\ts = delta / (2 - max - min);\n\t}\n\n\treturn [h, s * 100, l * 100];\n};\n\nconvert.rgb.hsv = function (rgb) {\n\tvar rdif;\n\tvar gdif;\n\tvar bdif;\n\tvar h;\n\tvar s;\n\n\tvar r = rgb[0] / 255;\n\tvar g = rgb[1] / 255;\n\tvar b = rgb[2] / 255;\n\tvar v = Math.max(r, g, b);\n\tvar diff = v - Math.min(r, g, b);\n\tvar diffc = function (c) {\n\t\treturn (v - c) / 6 / diff + 1 / 2;\n\t};\n\n\tif (diff === 0) {\n\t\th = s = 0;\n\t} else {\n\t\ts = diff / v;\n\t\trdif = diffc(r);\n\t\tgdif = diffc(g);\n\t\tbdif = diffc(b);\n\n\t\tif (r === v) {\n\t\t\th = bdif - gdif;\n\t\t} else if (g === v) {\n\t\t\th = (1 / 3) + rdif - bdif;\n\t\t} else if (b === v) {\n\t\t\th = (2 / 3) + gdif - rdif;\n\t\t}\n\t\tif (h < 0) {\n\t\t\th += 1;\n\t\t} else if (h > 1) {\n\t\t\th -= 1;\n\t\t}\n\t}\n\n\treturn [\n\t\th * 360,\n\t\ts * 100,\n\t\tv * 100\n\t];\n};\n\nconvert.rgb.hwb = function (rgb) {\n\tvar r = rgb[0];\n\tvar g = rgb[1];\n\tvar b = rgb[2];\n\tvar h = convert.rgb.hsl(rgb)[0];\n\tvar w = 1 / 255 * Math.min(r, Math.min(g, b));\n\n\tb = 1 - 1 / 255 * Math.max(r, Math.max(g, b));\n\n\treturn [h, w * 100, b * 100];\n};\n\nconvert.rgb.cmyk = function (rgb) {\n\tvar r = rgb[0] / 255;\n\tvar g = rgb[1] / 255;\n\tvar b = rgb[2] / 255;\n\tvar c;\n\tvar m;\n\tvar y;\n\tvar k;\n\n\tk = Math.min(1 - r, 1 - g, 1 - b);\n\tc = (1 - r - k) / (1 - k) || 0;\n\tm = (1 - g - k) / (1 - k) || 0;\n\ty = (1 - b - k) / (1 - k) || 0;\n\n\treturn [c * 100, m * 100, y * 100, k * 100];\n};\n\n/**\n * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance\n * */\nfunction comparativeDistance(x, y) {\n\treturn (\n\t\tMath.pow(x[0] - y[0], 2) +\n\t\tMath.pow(x[1] - y[1], 2) +\n\t\tMath.pow(x[2] - y[2], 2)\n\t);\n}\n\nconvert.rgb.keyword = function (rgb) {\n\tvar reversed = reverseKeywords[rgb];\n\tif (reversed) {\n\t\treturn reversed;\n\t}\n\n\tvar currentClosestDistance = Infinity;\n\tvar currentClosestKeyword;\n\n\tfor (var keyword in cssKeywords) {\n\t\tif (cssKeywords.hasOwnProperty(keyword)) {\n\t\t\tvar value = cssKeywords[keyword];\n\n\t\t\t// Compute comparative distance\n\t\t\tvar distance = comparativeDistance(rgb, value);\n\n\t\t\t// Check if its less, if so set as closest\n\t\t\tif (distance < currentClosestDistance) {\n\t\t\t\tcurrentClosestDistance = distance;\n\t\t\t\tcurrentClosestKeyword = keyword;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn currentClosestKeyword;\n};\n\nconvert.keyword.rgb = function (keyword) {\n\treturn cssKeywords[keyword];\n};\n\nconvert.rgb.xyz = function (rgb) {\n\tvar r = rgb[0] / 255;\n\tvar g = rgb[1] / 255;\n\tvar b = rgb[2] / 255;\n\n\t// assume sRGB\n\tr = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);\n\tg = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);\n\tb = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);\n\n\tvar x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);\n\tvar y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);\n\tvar z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);\n\n\treturn [x * 100, y * 100, z * 100];\n};\n\nconvert.rgb.lab = function (rgb) {\n\tvar xyz = convert.rgb.xyz(rgb);\n\tvar x = xyz[0];\n\tvar y = xyz[1];\n\tvar z = xyz[2];\n\tvar l;\n\tvar a;\n\tvar b;\n\n\tx /= 95.047;\n\ty /= 100;\n\tz /= 108.883;\n\n\tx = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);\n\ty = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);\n\tz = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);\n\n\tl = (116 * y) - 16;\n\ta = 500 * (x - y);\n\tb = 200 * (y - z);\n\n\treturn [l, a, b];\n};\n\nconvert.hsl.rgb = function (hsl) {\n\tvar h = hsl[0] / 360;\n\tvar s = hsl[1] / 100;\n\tvar l = hsl[2] / 100;\n\tvar t1;\n\tvar t2;\n\tvar t3;\n\tvar rgb;\n\tvar val;\n\n\tif (s === 0) {\n\t\tval = l * 255;\n\t\treturn [val, val, val];\n\t}\n\n\tif (l < 0.5) {\n\t\tt2 = l * (1 + s);\n\t} else {\n\t\tt2 = l + s - l * s;\n\t}\n\n\tt1 = 2 * l - t2;\n\n\trgb = [0, 0, 0];\n\tfor (var i = 0; i < 3; i++) {\n\t\tt3 = h + 1 / 3 * -(i - 1);\n\t\tif (t3 < 0) {\n\t\t\tt3++;\n\t\t}\n\t\tif (t3 > 1) {\n\t\t\tt3--;\n\t\t}\n\n\t\tif (6 * t3 < 1) {\n\t\t\tval = t1 + (t2 - t1) * 6 * t3;\n\t\t} else if (2 * t3 < 1) {\n\t\t\tval = t2;\n\t\t} else if (3 * t3 < 2) {\n\t\t\tval = t1 + (t2 - t1) * (2 / 3 - t3) * 6;\n\t\t} else {\n\t\t\tval = t1;\n\t\t}\n\n\t\trgb[i] = val * 255;\n\t}\n\n\treturn rgb;\n};\n\nconvert.hsl.hsv = function (hsl) {\n\tvar h = hsl[0];\n\tvar s = hsl[1] / 100;\n\tvar l = hsl[2] / 100;\n\tvar smin = s;\n\tvar lmin = Math.max(l, 0.01);\n\tvar sv;\n\tvar v;\n\n\tl *= 2;\n\ts *= (l <= 1) ? l : 2 - l;\n\tsmin *= lmin <= 1 ? lmin : 2 - lmin;\n\tv = (l + s) / 2;\n\tsv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s);\n\n\treturn [h, sv * 100, v * 100];\n};\n\nconvert.hsv.rgb = function (hsv) {\n\tvar h = hsv[0] / 60;\n\tvar s = hsv[1] / 100;\n\tvar v = hsv[2] / 100;\n\tvar hi = Math.floor(h) % 6;\n\n\tvar f = h - Math.floor(h);\n\tvar p = 255 * v * (1 - s);\n\tvar q = 255 * v * (1 - (s * f));\n\tvar t = 255 * v * (1 - (s * (1 - f)));\n\tv *= 255;\n\n\tswitch (hi) {\n\t\tcase 0:\n\t\t\treturn [v, t, p];\n\t\tcase 1:\n\t\t\treturn [q, v, p];\n\t\tcase 2:\n\t\t\treturn [p, v, t];\n\t\tcase 3:\n\t\t\treturn [p, q, v];\n\t\tcase 4:\n\t\t\treturn [t, p, v];\n\t\tcase 5:\n\t\t\treturn [v, p, q];\n\t}\n};\n\nconvert.hsv.hsl = function (hsv) {\n\tvar h = hsv[0];\n\tvar s = hsv[1] / 100;\n\tvar v = hsv[2] / 100;\n\tvar vmin = Math.max(v, 0.01);\n\tvar lmin;\n\tvar sl;\n\tvar l;\n\n\tl = (2 - s) * v;\n\tlmin = (2 - s) * vmin;\n\tsl = s * vmin;\n\tsl /= (lmin <= 1) ? lmin : 2 - lmin;\n\tsl = sl || 0;\n\tl /= 2;\n\n\treturn [h, sl * 100, l * 100];\n};\n\n// http://dev.w3.org/csswg/css-color/#hwb-to-rgb\nconvert.hwb.rgb = function (hwb) {\n\tvar h = hwb[0] / 360;\n\tvar wh = hwb[1] / 100;\n\tvar bl = hwb[2] / 100;\n\tvar ratio = wh + bl;\n\tvar i;\n\tvar v;\n\tvar f;\n\tvar n;\n\n\t// wh + bl cant be > 1\n\tif (ratio > 1) {\n\t\twh /= ratio;\n\t\tbl /= ratio;\n\t}\n\n\ti = Math.floor(6 * h);\n\tv = 1 - bl;\n\tf = 6 * h - i;\n\n\tif ((i & 0x01) !== 0) {\n\t\tf = 1 - f;\n\t}\n\n\tn = wh + f * (v - wh); // linear interpolation\n\n\tvar r;\n\tvar g;\n\tvar b;\n\tswitch (i) {\n\t\tdefault:\n\t\tcase 6:\n\t\tcase 0: r = v; g = n; b = wh; break;\n\t\tcase 1: r = n; g = v; b = wh; break;\n\t\tcase 2: r = wh; g = v; b = n; break;\n\t\tcase 3: r = wh; g = n; b = v; break;\n\t\tcase 4: r = n; g = wh; b = v; break;\n\t\tcase 5: r = v; g = wh; b = n; break;\n\t}\n\n\treturn [r * 255, g * 255, b * 255];\n};\n\nconvert.cmyk.rgb = function (cmyk) {\n\tvar c = cmyk[0] / 100;\n\tvar m = cmyk[1] / 100;\n\tvar y = cmyk[2] / 100;\n\tvar k = cmyk[3] / 100;\n\tvar r;\n\tvar g;\n\tvar b;\n\n\tr = 1 - Math.min(1, c * (1 - k) + k);\n\tg = 1 - Math.min(1, m * (1 - k) + k);\n\tb = 1 - Math.min(1, y * (1 - k) + k);\n\n\treturn [r * 255, g * 255, b * 255];\n};\n\nconvert.xyz.rgb = function (xyz) {\n\tvar x = xyz[0] / 100;\n\tvar y = xyz[1] / 100;\n\tvar z = xyz[2] / 100;\n\tvar r;\n\tvar g;\n\tvar b;\n\n\tr = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);\n\tg = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);\n\tb = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);\n\n\t// assume sRGB\n\tr = r > 0.0031308\n\t\t? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)\n\t\t: r * 12.92;\n\n\tg = g > 0.0031308\n\t\t? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)\n\t\t: g * 12.92;\n\n\tb = b > 0.0031308\n\t\t? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)\n\t\t: b * 12.92;\n\n\tr = Math.min(Math.max(0, r), 1);\n\tg = Math.min(Math.max(0, g), 1);\n\tb = Math.min(Math.max(0, b), 1);\n\n\treturn [r * 255, g * 255, b * 255];\n};\n\nconvert.xyz.lab = function (xyz) {\n\tvar x = xyz[0];\n\tvar y = xyz[1];\n\tvar z = xyz[2];\n\tvar l;\n\tvar a;\n\tvar b;\n\n\tx /= 95.047;\n\ty /= 100;\n\tz /= 108.883;\n\n\tx = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);\n\ty = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);\n\tz = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);\n\n\tl = (116 * y) - 16;\n\ta = 500 * (x - y);\n\tb = 200 * (y - z);\n\n\treturn [l, a, b];\n};\n\nconvert.lab.xyz = function (lab) {\n\tvar l = lab[0];\n\tvar a = lab[1];\n\tvar b = lab[2];\n\tvar x;\n\tvar y;\n\tvar z;\n\n\ty = (l + 16) / 116;\n\tx = a / 500 + y;\n\tz = y - b / 200;\n\n\tvar y2 = Math.pow(y, 3);\n\tvar x2 = Math.pow(x, 3);\n\tvar z2 = Math.pow(z, 3);\n\ty = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787;\n\tx = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787;\n\tz = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787;\n\n\tx *= 95.047;\n\ty *= 100;\n\tz *= 108.883;\n\n\treturn [x, y, z];\n};\n\nconvert.lab.lch = function (lab) {\n\tvar l = lab[0];\n\tvar a = lab[1];\n\tvar b = lab[2];\n\tvar hr;\n\tvar h;\n\tvar c;\n\n\thr = Math.atan2(b, a);\n\th = hr * 360 / 2 / Math.PI;\n\n\tif (h < 0) {\n\t\th += 360;\n\t}\n\n\tc = Math.sqrt(a * a + b * b);\n\n\treturn [l, c, h];\n};\n\nconvert.lch.lab = function (lch) {\n\tvar l = lch[0];\n\tvar c = lch[1];\n\tvar h = lch[2];\n\tvar a;\n\tvar b;\n\tvar hr;\n\n\thr = h / 360 * 2 * Math.PI;\n\ta = c * Math.cos(hr);\n\tb = c * Math.sin(hr);\n\n\treturn [l, a, b];\n};\n\nconvert.rgb.ansi16 = function (args) {\n\tvar r = args[0];\n\tvar g = args[1];\n\tvar b = args[2];\n\tvar value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization\n\n\tvalue = Math.round(value / 50);\n\n\tif (value === 0) {\n\t\treturn 30;\n\t}\n\n\tvar ansi = 30\n\t\t+ ((Math.round(b / 255) << 2)\n\t\t| (Math.round(g / 255) << 1)\n\t\t| Math.round(r / 255));\n\n\tif (value === 2) {\n\t\tansi += 60;\n\t}\n\n\treturn ansi;\n};\n\nconvert.hsv.ansi16 = function (args) {\n\t// optimization here; we already know the value and don't need to get\n\t// it converted for us.\n\treturn convert.rgb.ansi16(convert.hsv.rgb(args), args[2]);\n};\n\nconvert.rgb.ansi256 = function (args) {\n\tvar r = args[0];\n\tvar g = args[1];\n\tvar b = args[2];\n\n\t// we use the extended greyscale palette here, with the exception of\n\t// black and white. normal palette only has 4 greyscale shades.\n\tif (r === g && g === b) {\n\t\tif (r < 8) {\n\t\t\treturn 16;\n\t\t}\n\n\t\tif (r > 248) {\n\t\t\treturn 231;\n\t\t}\n\n\t\treturn Math.round(((r - 8) / 247) * 24) + 232;\n\t}\n\n\tvar ansi = 16\n\t\t+ (36 * Math.round(r / 255 * 5))\n\t\t+ (6 * Math.round(g / 255 * 5))\n\t\t+ Math.round(b / 255 * 5);\n\n\treturn ansi;\n};\n\nconvert.ansi16.rgb = function (args) {\n\tvar color = args % 10;\n\n\t// handle greyscale\n\tif (color === 0 || color === 7) {\n\t\tif (args > 50) {\n\t\t\tcolor += 3.5;\n\t\t}\n\n\t\tcolor = color / 10.5 * 255;\n\n\t\treturn [color, color, color];\n\t}\n\n\tvar mult = (~~(args > 50) + 1) * 0.5;\n\tvar r = ((color & 1) * mult) * 255;\n\tvar g = (((color >> 1) & 1) * mult) * 255;\n\tvar b = (((color >> 2) & 1) * mult) * 255;\n\n\treturn [r, g, b];\n};\n\nconvert.ansi256.rgb = function (args) {\n\t// handle greyscale\n\tif (args >= 232) {\n\t\tvar c = (args - 232) * 10 + 8;\n\t\treturn [c, c, c];\n\t}\n\n\targs -= 16;\n\n\tvar rem;\n\tvar r = Math.floor(args / 36) / 5 * 255;\n\tvar g = Math.floor((rem = args % 36) / 6) / 5 * 255;\n\tvar b = (rem % 6) / 5 * 255;\n\n\treturn [r, g, b];\n};\n\nconvert.rgb.hex = function (args) {\n\tvar integer = ((Math.round(args[0]) & 0xFF) << 16)\n\t\t+ ((Math.round(args[1]) & 0xFF) << 8)\n\t\t+ (Math.round(args[2]) & 0xFF);\n\n\tvar string = integer.toString(16).toUpperCase();\n\treturn '000000'.substring(string.length) + string;\n};\n\nconvert.hex.rgb = function (args) {\n\tvar match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);\n\tif (!match) {\n\t\treturn [0, 0, 0];\n\t}\n\n\tvar colorString = match[0];\n\n\tif (match[0].length === 3) {\n\t\tcolorString = colorString.split('').map(function (char) {\n\t\t\treturn char + char;\n\t\t}).join('');\n\t}\n\n\tvar integer = parseInt(colorString, 16);\n\tvar r = (integer >> 16) & 0xFF;\n\tvar g = (integer >> 8) & 0xFF;\n\tvar b = integer & 0xFF;\n\n\treturn [r, g, b];\n};\n\nconvert.rgb.hcg = function (rgb) {\n\tvar r = rgb[0] / 255;\n\tvar g = rgb[1] / 255;\n\tvar b = rgb[2] / 255;\n\tvar max = Math.max(Math.max(r, g), b);\n\tvar min = Math.min(Math.min(r, g), b);\n\tvar chroma = (max - min);\n\tvar grayscale;\n\tvar hue;\n\n\tif (chroma < 1) {\n\t\tgrayscale = min / (1 - chroma);\n\t} else {\n\t\tgrayscale = 0;\n\t}\n\n\tif (chroma <= 0) {\n\t\thue = 0;\n\t} else\n\tif (max === r) {\n\t\thue = ((g - b) / chroma) % 6;\n\t} else\n\tif (max === g) {\n\t\thue = 2 + (b - r) / chroma;\n\t} else {\n\t\thue = 4 + (r - g) / chroma + 4;\n\t}\n\n\thue /= 6;\n\thue %= 1;\n\n\treturn [hue * 360, chroma * 100, grayscale * 100];\n};\n\nconvert.hsl.hcg = function (hsl) {\n\tvar s = hsl[1] / 100;\n\tvar l = hsl[2] / 100;\n\tvar c = 1;\n\tvar f = 0;\n\n\tif (l < 0.5) {\n\t\tc = 2.0 * s * l;\n\t} else {\n\t\tc = 2.0 * s * (1.0 - l);\n\t}\n\n\tif (c < 1.0) {\n\t\tf = (l - 0.5 * c) / (1.0 - c);\n\t}\n\n\treturn [hsl[0], c * 100, f * 100];\n};\n\nconvert.hsv.hcg = function (hsv) {\n\tvar s = hsv[1] / 100;\n\tvar v = hsv[2] / 100;\n\n\tvar c = s * v;\n\tvar f = 0;\n\n\tif (c < 1.0) {\n\t\tf = (v - c) / (1 - c);\n\t}\n\n\treturn [hsv[0], c * 100, f * 100];\n};\n\nconvert.hcg.rgb = function (hcg) {\n\tvar h = hcg[0] / 360;\n\tvar c = hcg[1] / 100;\n\tvar g = hcg[2] / 100;\n\n\tif (c === 0.0) {\n\t\treturn [g * 255, g * 255, g * 255];\n\t}\n\n\tvar pure = [0, 0, 0];\n\tvar hi = (h % 1) * 6;\n\tvar v = hi % 1;\n\tvar w = 1 - v;\n\tvar mg = 0;\n\n\tswitch (Math.floor(hi)) {\n\t\tcase 0:\n\t\t\tpure[0] = 1; pure[1] = v; pure[2] = 0; break;\n\t\tcase 1:\n\t\t\tpure[0] = w; pure[1] = 1; pure[2] = 0; break;\n\t\tcase 2:\n\t\t\tpure[0] = 0; pure[1] = 1; pure[2] = v; break;\n\t\tcase 3:\n\t\t\tpure[0] = 0; pure[1] = w; pure[2] = 1; break;\n\t\tcase 4:\n\t\t\tpure[0] = v; pure[1] = 0; pure[2] = 1; break;\n\t\tdefault:\n\t\t\tpure[0] = 1; pure[1] = 0; pure[2] = w;\n\t}\n\n\tmg = (1.0 - c) * g;\n\n\treturn [\n\t\t(c * pure[0] + mg) * 255,\n\t\t(c * pure[1] + mg) * 255,\n\t\t(c * pure[2] + mg) * 255\n\t];\n};\n\nconvert.hcg.hsv = function (hcg) {\n\tvar c = hcg[1] / 100;\n\tvar g = hcg[2] / 100;\n\n\tvar v = c + g * (1.0 - c);\n\tvar f = 0;\n\n\tif (v > 0.0) {\n\t\tf = c / v;\n\t}\n\n\treturn [hcg[0], f * 100, v * 100];\n};\n\nconvert.hcg.hsl = function (hcg) {\n\tvar c = hcg[1] / 100;\n\tvar g = hcg[2] / 100;\n\n\tvar l = g * (1.0 - c) + 0.5 * c;\n\tvar s = 0;\n\n\tif (l > 0.0 && l < 0.5) {\n\t\ts = c / (2 * l);\n\t} else\n\tif (l >= 0.5 && l < 1.0) {\n\t\ts = c / (2 * (1 - l));\n\t}\n\n\treturn [hcg[0], s * 100, l * 100];\n};\n\nconvert.hcg.hwb = function (hcg) {\n\tvar c = hcg[1] / 100;\n\tvar g = hcg[2] / 100;\n\tvar v = c + g * (1.0 - c);\n\treturn [hcg[0], (v - c) * 100, (1 - v) * 100];\n};\n\nconvert.hwb.hcg = function (hwb) {\n\tvar w = hwb[1] / 100;\n\tvar b = hwb[2] / 100;\n\tvar v = 1 - b;\n\tvar c = v - w;\n\tvar g = 0;\n\n\tif (c < 1) {\n\t\tg = (v - c) / (1 - c);\n\t}\n\n\treturn [hwb[0], c * 100, g * 100];\n};\n\nconvert.apple.rgb = function (apple) {\n\treturn [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255];\n};\n\nconvert.rgb.apple = function (rgb) {\n\treturn [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535];\n};\n\nconvert.gray.rgb = function (args) {\n\treturn [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255];\n};\n\nconvert.gray.hsl = convert.gray.hsv = function (args) {\n\treturn [0, 0, args[0]];\n};\n\nconvert.gray.hwb = function (gray) {\n\treturn [0, 100, gray[0]];\n};\n\nconvert.gray.cmyk = function (gray) {\n\treturn [0, 0, 0, gray[0]];\n};\n\nconvert.gray.lab = function (gray) {\n\treturn [gray[0], 0, 0];\n};\n\nconvert.gray.hex = function (gray) {\n\tvar val = Math.round(gray[0] / 100 * 255) & 0xFF;\n\tvar integer = (val << 16) + (val << 8) + val;\n\n\tvar string = integer.toString(16).toUpperCase();\n\treturn '000000'.substring(string.length) + string;\n};\n\nconvert.rgb.gray = function (rgb) {\n\tvar val = (rgb[0] + rgb[1] + rgb[2]) / 3;\n\treturn [val / 255 * 100];\n};\n","/**\n*\n* jquery.sparkline.js\n*\n* v2.4.1\n* (c) Splunk, Inc\n* Contact: Gareth Watts (gareth@splunk.com)\n* http://omnipotent.net/jquery.sparkline/\n*\n* Generates inline sparkline charts from data supplied either to the method\n* or inline in HTML\n*\n* Compatible with Internet Explorer 6.0+ and modern browsers equipped with the canvas tag\n* (Firefox 2.0+, Safari, Opera, etc)\n*\n* License: New BSD License\n*\n* Copyright (c) 2012, Splunk Inc.\n* All rights reserved.\n*\n* Redistribution and use in source and binary forms, with or without modification,\n* are permitted provided that the following conditions are met:\n*\n* * Redistributions of source code must retain the above copyright notice,\n* this list of conditions and the following disclaimer.\n* * Redistributions in binary form must reproduce the above copyright notice,\n* this list of conditions and the following disclaimer in the documentation\n* and/or other materials provided with the distribution.\n* * Neither the name of Splunk Inc nor the names of its contributors may\n* be used to endorse or promote products derived from this software without\n* specific prior written permission.\n*\n* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY\n* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT\n* SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT\n* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*\n*\n* Usage:\n* $(selector).sparkline(values, options)\n*\n* If values is undefined or set to 'html' then the data values are read from the specified tag:\n*

Sparkline: 1,4,6,6,8,5,3,5

\n* $('.sparkline').sparkline();\n* There must be no spaces in the enclosed data set\n*\n* Otherwise values must be an array of numbers or null values\n*

Sparkline: This text replaced if the browser is compatible

\n* $('#sparkline1').sparkline([1,4,6,6,8,5,3,5])\n* $('#sparkline2').sparkline([1,4,6,null,null,5,3,5])\n*\n* Values can also be specified in an HTML comment, or as a values attribute:\n*

Sparkline:

\n*

Sparkline:

\n* $('.sparkline').sparkline();\n*\n* For line charts, x values can also be specified:\n*

Sparkline: 1:1,2.7:4,3.4:6,5:6,6:8,8.7:5,9:3,10:5

\n* $('#sparkline1').sparkline([ [1,1], [2.7,4], [3.4,6], [5,6], [6,8], [8.7,5], [9,3], [10,5] ])\n*\n* By default, options should be passed in as the second argument to the sparkline function:\n* $('.sparkline').sparkline([1,2,3,4], {type: 'bar'})\n*\n* Options can also be set by passing them on the tag itself. This feature is disabled by default though\n* as there's a slight performance overhead:\n* $('.sparkline').sparkline([1,2,3,4], {enableTagOptions: true})\n*

Sparkline: loading

\n* Prefix all options supplied as tag attribute with \"spark\" (configurable by setting tagOptionsPrefix)\n*\n* Supported options:\n* lineColor - Color of the line used for the chart\n* fillColor - Color used to fill in the chart - Set to '' or false for a transparent chart\n* width - Width of the chart - Defaults to 3 times the number of values in pixels\n* height - Height of the chart - Defaults to the height of the containing element\n* chartRangeMin - Specify the minimum value to use for the Y range of the chart - Defaults to the minimum value supplied\n* chartRangeMax - Specify the maximum value to use for the Y range of the chart - Defaults to the maximum value supplied\n* chartRangeClip - Clip out of range values to the max/min specified by chartRangeMin and chartRangeMax\n* chartRangeMinX - Specify the minimum value to use for the X range of the chart - Defaults to the minimum value supplied\n* chartRangeMaxX - Specify the maximum value to use for the X range of the chart - Defaults to the maximum value supplied\n* composite - If true then don't erase any existing chart attached to the tag, but draw\n* another chart over the top - Note that width and height are ignored if an\n* existing chart is detected.\n* tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values'\n* enableTagOptions - Whether to check tags for sparkline options\n* tagOptionsPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark'\n* disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a\n* hidden dom element, avoding a browser reflow\n* disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled,\n* making the plugin perform much like it did in 1.x\n* disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled)\n* disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled\n* defaults to false (highlights enabled)\n* highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase\n* tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body\n* tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied\n* tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis\n* tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis\n* tooltipFormatter - Optional callback that allows you to override the HTML displayed in the tooltip\n* callback is given arguments of (sparkline, options, fields)\n* tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title\n* tooltipFormat - A format string or SPFormat object (or an array thereof for multiple entries)\n* to control the format of the tooltip\n* tooltipPrefix - A string to prepend to each field displayed in a tooltip\n* tooltipSuffix - A string to append to each field displayed in a tooltip\n* tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true)\n* tooltipValueLookups - An object or range map to map field values to tooltip strings\n* (eg. to map -1 to \"Lost\", 0 to \"Draw\", and 1 to \"Win\")\n* numberFormatter - Optional callback for formatting numbers in tooltips\n* numberDigitGroupSep - Character to use for group separator in numbers \"1,234\" - Defaults to \",\"\n* numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to \".\"\n* numberDigitGroupCount - Number of digits between group separator - Defaults to 3\n*\n* There are 7 types of sparkline, selected by supplying a \"type\" option of 'line' (default),\n* 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box'\n* line - Line chart. Options:\n* spotColor - Set to '' to not end each line in a circular spot\n* minSpotColor - If set, color of spot at minimum value\n* maxSpotColor - If set, color of spot at maximum value\n* spotRadius - Radius in pixels\n* lineWidth - Width of line in pixels\n* normalRangeMin\n* normalRangeMax - If set draws a filled horizontal bar between these two values marking the \"normal\"\n* or expected range of values\n* normalRangeColor - Color to use for the above bar\n* drawNormalOnTop - Draw the normal range above the chart fill color if true\n* defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart\n* highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable\n* highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable\n* valueSpots - Specify which points to draw spots on, and in which color. Accepts a range map\n*\n* bar - Bar chart. Options:\n* barColor - Color of bars for postive values\n* negBarColor - Color of bars for negative values\n* zeroColor - Color of bars with zero values\n* nullColor - Color of bars with null values - Defaults to omitting the bar entirely\n* barWidth - Width of bars in pixels\n* colorMap - Optional mappnig of values to colors to override the *BarColor values above\n* can be an Array of values to control the color of individual bars or a range map\n* to specify colors for individual ranges of values\n* barSpacing - Gap between bars in pixels\n* zeroAxis - Centers the y-axis around zero if true\n*\n* tristate - Charts values of win (>0), lose (<0) or draw (=0)\n* posBarColor - Color of win values\n* negBarColor - Color of lose values\n* zeroBarColor - Color of draw values\n* barWidth - Width of bars in pixels\n* barSpacing - Gap between bars in pixels\n* colorMap - Optional mappnig of values to colors to override the *BarColor values above\n* can be an Array of values to control the color of individual bars or a range map\n* to specify colors for individual ranges of values\n*\n* discrete - Options:\n* lineHeight - Height of each line in pixels - Defaults to 30% of the graph height\n* thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor\n* thresholdColor\n*\n* bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ...\n* options:\n* targetColor - The color of the vertical target marker\n* targetWidth - The width of the target marker in pixels\n* performanceColor - The color of the performance measure horizontal bar\n* rangeColors - Colors to use for each qualitative range background color\n*\n* pie - Pie chart. Options:\n* sliceColors - An array of colors to use for pie slices\n* offset - Angle in degrees to offset the first slice - Try -90 or +90\n* borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border)\n* borderColor - Color to use for the pie chart border - Defaults to #000\n*\n* box - Box plot. Options:\n* raw - Set to true to supply pre-computed plot points as values\n* values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier\n* When set to false you can supply any number of values and the box plot will\n* be computed for you. Default is false.\n* showOutliers - Set to true (default) to display outliers as circles\n* outlierIQR - Interquartile range used to determine outliers. Default 1.5\n* boxLineColor - Outline color of the box\n* boxFillColor - Fill color for the box\n* whiskerColor - Line color used for whiskers\n* outlierLineColor - Outline color of outlier circles\n* outlierFillColor - Fill color of the outlier circles\n* spotRadius - Radius of outlier circles\n* medianColor - Line color of the median line\n* target - Draw a target cross hair at the supplied value (default undefined)\n*\n*\n*\n* Examples:\n* $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false });\n* $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 });\n* $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }):\n* $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' });\n* $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' });\n* $('#pie').sparkline([1,1,2], { type:'pie' });\n*/\n\n/*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */\n\n(function(document, Math, undefined) { // performance/minified-size optimization\n(function(factory) {\n if(typeof define === 'function' && define.amd) {\n define(['jquery'], factory);\n } else if (jQuery && !jQuery.fn.sparkline) {\n factory(jQuery);\n }\n}\n(function($) {\n 'use strict';\n\n var UNSET_OPTION = {},\n getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues,\n remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap,\n MouseHandler, Tooltip, barHighlightMixin,\n line, bar, tristate, discrete, bullet, pie, box, defaultStyles, initStyles,\n VShape, VCanvas_base, VCanvas_canvas, VCanvas_vml, pending, shapeCount = 0;\n\n /**\n * Default configuration settings\n */\n getDefaults = function () {\n return {\n // Settings common to most/all chart types\n common: {\n type: 'line',\n lineColor: '#00f',\n fillColor: '#cdf',\n defaultPixelsPerValue: 3,\n width: 'auto',\n height: 'auto',\n composite: false,\n tagValuesAttribute: 'values',\n tagOptionsPrefix: 'spark',\n enableTagOptions: false,\n enableHighlight: true,\n highlightLighten: 1.4,\n tooltipSkipNull: true,\n tooltipPrefix: '',\n tooltipSuffix: '',\n disableHiddenCheck: false,\n numberFormatter: false,\n numberDigitGroupCount: 3,\n numberDigitGroupSep: ',',\n numberDecimalMark: '.',\n disableTooltips: false,\n disableInteraction: false\n },\n // Defaults for line charts\n line: {\n spotColor: '#f80',\n highlightSpotColor: '#5f5',\n highlightLineColor: '#f22',\n spotRadius: 1.5,\n minSpotColor: '#f80',\n maxSpotColor: '#f80',\n lineWidth: 1,\n normalRangeMin: undefined,\n normalRangeMax: undefined,\n normalRangeColor: '#ccc',\n drawNormalOnTop: false,\n chartRangeMin: undefined,\n chartRangeMax: undefined,\n chartRangeMinX: undefined,\n chartRangeMaxX: undefined,\n tooltipFormat: new SPFormat(' {{prefix}}{{y}}{{suffix}}')\n },\n // Defaults for bar charts\n bar: {\n barColor: '#3366cc',\n negBarColor: '#f44',\n stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',\n '#dd4477', '#0099c6', '#990099'],\n zeroColor: undefined,\n nullColor: undefined,\n zeroAxis: true,\n barWidth: 4,\n barSpacing: 1,\n chartRangeMax: undefined,\n chartRangeMin: undefined,\n chartRangeClip: false,\n colorMap: undefined,\n tooltipFormat: new SPFormat(' {{prefix}}{{value}}{{suffix}}')\n },\n // Defaults for tristate charts\n tristate: {\n barWidth: 4,\n barSpacing: 1,\n posBarColor: '#6f6',\n negBarColor: '#f44',\n zeroBarColor: '#999',\n colorMap: {},\n tooltipFormat: new SPFormat(' {{value:map}}'),\n tooltipValueLookups: { map: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } }\n },\n // Defaults for discrete charts\n discrete: {\n lineHeight: 'auto',\n thresholdColor: undefined,\n thresholdValue: 0,\n chartRangeMax: undefined,\n chartRangeMin: undefined,\n chartRangeClip: false,\n tooltipFormat: new SPFormat('{{prefix}}{{value}}{{suffix}}')\n },\n // Defaults for bullet charts\n bullet: {\n targetColor: '#f33',\n targetWidth: 3, // width of the target bar in pixels\n performanceColor: '#33f',\n rangeColors: ['#d3dafe', '#a8b6ff', '#7f94ff'],\n base: undefined, // set this to a number to change the base start number\n tooltipFormat: new SPFormat('{{fieldkey:fields}} - {{value}}'),\n tooltipValueLookups: { fields: {r: 'Range', p: 'Performance', t: 'Target'} }\n },\n // Defaults for pie charts\n pie: {\n offset: 0,\n sliceColors: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',\n '#dd4477', '#0099c6', '#990099'],\n borderWidth: 0,\n borderColor: '#000',\n tooltipFormat: new SPFormat(' {{value}} ({{percent.1}}%)')\n },\n // Defaults for box plots\n box: {\n raw: false,\n boxLineColor: '#000',\n boxFillColor: '#cdf',\n whiskerColor: '#000',\n outlierLineColor: '#333',\n outlierFillColor: '#fff',\n medianColor: '#f00',\n showOutliers: true,\n outlierIQR: 1.5,\n spotRadius: 1.5,\n target: undefined,\n targetColor: '#4a2',\n chartRangeMax: undefined,\n chartRangeMin: undefined,\n tooltipFormat: new SPFormat('{{field:fields}}: {{value}}'),\n tooltipFormatFieldlistKey: 'field',\n tooltipValueLookups: { fields: { lq: 'Lower Quartile', med: 'Median',\n uq: 'Upper Quartile', lo: 'Left Outlier', ro: 'Right Outlier',\n lw: 'Left Whisker', rw: 'Right Whisker'} }\n }\n };\n };\n\n // You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname\n defaultStyles = '.jqstooltip { ' +\n 'position: absolute;' +\n 'left: 0px;' +\n 'top: 0px;' +\n 'visibility: hidden;' +\n 'background: rgb(0, 0, 0) transparent;' +\n 'background-color: rgba(0,0,0,0.6);' +\n 'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' +\n '-ms-filter: \"progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)\";' +\n 'color: white;' +\n 'font: 10px arial, san serif;' +\n 'text-align: left;' +\n 'white-space: nowrap;' +\n 'padding: 5px;' +\n 'border: 1px solid white;' +\n 'box-sizing: content-box;' +\n 'z-index: 10000;' +\n '}' +\n '.jqsfield { ' +\n 'color: white;' +\n 'font: 10px arial, san serif;' +\n 'text-align: left;' +\n '}';\n\n /**\n * Utilities\n */\n\n createClass = function (/* [baseclass, [mixin, ...]], definition */) {\n var Class, args;\n Class = function () {\n this.init.apply(this, arguments);\n };\n if (arguments.length > 1) {\n if (arguments[0]) {\n Class.prototype = $.extend(new arguments[0](), arguments[arguments.length - 1]);\n Class._super = arguments[0].prototype;\n } else {\n Class.prototype = arguments[arguments.length - 1];\n }\n if (arguments.length > 2) {\n args = Array.prototype.slice.call(arguments, 1, -1);\n args.unshift(Class.prototype);\n $.extend.apply($, args);\n }\n } else {\n Class.prototype = arguments[0];\n }\n Class.prototype.cls = Class;\n return Class;\n };\n\n /**\n * Wraps a format string for tooltips\n * {{x}}\n * {{x.2}\n * {{x:months}}\n */\n $.SPFormatClass = SPFormat = createClass({\n fre: /\\{\\{([\\w.]+?)(:(.+?))?\\}\\}/g,\n precre: /(\\w+)\\.(\\d+)/,\n\n init: function (format, fclass) {\n this.format = format;\n this.fclass = fclass;\n },\n\n render: function (fieldset, lookups, options) {\n var self = this,\n fields = fieldset,\n match, token, lookupkey, fieldvalue, prec;\n return this.format.replace(this.fre, function () {\n var lookup;\n token = arguments[1];\n lookupkey = arguments[3];\n match = self.precre.exec(token);\n if (match) {\n prec = match[2];\n token = match[1];\n } else {\n prec = false;\n }\n fieldvalue = fields[token];\n if (fieldvalue === undefined) {\n return '';\n }\n if (lookupkey && lookups && lookups[lookupkey]) {\n lookup = lookups[lookupkey];\n if (lookup.get) { // RangeMap\n return lookups[lookupkey].get(fieldvalue) || fieldvalue;\n } else {\n return lookups[lookupkey][fieldvalue] || fieldvalue;\n }\n }\n if (isNumber(fieldvalue)) {\n if (options.get('numberFormatter')) {\n fieldvalue = options.get('numberFormatter')(fieldvalue);\n } else {\n fieldvalue = formatNumber(fieldvalue, prec,\n options.get('numberDigitGroupCount'),\n options.get('numberDigitGroupSep'),\n options.get('numberDecimalMark'));\n }\n }\n return fieldvalue;\n });\n }\n });\n\n // convience method to avoid needing the new operator\n $.spformat = function(format, fclass) {\n return new SPFormat(format, fclass);\n };\n\n clipval = function (val, min, max) {\n if (val < min) {\n return min;\n }\n if (val > max) {\n return max;\n }\n return val;\n };\n\n quartile = function (values, q) {\n var vl;\n if (q === 2) {\n vl = Math.floor(values.length / 2);\n return values.length % 2 ? values[vl] : (values[vl-1] + values[vl]) / 2;\n } else {\n if (values.length % 2 ) { // odd\n vl = (values.length * q + q) / 4;\n return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];\n } else { //even\n vl = (values.length * q + 2) / 4;\n return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];\n\n }\n }\n };\n\n normalizeValue = function (val) {\n var nf;\n switch (val) {\n case 'undefined':\n val = undefined;\n break;\n case 'null':\n val = null;\n break;\n case 'true':\n val = true;\n break;\n case 'false':\n val = false;\n break;\n default:\n nf = parseFloat(val);\n if (val == nf) {\n val = nf;\n }\n }\n return val;\n };\n\n normalizeValues = function (vals) {\n var i, result = [];\n for (i = vals.length; i--;) {\n result[i] = normalizeValue(vals[i]);\n }\n return result;\n };\n\n remove = function (vals, filter) {\n var i, vl, result = [];\n for (i = 0, vl = vals.length; i < vl; i++) {\n if (vals[i] !== filter) {\n result.push(vals[i]);\n }\n }\n return result;\n };\n\n isNumber = function (num) {\n return !isNaN(parseFloat(num)) && isFinite(num);\n };\n\n formatNumber = function (num, prec, groupsize, groupsep, decsep) {\n var p, i;\n num = (prec === false ? parseFloat(num).toString() : num.toFixed(prec)).split('');\n p = (p = $.inArray('.', num)) < 0 ? num.length : p;\n if (p < num.length) {\n num[p] = decsep;\n }\n for (i = p - groupsize; i > 0; i -= groupsize) {\n num.splice(i, 0, groupsep);\n }\n return num.join('');\n };\n\n // determine if all values of an array match a value\n // returns true if the array is empty\n all = function (val, arr, ignoreNull) {\n var i;\n for (i = arr.length; i--; ) {\n if (ignoreNull && arr[i] === null) continue;\n if (arr[i] !== val) {\n return false;\n }\n }\n return true;\n };\n\n // sums the numeric values in an array, ignoring other values\n sum = function (vals) {\n var total = 0, i;\n for (i = vals.length; i--;) {\n total += typeof vals[i] === 'number' ? vals[i] : 0;\n }\n return total;\n };\n\n ensureArray = function (val) {\n return $.isArray(val) ? val : [val];\n };\n\n // http://paulirish.com/2008/bookmarklet-inject-new-css-rules/\n addCSS = function(css) {\n var tag, iefail;\n if (document.createStyleSheet) {\n try {\n document.createStyleSheet().cssText = css;\n return;\n } catch (e) {\n // IE <= 9 maxes out at 31 stylesheets; inject into page instead.\n iefail = true;\n }\n }\n tag = document.createElement('style');\n tag.type = 'text/css';\n document.getElementsByTagName('head')[0].appendChild(tag);\n if (iefail) {\n document.styleSheets[document.styleSheets.length - 1].cssText = css;\n } else {\n tag[(typeof document.body.style.WebkitAppearance == 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css;\n }\n };\n\n // Provide a cross-browser interface to a few simple drawing primitives\n $.fn.simpledraw = function (width, height, useExisting, interact) {\n var target, mhandler;\n if (useExisting && (target = this.data('_jqs_vcanvas'))) {\n return target;\n }\n\n if ($.fn.sparkline.canvas === false) {\n // We've already determined that neither Canvas nor VML are available\n return false;\n\n } else if ($.fn.sparkline.canvas === undefined) {\n // No function defined yet -- need to see if we support Canvas or VML\n var el = document.createElement('canvas');\n if (!!(el.getContext && el.getContext('2d'))) {\n // Canvas is available\n $.fn.sparkline.canvas = function(width, height, target, interact) {\n return new VCanvas_canvas(width, height, target, interact);\n };\n } else if (document.namespaces && !document.namespaces.v) {\n // VML is available\n document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');\n $.fn.sparkline.canvas = function(width, height, target, interact) {\n return new VCanvas_vml(width, height, target);\n };\n } else {\n // Neither Canvas nor VML are available\n $.fn.sparkline.canvas = false;\n return false;\n }\n }\n\n if (width === undefined) {\n width = $(this).innerWidth();\n }\n if (height === undefined) {\n height = $(this).innerHeight();\n }\n\n target = $.fn.sparkline.canvas(width, height, this, interact);\n\n mhandler = $(this).data('_jqs_mhandler');\n if (mhandler) {\n mhandler.registerCanvas(target);\n }\n return target;\n };\n\n $.fn.cleardraw = function () {\n var target = this.data('_jqs_vcanvas');\n if (target) {\n target.reset();\n }\n };\n\n $.RangeMapClass = RangeMap = createClass({\n init: function (map) {\n var key, range, rangelist = [];\n for (key in map) {\n if (map.hasOwnProperty(key) && typeof key === 'string' && key.indexOf(':') > -1) {\n range = key.split(':');\n range[0] = range[0].length === 0 ? -Infinity : parseFloat(range[0]);\n range[1] = range[1].length === 0 ? Infinity : parseFloat(range[1]);\n range[2] = map[key];\n rangelist.push(range);\n }\n }\n this.map = map;\n this.rangelist = rangelist || false;\n },\n\n get: function (value) {\n var rangelist = this.rangelist,\n i, range, result;\n if ((result = this.map[value]) !== undefined) {\n return result;\n }\n if (rangelist) {\n for (i = rangelist.length; i--;) {\n range = rangelist[i];\n if (range[0] <= value && range[1] >= value) {\n return range[2];\n }\n }\n }\n return undefined;\n }\n });\n\n // Convenience function\n $.range_map = function(map) {\n return new RangeMap(map);\n };\n\n MouseHandler = createClass({\n init: function (el, options) {\n var $el = $(el);\n this.$el = $el;\n this.options = options;\n this.currentPageX = 0;\n this.currentPageY = 0;\n this.el = el;\n this.splist = [];\n this.tooltip = null;\n this.over = false;\n this.displayTooltips = !options.get('disableTooltips');\n this.highlightEnabled = !options.get('disableHighlight');\n },\n\n registerSparkline: function (sp) {\n this.splist.push(sp);\n if (this.over) {\n this.updateDisplay();\n }\n },\n\n registerCanvas: function (canvas) {\n var $canvas = $(canvas.canvas);\n this.canvas = canvas;\n this.$canvas = $canvas;\n $canvas.mouseenter($.proxy(this.mouseenter, this));\n $canvas.mouseleave($.proxy(this.mouseleave, this));\n $canvas.click($.proxy(this.mouseclick, this));\n },\n\n reset: function (removeTooltip) {\n this.splist = [];\n if (this.tooltip && removeTooltip) {\n this.tooltip.remove();\n this.tooltip = undefined;\n }\n },\n\n mouseclick: function (e) {\n var clickEvent = $.Event('sparklineClick');\n clickEvent.originalEvent = e;\n clickEvent.sparklines = this.splist;\n this.$el.trigger(clickEvent);\n },\n\n mouseenter: function (e) {\n $(document.body).unbind('mousemove.jqs');\n $(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this));\n this.over = true;\n this.currentPageX = e.pageX;\n this.currentPageY = e.pageY;\n this.currentEl = e.target;\n if (!this.tooltip && this.displayTooltips) {\n this.tooltip = new Tooltip(this.options);\n this.tooltip.updatePosition(e.pageX, e.pageY);\n }\n this.updateDisplay();\n },\n\n mouseleave: function () {\n $(document.body).unbind('mousemove.jqs');\n var splist = this.splist,\n spcount = splist.length,\n needsRefresh = false,\n sp, i;\n this.over = false;\n this.currentEl = null;\n\n if (this.tooltip) {\n this.tooltip.remove();\n this.tooltip = null;\n }\n\n for (i = 0; i < spcount; i++) {\n sp = splist[i];\n if (sp.clearRegionHighlight()) {\n needsRefresh = true;\n }\n }\n\n if (needsRefresh) {\n this.canvas.render();\n }\n },\n\n mousemove: function (e) {\n this.currentPageX = e.pageX;\n this.currentPageY = e.pageY;\n this.currentEl = e.target;\n if (this.tooltip) {\n this.tooltip.updatePosition(e.pageX, e.pageY);\n }\n this.updateDisplay();\n },\n\n updateDisplay: function () {\n var splist = this.splist,\n spcount = splist.length,\n needsRefresh = false,\n offset = this.$canvas.offset(),\n localX = this.currentPageX - offset.left,\n localY = this.currentPageY - offset.top,\n tooltiphtml, sp, i, result, changeEvent;\n if (!this.over) {\n return;\n }\n for (i = 0; i < spcount; i++) {\n sp = splist[i];\n result = sp.setRegionHighlight(this.currentEl, localX, localY);\n if (result) {\n needsRefresh = true;\n }\n }\n if (needsRefresh) {\n changeEvent = $.Event('sparklineRegionChange');\n changeEvent.sparklines = this.splist;\n this.$el.trigger(changeEvent);\n if (this.tooltip) {\n tooltiphtml = '';\n for (i = 0; i < spcount; i++) {\n sp = splist[i];\n tooltiphtml += sp.getCurrentRegionTooltip();\n }\n this.tooltip.setContent(tooltiphtml);\n }\n if (!this.disableHighlight) {\n this.canvas.render();\n }\n }\n if (result === null) {\n this.mouseleave();\n }\n }\n });\n\n\n Tooltip = createClass({\n sizeStyle: 'position: static !important;' +\n 'display: block !important;' +\n 'visibility: hidden !important;' +\n 'float: left !important;',\n\n init: function (options) {\n var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'),\n sizetipStyle = this.sizeStyle,\n offset;\n this.container = options.get('tooltipContainer') || document.body;\n this.tooltipOffsetX = options.get('tooltipOffsetX', 10);\n this.tooltipOffsetY = options.get('tooltipOffsetY', 12);\n // remove any previous lingering tooltip\n $('#jqssizetip').remove();\n $('#jqstooltip').remove();\n this.sizetip = $('
', {\n id: 'jqssizetip',\n style: sizetipStyle,\n 'class': tooltipClassname\n });\n this.tooltip = $('
', {\n id: 'jqstooltip',\n 'class': tooltipClassname\n }).appendTo(this.container);\n // account for the container's location\n offset = this.tooltip.offset();\n this.offsetLeft = offset.left;\n this.offsetTop = offset.top;\n this.hidden = true;\n $(window).unbind('resize.jqs scroll.jqs');\n $(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this));\n this.updateWindowDims();\n },\n\n updateWindowDims: function () {\n this.scrollTop = $(window).scrollTop();\n this.scrollLeft = $(window).scrollLeft();\n this.scrollRight = this.scrollLeft + $(window).width();\n this.updatePosition();\n },\n\n getSize: function (content) {\n this.sizetip.html(content).appendTo(this.container);\n this.width = this.sizetip.width() + 1;\n this.height = this.sizetip.height();\n this.sizetip.remove();\n },\n\n setContent: function (content) {\n if (!content) {\n this.tooltip.css('visibility', 'hidden');\n this.hidden = true;\n return;\n }\n this.getSize(content);\n this.tooltip.html(content)\n .css({\n 'width': this.width,\n 'height': this.height,\n 'visibility': 'visible'\n });\n if (this.hidden) {\n this.hidden = false;\n this.updatePosition();\n }\n },\n\n updatePosition: function (x, y) {\n if (x === undefined) {\n if (this.mousex === undefined) {\n return;\n }\n x = this.mousex - this.offsetLeft;\n y = this.mousey - this.offsetTop;\n\n } else {\n this.mousex = x = x - this.offsetLeft;\n this.mousey = y = y - this.offsetTop;\n }\n if (!this.height || !this.width || this.hidden) {\n return;\n }\n\n y -= this.height + this.tooltipOffsetY;\n x += this.tooltipOffsetX;\n\n if (y < this.scrollTop) {\n y = this.scrollTop;\n }\n if (x < this.scrollLeft) {\n x = this.scrollLeft;\n } else if (x + this.width > this.scrollRight) {\n x = this.scrollRight - this.width;\n }\n\n this.tooltip.css({\n 'left': x,\n 'top': y\n });\n },\n\n remove: function () {\n this.tooltip.remove();\n this.sizetip.remove();\n this.sizetip = this.tooltip = undefined;\n $(window).unbind('resize.jqs scroll.jqs');\n }\n });\n\n initStyles = function() {\n addCSS(defaultStyles);\n };\n\n $(initStyles);\n\n pending = [];\n $.fn.sparkline = function (userValues, userOptions) {\n return this.each(function () {\n var options = new $.fn.sparkline.options(this, userOptions),\n $this = $(this),\n render, i;\n render = function () {\n var values, width, height, tmp, mhandler, sp, vals;\n if (userValues === 'html' || userValues === undefined) {\n vals = this.getAttribute(options.get('tagValuesAttribute'));\n if (vals === undefined || vals === null) {\n vals = $this.html();\n }\n values = vals.replace(/(^\\s*\\s*$)|\\s+/g, '').split(',');\n } else {\n values = userValues;\n }\n\n width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width');\n if (options.get('height') === 'auto') {\n if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) {\n // must be a better way to get the line height\n tmp = document.createElement('span');\n tmp.innerHTML = 'a';\n $this.html(tmp);\n height = $(tmp).innerHeight() || $(tmp).height();\n $(tmp).remove();\n tmp = null;\n }\n } else {\n height = options.get('height');\n }\n\n if (!options.get('disableInteraction')) {\n mhandler = $.data(this, '_jqs_mhandler');\n if (!mhandler) {\n mhandler = new MouseHandler(this, options);\n $.data(this, '_jqs_mhandler', mhandler);\n } else if (!options.get('composite')) {\n mhandler.reset();\n }\n } else {\n mhandler = false;\n }\n\n if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) {\n if (!$.data(this, '_jqs_errnotify')) {\n alert('Attempted to attach a composite sparkline to an element with no existing sparkline');\n $.data(this, '_jqs_errnotify', true);\n }\n return;\n }\n\n sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height);\n\n sp.render();\n\n if (mhandler) {\n mhandler.registerSparkline(sp);\n }\n };\n if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || !$(this).parents('body').length) {\n if (!options.get('composite') && $.data(this, '_jqs_pending')) {\n // remove any existing references to the element\n for (i = pending.length; i; i--) {\n if (pending[i - 1][0] == this) {\n pending.splice(i - 1, 1);\n }\n }\n }\n pending.push([this, render]);\n $.data(this, '_jqs_pending', true);\n } else {\n render.call(this);\n }\n });\n };\n\n $.fn.sparkline.defaults = getDefaults();\n\n\n $.sparkline_display_visible = function () {\n var el, i, pl;\n var done = [];\n for (i = 0, pl = pending.length; i < pl; i++) {\n el = pending[i][0];\n if ($(el).is(':visible') && !$(el).parents().is(':hidden')) {\n pending[i][1].call(el);\n $.data(pending[i][0], '_jqs_pending', false);\n done.push(i);\n } else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) {\n // element has been inserted and removed from the DOM\n // If it was not yet inserted into the dom then the .data request\n // will return true.\n // removing from the dom causes the data to be removed.\n $.data(pending[i][0], '_jqs_pending', false);\n done.push(i);\n }\n }\n for (i = done.length; i; i--) {\n pending.splice(done[i - 1], 1);\n }\n };\n\n\n /**\n * User option handler\n */\n $.fn.sparkline.options = createClass({\n init: function (tag, userOptions) {\n var extendedOptions, defaults, base, tagOptionType;\n this.userOptions = userOptions = userOptions || {};\n this.tag = tag;\n this.tagValCache = {};\n defaults = $.fn.sparkline.defaults;\n base = defaults.common;\n this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix);\n\n tagOptionType = this.getTagSetting('type');\n if (tagOptionType === UNSET_OPTION) {\n extendedOptions = defaults[userOptions.type || base.type];\n } else {\n extendedOptions = defaults[tagOptionType];\n }\n this.mergedOptions = $.extend({}, base, extendedOptions, userOptions);\n },\n\n\n getTagSetting: function (key) {\n var prefix = this.tagOptionsPrefix,\n val, i, pairs, keyval;\n if (prefix === false || prefix === undefined) {\n return UNSET_OPTION;\n }\n if (this.tagValCache.hasOwnProperty(key)) {\n val = this.tagValCache.key;\n } else {\n val = this.tag.getAttribute(prefix + key);\n if (val === undefined || val === null) {\n val = UNSET_OPTION;\n } else if (val.substr(0, 1) === '[') {\n val = val.substr(1, val.length - 2).split(',');\n for (i = val.length; i--;) {\n val[i] = normalizeValue(val[i].replace(/(^\\s*)|(\\s*$)/g, ''));\n }\n } else if (val.substr(0, 1) === '{') {\n pairs = val.substr(1, val.length - 2).split(',');\n val = {};\n for (i = pairs.length; i--;) {\n keyval = pairs[i].split(':', 2);\n val[keyval[0].replace(/(^\\s*)|(\\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\\s*)|(\\s*$)/g, ''));\n }\n } else {\n val = normalizeValue(val);\n }\n this.tagValCache.key = val;\n }\n return val;\n },\n\n get: function (key, defaultval) {\n var tagOption = this.getTagSetting(key),\n result;\n if (tagOption !== UNSET_OPTION) {\n return tagOption;\n }\n return (result = this.mergedOptions[key]) === undefined ? defaultval : result;\n }\n });\n\n\n $.fn.sparkline._base = createClass({\n disabled: false,\n\n init: function (el, values, options, width, height) {\n this.el = el;\n this.$el = $(el);\n this.values = values;\n this.options = options;\n this.width = width;\n this.height = height;\n this.currentRegion = undefined;\n },\n\n /**\n * Setup the canvas\n */\n initTarget: function () {\n var interactive = !this.options.get('disableInteraction');\n if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) {\n this.disabled = true;\n } else {\n this.canvasWidth = this.target.pixelWidth;\n this.canvasHeight = this.target.pixelHeight;\n }\n },\n\n /**\n * Actually render the chart to the canvas\n */\n render: function () {\n if (this.disabled) {\n this.el.innerHTML = '';\n return false;\n }\n return true;\n },\n\n /**\n * Return a region id for a given x/y co-ordinate\n */\n getRegion: function (x, y) {\n },\n\n /**\n * Highlight an item based on the moused-over x,y co-ordinate\n */\n setRegionHighlight: function (el, x, y) {\n var currentRegion = this.currentRegion,\n highlightEnabled = !this.options.get('disableHighlight'),\n newRegion;\n if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) {\n return null;\n }\n newRegion = this.getRegion(el, x, y);\n if (currentRegion !== newRegion) {\n if (currentRegion !== undefined && highlightEnabled) {\n this.removeHighlight();\n }\n this.currentRegion = newRegion;\n if (newRegion !== undefined && highlightEnabled) {\n this.renderHighlight();\n }\n return true;\n }\n return false;\n },\n\n /**\n * Reset any currently highlighted item\n */\n clearRegionHighlight: function () {\n if (this.currentRegion !== undefined) {\n this.removeHighlight();\n this.currentRegion = undefined;\n return true;\n }\n return false;\n },\n\n renderHighlight: function () {\n this.changeHighlight(true);\n },\n\n removeHighlight: function () {\n this.changeHighlight(false);\n },\n\n changeHighlight: function (highlight) {},\n\n /**\n * Fetch the HTML to display as a tooltip\n */\n getCurrentRegionTooltip: function () {\n var options = this.options,\n header = '',\n entries = [],\n fields, formats, formatlen, fclass, text, i,\n showFields, showFieldsKey, newFields, fv,\n formatter, format, fieldlen, j;\n if (this.currentRegion === undefined) {\n return '';\n }\n fields = this.getCurrentRegionFields();\n formatter = options.get('tooltipFormatter');\n if (formatter) {\n return formatter(this, options, fields);\n }\n if (options.get('tooltipChartTitle')) {\n header += '
' + options.get('tooltipChartTitle') + '
\\n';\n }\n formats = this.options.get('tooltipFormat');\n if (!formats) {\n return '';\n }\n if (!$.isArray(formats)) {\n formats = [formats];\n }\n if (!$.isArray(fields)) {\n fields = [fields];\n }\n showFields = this.options.get('tooltipFormatFieldlist');\n showFieldsKey = this.options.get('tooltipFormatFieldlistKey');\n if (showFields && showFieldsKey) {\n // user-selected ordering of fields\n newFields = [];\n for (i = fields.length; i--;) {\n fv = fields[i][showFieldsKey];\n if ((j = $.inArray(fv, showFields)) != -1) {\n newFields[j] = fields[i];\n }\n }\n fields = newFields;\n }\n formatlen = formats.length;\n fieldlen = fields.length;\n for (i = 0; i < formatlen; i++) {\n format = formats[i];\n if (typeof format === 'string') {\n format = new SPFormat(format);\n }\n fclass = format.fclass || 'jqsfield';\n for (j = 0; j < fieldlen; j++) {\n if (!fields[j].isNull || !options.get('tooltipSkipNull')) {\n $.extend(fields[j], {\n prefix: options.get('tooltipPrefix'),\n suffix: options.get('tooltipSuffix')\n });\n text = format.render(fields[j], options.get('tooltipValueLookups'), options);\n entries.push('
' + text + '
');\n }\n }\n }\n if (entries.length) {\n return header + entries.join('\\n');\n }\n return '';\n },\n\n getCurrentRegionFields: function () {},\n\n calcHighlightColor: function (color, options) {\n var highlightColor = options.get('highlightColor'),\n lighten = options.get('highlightLighten'),\n parse, mult, rgbnew, i;\n if (highlightColor) {\n return highlightColor;\n }\n if (lighten) {\n // extract RGB values\n parse = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color) || /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);\n if (parse) {\n rgbnew = [];\n mult = color.length === 4 ? 16 : 1;\n for (i = 0; i < 3; i++) {\n rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255);\n }\n return 'rgb(' + rgbnew.join(',') + ')';\n }\n\n }\n return color;\n }\n\n });\n\n barHighlightMixin = {\n changeHighlight: function (highlight) {\n var currentRegion = this.currentRegion,\n target = this.target,\n shapeids = this.regionShapes[currentRegion],\n newShapes;\n // will be null if the region value was null\n if (shapeids) {\n newShapes = this.renderRegion(currentRegion, highlight);\n if ($.isArray(newShapes) || $.isArray(shapeids)) {\n target.replaceWithShapes(shapeids, newShapes);\n this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) {\n return newShape.id;\n });\n } else {\n target.replaceWithShape(shapeids, newShapes);\n this.regionShapes[currentRegion] = newShapes.id;\n }\n }\n },\n\n render: function () {\n var values = this.values,\n target = this.target,\n regionShapes = this.regionShapes,\n shapes, ids, i, j;\n\n if (!this.cls._super.render.call(this)) {\n return;\n }\n for (i = values.length; i--;) {\n shapes = this.renderRegion(i);\n if (shapes) {\n if ($.isArray(shapes)) {\n ids = [];\n for (j = shapes.length; j--;) {\n shapes[j].append();\n ids.push(shapes[j].id);\n }\n regionShapes[i] = ids;\n } else {\n shapes.append();\n regionShapes[i] = shapes.id; // store just the shapeid\n }\n } else {\n // null value\n regionShapes[i] = null;\n }\n }\n target.render();\n }\n };\n\n /**\n * Line charts\n */\n $.fn.sparkline.line = line = createClass($.fn.sparkline._base, {\n type: 'line',\n\n init: function (el, values, options, width, height) {\n line._super.init.call(this, el, values, options, width, height);\n this.vertices = [];\n this.regionMap = [];\n this.xvalues = [];\n this.yvalues = [];\n this.yminmax = [];\n this.hightlightSpotId = null;\n this.lastShapeId = null;\n this.initTarget();\n },\n\n getRegion: function (el, x, y) {\n var i,\n regionMap = this.regionMap; // maps regions to value positions\n for (i = regionMap.length; i--;) {\n if (regionMap[i] !== null && x >= regionMap[i][0] && x <= regionMap[i][1]) {\n return regionMap[i][2];\n }\n }\n return undefined;\n },\n\n getCurrentRegionFields: function () {\n var currentRegion = this.currentRegion;\n return {\n isNull: this.yvalues[currentRegion] === null,\n x: this.xvalues[currentRegion],\n y: this.yvalues[currentRegion],\n color: this.options.get('lineColor'),\n fillColor: this.options.get('fillColor'),\n offset: currentRegion\n };\n },\n\n renderHighlight: function () {\n var currentRegion = this.currentRegion,\n target = this.target,\n vertex = this.vertices[currentRegion],\n options = this.options,\n spotRadius = options.get('spotRadius'),\n highlightSpotColor = options.get('highlightSpotColor'),\n highlightLineColor = options.get('highlightLineColor'),\n highlightSpot, highlightLine;\n\n if (!vertex) {\n return;\n }\n if (spotRadius && highlightSpotColor) {\n highlightSpot = target.drawCircle(vertex[0], vertex[1],\n spotRadius, undefined, highlightSpotColor);\n this.highlightSpotId = highlightSpot.id;\n target.insertAfterShape(this.lastShapeId, highlightSpot);\n }\n if (highlightLineColor) {\n highlightLine = target.drawLine(vertex[0], this.canvasTop, vertex[0],\n this.canvasTop + this.canvasHeight, highlightLineColor);\n this.highlightLineId = highlightLine.id;\n target.insertAfterShape(this.lastShapeId, highlightLine);\n }\n },\n\n removeHighlight: function () {\n var target = this.target;\n if (this.highlightSpotId) {\n target.removeShapeId(this.highlightSpotId);\n this.highlightSpotId = null;\n }\n if (this.highlightLineId) {\n target.removeShapeId(this.highlightLineId);\n this.highlightLineId = null;\n }\n },\n\n scanValues: function () {\n var values = this.values,\n valcount = values.length,\n xvalues = this.xvalues,\n yvalues = this.yvalues,\n yminmax = this.yminmax,\n i, val, isStr, isArray, sp;\n for (i = 0; i < valcount; i++) {\n val = values[i];\n isStr = typeof(values[i]) === 'string';\n isArray = typeof(values[i]) === 'object' && values[i] instanceof Array;\n sp = isStr && values[i].split(':');\n if (isStr && sp.length === 2) { // x:y\n xvalues.push(Number(sp[0]));\n yvalues.push(Number(sp[1]));\n yminmax.push(Number(sp[1]));\n } else if (isArray) {\n xvalues.push(val[0]);\n yvalues.push(val[1]);\n yminmax.push(val[1]);\n } else {\n xvalues.push(i);\n if (values[i] === null || values[i] === 'null') {\n yvalues.push(null);\n } else {\n yvalues.push(Number(val));\n yminmax.push(Number(val));\n }\n }\n }\n if (this.options.get('xvalues')) {\n xvalues = this.options.get('xvalues');\n }\n\n this.maxy = this.maxyorg = Math.max.apply(Math, yminmax);\n this.miny = this.minyorg = Math.min.apply(Math, yminmax);\n\n this.maxx = Math.max.apply(Math, xvalues);\n this.minx = Math.min.apply(Math, xvalues);\n\n this.xvalues = xvalues;\n this.yvalues = yvalues;\n this.yminmax = yminmax;\n\n },\n\n processRangeOptions: function () {\n var options = this.options,\n normalRangeMin = options.get('normalRangeMin'),\n normalRangeMax = options.get('normalRangeMax');\n\n if (normalRangeMin !== undefined) {\n if (normalRangeMin < this.miny) {\n this.miny = normalRangeMin;\n }\n if (normalRangeMax > this.maxy) {\n this.maxy = normalRangeMax;\n }\n }\n if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.miny)) {\n this.miny = options.get('chartRangeMin');\n }\n if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.maxy)) {\n this.maxy = options.get('chartRangeMax');\n }\n if (options.get('chartRangeMinX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX') < this.minx)) {\n this.minx = options.get('chartRangeMinX');\n }\n if (options.get('chartRangeMaxX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX') > this.maxx)) {\n this.maxx = options.get('chartRangeMaxX');\n }\n\n },\n\n drawNormalRange: function (canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey) {\n var normalRangeMin = this.options.get('normalRangeMin'),\n normalRangeMax = this.options.get('normalRangeMax'),\n ytop = canvasTop + Math.round(canvasHeight - (canvasHeight * ((normalRangeMax - this.miny) / rangey))),\n height = Math.round((canvasHeight * (normalRangeMax - normalRangeMin)) / rangey);\n this.target.drawRect(canvasLeft, ytop, canvasWidth, height, undefined, this.options.get('normalRangeColor')).append();\n },\n\n render: function () {\n var options = this.options,\n target = this.target,\n canvasWidth = this.canvasWidth,\n canvasHeight = this.canvasHeight,\n vertices = this.vertices,\n spotRadius = options.get('spotRadius'),\n regionMap = this.regionMap,\n rangex, rangey, yvallast,\n canvasTop, canvasLeft,\n vertex, path, paths, x, y, xnext, xpos, xposnext,\n last, next, yvalcount, lineShapes, fillShapes, plen,\n valueSpots, hlSpotsEnabled, color, xvalues, yvalues, i;\n\n if (!line._super.render.call(this)) {\n return;\n }\n\n this.scanValues();\n this.processRangeOptions();\n\n xvalues = this.xvalues;\n yvalues = this.yvalues;\n\n if (!this.yminmax.length || this.yvalues.length < 2) {\n // empty or all null valuess\n return;\n }\n\n canvasTop = canvasLeft = 0;\n\n rangex = this.maxx - this.minx === 0 ? 1 : this.maxx - this.minx;\n rangey = this.maxy - this.miny === 0 ? 1 : this.maxy - this.miny;\n yvallast = this.yvalues.length - 1;\n\n if (spotRadius && (canvasWidth < (spotRadius * 4) || canvasHeight < (spotRadius * 4))) {\n spotRadius = 0;\n }\n if (spotRadius) {\n // adjust the canvas size as required so that spots will fit\n hlSpotsEnabled = options.get('highlightSpotColor') && !options.get('disableInteraction');\n if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) {\n canvasHeight -= Math.ceil(spotRadius);\n }\n if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) {\n canvasHeight -= Math.ceil(spotRadius);\n canvasTop += Math.ceil(spotRadius);\n }\n if (hlSpotsEnabled ||\n ((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy))) {\n canvasLeft += Math.ceil(spotRadius);\n canvasWidth -= Math.ceil(spotRadius);\n }\n if (hlSpotsEnabled || options.get('spotColor') ||\n (options.get('minSpotColor') || options.get('maxSpotColor') &&\n (yvalues[yvallast] === this.miny || yvalues[yvallast] === this.maxy))) {\n canvasWidth -= Math.ceil(spotRadius);\n }\n }\n\n\n canvasHeight--;\n\n if (options.get('normalRangeMin') !== undefined && !options.get('drawNormalOnTop')) {\n this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);\n }\n\n path = [];\n paths = [path];\n last = next = null;\n yvalcount = yvalues.length;\n for (i = 0; i < yvalcount; i++) {\n x = xvalues[i];\n xnext = xvalues[i + 1];\n y = yvalues[i];\n xpos = canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex));\n xposnext = i < yvalcount - 1 ? canvasLeft + Math.round((xnext - this.minx) * (canvasWidth / rangex)) : canvasWidth;\n next = xpos + ((xposnext - xpos) / 2);\n regionMap[i] = [last || 0, next, i];\n last = next;\n if (y === null) {\n if (i) {\n if (yvalues[i - 1] !== null) {\n path = [];\n paths.push(path);\n }\n vertices.push(null);\n }\n } else {\n if (y < this.miny) {\n y = this.miny;\n }\n if (y > this.maxy) {\n y = this.maxy;\n }\n if (!path.length) {\n // previous value was null\n path.push([xpos, canvasTop + canvasHeight]);\n }\n vertex = [xpos, canvasTop + Math.round(canvasHeight - (canvasHeight * ((y - this.miny) / rangey)))];\n path.push(vertex);\n vertices.push(vertex);\n }\n }\n\n lineShapes = [];\n fillShapes = [];\n plen = paths.length;\n for (i = 0; i < plen; i++) {\n path = paths[i];\n if (path.length) {\n if (options.get('fillColor')) {\n path.push([path[path.length - 1][0], (canvasTop + canvasHeight)]);\n fillShapes.push(path.slice(0));\n path.pop();\n }\n // if there's only a single point in this path, then we want to display it\n // as a vertical line which means we keep path[0] as is\n if (path.length > 2) {\n // else we want the first value\n path[0] = [path[0][0], path[1][1]];\n }\n lineShapes.push(path);\n }\n }\n\n // draw the fill first, then optionally the normal range, then the line on top of that\n plen = fillShapes.length;\n for (i = 0; i < plen; i++) {\n target.drawShape(fillShapes[i],\n options.get('fillColor'), options.get('fillColor')).append();\n }\n\n if (options.get('normalRangeMin') !== undefined && options.get('drawNormalOnTop')) {\n this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);\n }\n\n plen = lineShapes.length;\n for (i = 0; i < plen; i++) {\n target.drawShape(lineShapes[i], options.get('lineColor'), undefined,\n options.get('lineWidth')).append();\n }\n\n if (spotRadius && options.get('valueSpots')) {\n valueSpots = options.get('valueSpots');\n if (valueSpots.get === undefined) {\n valueSpots = new RangeMap(valueSpots);\n }\n for (i = 0; i < yvalcount; i++) {\n color = valueSpots.get(yvalues[i]);\n if (color) {\n target.drawCircle(canvasLeft + Math.round((xvalues[i] - this.minx) * (canvasWidth / rangex)),\n canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[i] - this.miny) / rangey))),\n spotRadius, undefined,\n color).append();\n }\n }\n\n }\n if (spotRadius && options.get('spotColor') && yvalues[yvallast] !== null) {\n target.drawCircle(canvasLeft + Math.round((xvalues[xvalues.length - 1] - this.minx) * (canvasWidth / rangex)),\n canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[yvallast] - this.miny) / rangey))),\n spotRadius, undefined,\n options.get('spotColor')).append();\n }\n if (this.maxy !== this.minyorg) {\n if (spotRadius && options.get('minSpotColor')) {\n x = xvalues[$.inArray(this.minyorg, yvalues)];\n target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),\n canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.minyorg - this.miny) / rangey))),\n spotRadius, undefined,\n options.get('minSpotColor')).append();\n }\n if (spotRadius && options.get('maxSpotColor')) {\n x = xvalues[$.inArray(this.maxyorg, yvalues)];\n target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),\n canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.maxyorg - this.miny) / rangey))),\n spotRadius, undefined,\n options.get('maxSpotColor')).append();\n }\n }\n\n this.lastShapeId = target.getLastShapeId();\n this.canvasTop = canvasTop;\n target.render();\n }\n });\n\n /**\n * Bar charts\n */\n $.fn.sparkline.bar = bar = createClass($.fn.sparkline._base, barHighlightMixin, {\n type: 'bar',\n\n init: function (el, values, options, width, height) {\n var barWidth = parseInt(options.get('barWidth'), 10),\n barSpacing = parseInt(options.get('barSpacing'), 10),\n chartRangeMin = options.get('chartRangeMin'),\n chartRangeMax = options.get('chartRangeMax'),\n chartRangeClip = options.get('chartRangeClip'),\n stackMin = Infinity,\n stackMax = -Infinity,\n isStackString, groupMin, groupMax, stackRanges,\n numValues, i, vlen, range, zeroAxis, xaxisOffset, min, max, clipMin, clipMax,\n stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc, canvasHeightEf;\n bar._super.init.call(this, el, values, options, width, height);\n\n // scan values to determine whether to stack bars\n for (i = 0, vlen = values.length; i < vlen; i++) {\n val = values[i];\n isStackString = typeof(val) === 'string' && val.indexOf(':') > -1;\n if (isStackString || $.isArray(val)) {\n stacked = true;\n if (isStackString) {\n val = values[i] = normalizeValues(val.split(':'));\n }\n val = remove(val, null); // min/max will treat null as zero\n groupMin = Math.min.apply(Math, val);\n groupMax = Math.max.apply(Math, val);\n if (groupMin < stackMin) {\n stackMin = groupMin;\n }\n if (groupMax > stackMax) {\n stackMax = groupMax;\n }\n }\n }\n\n this.stacked = stacked;\n this.regionShapes = {};\n this.barWidth = barWidth;\n this.barSpacing = barSpacing;\n this.totalBarWidth = barWidth + barSpacing;\n this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);\n\n this.initTarget();\n\n if (chartRangeClip) {\n clipMin = chartRangeMin === undefined ? -Infinity : chartRangeMin;\n clipMax = chartRangeMax === undefined ? Infinity : chartRangeMax;\n }\n\n numValues = [];\n stackRanges = stacked ? [] : numValues;\n var stackTotals = [];\n var stackRangesNeg = [];\n for (i = 0, vlen = values.length; i < vlen; i++) {\n if (stacked) {\n vlist = values[i];\n values[i] = svals = [];\n stackTotals[i] = 0;\n stackRanges[i] = stackRangesNeg[i] = 0;\n for (j = 0, slen = vlist.length; j < slen; j++) {\n val = svals[j] = chartRangeClip ? clipval(vlist[j], clipMin, clipMax) : vlist[j];\n if (val !== null) {\n if (val > 0) {\n stackTotals[i] += val;\n }\n if (stackMin < 0 && stackMax > 0) {\n if (val < 0) {\n stackRangesNeg[i] += Math.abs(val);\n } else {\n stackRanges[i] += val;\n }\n } else {\n stackRanges[i] += Math.abs(val - (val < 0 ? stackMax : stackMin));\n }\n numValues.push(val);\n }\n }\n } else {\n val = chartRangeClip ? clipval(values[i], clipMin, clipMax) : values[i];\n val = values[i] = normalizeValue(val);\n if (val !== null) {\n numValues.push(val);\n }\n }\n }\n this.max = max = Math.max.apply(Math, numValues);\n this.min = min = Math.min.apply(Math, numValues);\n this.stackMax = stackMax = stacked ? Math.max.apply(Math, stackTotals) : max;\n this.stackMin = stackMin = stacked ? Math.min.apply(Math, numValues) : min;\n\n if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < min)) {\n min = options.get('chartRangeMin');\n }\n if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > max)) {\n max = options.get('chartRangeMax');\n }\n\n this.zeroAxis = zeroAxis = options.get('zeroAxis', true);\n if (min <= 0 && max >= 0 && zeroAxis) {\n xaxisOffset = 0;\n } else if (zeroAxis == false) {\n xaxisOffset = min;\n } else if (min > 0) {\n xaxisOffset = min;\n } else {\n xaxisOffset = max;\n }\n this.xaxisOffset = xaxisOffset;\n\n range = stacked ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg)) : max - min;\n\n // as we plot zero/min values a single pixel line, we add a pixel to all other\n // values - Reduce the effective canvas size to suit\n this.canvasHeightEf = (zeroAxis && min < 0) ? this.canvasHeight - 2 : this.canvasHeight - 1;\n\n if (min < xaxisOffset) {\n yMaxCalc = (stacked && max >= 0) ? stackMax : max;\n yoffset = (yMaxCalc - xaxisOffset) / range * this.canvasHeight;\n if (yoffset !== Math.ceil(yoffset)) {\n this.canvasHeightEf -= 2;\n yoffset = Math.ceil(yoffset);\n }\n } else {\n yoffset = this.canvasHeight;\n }\n this.yoffset = yoffset;\n\n if ($.isArray(options.get('colorMap'))) {\n this.colorMapByIndex = options.get('colorMap');\n this.colorMapByValue = null;\n } else {\n this.colorMapByIndex = null;\n this.colorMapByValue = options.get('colorMap');\n if (this.colorMapByValue && this.colorMapByValue.get === undefined) {\n this.colorMapByValue = new RangeMap(this.colorMapByValue);\n }\n }\n\n this.range = range;\n },\n\n getRegion: function (el, x, y) {\n var result = Math.floor(x / this.totalBarWidth);\n return (result < 0 || result >= this.values.length) ? undefined : result;\n },\n\n getCurrentRegionFields: function () {\n var currentRegion = this.currentRegion,\n values = ensureArray(this.values[currentRegion]),\n result = [],\n value, i;\n for (i = values.length; i--;) {\n value = values[i];\n result.push({\n isNull: value === null,\n value: value,\n color: this.calcColor(i, value, currentRegion),\n offset: currentRegion\n });\n }\n return result;\n },\n\n calcColor: function (stacknum, value, valuenum) {\n var colorMapByIndex = this.colorMapByIndex,\n colorMapByValue = this.colorMapByValue,\n options = this.options,\n color, newColor;\n if (this.stacked) {\n color = options.get('stackedBarColor');\n } else {\n color = (value < 0) ? options.get('negBarColor') : options.get('barColor');\n }\n if (value === 0 && options.get('zeroColor') !== undefined) {\n color = options.get('zeroColor');\n }\n if (colorMapByValue && (newColor = colorMapByValue.get(value))) {\n color = newColor;\n } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {\n color = colorMapByIndex[valuenum];\n }\n return $.isArray(color) ? color[stacknum % color.length] : color;\n },\n\n /**\n * Render bar(s) for a region\n */\n renderRegion: function (valuenum, highlight) {\n var vals = this.values[valuenum],\n options = this.options,\n xaxisOffset = this.xaxisOffset,\n result = [],\n range = this.range,\n stacked = this.stacked,\n target = this.target,\n x = valuenum * this.totalBarWidth,\n canvasHeightEf = this.canvasHeightEf,\n yoffset = this.yoffset,\n y, height, color, isNull, yoffsetNeg, i, valcount, val, minPlotted, allMin;\n\n vals = $.isArray(vals) ? vals : [vals];\n valcount = vals.length;\n val = vals[0];\n isNull = all(null, vals);\n allMin = all(xaxisOffset, vals, true);\n\n if (isNull) {\n if (options.get('nullColor')) {\n color = highlight ? options.get('nullColor') : this.calcHighlightColor(options.get('nullColor'), options);\n y = (yoffset > 0) ? yoffset - 1 : yoffset;\n return target.drawRect(x, y, this.barWidth - 1, 0, color, color);\n } else {\n return undefined;\n }\n }\n yoffsetNeg = yoffset;\n for (i = 0; i < valcount; i++) {\n val = vals[i];\n\n if (stacked && val === xaxisOffset) {\n if (!allMin || minPlotted) {\n continue;\n }\n minPlotted = true;\n }\n\n if (range > 0) {\n height = Math.floor(canvasHeightEf * ((Math.abs(val - xaxisOffset) / range))) + 1;\n } else {\n height = 1;\n }\n if (val < xaxisOffset || (val === xaxisOffset && yoffset === 0)) {\n y = yoffsetNeg;\n yoffsetNeg += height;\n } else {\n y = yoffset - height;\n yoffset -= height;\n }\n color = this.calcColor(i, val, valuenum);\n if (highlight) {\n color = this.calcHighlightColor(color, options);\n }\n result.push(target.drawRect(x, y, this.barWidth - 1, height - 1, color, color));\n }\n if (result.length === 1) {\n return result[0];\n }\n return result;\n }\n });\n\n /**\n * Tristate charts\n */\n $.fn.sparkline.tristate = tristate = createClass($.fn.sparkline._base, barHighlightMixin, {\n type: 'tristate',\n\n init: function (el, values, options, width, height) {\n var barWidth = parseInt(options.get('barWidth'), 10),\n barSpacing = parseInt(options.get('barSpacing'), 10);\n tristate._super.init.call(this, el, values, options, width, height);\n\n this.regionShapes = {};\n this.barWidth = barWidth;\n this.barSpacing = barSpacing;\n this.totalBarWidth = barWidth + barSpacing;\n this.values = $.map(values, Number);\n this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);\n\n if ($.isArray(options.get('colorMap'))) {\n this.colorMapByIndex = options.get('colorMap');\n this.colorMapByValue = null;\n } else {\n this.colorMapByIndex = null;\n this.colorMapByValue = options.get('colorMap');\n if (this.colorMapByValue && this.colorMapByValue.get === undefined) {\n this.colorMapByValue = new RangeMap(this.colorMapByValue);\n }\n }\n this.initTarget();\n },\n\n getRegion: function (el, x, y) {\n return Math.floor(x / this.totalBarWidth);\n },\n\n getCurrentRegionFields: function () {\n var currentRegion = this.currentRegion;\n return {\n isNull: this.values[currentRegion] === undefined,\n value: this.values[currentRegion],\n color: this.calcColor(this.values[currentRegion], currentRegion),\n offset: currentRegion\n };\n },\n\n calcColor: function (value, valuenum) {\n var values = this.values,\n options = this.options,\n colorMapByIndex = this.colorMapByIndex,\n colorMapByValue = this.colorMapByValue,\n color, newColor;\n\n if (colorMapByValue && (newColor = colorMapByValue.get(value))) {\n color = newColor;\n } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {\n color = colorMapByIndex[valuenum];\n } else if (values[valuenum] < 0) {\n color = options.get('negBarColor');\n } else if (values[valuenum] > 0) {\n color = options.get('posBarColor');\n } else {\n color = options.get('zeroBarColor');\n }\n return color;\n },\n\n renderRegion: function (valuenum, highlight) {\n var values = this.values,\n options = this.options,\n target = this.target,\n canvasHeight, height, halfHeight,\n x, y, color;\n\n canvasHeight = target.pixelHeight;\n halfHeight = Math.round(canvasHeight / 2);\n\n x = valuenum * this.totalBarWidth;\n if (values[valuenum] < 0) {\n y = halfHeight;\n height = halfHeight - 1;\n } else if (values[valuenum] > 0) {\n y = 0;\n height = halfHeight - 1;\n } else {\n y = halfHeight - 1;\n height = 2;\n }\n color = this.calcColor(values[valuenum], valuenum);\n if (color === null) {\n return;\n }\n if (highlight) {\n color = this.calcHighlightColor(color, options);\n }\n return target.drawRect(x, y, this.barWidth - 1, height - 1, color, color);\n }\n });\n\n /**\n * Discrete charts\n */\n $.fn.sparkline.discrete = discrete = createClass($.fn.sparkline._base, barHighlightMixin, {\n type: 'discrete',\n\n init: function (el, values, options, width, height) {\n discrete._super.init.call(this, el, values, options, width, height);\n\n this.regionShapes = {};\n this.values = values = $.map(values, Number);\n this.min = Math.min.apply(Math, values);\n this.max = Math.max.apply(Math, values);\n this.range = this.max - this.min;\n this.width = width = options.get('width') === 'auto' ? values.length * 2 : this.width;\n this.interval = Math.floor(width / values.length);\n this.itemWidth = width / values.length;\n if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.min)) {\n this.min = options.get('chartRangeMin');\n }\n if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.max)) {\n this.max = options.get('chartRangeMax');\n }\n this.initTarget();\n if (this.target) {\n this.lineHeight = options.get('lineHeight') === 'auto' ? Math.round(this.canvasHeight * 0.3) : options.get('lineHeight');\n }\n },\n\n getRegion: function (el, x, y) {\n return Math.floor(x / this.itemWidth);\n },\n\n getCurrentRegionFields: function () {\n var currentRegion = this.currentRegion;\n return {\n isNull: this.values[currentRegion] === undefined,\n value: this.values[currentRegion],\n offset: currentRegion\n };\n },\n\n renderRegion: function (valuenum, highlight) {\n var values = this.values,\n options = this.options,\n min = this.min,\n max = this.max,\n range = this.range,\n interval = this.interval,\n target = this.target,\n canvasHeight = this.canvasHeight,\n lineHeight = this.lineHeight,\n pheight = canvasHeight - lineHeight,\n ytop, val, color, x;\n\n val = clipval(values[valuenum], min, max);\n x = valuenum * interval;\n ytop = Math.round(pheight - pheight * ((val - min) / range));\n color = (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor');\n if (highlight) {\n color = this.calcHighlightColor(color, options);\n }\n return target.drawLine(x, ytop, x, ytop + lineHeight, color);\n }\n });\n\n /**\n * Bullet charts\n */\n $.fn.sparkline.bullet = bullet = createClass($.fn.sparkline._base, {\n type: 'bullet',\n\n init: function (el, values, options, width, height) {\n var min, max, vals;\n bullet._super.init.call(this, el, values, options, width, height);\n\n // values: target, performance, range1, range2, range3\n this.values = values = normalizeValues(values);\n // target or performance could be null\n vals = values.slice();\n vals[0] = vals[0] === null ? vals[2] : vals[0];\n vals[1] = values[1] === null ? vals[2] : vals[1];\n min = Math.min.apply(Math, values);\n max = Math.max.apply(Math, values);\n if (options.get('base') === undefined) {\n min = min < 0 ? min : 0;\n } else {\n min = options.get('base');\n }\n this.min = min;\n this.max = max;\n this.range = max - min;\n this.shapes = {};\n this.valueShapes = {};\n this.regiondata = {};\n this.width = width = options.get('width') === 'auto' ? '4.0em' : width;\n this.target = this.$el.simpledraw(width, height, options.get('composite'));\n if (!values.length) {\n this.disabled = true;\n }\n this.initTarget();\n },\n\n getRegion: function (el, x, y) {\n var shapeid = this.target.getShapeAt(el, x, y);\n return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;\n },\n\n getCurrentRegionFields: function () {\n var currentRegion = this.currentRegion;\n return {\n fieldkey: currentRegion.substr(0, 1),\n value: this.values[currentRegion.substr(1)],\n region: currentRegion\n };\n },\n\n changeHighlight: function (highlight) {\n var currentRegion = this.currentRegion,\n shapeid = this.valueShapes[currentRegion],\n shape;\n delete this.shapes[shapeid];\n switch (currentRegion.substr(0, 1)) {\n case 'r':\n shape = this.renderRange(currentRegion.substr(1), highlight);\n break;\n case 'p':\n shape = this.renderPerformance(highlight);\n break;\n case 't':\n shape = this.renderTarget(highlight);\n break;\n }\n this.valueShapes[currentRegion] = shape.id;\n this.shapes[shape.id] = currentRegion;\n this.target.replaceWithShape(shapeid, shape);\n },\n\n renderRange: function (rn, highlight) {\n var rangeval = this.values[rn],\n rangewidth = Math.round(this.canvasWidth * ((rangeval - this.min) / this.range)),\n color = this.options.get('rangeColors')[rn - 2];\n if (highlight) {\n color = this.calcHighlightColor(color, this.options);\n }\n return this.target.drawRect(0, 0, rangewidth - 1, this.canvasHeight - 1, color, color);\n },\n\n renderPerformance: function (highlight) {\n var perfval = this.values[1],\n perfwidth = Math.round(this.canvasWidth * ((perfval - this.min) / this.range)),\n color = this.options.get('performanceColor');\n if (highlight) {\n color = this.calcHighlightColor(color, this.options);\n }\n return this.target.drawRect(0, Math.round(this.canvasHeight * 0.3), perfwidth - 1,\n Math.round(this.canvasHeight * 0.4) - 1, color, color);\n },\n\n renderTarget: function (highlight) {\n var targetval = this.values[0],\n x = Math.round(this.canvasWidth * ((targetval - this.min) / this.range) - (this.options.get('targetWidth') / 2)),\n targettop = Math.round(this.canvasHeight * 0.10),\n targetheight = this.canvasHeight - (targettop * 2),\n color = this.options.get('targetColor');\n if (highlight) {\n color = this.calcHighlightColor(color, this.options);\n }\n return this.target.drawRect(x, targettop, this.options.get('targetWidth') - 1, targetheight - 1, color, color);\n },\n\n render: function () {\n var vlen = this.values.length,\n target = this.target,\n i, shape;\n if (!bullet._super.render.call(this)) {\n return;\n }\n for (i = 2; i < vlen; i++) {\n shape = this.renderRange(i).append();\n this.shapes[shape.id] = 'r' + i;\n this.valueShapes['r' + i] = shape.id;\n }\n if (this.values[1] !== null) {\n shape = this.renderPerformance().append();\n this.shapes[shape.id] = 'p1';\n this.valueShapes.p1 = shape.id;\n }\n if (this.values[0] !== null) {\n shape = this.renderTarget().append();\n this.shapes[shape.id] = 't0';\n this.valueShapes.t0 = shape.id;\n }\n target.render();\n }\n });\n\n /**\n * Pie charts\n */\n $.fn.sparkline.pie = pie = createClass($.fn.sparkline._base, {\n type: 'pie',\n\n init: function (el, values, options, width, height) {\n var total = 0, i;\n\n pie._super.init.call(this, el, values, options, width, height);\n\n this.shapes = {}; // map shape ids to value offsets\n this.valueShapes = {}; // maps value offsets to shape ids\n this.values = values = $.map(values, Number);\n\n if (options.get('width') === 'auto') {\n this.width = this.height;\n }\n\n if (values.length > 0) {\n for (i = values.length; i--;) {\n total += values[i];\n }\n }\n this.total = total;\n this.initTarget();\n this.radius = Math.floor(Math.min(this.canvasWidth, this.canvasHeight) / 2);\n },\n\n getRegion: function (el, x, y) {\n var shapeid = this.target.getShapeAt(el, x, y);\n return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;\n },\n\n getCurrentRegionFields: function () {\n var currentRegion = this.currentRegion;\n return {\n isNull: this.values[currentRegion] === undefined,\n value: this.values[currentRegion],\n percent: this.values[currentRegion] / this.total * 100,\n color: this.options.get('sliceColors')[currentRegion % this.options.get('sliceColors').length],\n offset: currentRegion\n };\n },\n\n changeHighlight: function (highlight) {\n var currentRegion = this.currentRegion,\n newslice = this.renderSlice(currentRegion, highlight),\n shapeid = this.valueShapes[currentRegion];\n delete this.shapes[shapeid];\n this.target.replaceWithShape(shapeid, newslice);\n this.valueShapes[currentRegion] = newslice.id;\n this.shapes[newslice.id] = currentRegion;\n },\n\n renderSlice: function (valuenum, highlight) {\n var target = this.target,\n options = this.options,\n radius = this.radius,\n borderWidth = options.get('borderWidth'),\n offset = options.get('offset'),\n circle = 2 * Math.PI,\n values = this.values,\n total = this.total,\n next = offset ? (2*Math.PI)*(offset/360) : 0,\n start, end, i, vlen, color;\n\n vlen = values.length;\n for (i = 0; i < vlen; i++) {\n start = next;\n end = next;\n if (total > 0) { // avoid divide by zero\n end = next + (circle * (values[i] / total));\n }\n if (valuenum === i) {\n color = options.get('sliceColors')[i % options.get('sliceColors').length];\n if (highlight) {\n color = this.calcHighlightColor(color, options);\n }\n\n return target.drawPieSlice(radius, radius, radius - borderWidth, start, end, undefined, color);\n }\n next = end;\n }\n },\n\n render: function () {\n var target = this.target,\n values = this.values,\n options = this.options,\n radius = this.radius,\n borderWidth = options.get('borderWidth'),\n donutWidth = options.get('donutWidth'),\n shape, i;\n\n if (!pie._super.render.call(this)) {\n return;\n }\n if (borderWidth) {\n target.drawCircle(radius, radius, Math.floor(radius - (borderWidth / 2)),\n options.get('borderColor'), undefined, borderWidth).append();\n }\n for (i = values.length; i--;) {\n if (values[i]) { // don't render zero values\n shape = this.renderSlice(i).append();\n this.valueShapes[i] = shape.id; // store just the shapeid\n this.shapes[shape.id] = i;\n }\n }\n if (donutWidth) {\n target.drawCircle(radius, radius, radius - donutWidth, options.get('donutColor'), \n options.get('donutColor'), 0).append();\n }\n target.render();\n }\n });\n\n /**\n * Box plots\n */\n $.fn.sparkline.box = box = createClass($.fn.sparkline._base, {\n type: 'box',\n\n init: function (el, values, options, width, height) {\n box._super.init.call(this, el, values, options, width, height);\n this.values = $.map(values, Number);\n this.width = options.get('width') === 'auto' ? '4.0em' : width;\n this.initTarget();\n if (!this.values.length) {\n this.disabled = 1;\n }\n },\n\n /**\n * Simulate a single region\n */\n getRegion: function () {\n return 1;\n },\n\n getCurrentRegionFields: function () {\n var result = [\n { field: 'lq', value: this.quartiles[0] },\n { field: 'med', value: this.quartiles[1] },\n { field: 'uq', value: this.quartiles[2] }\n ];\n if (this.loutlier !== undefined) {\n result.push({ field: 'lo', value: this.loutlier});\n }\n if (this.routlier !== undefined) {\n result.push({ field: 'ro', value: this.routlier});\n }\n if (this.lwhisker !== undefined) {\n result.push({ field: 'lw', value: this.lwhisker});\n }\n if (this.rwhisker !== undefined) {\n result.push({ field: 'rw', value: this.rwhisker});\n }\n return result;\n },\n\n render: function () {\n var target = this.target,\n values = this.values,\n vlen = values.length,\n options = this.options,\n canvasWidth = this.canvasWidth,\n canvasHeight = this.canvasHeight,\n minValue = options.get('chartRangeMin') === undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'),\n maxValue = options.get('chartRangeMax') === undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'),\n canvasLeft = 0,\n lwhisker, loutlier, iqr, q1, q2, q3, rwhisker, routlier, i,\n size, unitSize;\n\n if (!box._super.render.call(this)) {\n return;\n }\n\n if (options.get('raw')) {\n if (options.get('showOutliers') && values.length > 5) {\n loutlier = values[0];\n lwhisker = values[1];\n q1 = values[2];\n q2 = values[3];\n q3 = values[4];\n rwhisker = values[5];\n routlier = values[6];\n } else {\n lwhisker = values[0];\n q1 = values[1];\n q2 = values[2];\n q3 = values[3];\n rwhisker = values[4];\n }\n } else {\n values.sort(function (a, b) { return a - b; });\n q1 = quartile(values, 1);\n q2 = quartile(values, 2);\n q3 = quartile(values, 3);\n iqr = q3 - q1;\n if (options.get('showOutliers')) {\n lwhisker = rwhisker = undefined;\n for (i = 0; i < vlen; i++) {\n if (lwhisker === undefined && values[i] > q1 - (iqr * options.get('outlierIQR'))) {\n lwhisker = values[i];\n }\n if (values[i] < q3 + (iqr * options.get('outlierIQR'))) {\n rwhisker = values[i];\n }\n }\n loutlier = values[0];\n routlier = values[vlen - 1];\n } else {\n lwhisker = values[0];\n rwhisker = values[vlen - 1];\n }\n }\n this.quartiles = [q1, q2, q3];\n this.lwhisker = lwhisker;\n this.rwhisker = rwhisker;\n this.loutlier = loutlier;\n this.routlier = routlier;\n\n unitSize = canvasWidth / (maxValue - minValue + 1);\n if (options.get('showOutliers')) {\n canvasLeft = Math.ceil(options.get('spotRadius'));\n canvasWidth -= 2 * Math.ceil(options.get('spotRadius'));\n unitSize = canvasWidth / (maxValue - minValue + 1);\n if (loutlier < lwhisker) {\n target.drawCircle((loutlier - minValue) * unitSize + canvasLeft,\n canvasHeight / 2,\n options.get('spotRadius'),\n options.get('outlierLineColor'),\n options.get('outlierFillColor')).append();\n }\n if (routlier > rwhisker) {\n target.drawCircle((routlier - minValue) * unitSize + canvasLeft,\n canvasHeight / 2,\n options.get('spotRadius'),\n options.get('outlierLineColor'),\n options.get('outlierFillColor')).append();\n }\n }\n\n // box\n target.drawRect(\n Math.round((q1 - minValue) * unitSize + canvasLeft),\n Math.round(canvasHeight * 0.1),\n Math.round((q3 - q1) * unitSize),\n Math.round(canvasHeight * 0.8),\n options.get('boxLineColor'),\n options.get('boxFillColor')).append();\n // left whisker\n target.drawLine(\n Math.round((lwhisker - minValue) * unitSize + canvasLeft),\n Math.round(canvasHeight / 2),\n Math.round((q1 - minValue) * unitSize + canvasLeft),\n Math.round(canvasHeight / 2),\n options.get('lineColor')).append();\n target.drawLine(\n Math.round((lwhisker - minValue) * unitSize + canvasLeft),\n Math.round(canvasHeight / 4),\n Math.round((lwhisker - minValue) * unitSize + canvasLeft),\n Math.round(canvasHeight - canvasHeight / 4),\n options.get('whiskerColor')).append();\n // right whisker\n target.drawLine(Math.round((rwhisker - minValue) * unitSize + canvasLeft),\n Math.round(canvasHeight / 2),\n Math.round((q3 - minValue) * unitSize + canvasLeft),\n Math.round(canvasHeight / 2),\n options.get('lineColor')).append();\n target.drawLine(\n Math.round((rwhisker - minValue) * unitSize + canvasLeft),\n Math.round(canvasHeight / 4),\n Math.round((rwhisker - minValue) * unitSize + canvasLeft),\n Math.round(canvasHeight - canvasHeight / 4),\n options.get('whiskerColor')).append();\n // median line\n target.drawLine(\n Math.round((q2 - minValue) * unitSize + canvasLeft),\n Math.round(canvasHeight * 0.1),\n Math.round((q2 - minValue) * unitSize + canvasLeft),\n Math.round(canvasHeight * 0.9),\n options.get('medianColor')).append();\n if (options.get('target')) {\n size = Math.ceil(options.get('spotRadius'));\n target.drawLine(\n Math.round((options.get('target') - minValue) * unitSize + canvasLeft),\n Math.round((canvasHeight / 2) - size),\n Math.round((options.get('target') - minValue) * unitSize + canvasLeft),\n Math.round((canvasHeight / 2) + size),\n options.get('targetColor')).append();\n target.drawLine(\n Math.round((options.get('target') - minValue) * unitSize + canvasLeft - size),\n Math.round(canvasHeight / 2),\n Math.round((options.get('target') - minValue) * unitSize + canvasLeft + size),\n Math.round(canvasHeight / 2),\n options.get('targetColor')).append();\n }\n target.render();\n }\n });\n\n // Setup a very simple \"virtual canvas\" to make drawing the few shapes we need easier\n // This is accessible as $(foo).simpledraw()\n\n VShape = createClass({\n init: function (target, id, type, args) {\n this.target = target;\n this.id = id;\n this.type = type;\n this.args = args;\n },\n append: function () {\n this.target.appendShape(this);\n return this;\n }\n });\n\n VCanvas_base = createClass({\n _pxregex: /(\\d+)(px)?\\s*$/i,\n\n init: function (width, height, target) {\n if (!width) {\n return;\n }\n this.width = width;\n this.height = height;\n this.target = target;\n this.lastShapeId = null;\n if (target[0]) {\n target = target[0];\n }\n $.data(target, '_jqs_vcanvas', this);\n },\n\n drawLine: function (x1, y1, x2, y2, lineColor, lineWidth) {\n return this.drawShape([[x1, y1], [x2, y2]], lineColor, lineWidth);\n },\n\n drawShape: function (path, lineColor, fillColor, lineWidth) {\n return this._genShape('Shape', [path, lineColor, fillColor, lineWidth]);\n },\n\n drawCircle: function (x, y, radius, lineColor, fillColor, lineWidth) {\n return this._genShape('Circle', [x, y, radius, lineColor, fillColor, lineWidth]);\n },\n\n drawPieSlice: function (x, y, radius, startAngle, endAngle, lineColor, fillColor) {\n return this._genShape('PieSlice', [x, y, radius, startAngle, endAngle, lineColor, fillColor]);\n },\n\n drawRect: function (x, y, width, height, lineColor, fillColor) {\n return this._genShape('Rect', [x, y, width, height, lineColor, fillColor]);\n },\n\n getElement: function () {\n return this.canvas;\n },\n\n /**\n * Return the most recently inserted shape id\n */\n getLastShapeId: function () {\n return this.lastShapeId;\n },\n\n /**\n * Clear and reset the canvas\n */\n reset: function () {\n alert('reset not implemented');\n },\n\n _insert: function (el, target) {\n $(target).html(el);\n },\n\n /**\n * Calculate the pixel dimensions of the canvas\n */\n _calculatePixelDims: function (width, height, canvas) {\n // XXX This should probably be a configurable option\n var match;\n match = this._pxregex.exec(height);\n if (match) {\n this.pixelHeight = match[1];\n } else {\n this.pixelHeight = $(canvas).height();\n }\n match = this._pxregex.exec(width);\n if (match) {\n this.pixelWidth = match[1];\n } else {\n this.pixelWidth = $(canvas).width();\n }\n },\n\n /**\n * Generate a shape object and id for later rendering\n */\n _genShape: function (shapetype, shapeargs) {\n var id = shapeCount++;\n shapeargs.unshift(id);\n return new VShape(this, id, shapetype, shapeargs);\n },\n\n /**\n * Add a shape to the end of the render queue\n */\n appendShape: function (shape) {\n alert('appendShape not implemented');\n },\n\n /**\n * Replace one shape with another\n */\n replaceWithShape: function (shapeid, shape) {\n alert('replaceWithShape not implemented');\n },\n\n /**\n * Insert one shape after another in the render queue\n */\n insertAfterShape: function (shapeid, shape) {\n alert('insertAfterShape not implemented');\n },\n\n /**\n * Remove a shape from the queue\n */\n removeShapeId: function (shapeid) {\n alert('removeShapeId not implemented');\n },\n\n /**\n * Find a shape at the specified x/y co-ordinates\n */\n getShapeAt: function (el, x, y) {\n alert('getShapeAt not implemented');\n },\n\n /**\n * Render all queued shapes onto the canvas\n */\n render: function () {\n alert('render not implemented');\n }\n });\n\n VCanvas_canvas = createClass(VCanvas_base, {\n init: function (width, height, target, interact) {\n VCanvas_canvas._super.init.call(this, width, height, target);\n this.canvas = document.createElement('canvas');\n if (target[0]) {\n target = target[0];\n }\n $.data(target, '_jqs_vcanvas', this);\n $(this.canvas).css({ display: 'inline-block', width: width, height: height, verticalAlign: 'top' });\n this._insert(this.canvas, target);\n this._calculatePixelDims(width, height, this.canvas);\n this.canvas.width = this.pixelWidth;\n this.canvas.height = this.pixelHeight;\n this.interact = interact;\n this.shapes = {};\n this.shapeseq = [];\n this.currentTargetShapeId = undefined;\n $(this.canvas).css({width: this.pixelWidth, height: this.pixelHeight});\n },\n\n _getContext: function (lineColor, fillColor, lineWidth) {\n var context = this.canvas.getContext('2d');\n if (lineColor !== undefined) {\n context.strokeStyle = lineColor;\n }\n context.lineWidth = lineWidth === undefined ? 1 : lineWidth;\n if (fillColor !== undefined) {\n context.fillStyle = fillColor;\n }\n return context;\n },\n\n reset: function () {\n var context = this._getContext();\n context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);\n this.shapes = {};\n this.shapeseq = [];\n this.currentTargetShapeId = undefined;\n },\n\n _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {\n var context = this._getContext(lineColor, fillColor, lineWidth),\n i, plen;\n context.beginPath();\n context.moveTo(path[0][0] + 0.5, path[0][1] + 0.5);\n for (i = 1, plen = path.length; i < plen; i++) {\n context.lineTo(path[i][0] + 0.5, path[i][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines\n }\n if (lineColor !== undefined) {\n context.stroke();\n }\n if (fillColor !== undefined) {\n context.fill();\n }\n if (this.targetX !== undefined && this.targetY !== undefined &&\n context.isPointInPath(this.targetX, this.targetY)) {\n this.currentTargetShapeId = shapeid;\n }\n },\n\n _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {\n var context = this._getContext(lineColor, fillColor, lineWidth);\n context.beginPath();\n context.arc(x, y, radius, 0, 2 * Math.PI, false);\n if (this.targetX !== undefined && this.targetY !== undefined &&\n context.isPointInPath(this.targetX, this.targetY)) {\n this.currentTargetShapeId = shapeid;\n }\n if (lineColor !== undefined) {\n context.stroke();\n }\n if (fillColor !== undefined) {\n context.fill();\n }\n },\n\n _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {\n var context = this._getContext(lineColor, fillColor);\n context.beginPath();\n context.moveTo(x, y);\n context.arc(x, y, radius, startAngle, endAngle, false);\n context.lineTo(x, y);\n context.closePath();\n if (lineColor !== undefined) {\n context.stroke();\n }\n if (fillColor) {\n context.fill();\n }\n if (this.targetX !== undefined && this.targetY !== undefined &&\n context.isPointInPath(this.targetX, this.targetY)) {\n this.currentTargetShapeId = shapeid;\n }\n },\n\n _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {\n return this._drawShape(shapeid, [[x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y]], lineColor, fillColor);\n },\n\n appendShape: function (shape) {\n this.shapes[shape.id] = shape;\n this.shapeseq.push(shape.id);\n this.lastShapeId = shape.id;\n return shape.id;\n },\n\n replaceWithShape: function (shapeid, shape) {\n var shapeseq = this.shapeseq,\n i;\n this.shapes[shape.id] = shape;\n for (i = shapeseq.length; i--;) {\n if (shapeseq[i] == shapeid) {\n shapeseq[i] = shape.id;\n }\n }\n delete this.shapes[shapeid];\n },\n\n replaceWithShapes: function (shapeids, shapes) {\n var shapeseq = this.shapeseq,\n shapemap = {},\n sid, i, first;\n\n for (i = shapeids.length; i--;) {\n shapemap[shapeids[i]] = true;\n }\n for (i = shapeseq.length; i--;) {\n sid = shapeseq[i];\n if (shapemap[sid]) {\n shapeseq.splice(i, 1);\n delete this.shapes[sid];\n first = i;\n }\n }\n for (i = shapes.length; i--;) {\n shapeseq.splice(first, 0, shapes[i].id);\n this.shapes[shapes[i].id] = shapes[i];\n }\n\n },\n\n insertAfterShape: function (shapeid, shape) {\n var shapeseq = this.shapeseq,\n i;\n for (i = shapeseq.length; i--;) {\n if (shapeseq[i] === shapeid) {\n shapeseq.splice(i + 1, 0, shape.id);\n this.shapes[shape.id] = shape;\n return;\n }\n }\n },\n\n removeShapeId: function (shapeid) {\n var shapeseq = this.shapeseq,\n i;\n for (i = shapeseq.length; i--;) {\n if (shapeseq[i] === shapeid) {\n shapeseq.splice(i, 1);\n break;\n }\n }\n delete this.shapes[shapeid];\n },\n\n getShapeAt: function (el, x, y) {\n this.targetX = x;\n this.targetY = y;\n this.render();\n return this.currentTargetShapeId;\n },\n\n render: function () {\n var shapeseq = this.shapeseq,\n shapes = this.shapes,\n shapeCount = shapeseq.length,\n context = this._getContext(),\n shapeid, shape, i;\n context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);\n for (i = 0; i < shapeCount; i++) {\n shapeid = shapeseq[i];\n shape = shapes[shapeid];\n this['_draw' + shape.type].apply(this, shape.args);\n }\n if (!this.interact) {\n // not interactive so no need to keep the shapes array\n this.shapes = {};\n this.shapeseq = [];\n }\n }\n\n });\n\n VCanvas_vml = createClass(VCanvas_base, {\n init: function (width, height, target) {\n var groupel;\n VCanvas_vml._super.init.call(this, width, height, target);\n if (target[0]) {\n target = target[0];\n }\n $.data(target, '_jqs_vcanvas', this);\n this.canvas = document.createElement('span');\n $(this.canvas).css({ display: 'inline-block', position: 'relative', overflow: 'hidden', width: width, height: height, margin: '0px', padding: '0px', verticalAlign: 'top'});\n this._insert(this.canvas, target);\n this._calculatePixelDims(width, height, this.canvas);\n this.canvas.width = this.pixelWidth;\n this.canvas.height = this.pixelHeight;\n groupel = '';\n this.canvas.insertAdjacentHTML('beforeEnd', groupel);\n this.group = $(this.canvas).children()[0];\n this.rendered = false;\n this.prerender = '';\n },\n\n _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {\n var vpath = [],\n initial, stroke, fill, closed, vel, plen, i;\n for (i = 0, plen = path.length; i < plen; i++) {\n vpath[i] = '' + (path[i][0]) + ',' + (path[i][1]);\n }\n initial = vpath.splice(0, 1);\n lineWidth = lineWidth === undefined ? 1 : lineWidth;\n stroke = lineColor === undefined ? ' stroked=\"false\" ' : ' strokeWeight=\"' + lineWidth + 'px\" strokeColor=\"' + lineColor + '\" ';\n fill = fillColor === undefined ? ' filled=\"false\"' : ' fillColor=\"' + fillColor + '\" filled=\"true\" ';\n closed = vpath[0] === vpath[vpath.length - 1] ? 'x ' : '';\n vel = '' +\n ' ';\n return vel;\n },\n\n _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {\n var stroke, fill, vel;\n x -= radius;\n y -= radius;\n stroke = lineColor === undefined ? ' stroked=\"false\" ' : ' strokeWeight=\"' + lineWidth + 'px\" strokeColor=\"' + lineColor + '\" ';\n fill = fillColor === undefined ? ' filled=\"false\"' : ' fillColor=\"' + fillColor + '\" filled=\"true\" ';\n vel = '';\n return vel;\n\n },\n\n _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {\n var vpath, startx, starty, endx, endy, stroke, fill, vel;\n if (startAngle === endAngle) {\n return ''; // VML seems to have problem when start angle equals end angle.\n }\n if ((endAngle - startAngle) === (2 * Math.PI)) {\n startAngle = 0.0; // VML seems to have a problem when drawing a full circle that doesn't start 0\n endAngle = (2 * Math.PI);\n }\n\n startx = x + Math.round(Math.cos(startAngle) * radius);\n starty = y + Math.round(Math.sin(startAngle) * radius);\n endx = x + Math.round(Math.cos(endAngle) * radius);\n endy = y + Math.round(Math.sin(endAngle) * radius);\n\n if (startx === endx && starty === endy) {\n if ((endAngle - startAngle) < Math.PI) {\n // Prevent very small slices from being mistaken as a whole pie\n return '';\n }\n // essentially going to be the entire circle, so ignore startAngle\n startx = endx = x + radius;\n starty = endy = y;\n }\n\n if (startx === endx && starty === endy && (endAngle - startAngle) < Math.PI) {\n return '';\n }\n\n vpath = [x - radius, y - radius, x + radius, y + radius, startx, starty, endx, endy];\n stroke = lineColor === undefined ? ' stroked=\"false\" ' : ' strokeWeight=\"1px\" strokeColor=\"' + lineColor + '\" ';\n fill = fillColor === undefined ? ' filled=\"false\"' : ' fillColor=\"' + fillColor + '\" filled=\"true\" ';\n vel = '' +\n ' ';\n return vel;\n },\n\n _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {\n return this._drawShape(shapeid, [[x, y], [x, y + height], [x + width, y + height], [x + width, y], [x, y]], lineColor, fillColor);\n },\n\n reset: function () {\n this.group.innerHTML = '';\n },\n\n appendShape: function (shape) {\n var vel = this['_draw' + shape.type].apply(this, shape.args);\n if (this.rendered) {\n this.group.insertAdjacentHTML('beforeEnd', vel);\n } else {\n this.prerender += vel;\n }\n this.lastShapeId = shape.id;\n return shape.id;\n },\n\n replaceWithShape: function (shapeid, shape) {\n var existing = $('#jqsshape' + shapeid),\n vel = this['_draw' + shape.type].apply(this, shape.args);\n existing[0].outerHTML = vel;\n },\n\n replaceWithShapes: function (shapeids, shapes) {\n // replace the first shapeid with all the new shapes then toast the remaining old shapes\n var existing = $('#jqsshape' + shapeids[0]),\n replace = '',\n slen = shapes.length,\n i;\n for (i = 0; i < slen; i++) {\n replace += this['_draw' + shapes[i].type].apply(this, shapes[i].args);\n }\n existing[0].outerHTML = replace;\n for (i = 1; i < shapeids.length; i++) {\n $('#jqsshape' + shapeids[i]).remove();\n }\n },\n\n insertAfterShape: function (shapeid, shape) {\n var existing = $('#jqsshape' + shapeid),\n vel = this['_draw' + shape.type].apply(this, shape.args);\n existing[0].insertAdjacentHTML('afterEnd', vel);\n },\n\n removeShapeId: function (shapeid) {\n var existing = $('#jqsshape' + shapeid);\n this.group.removeChild(existing[0]);\n },\n\n getShapeAt: function (el, x, y) {\n var shapeid = el.id.substr(8);\n return shapeid;\n },\n\n render: function () {\n if (!this.rendered) {\n // batch the intial render into a single repaint\n this.group.innerHTML = this.prerender;\n this.rendered = true;\n }\n }\n });\n\n}))}(document, Math));\n","/* istanbul ignore file */\nexport var eventsMap = {\n blur: 'onBlur',\n cancel: 'onCancel',\n click: 'onClick',\n close: 'onClose',\n contextmenu: 'onContextMenu',\n copy: 'onCopy',\n cut: 'onCut',\n auxclick: 'onAuxClick',\n doubleclick: 'onDoubleClick',\n dragend: 'onDragEnd',\n dragstart: 'onDragStart',\n drop: 'onDrop',\n focus: 'onFocus',\n input: 'onInput',\n invalid: 'onInvalid',\n keydown: 'onKeyDown',\n keypress: 'onKeyPress',\n keyup: 'onKeyUp',\n mousedown: 'onMouseDown',\n mouseup: 'onMouseUp',\n paste: 'onPaste',\n pause: 'onPause',\n play: 'onPlay',\n pointercancel: 'onPointerCancel',\n pointerdown: 'onPointerDown',\n pointerup: 'onPointerUp',\n ratechange: 'onRateChange',\n reset: 'onReset',\n seeked: 'onSeeked',\n submit: 'onSubmit',\n touchcancel: 'onTouchCancel',\n touchend: 'onTouchEnd',\n touchstart: 'onTouchStart',\n volumechange: 'onVolumeChange',\n abort: 'onAbort',\n animationend: 'onAnimationEnd',\n animationiteration: 'onAnimationIteration',\n animationstart: 'onAnimationStart',\n canplay: 'onCanPlay',\n canplaythrough: 'onCanPlayThrough',\n drag: 'onDrag',\n dragenter: 'onDragEnter',\n dragexit: 'onDragExit',\n dragleave: 'onDragLeave',\n dragover: 'onDragOver',\n durationchange: 'onDurationChange',\n emptied: 'onEmptied',\n encrypted: 'onEncrypted',\n ended: 'onEnded',\n error: 'onError',\n gotpointercapture: 'onGotPointerCapture',\n load: 'onLoad',\n loadeddata: 'onLoadedData',\n loadedmetadata: 'onLoadedMetadata',\n loadstart: 'onLoadStart',\n lostpointercapture: 'onLostPointerCapture',\n mousemove: 'onMouseMove',\n mouseout: 'onMouseOut',\n mouseover: 'onMouseOver',\n playing: 'onPlaying',\n pointermove: 'onPointerMove',\n pointerout: 'onPointerOut',\n pointerover: 'onPointerOver',\n progress: 'onProgress',\n scroll: 'onScroll',\n seeking: 'onSeeking',\n stalled: 'onStalled',\n suspend: 'onSuspend',\n timeupdate: 'onTimeUpdate',\n toggle: 'onToggle',\n touchmove: 'onTouchMove',\n transitionend: 'onTransitionEnd',\n waiting: 'onWaiting',\n wheel: 'onWheel',\n mouseenter: 'onMouseEnter',\n mouseleave: 'onMouseLeave',\n pointerenter: 'onPointerEnter',\n pointerleave: 'onPointerLeave',\n change: 'onChange',\n select: 'onSelect',\n beforeinput: 'onBeforeInput',\n compositionend: 'onCompositionEnd',\n compositionstart: 'onCompositionStart',\n compositionupdate: 'onCompositionUpdate'\n};\n","export var debounce = function (func, wait) {\n var timeout;\n return function () {\n // @ts-ignore\n var context = this, args = arguments;\n var later = function () {\n timeout = null;\n func.apply(context, args);\n };\n timeout !== null && clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n };\n};\n","var __extends = (this && this.__extends) || (function () {\n var extendStatics = function (d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };\n return extendStatics(d, b);\n };\n return function (d, b) {\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n };\n})();\nvar __assign = (this && this.__assign) || function () {\n __assign = Object.assign || function(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))\n t[p] = s[p];\n }\n return t;\n };\n return __assign.apply(this, arguments);\n};\nvar __read = (this && this.__read) || function (o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n};\nvar __spread = (this && this.__spread) || function () {\n for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));\n return ar;\n};\nimport * as React from 'react';\nimport classNames from 'classnames';\nimport { eventsMap } from './utils/events-map';\nimport { debounce } from './utils/debounce';\nimport { toCamel } from './utils/strings';\nvar reactPropFromEventName = function (evtName) {\n return eventsMap[evtName] || evtName;\n};\nvar FoundationElement = /** @class */ (function () {\n function FoundationElement(onChange) {\n this._classes = new Set();\n this._events = {};\n this._style = {};\n this._props = {};\n this._ref = null;\n this._onChange = null;\n this._onChange = onChange;\n this.onChange = this.onChange.bind(this);\n this.addClass = this.addClass.bind(this);\n this.removeClass = this.removeClass.bind(this);\n this.hasClass = this.hasClass.bind(this);\n this.setProp = this.setProp.bind(this);\n this.getProp = this.getProp.bind(this);\n this.removeProp = this.removeProp.bind(this);\n this.setStyle = this.setStyle.bind(this);\n this.addEventListener = this.addEventListener.bind(this);\n this.removeEventListener = this.removeEventListener.bind(this);\n this.setRef = this.setRef.bind(this);\n }\n FoundationElement.prototype.onChange = function () {\n this._onChange && this._onChange();\n };\n FoundationElement.prototype.destroy = function () {\n this._onChange = null;\n this._ref = null;\n this._events = {};\n this._style = {};\n this._props = {};\n this._classes = new Set();\n };\n /**************************************************\n * Classes\n **************************************************/\n FoundationElement.prototype.addClass = function (className) {\n if (!this._classes.has(className)) {\n this._classes.add(className);\n this.onChange();\n }\n };\n FoundationElement.prototype.removeClass = function (className) {\n if (this._classes.has(className)) {\n this._classes.delete(className);\n this.onChange();\n }\n };\n FoundationElement.prototype.hasClass = function (className) {\n return this._classes.has(className);\n };\n /**************************************************\n * Props\n **************************************************/\n FoundationElement.prototype.setProp = function (propName, value) {\n if (this._props[propName] !== value) {\n this._props[propName] = value;\n this.onChange();\n }\n };\n FoundationElement.prototype.getProp = function (propName) {\n return this._props[propName];\n };\n FoundationElement.prototype.removeProp = function (propName) {\n if (this._props[propName] !== undefined) {\n delete this._props[propName];\n this.onChange();\n }\n };\n FoundationElement.prototype.props = function (propsToMerge) {\n var _this = this;\n var _a = propsToMerge.className, className = _a === void 0 ? '' : _a, _b = propsToMerge.style, style = _b === void 0 ? {} : _b;\n // handle merging events\n // the foundation should be able to pass something onClick as well as a user\n // This wraps them in a function that calls both\n var mergedEvents = Object.entries(propsToMerge).reduce(function (acc, _a) {\n var _b = __read(_a, 2), key = _b[0], possibleCallback = _b[1];\n var existingCallback = _this._events[key];\n if (typeof possibleCallback === 'function' &&\n typeof existingCallback === 'function') {\n var wrappedCallback = function (evt) {\n existingCallback(evt);\n return possibleCallback(evt);\n };\n acc[key] = wrappedCallback;\n }\n return acc;\n }, __assign({}, this._events));\n // handle className\n var mergedClasses = classNames(className, __spread(this._classes));\n // handle styles\n var mergedStyles = __assign({}, this._style, style);\n return __assign({}, propsToMerge, this._props, mergedEvents, { style: mergedStyles, className: mergedClasses });\n };\n /**************************************************\n * Styles\n **************************************************/\n FoundationElement.prototype.setStyle = function (propertyName, value) {\n propertyName = propertyName.startsWith('--')\n ? propertyName\n : toCamel(propertyName);\n if (this._style[propertyName] !== value) {\n this._style[propertyName] = value;\n this.onChange();\n }\n };\n /**************************************************\n * Events\n **************************************************/\n FoundationElement.prototype.addEventListener = function (evtName, callback) {\n var propName = reactPropFromEventName(evtName);\n if (this._events[propName] !== callback) {\n this._events[propName] = callback;\n this.onChange();\n }\n };\n FoundationElement.prototype.removeEventListener = function (evtName, callback) {\n var propName = reactPropFromEventName(evtName);\n if (this._events[propName]) {\n delete this._events[propName];\n this.onChange();\n }\n };\n /**************************************************\n * Refs\n **************************************************/\n FoundationElement.prototype.setRef = function (el) {\n if (el) {\n this._ref = el;\n }\n };\n Object.defineProperty(FoundationElement.prototype, \"ref\", {\n get: function () {\n return this._ref;\n },\n enumerable: true,\n configurable: true\n });\n return FoundationElement;\n}());\nexport { FoundationElement };\nvar FoundationComponent = /** @class */ (function (_super) {\n __extends(FoundationComponent, _super);\n function FoundationComponent(props) {\n var _this = _super.call(this, props) || this;\n _this.elements = {};\n //@ts-ignore\n if (_this.constructor.shouldDebounce) {\n _this.update = debounce(_this.update.bind(_this), 0);\n }\n else {\n _this.update = _this.update.bind(_this);\n }\n return _this;\n }\n FoundationComponent.prototype.componentDidMount = function () {\n this.foundation = this.getDefaultFoundation();\n this.foundation.init();\n this.sync(this.props, {});\n };\n FoundationComponent.prototype.componentDidUpdate = function (prevProps) {\n this.sync(this.props, prevProps);\n };\n FoundationComponent.prototype.componentWillUnmount = function () {\n this.foundation && this.foundation.destroy();\n // @ts-ignore\n this.foundation = undefined;\n Object.values(this.elements).forEach(function (el) { return el.destroy(); });\n };\n FoundationComponent.prototype.createElement = function (elementName) {\n var el = new FoundationElement(this.update);\n this.elements[elementName] = el;\n return el;\n };\n FoundationComponent.prototype.update = function () {\n this.foundation && this.setState({});\n };\n FoundationComponent.prototype.sync = function (props, prevProps) { };\n FoundationComponent.prototype.syncProp = function (prop, prevProp, callback) {\n if ((prop !== undefined || (prevProp !== undefined && prop === undefined)) &&\n prop !== prevProp) {\n callback();\n }\n };\n FoundationComponent.prototype.getDefaultFoundation = function () {\n return {\n init: function () { },\n destroy: function () { }\n };\n };\n /**\n * Fires a cross-browser-compatible custom event from the component root of the given type,\n */\n FoundationComponent.prototype.emit = function (evtType, evtData, shouldBubble) {\n if (shouldBubble === void 0) { shouldBubble = false; }\n var evt;\n evt = new CustomEvent(evtType, {\n detail: evtData,\n bubbles: shouldBubble\n });\n // bugfix for events coming from form elements\n // and also fits with reacts form pattern better...\n // This should always otherwise be null since there is no target\n // for Custom Events\n Object.defineProperty(evt, 'target', {\n value: evtData,\n writable: false\n });\n Object.defineProperty(evt, 'currentTarget', {\n value: evtData,\n writable: false\n });\n // Custom handling for React\n var propName = evtType;\n // check to see if the foundation still exists. If not, we are\n // probably unmounted or destroyed and dont want to call any more handlers\n // This happens when MDC broadcasts certain events on timers\n if (this.foundation) {\n //@ts-ignore\n this.props[propName] && this.props[propName](evt);\n }\n return evt;\n };\n FoundationComponent.shouldDebounce = false;\n return FoundationComponent;\n}(React.Component));\nexport { FoundationComponent };\n","export default function symbolObservablePonyfill(root) {\n\tvar result;\n\tvar Symbol = root.Symbol;\n\n\tif (typeof Symbol === 'function') {\n\t\tif (Symbol.observable) {\n\t\t\tresult = Symbol.observable;\n\t\t} else {\n\t\t\tresult = Symbol('observable');\n\t\t\tSymbol.observable = result;\n\t\t}\n\t} else {\n\t\tresult = '@@observable';\n\t}\n\n\treturn result;\n};\n","import _checkForMethod from './internal/_checkForMethod.js';\nimport _curry1 from './internal/_curry1.js';\nimport slice from './slice.js';\n\n/**\n * Returns all but the first element of the given list or string (or object\n * with a `tail` method).\n *\n * Dispatches to the `slice` method of the first argument, if present.\n *\n * @func\n * @memberOf R\n * @since v0.1.0\n * @category List\n * @sig [a] -> [a]\n * @sig String -> String\n * @param {*} list\n * @return {*}\n * @see R.head, R.init, R.last\n * @example\n *\n * R.tail([1, 2, 3]); //=> [2, 3]\n * R.tail([1, 2]); //=> [2]\n * R.tail([1]); //=> []\n * R.tail([]); //=> []\n *\n * R.tail('abc'); //=> 'bc'\n * R.tail('ab'); //=> 'b'\n * R.tail('a'); //=> ''\n * R.tail(''); //=> ''\n */\nvar tail = /*#__PURE__*/_curry1( /*#__PURE__*/_checkForMethod('tail', /*#__PURE__*/slice(1, Infinity)));\nexport default tail;","/** PURE_IMPORTS_START tslib,_util_subscribeToResult,_OuterSubscriber,_InnerSubscriber,_map,_observable_from PURE_IMPORTS_END */\nimport * as tslib_1 from \"tslib\";\nimport { subscribeToResult } from '../util/subscribeToResult';\nimport { OuterSubscriber } from '../OuterSubscriber';\nimport { InnerSubscriber } from '../InnerSubscriber';\nimport { map } from './map';\nimport { from } from '../observable/from';\nexport function mergeMap(project, resultSelector, concurrent) {\n if (concurrent === void 0) {\n concurrent = Number.POSITIVE_INFINITY;\n }\n if (typeof resultSelector === 'function') {\n return function (source) { return source.pipe(mergeMap(function (a, i) { return from(project(a, i)).pipe(map(function (b, ii) { return resultSelector(a, b, i, ii); })); }, concurrent)); };\n }\n else if (typeof resultSelector === 'number') {\n concurrent = resultSelector;\n }\n return function (source) { return source.lift(new MergeMapOperator(project, concurrent)); };\n}\nvar MergeMapOperator = /*@__PURE__*/ (function () {\n function MergeMapOperator(project, concurrent) {\n if (concurrent === void 0) {\n concurrent = Number.POSITIVE_INFINITY;\n }\n this.project = project;\n this.concurrent = concurrent;\n }\n MergeMapOperator.prototype.call = function (observer, source) {\n return source.subscribe(new MergeMapSubscriber(observer, this.project, this.concurrent));\n };\n return MergeMapOperator;\n}());\nexport { MergeMapOperator };\nvar MergeMapSubscriber = /*@__PURE__*/ (function (_super) {\n tslib_1.__extends(MergeMapSubscriber, _super);\n function MergeMapSubscriber(destination, project, concurrent) {\n if (concurrent === void 0) {\n concurrent = Number.POSITIVE_INFINITY;\n }\n var _this = _super.call(this, destination) || this;\n _this.project = project;\n _this.concurrent = concurrent;\n _this.hasCompleted = false;\n _this.buffer = [];\n _this.active = 0;\n _this.index = 0;\n return _this;\n }\n MergeMapSubscriber.prototype._next = function (value) {\n if (this.active < this.concurrent) {\n this._tryNext(value);\n }\n else {\n this.buffer.push(value);\n }\n };\n MergeMapSubscriber.prototype._tryNext = function (value) {\n var result;\n var index = this.index++;\n try {\n result = this.project(value, index);\n }\n catch (err) {\n this.destination.error(err);\n return;\n }\n this.active++;\n this._innerSub(result, value, index);\n };\n MergeMapSubscriber.prototype._innerSub = function (ish, value, index) {\n var innerSubscriber = new InnerSubscriber(this, value, index);\n var destination = this.destination;\n destination.add(innerSubscriber);\n var innerSubscription = subscribeToResult(this, ish, undefined, undefined, innerSubscriber);\n if (innerSubscription !== innerSubscriber) {\n destination.add(innerSubscription);\n }\n };\n MergeMapSubscriber.prototype._complete = function () {\n this.hasCompleted = true;\n if (this.active === 0 && this.buffer.length === 0) {\n this.destination.complete();\n }\n this.unsubscribe();\n };\n MergeMapSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) {\n this.destination.next(innerValue);\n };\n MergeMapSubscriber.prototype.notifyComplete = function (innerSub) {\n var buffer = this.buffer;\n this.remove(innerSub);\n this.active--;\n if (buffer.length > 0) {\n this._next(buffer.shift());\n }\n else if (this.active === 0 && this.hasCompleted) {\n this.destination.complete();\n }\n };\n return MergeMapSubscriber;\n}(OuterSubscriber));\nexport { MergeMapSubscriber };\n//# sourceMappingURL=mergeMap.js.map\n","\"use strict\";\n\nexports.__esModule = true;\nexports[\"default\"] = math;\n\nvar _defaultSymbols = /*#__PURE__*/_interopRequireDefault( /*#__PURE__*/require(\"./presets/defaultSymbols\"));\n\nvar _errors = /*#__PURE__*/_interopRequireDefault( /*#__PURE__*/require(\"../internalHelpers/_errors\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nvar unitRegExp = /((?!\\w)a|na|hc|mc|dg|me[r]?|xe|ni(?![a-zA-Z])|mm|cp|tp|xp|q(?!s)|hv|xamv|nimv|wv|sm|s(?!\\D|$)|ged|darg?|nrut)/g; // Merges additional math functionality into the defaults.\n\nfunction mergeSymbolMaps(additionalSymbols) {\n var symbolMap = {};\n symbolMap.symbols = additionalSymbols ? _extends({}, _defaultSymbols[\"default\"].symbols, additionalSymbols.symbols) : _extends({}, _defaultSymbols[\"default\"].symbols);\n return symbolMap;\n}\n\nfunction exec(operators, values) {\n var _ref;\n\n var op = operators.pop();\n values.push(op.f.apply(op, (_ref = []).concat.apply(_ref, values.splice(-op.argCount))));\n return op.precedence;\n}\n\nfunction calculate(expression, additionalSymbols) {\n var symbolMap = mergeSymbolMaps(additionalSymbols);\n var match;\n var operators = [symbolMap.symbols['('].prefix];\n var values = [];\n var pattern = new RegExp( // Pattern for numbers\n \"\\\\d+(?:\\\\.\\\\d+)?|\" + // ...and patterns for individual operators/function names\n Object.keys(symbolMap.symbols).map(function (key) {\n return symbolMap.symbols[key];\n }) // longer symbols should be listed first\n // $FlowFixMe\n .sort(function (a, b) {\n return b.symbol.length - a.symbol.length;\n }) // $FlowFixMe\n .map(function (val) {\n return val.regSymbol;\n }).join('|') + \"|(\\\\S)\", 'g');\n pattern.lastIndex = 0; // Reset regular expression object\n\n var afterValue = false;\n\n do {\n match = pattern.exec(expression);\n\n var _ref2 = match || [')', undefined],\n token = _ref2[0],\n bad = _ref2[1];\n\n var notNumber = symbolMap.symbols[token];\n var notNewValue = notNumber && !notNumber.prefix && !notNumber.func;\n var notAfterValue = !notNumber || !notNumber.postfix && !notNumber.infix; // Check for syntax errors:\n\n if (bad || (afterValue ? notAfterValue : notNewValue)) {\n throw new _errors[\"default\"](37, match ? match.index : expression.length, expression);\n }\n\n if (afterValue) {\n // We either have an infix or postfix operator (they should be mutually exclusive)\n var curr = notNumber.postfix || notNumber.infix;\n\n do {\n var prev = operators[operators.length - 1];\n if ((curr.precedence - prev.precedence || prev.rightToLeft) > 0) break; // Apply previous operator, since it has precedence over current one\n } while (exec(operators, values)); // Exit loop after executing an opening parenthesis or function\n\n\n afterValue = curr.notation === 'postfix';\n\n if (curr.symbol !== ')') {\n operators.push(curr); // Postfix always has precedence over any operator that follows after it\n\n if (afterValue) exec(operators, values);\n }\n } else if (notNumber) {\n // prefix operator or function\n operators.push(notNumber.prefix || notNumber.func);\n\n if (notNumber.func) {\n // Require an opening parenthesis\n match = pattern.exec(expression);\n\n if (!match || match[0] !== '(') {\n throw new _errors[\"default\"](38, match ? match.index : expression.length, expression);\n }\n }\n } else {\n // number\n values.push(+token);\n afterValue = true;\n }\n } while (match && operators.length);\n\n if (operators.length) {\n throw new _errors[\"default\"](39, match ? match.index : expression.length, expression);\n } else if (match) {\n throw new _errors[\"default\"](40, match ? match.index : expression.length, expression);\n } else {\n return values.pop();\n }\n}\n\nfunction reverseString(str) {\n return str.split('').reverse().join('');\n}\n/**\n * Helper for doing math with CSS Units. Accepts a formula as a string. All values in the formula must have the same unit (or be unitless). Supports complex formulas utliziing addition, subtraction, multiplication, division, square root, powers, factorial, min, max, as well as parentheses for order of operation.\n *\n *In cases where you need to do calculations with mixed units where one unit is a [relative length unit](https://developer.mozilla.org/en-US/docs/Web/CSS/length#Relative_length_units), you will want to use [CSS Calc](https://developer.mozilla.org/en-US/docs/Web/CSS/calc).\n *\n * *warning* While we've done everything possible to ensure math safely evalutes formulas expressed as strings, you should always use extreme caution when passing `math` user provided values.\n * @example\n * // Styles as object usage\n * const styles = {\n * fontSize: math('12rem + 8rem'),\n * fontSize: math('(12px + 2px) * 3'),\n * fontSize: math('3px^2 + sqrt(4)'),\n * }\n *\n * // styled-components usage\n * const div = styled.div`\n * fontSize: ${math('12rem + 8rem')};\n * fontSize: ${math('(12px + 2px) * 3')};\n * fontSize: ${math('3px^2 + sqrt(4)')};\n * `\n *\n * // CSS as JS Output\n *\n * div: {\n * fontSize: '20rem',\n * fontSize: '42px',\n * fontSize: '11px',\n * }\n */\n\n\nfunction math(formula, additionalSymbols) {\n var reversedFormula = reverseString(formula);\n var formulaMatch = reversedFormula.match(unitRegExp); // Check that all units are the same\n\n if (formulaMatch && !formulaMatch.every(function (unit) {\n return unit === formulaMatch[0];\n })) {\n throw new _errors[\"default\"](41);\n }\n\n var cleanFormula = reverseString(reversedFormula.replace(unitRegExp, ''));\n return \"\" + calculate(cleanFormula, additionalSymbols) + (formulaMatch ? reverseString(formulaMatch[0]) : '');\n}\n\nmodule.exports = exports.default;","//\n\nmodule.exports = function shallowEqual(objA, objB, compare, compareContext) {\n var ret = compare ? compare.call(compareContext, objA, objB) : void 0;\n\n if (ret !== void 0) {\n return !!ret;\n }\n\n if (objA === objB) {\n return true;\n }\n\n if (typeof objA !== \"object\" || !objA || typeof objB !== \"object\" || !objB) {\n return false;\n }\n\n var keysA = Object.keys(objA);\n var keysB = Object.keys(objB);\n\n if (keysA.length !== keysB.length) {\n return false;\n }\n\n var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB);\n\n // Test for A's keys different from B.\n for (var idx = 0; idx < keysA.length; idx++) {\n var key = keysA[idx];\n\n if (!bHasOwnProperty(key)) {\n return false;\n }\n\n var valueA = objA[key];\n var valueB = objB[key];\n\n ret = compare ? compare.call(compareContext, valueA, valueB, key) : void 0;\n\n if (ret === false || (ret === void 0 && valueA !== valueB)) {\n return false;\n }\n }\n\n return true;\n};\n","function stylis_min (W) {\n function M(d, c, e, h, a) {\n for (var m = 0, b = 0, v = 0, n = 0, q, g, x = 0, K = 0, k, u = k = q = 0, l = 0, r = 0, I = 0, t = 0, B = e.length, J = B - 1, y, f = '', p = '', F = '', G = '', C; l < B;) {\n g = e.charCodeAt(l);\n l === J && 0 !== b + n + v + m && (0 !== b && (g = 47 === b ? 10 : 47), n = v = m = 0, B++, J++);\n\n if (0 === b + n + v + m) {\n if (l === J && (0 < r && (f = f.replace(N, '')), 0 < f.trim().length)) {\n switch (g) {\n case 32:\n case 9:\n case 59:\n case 13:\n case 10:\n break;\n\n default:\n f += e.charAt(l);\n }\n\n g = 59;\n }\n\n switch (g) {\n case 123:\n f = f.trim();\n q = f.charCodeAt(0);\n k = 1;\n\n for (t = ++l; l < B;) {\n switch (g = e.charCodeAt(l)) {\n case 123:\n k++;\n break;\n\n case 125:\n k--;\n break;\n\n case 47:\n switch (g = e.charCodeAt(l + 1)) {\n case 42:\n case 47:\n a: {\n for (u = l + 1; u < J; ++u) {\n switch (e.charCodeAt(u)) {\n case 47:\n if (42 === g && 42 === e.charCodeAt(u - 1) && l + 2 !== u) {\n l = u + 1;\n break a;\n }\n\n break;\n\n case 10:\n if (47 === g) {\n l = u + 1;\n break a;\n }\n\n }\n }\n\n l = u;\n }\n\n }\n\n break;\n\n case 91:\n g++;\n\n case 40:\n g++;\n\n case 34:\n case 39:\n for (; l++ < J && e.charCodeAt(l) !== g;) {\n }\n\n }\n\n if (0 === k) break;\n l++;\n }\n\n k = e.substring(t, l);\n 0 === q && (q = (f = f.replace(ca, '').trim()).charCodeAt(0));\n\n switch (q) {\n case 64:\n 0 < r && (f = f.replace(N, ''));\n g = f.charCodeAt(1);\n\n switch (g) {\n case 100:\n case 109:\n case 115:\n case 45:\n r = c;\n break;\n\n default:\n r = O;\n }\n\n k = M(c, r, k, g, a + 1);\n t = k.length;\n 0 < A && (r = X(O, f, I), C = H(3, k, r, c, D, z, t, g, a, h), f = r.join(''), void 0 !== C && 0 === (t = (k = C.trim()).length) && (g = 0, k = ''));\n if (0 < t) switch (g) {\n case 115:\n f = f.replace(da, ea);\n\n case 100:\n case 109:\n case 45:\n k = f + '{' + k + '}';\n break;\n\n case 107:\n f = f.replace(fa, '$1 $2');\n k = f + '{' + k + '}';\n k = 1 === w || 2 === w && L('@' + k, 3) ? '@-webkit-' + k + '@' + k : '@' + k;\n break;\n\n default:\n k = f + k, 112 === h && (k = (p += k, ''));\n } else k = '';\n break;\n\n default:\n k = M(c, X(c, f, I), k, h, a + 1);\n }\n\n F += k;\n k = I = r = u = q = 0;\n f = '';\n g = e.charCodeAt(++l);\n break;\n\n case 125:\n case 59:\n f = (0 < r ? f.replace(N, '') : f).trim();\n if (1 < (t = f.length)) switch (0 === u && (q = f.charCodeAt(0), 45 === q || 96 < q && 123 > q) && (t = (f = f.replace(' ', ':')).length), 0 < A && void 0 !== (C = H(1, f, c, d, D, z, p.length, h, a, h)) && 0 === (t = (f = C.trim()).length) && (f = '\\x00\\x00'), q = f.charCodeAt(0), g = f.charCodeAt(1), q) {\n case 0:\n break;\n\n case 64:\n if (105 === g || 99 === g) {\n G += f + e.charAt(l);\n break;\n }\n\n default:\n 58 !== f.charCodeAt(t - 1) && (p += P(f, q, g, f.charCodeAt(2)));\n }\n I = r = u = q = 0;\n f = '';\n g = e.charCodeAt(++l);\n }\n }\n\n switch (g) {\n case 13:\n case 10:\n 47 === b ? b = 0 : 0 === 1 + q && 107 !== h && 0 < f.length && (r = 1, f += '\\x00');\n 0 < A * Y && H(0, f, c, d, D, z, p.length, h, a, h);\n z = 1;\n D++;\n break;\n\n case 59:\n case 125:\n if (0 === b + n + v + m) {\n z++;\n break;\n }\n\n default:\n z++;\n y = e.charAt(l);\n\n switch (g) {\n case 9:\n case 32:\n if (0 === n + m + b) switch (x) {\n case 44:\n case 58:\n case 9:\n case 32:\n y = '';\n break;\n\n default:\n 32 !== g && (y = ' ');\n }\n break;\n\n case 0:\n y = '\\\\0';\n break;\n\n case 12:\n y = '\\\\f';\n break;\n\n case 11:\n y = '\\\\v';\n break;\n\n case 38:\n 0 === n + b + m && (r = I = 1, y = '\\f' + y);\n break;\n\n case 108:\n if (0 === n + b + m + E && 0 < u) switch (l - u) {\n case 2:\n 112 === x && 58 === e.charCodeAt(l - 3) && (E = x);\n\n case 8:\n 111 === K && (E = K);\n }\n break;\n\n case 58:\n 0 === n + b + m && (u = l);\n break;\n\n case 44:\n 0 === b + v + n + m && (r = 1, y += '\\r');\n break;\n\n case 34:\n case 39:\n 0 === b && (n = n === g ? 0 : 0 === n ? g : n);\n break;\n\n case 91:\n 0 === n + b + v && m++;\n break;\n\n case 93:\n 0 === n + b + v && m--;\n break;\n\n case 41:\n 0 === n + b + m && v--;\n break;\n\n case 40:\n if (0 === n + b + m) {\n if (0 === q) switch (2 * x + 3 * K) {\n case 533:\n break;\n\n default:\n q = 1;\n }\n v++;\n }\n\n break;\n\n case 64:\n 0 === b + v + n + m + u + k && (k = 1);\n break;\n\n case 42:\n case 47:\n if (!(0 < n + m + v)) switch (b) {\n case 0:\n switch (2 * g + 3 * e.charCodeAt(l + 1)) {\n case 235:\n b = 47;\n break;\n\n case 220:\n t = l, b = 42;\n }\n\n break;\n\n case 42:\n 47 === g && 42 === x && t + 2 !== l && (33 === e.charCodeAt(t + 2) && (p += e.substring(t, l + 1)), y = '', b = 0);\n }\n }\n\n 0 === b && (f += y);\n }\n\n K = x;\n x = g;\n l++;\n }\n\n t = p.length;\n\n if (0 < t) {\n r = c;\n if (0 < A && (C = H(2, p, r, d, D, z, t, h, a, h), void 0 !== C && 0 === (p = C).length)) return G + p + F;\n p = r.join(',') + '{' + p + '}';\n\n if (0 !== w * E) {\n 2 !== w || L(p, 2) || (E = 0);\n\n switch (E) {\n case 111:\n p = p.replace(ha, ':-moz-$1') + p;\n break;\n\n case 112:\n p = p.replace(Q, '::-webkit-input-$1') + p.replace(Q, '::-moz-$1') + p.replace(Q, ':-ms-input-$1') + p;\n }\n\n E = 0;\n }\n }\n\n return G + p + F;\n }\n\n function X(d, c, e) {\n var h = c.trim().split(ia);\n c = h;\n var a = h.length,\n m = d.length;\n\n switch (m) {\n case 0:\n case 1:\n var b = 0;\n\n for (d = 0 === m ? '' : d[0] + ' '; b < a; ++b) {\n c[b] = Z(d, c[b], e).trim();\n }\n\n break;\n\n default:\n var v = b = 0;\n\n for (c = []; b < a; ++b) {\n for (var n = 0; n < m; ++n) {\n c[v++] = Z(d[n] + ' ', h[b], e).trim();\n }\n }\n\n }\n\n return c;\n }\n\n function Z(d, c, e) {\n var h = c.charCodeAt(0);\n 33 > h && (h = (c = c.trim()).charCodeAt(0));\n\n switch (h) {\n case 38:\n return c.replace(F, '$1' + d.trim());\n\n case 58:\n return d.trim() + c.replace(F, '$1' + d.trim());\n\n default:\n if (0 < 1 * e && 0 < c.indexOf('\\f')) return c.replace(F, (58 === d.charCodeAt(0) ? '' : '$1') + d.trim());\n }\n\n return d + c;\n }\n\n function P(d, c, e, h) {\n var a = d + ';',\n m = 2 * c + 3 * e + 4 * h;\n\n if (944 === m) {\n d = a.indexOf(':', 9) + 1;\n var b = a.substring(d, a.length - 1).trim();\n b = a.substring(0, d).trim() + b + ';';\n return 1 === w || 2 === w && L(b, 1) ? '-webkit-' + b + b : b;\n }\n\n if (0 === w || 2 === w && !L(a, 1)) return a;\n\n switch (m) {\n case 1015:\n return 97 === a.charCodeAt(10) ? '-webkit-' + a + a : a;\n\n case 951:\n return 116 === a.charCodeAt(3) ? '-webkit-' + a + a : a;\n\n case 963:\n return 110 === a.charCodeAt(5) ? '-webkit-' + a + a : a;\n\n case 1009:\n if (100 !== a.charCodeAt(4)) break;\n\n case 969:\n case 942:\n return '-webkit-' + a + a;\n\n case 978:\n return '-webkit-' + a + '-moz-' + a + a;\n\n case 1019:\n case 983:\n return '-webkit-' + a + '-moz-' + a + '-ms-' + a + a;\n\n case 883:\n if (45 === a.charCodeAt(8)) return '-webkit-' + a + a;\n if (0 < a.indexOf('image-set(', 11)) return a.replace(ja, '$1-webkit-$2') + a;\n break;\n\n case 932:\n if (45 === a.charCodeAt(4)) switch (a.charCodeAt(5)) {\n case 103:\n return '-webkit-box-' + a.replace('-grow', '') + '-webkit-' + a + '-ms-' + a.replace('grow', 'positive') + a;\n\n case 115:\n return '-webkit-' + a + '-ms-' + a.replace('shrink', 'negative') + a;\n\n case 98:\n return '-webkit-' + a + '-ms-' + a.replace('basis', 'preferred-size') + a;\n }\n return '-webkit-' + a + '-ms-' + a + a;\n\n case 964:\n return '-webkit-' + a + '-ms-flex-' + a + a;\n\n case 1023:\n if (99 !== a.charCodeAt(8)) break;\n b = a.substring(a.indexOf(':', 15)).replace('flex-', '').replace('space-between', 'justify');\n return '-webkit-box-pack' + b + '-webkit-' + a + '-ms-flex-pack' + b + a;\n\n case 1005:\n return ka.test(a) ? a.replace(aa, ':-webkit-') + a.replace(aa, ':-moz-') + a : a;\n\n case 1e3:\n b = a.substring(13).trim();\n c = b.indexOf('-') + 1;\n\n switch (b.charCodeAt(0) + b.charCodeAt(c)) {\n case 226:\n b = a.replace(G, 'tb');\n break;\n\n case 232:\n b = a.replace(G, 'tb-rl');\n break;\n\n case 220:\n b = a.replace(G, 'lr');\n break;\n\n default:\n return a;\n }\n\n return '-webkit-' + a + '-ms-' + b + a;\n\n case 1017:\n if (-1 === a.indexOf('sticky', 9)) break;\n\n case 975:\n c = (a = d).length - 10;\n b = (33 === a.charCodeAt(c) ? a.substring(0, c) : a).substring(d.indexOf(':', 7) + 1).trim();\n\n switch (m = b.charCodeAt(0) + (b.charCodeAt(7) | 0)) {\n case 203:\n if (111 > b.charCodeAt(8)) break;\n\n case 115:\n a = a.replace(b, '-webkit-' + b) + ';' + a;\n break;\n\n case 207:\n case 102:\n a = a.replace(b, '-webkit-' + (102 < m ? 'inline-' : '') + 'box') + ';' + a.replace(b, '-webkit-' + b) + ';' + a.replace(b, '-ms-' + b + 'box') + ';' + a;\n }\n\n return a + ';';\n\n case 938:\n if (45 === a.charCodeAt(5)) switch (a.charCodeAt(6)) {\n case 105:\n return b = a.replace('-items', ''), '-webkit-' + a + '-webkit-box-' + b + '-ms-flex-' + b + a;\n\n case 115:\n return '-webkit-' + a + '-ms-flex-item-' + a.replace(ba, '') + a;\n\n default:\n return '-webkit-' + a + '-ms-flex-line-pack' + a.replace('align-content', '').replace(ba, '') + a;\n }\n break;\n\n case 973:\n case 989:\n if (45 !== a.charCodeAt(3) || 122 === a.charCodeAt(4)) break;\n\n case 931:\n case 953:\n if (!0 === la.test(d)) return 115 === (b = d.substring(d.indexOf(':') + 1)).charCodeAt(0) ? P(d.replace('stretch', 'fill-available'), c, e, h).replace(':fill-available', ':stretch') : a.replace(b, '-webkit-' + b) + a.replace(b, '-moz-' + b.replace('fill-', '')) + a;\n break;\n\n case 962:\n if (a = '-webkit-' + a + (102 === a.charCodeAt(5) ? '-ms-' + a : '') + a, 211 === e + h && 105 === a.charCodeAt(13) && 0 < a.indexOf('transform', 10)) return a.substring(0, a.indexOf(';', 27) + 1).replace(ma, '$1-webkit-$2') + a;\n }\n\n return a;\n }\n\n function L(d, c) {\n var e = d.indexOf(1 === c ? ':' : '{'),\n h = d.substring(0, 3 !== c ? e : 10);\n e = d.substring(e + 1, d.length - 1);\n return R(2 !== c ? h : h.replace(na, '$1'), e, c);\n }\n\n function ea(d, c) {\n var e = P(c, c.charCodeAt(0), c.charCodeAt(1), c.charCodeAt(2));\n return e !== c + ';' ? e.replace(oa, ' or ($1)').substring(4) : '(' + c + ')';\n }\n\n function H(d, c, e, h, a, m, b, v, n, q) {\n for (var g = 0, x = c, w; g < A; ++g) {\n switch (w = S[g].call(B, d, x, e, h, a, m, b, v, n, q)) {\n case void 0:\n case !1:\n case !0:\n case null:\n break;\n\n default:\n x = w;\n }\n }\n\n if (x !== c) return x;\n }\n\n function T(d) {\n switch (d) {\n case void 0:\n case null:\n A = S.length = 0;\n break;\n\n default:\n if ('function' === typeof d) S[A++] = d;else if ('object' === typeof d) for (var c = 0, e = d.length; c < e; ++c) {\n T(d[c]);\n } else Y = !!d | 0;\n }\n\n return T;\n }\n\n function U(d) {\n d = d.prefix;\n void 0 !== d && (R = null, d ? 'function' !== typeof d ? w = 1 : (w = 2, R = d) : w = 0);\n return U;\n }\n\n function B(d, c) {\n var e = d;\n 33 > e.charCodeAt(0) && (e = e.trim());\n V = e;\n e = [V];\n\n if (0 < A) {\n var h = H(-1, c, e, e, D, z, 0, 0, 0, 0);\n void 0 !== h && 'string' === typeof h && (c = h);\n }\n\n var a = M(O, e, c, 0, 0);\n 0 < A && (h = H(-2, a, e, e, D, z, a.length, 0, 0, 0), void 0 !== h && (a = h));\n V = '';\n E = 0;\n z = D = 1;\n return a;\n }\n\n var ca = /^\\0+/g,\n N = /[\\0\\r\\f]/g,\n aa = /: */g,\n ka = /zoo|gra/,\n ma = /([,: ])(transform)/g,\n ia = /,\\r+?/g,\n F = /([\\t\\r\\n ])*\\f?&/g,\n fa = /@(k\\w+)\\s*(\\S*)\\s*/,\n Q = /::(place)/g,\n ha = /:(read-only)/g,\n G = /[svh]\\w+-[tblr]{2}/,\n da = /\\(\\s*(.*)\\s*\\)/g,\n oa = /([\\s\\S]*?);/g,\n ba = /-self|flex-/g,\n na = /[^]*?(:[rp][el]a[\\w-]+)[^]*/,\n la = /stretch|:\\s*\\w+\\-(?:conte|avail)/,\n ja = /([^-])(image-set\\()/,\n z = 1,\n D = 1,\n E = 0,\n w = 1,\n O = [],\n S = [],\n A = 0,\n R = null,\n Y = 0,\n V = '';\n B.use = T;\n B.set = U;\n void 0 !== W && U(W);\n return B;\n}\n\nexport default stylis_min;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar tslib_1 = require(\"tslib\");\nvar useEffectOnce_1 = tslib_1.__importDefault(require(\"./useEffectOnce\"));\nvar useMount = function (fn) {\n useEffectOnce_1.default(function () {\n fn();\n });\n};\nexports.default = useMount;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar react_1 = require(\"react\");\nvar useFirstMountState_1 = require(\"./useFirstMountState\");\nvar strictEquals = function (prev, next) { return prev === next; };\nfunction usePreviousDistinct(value, compare) {\n if (compare === void 0) { compare = strictEquals; }\n var prevRef = react_1.useRef();\n var curRef = react_1.useRef(value);\n var isFirstMount = useFirstMountState_1.useFirstMountState();\n if (!isFirstMount && !compare(curRef.current, value)) {\n prevRef.current = curRef.current;\n curRef.current = value;\n }\n return prevRef.current;\n}\nexports.default = usePreviousDistinct;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar react_1 = require(\"react\");\nvar useFirstMountState_1 = require(\"./useFirstMountState\");\nvar useUpdateEffect = function (effect, deps) {\n var isFirstMount = useFirstMountState_1.useFirstMountState();\n react_1.useEffect(function () {\n if (!isFirstMount) {\n return effect();\n }\n }, deps);\n};\nexports.default = useUpdateEffect;\n","module.exports = require('./lib/axios');","var objectKeys = require('object-keys');\nvar isArguments = require('is-arguments');\nvar is = require('object-is');\nvar isRegex = require('is-regex');\nvar flags = require('regexp.prototype.flags');\nvar isDate = require('is-date-object');\n\nvar getTime = Date.prototype.getTime;\n\nfunction deepEqual(actual, expected, options) {\n var opts = options || {};\n\n // 7.1. All identical values are equivalent, as determined by ===.\n if (opts.strict ? is(actual, expected) : actual === expected) {\n return true;\n }\n\n // 7.3. Other pairs that do not both pass typeof value == 'object', equivalence is determined by ==.\n if (!actual || !expected || (typeof actual !== 'object' && typeof expected !== 'object')) {\n return opts.strict ? is(actual, expected) : actual == expected;\n }\n\n /*\n * 7.4. For all other Object pairs, including Array objects, equivalence is\n * determined by having the same number of owned properties (as verified\n * with Object.prototype.hasOwnProperty.call), the same set of keys\n * (although not necessarily the same order), equivalent values for every\n * corresponding key, and an identical 'prototype' property. Note: this\n * accounts for both named and indexed properties on Arrays.\n */\n // eslint-disable-next-line no-use-before-define\n return objEquiv(actual, expected, opts);\n}\n\nfunction isUndefinedOrNull(value) {\n return value === null || value === undefined;\n}\n\nfunction isBuffer(x) {\n if (!x || typeof x !== 'object' || typeof x.length !== 'number') {\n return false;\n }\n if (typeof x.copy !== 'function' || typeof x.slice !== 'function') {\n return false;\n }\n if (x.length > 0 && typeof x[0] !== 'number') {\n return false;\n }\n return true;\n}\n\nfunction objEquiv(a, b, opts) {\n /* eslint max-statements: [2, 50] */\n var i, key;\n if (typeof a !== typeof b) { return false; }\n if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) { return false; }\n\n // an identical 'prototype' property.\n if (a.prototype !== b.prototype) { return false; }\n\n if (isArguments(a) !== isArguments(b)) { return false; }\n\n var aIsRegex = isRegex(a);\n var bIsRegex = isRegex(b);\n if (aIsRegex !== bIsRegex) { return false; }\n if (aIsRegex || bIsRegex) {\n return a.source === b.source && flags(a) === flags(b);\n }\n\n if (isDate(a) && isDate(b)) {\n return getTime.call(a) === getTime.call(b);\n }\n\n var aIsBuffer = isBuffer(a);\n var bIsBuffer = isBuffer(b);\n if (aIsBuffer !== bIsBuffer) { return false; }\n if (aIsBuffer || bIsBuffer) { // && would work too, because both are true or both false here\n if (a.length !== b.length) { return false; }\n for (i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) { return false; }\n }\n return true;\n }\n\n if (typeof a !== typeof b) { return false; }\n\n try {\n var ka = objectKeys(a);\n var kb = objectKeys(b);\n } catch (e) { // happens when one is a string literal and the other isn't\n return false;\n }\n // having the same number of owned properties (keys incorporates hasOwnProperty)\n if (ka.length !== kb.length) { return false; }\n\n // the same set of keys (although not necessarily the same order),\n ka.sort();\n kb.sort();\n // ~~~cheap key test\n for (i = ka.length - 1; i >= 0; i--) {\n if (ka[i] != kb[i]) { return false; }\n }\n // equivalent values for every corresponding key, and ~~~possibly expensive deep test\n for (i = ka.length - 1; i >= 0; i--) {\n key = ka[i];\n if (!deepEqual(a[key], b[key], opts)) { return false; }\n }\n\n return true;\n}\n\nmodule.exports = deepEqual;\n","var global = typeof self !== 'undefined' ? self : this;\nvar __self__ = (function () {\nfunction F() {\nthis.fetch = false;\nthis.DOMException = global.DOMException\n}\nF.prototype = global;\nreturn new F();\n})();\n(function(self) {\n\nvar irrelevant = (function (exports) {\n\n var support = {\n searchParams: 'URLSearchParams' in self,\n iterable: 'Symbol' in self && 'iterator' in Symbol,\n blob:\n 'FileReader' in self &&\n 'Blob' in self &&\n (function() {\n try {\n new Blob();\n return true\n } catch (e) {\n return false\n }\n })(),\n formData: 'FormData' in self,\n arrayBuffer: 'ArrayBuffer' in self\n };\n\n function isDataView(obj) {\n return obj && DataView.prototype.isPrototypeOf(obj)\n }\n\n if (support.arrayBuffer) {\n var viewClasses = [\n '[object Int8Array]',\n '[object Uint8Array]',\n '[object Uint8ClampedArray]',\n '[object Int16Array]',\n '[object Uint16Array]',\n '[object Int32Array]',\n '[object Uint32Array]',\n '[object Float32Array]',\n '[object Float64Array]'\n ];\n\n var isArrayBufferView =\n ArrayBuffer.isView ||\n function(obj) {\n return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1\n };\n }\n\n function normalizeName(name) {\n if (typeof name !== 'string') {\n name = String(name);\n }\n if (/[^a-z0-9\\-#$%&'*+.^_`|~]/i.test(name)) {\n throw new TypeError('Invalid character in header field name')\n }\n return name.toLowerCase()\n }\n\n function normalizeValue(value) {\n if (typeof value !== 'string') {\n value = String(value);\n }\n return value\n }\n\n // Build a destructive iterator for the value list\n function iteratorFor(items) {\n var iterator = {\n next: function() {\n var value = items.shift();\n return {done: value === undefined, value: value}\n }\n };\n\n if (support.iterable) {\n iterator[Symbol.iterator] = function() {\n return iterator\n };\n }\n\n return iterator\n }\n\n function Headers(headers) {\n this.map = {};\n\n if (headers instanceof Headers) {\n headers.forEach(function(value, name) {\n this.append(name, value);\n }, this);\n } else if (Array.isArray(headers)) {\n headers.forEach(function(header) {\n this.append(header[0], header[1]);\n }, this);\n } else if (headers) {\n Object.getOwnPropertyNames(headers).forEach(function(name) {\n this.append(name, headers[name]);\n }, this);\n }\n }\n\n Headers.prototype.append = function(name, value) {\n name = normalizeName(name);\n value = normalizeValue(value);\n var oldValue = this.map[name];\n this.map[name] = oldValue ? oldValue + ', ' + value : value;\n };\n\n Headers.prototype['delete'] = function(name) {\n delete this.map[normalizeName(name)];\n };\n\n Headers.prototype.get = function(name) {\n name = normalizeName(name);\n return this.has(name) ? this.map[name] : null\n };\n\n Headers.prototype.has = function(name) {\n return this.map.hasOwnProperty(normalizeName(name))\n };\n\n Headers.prototype.set = function(name, value) {\n this.map[normalizeName(name)] = normalizeValue(value);\n };\n\n Headers.prototype.forEach = function(callback, thisArg) {\n for (var name in this.map) {\n if (this.map.hasOwnProperty(name)) {\n callback.call(thisArg, this.map[name], name, this);\n }\n }\n };\n\n Headers.prototype.keys = function() {\n var items = [];\n this.forEach(function(value, name) {\n items.push(name);\n });\n return iteratorFor(items)\n };\n\n Headers.prototype.values = function() {\n var items = [];\n this.forEach(function(value) {\n items.push(value);\n });\n return iteratorFor(items)\n };\n\n Headers.prototype.entries = function() {\n var items = [];\n this.forEach(function(value, name) {\n items.push([name, value]);\n });\n return iteratorFor(items)\n };\n\n if (support.iterable) {\n Headers.prototype[Symbol.iterator] = Headers.prototype.entries;\n }\n\n function consumed(body) {\n if (body.bodyUsed) {\n return Promise.reject(new TypeError('Already read'))\n }\n body.bodyUsed = true;\n }\n\n function fileReaderReady(reader) {\n return new Promise(function(resolve, reject) {\n reader.onload = function() {\n resolve(reader.result);\n };\n reader.onerror = function() {\n reject(reader.error);\n };\n })\n }\n\n function readBlobAsArrayBuffer(blob) {\n var reader = new FileReader();\n var promise = fileReaderReady(reader);\n reader.readAsArrayBuffer(blob);\n return promise\n }\n\n function readBlobAsText(blob) {\n var reader = new FileReader();\n var promise = fileReaderReady(reader);\n reader.readAsText(blob);\n return promise\n }\n\n function readArrayBufferAsText(buf) {\n var view = new Uint8Array(buf);\n var chars = new Array(view.length);\n\n for (var i = 0; i < view.length; i++) {\n chars[i] = String.fromCharCode(view[i]);\n }\n return chars.join('')\n }\n\n function bufferClone(buf) {\n if (buf.slice) {\n return buf.slice(0)\n } else {\n var view = new Uint8Array(buf.byteLength);\n view.set(new Uint8Array(buf));\n return view.buffer\n }\n }\n\n function Body() {\n this.bodyUsed = false;\n\n this._initBody = function(body) {\n this._bodyInit = body;\n if (!body) {\n this._bodyText = '';\n } else if (typeof body === 'string') {\n this._bodyText = body;\n } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {\n this._bodyBlob = body;\n } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {\n this._bodyFormData = body;\n } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {\n this._bodyText = body.toString();\n } else if (support.arrayBuffer && support.blob && isDataView(body)) {\n this._bodyArrayBuffer = bufferClone(body.buffer);\n // IE 10-11 can't handle a DataView body.\n this._bodyInit = new Blob([this._bodyArrayBuffer]);\n } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {\n this._bodyArrayBuffer = bufferClone(body);\n } else {\n this._bodyText = body = Object.prototype.toString.call(body);\n }\n\n if (!this.headers.get('content-type')) {\n if (typeof body === 'string') {\n this.headers.set('content-type', 'text/plain;charset=UTF-8');\n } else if (this._bodyBlob && this._bodyBlob.type) {\n this.headers.set('content-type', this._bodyBlob.type);\n } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {\n this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');\n }\n }\n };\n\n if (support.blob) {\n this.blob = function() {\n var rejected = consumed(this);\n if (rejected) {\n return rejected\n }\n\n if (this._bodyBlob) {\n return Promise.resolve(this._bodyBlob)\n } else if (this._bodyArrayBuffer) {\n return Promise.resolve(new Blob([this._bodyArrayBuffer]))\n } else if (this._bodyFormData) {\n throw new Error('could not read FormData body as blob')\n } else {\n return Promise.resolve(new Blob([this._bodyText]))\n }\n };\n\n this.arrayBuffer = function() {\n if (this._bodyArrayBuffer) {\n return consumed(this) || Promise.resolve(this._bodyArrayBuffer)\n } else {\n return this.blob().then(readBlobAsArrayBuffer)\n }\n };\n }\n\n this.text = function() {\n var rejected = consumed(this);\n if (rejected) {\n return rejected\n }\n\n if (this._bodyBlob) {\n return readBlobAsText(this._bodyBlob)\n } else if (this._bodyArrayBuffer) {\n return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))\n } else if (this._bodyFormData) {\n throw new Error('could not read FormData body as text')\n } else {\n return Promise.resolve(this._bodyText)\n }\n };\n\n if (support.formData) {\n this.formData = function() {\n return this.text().then(decode)\n };\n }\n\n this.json = function() {\n return this.text().then(JSON.parse)\n };\n\n return this\n }\n\n // HTTP methods whose capitalization should be normalized\n var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];\n\n function normalizeMethod(method) {\n var upcased = method.toUpperCase();\n return methods.indexOf(upcased) > -1 ? upcased : method\n }\n\n function Request(input, options) {\n options = options || {};\n var body = options.body;\n\n if (input instanceof Request) {\n if (input.bodyUsed) {\n throw new TypeError('Already read')\n }\n this.url = input.url;\n this.credentials = input.credentials;\n if (!options.headers) {\n this.headers = new Headers(input.headers);\n }\n this.method = input.method;\n this.mode = input.mode;\n this.signal = input.signal;\n if (!body && input._bodyInit != null) {\n body = input._bodyInit;\n input.bodyUsed = true;\n }\n } else {\n this.url = String(input);\n }\n\n this.credentials = options.credentials || this.credentials || 'same-origin';\n if (options.headers || !this.headers) {\n this.headers = new Headers(options.headers);\n }\n this.method = normalizeMethod(options.method || this.method || 'GET');\n this.mode = options.mode || this.mode || null;\n this.signal = options.signal || this.signal;\n this.referrer = null;\n\n if ((this.method === 'GET' || this.method === 'HEAD') && body) {\n throw new TypeError('Body not allowed for GET or HEAD requests')\n }\n this._initBody(body);\n }\n\n Request.prototype.clone = function() {\n return new Request(this, {body: this._bodyInit})\n };\n\n function decode(body) {\n var form = new FormData();\n body\n .trim()\n .split('&')\n .forEach(function(bytes) {\n if (bytes) {\n var split = bytes.split('=');\n var name = split.shift().replace(/\\+/g, ' ');\n var value = split.join('=').replace(/\\+/g, ' ');\n form.append(decodeURIComponent(name), decodeURIComponent(value));\n }\n });\n return form\n }\n\n function parseHeaders(rawHeaders) {\n var headers = new Headers();\n // Replace instances of \\r\\n and \\n followed by at least one space or horizontal tab with a space\n // https://tools.ietf.org/html/rfc7230#section-3.2\n var preProcessedHeaders = rawHeaders.replace(/\\r?\\n[\\t ]+/g, ' ');\n preProcessedHeaders.split(/\\r?\\n/).forEach(function(line) {\n var parts = line.split(':');\n var key = parts.shift().trim();\n if (key) {\n var value = parts.join(':').trim();\n headers.append(key, value);\n }\n });\n return headers\n }\n\n Body.call(Request.prototype);\n\n function Response(bodyInit, options) {\n if (!options) {\n options = {};\n }\n\n this.type = 'default';\n this.status = options.status === undefined ? 200 : options.status;\n this.ok = this.status >= 200 && this.status < 300;\n this.statusText = 'statusText' in options ? options.statusText : 'OK';\n this.headers = new Headers(options.headers);\n this.url = options.url || '';\n this._initBody(bodyInit);\n }\n\n Body.call(Response.prototype);\n\n Response.prototype.clone = function() {\n return new Response(this._bodyInit, {\n status: this.status,\n statusText: this.statusText,\n headers: new Headers(this.headers),\n url: this.url\n })\n };\n\n Response.error = function() {\n var response = new Response(null, {status: 0, statusText: ''});\n response.type = 'error';\n return response\n };\n\n var redirectStatuses = [301, 302, 303, 307, 308];\n\n Response.redirect = function(url, status) {\n if (redirectStatuses.indexOf(status) === -1) {\n throw new RangeError('Invalid status code')\n }\n\n return new Response(null, {status: status, headers: {location: url}})\n };\n\n exports.DOMException = self.DOMException;\n try {\n new exports.DOMException();\n } catch (err) {\n exports.DOMException = function(message, name) {\n this.message = message;\n this.name = name;\n var error = Error(message);\n this.stack = error.stack;\n };\n exports.DOMException.prototype = Object.create(Error.prototype);\n exports.DOMException.prototype.constructor = exports.DOMException;\n }\n\n function fetch(input, init) {\n return new Promise(function(resolve, reject) {\n var request = new Request(input, init);\n\n if (request.signal && request.signal.aborted) {\n return reject(new exports.DOMException('Aborted', 'AbortError'))\n }\n\n var xhr = new XMLHttpRequest();\n\n function abortXhr() {\n xhr.abort();\n }\n\n xhr.onload = function() {\n var options = {\n status: xhr.status,\n statusText: xhr.statusText,\n headers: parseHeaders(xhr.getAllResponseHeaders() || '')\n };\n options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');\n var body = 'response' in xhr ? xhr.response : xhr.responseText;\n resolve(new Response(body, options));\n };\n\n xhr.onerror = function() {\n reject(new TypeError('Network request failed'));\n };\n\n xhr.ontimeout = function() {\n reject(new TypeError('Network request failed'));\n };\n\n xhr.onabort = function() {\n reject(new exports.DOMException('Aborted', 'AbortError'));\n };\n\n xhr.open(request.method, request.url, true);\n\n if (request.credentials === 'include') {\n xhr.withCredentials = true;\n } else if (request.credentials === 'omit') {\n xhr.withCredentials = false;\n }\n\n if ('responseType' in xhr && support.blob) {\n xhr.responseType = 'blob';\n }\n\n request.headers.forEach(function(value, name) {\n xhr.setRequestHeader(name, value);\n });\n\n if (request.signal) {\n request.signal.addEventListener('abort', abortXhr);\n\n xhr.onreadystatechange = function() {\n // DONE (success or failure)\n if (xhr.readyState === 4) {\n request.signal.removeEventListener('abort', abortXhr);\n }\n };\n }\n\n xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit);\n })\n }\n\n fetch.polyfill = true;\n\n if (!self.fetch) {\n self.fetch = fetch;\n self.Headers = Headers;\n self.Request = Request;\n self.Response = Response;\n }\n\n exports.Headers = Headers;\n exports.Request = Request;\n exports.Response = Response;\n exports.fetch = fetch;\n\n Object.defineProperty(exports, '__esModule', { value: true });\n\n return exports;\n\n})({});\n})(__self__);\n__self__.fetch.ponyfill = true;\n// Remove \"polyfill\" property added by whatwg-fetch\ndelete __self__.fetch.polyfill;\n// Choose between native implementation (global) or custom implementation (__self__)\n// var ctx = global.fetch ? global : __self__;\nvar ctx = __self__; // this line disable service worker support temporarily\nexports = ctx.fetch // To enable: import fetch from 'cross-fetch'\nexports.default = ctx.fetch // For TypeScript consumers without esModuleInterop.\nexports.fetch = ctx.fetch // To enable: import {fetch} from 'cross-fetch'\nexports.Headers = ctx.Headers\nexports.Request = ctx.Request\nexports.Response = ctx.Response\nmodule.exports = exports\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar react_1 = require(\"react\");\nvar util_1 = require(\"./misc/util\");\nvar useLocalStorage = function (key, initialValue, options) {\n if (!util_1.isBrowser) {\n return [initialValue, util_1.noop, util_1.noop];\n }\n if (!key) {\n throw new Error('useLocalStorage key may not be falsy');\n }\n var deserializer = options\n ? options.raw\n ? function (value) { return value; }\n : options.deserializer\n : JSON.parse;\n // eslint-disable-next-line react-hooks/rules-of-hooks\n var initializer = react_1.useRef(function (key) {\n try {\n var serializer = options ? (options.raw ? String : options.serializer) : JSON.stringify;\n var localStorageValue = localStorage.getItem(key);\n if (localStorageValue !== null) {\n return deserializer(localStorageValue);\n }\n else {\n initialValue && localStorage.setItem(key, serializer(initialValue));\n return initialValue;\n }\n }\n catch (_a) {\n // If user is in private mode or has storage restriction\n // localStorage can throw. JSON.parse and JSON.stringify\n // can throw, too.\n return initialValue;\n }\n });\n // eslint-disable-next-line react-hooks/rules-of-hooks\n var _a = react_1.useState(function () { return initializer.current(key); }), state = _a[0], setState = _a[1];\n // eslint-disable-next-line react-hooks/rules-of-hooks\n react_1.useLayoutEffect(function () { return setState(initializer.current(key)); }, [key]);\n // eslint-disable-next-line react-hooks/rules-of-hooks\n var set = react_1.useCallback(function (valOrFunc) {\n try {\n var newState = typeof valOrFunc === 'function' ? valOrFunc(state) : valOrFunc;\n if (typeof newState === 'undefined')\n return;\n var value = void 0;\n if (options)\n if (options.raw)\n if (typeof newState === 'string')\n value = newState;\n else\n value = JSON.stringify(newState);\n else if (options.serializer)\n value = options.serializer(newState);\n else\n value = JSON.stringify(newState);\n else\n value = JSON.stringify(newState);\n localStorage.setItem(key, value);\n setState(deserializer(value));\n }\n catch (_a) {\n // If user is in private mode or has storage restriction\n // localStorage can throw. Also JSON.stringify can throw.\n }\n }, [key, setState]);\n // eslint-disable-next-line react-hooks/rules-of-hooks\n var remove = react_1.useCallback(function () {\n try {\n localStorage.removeItem(key);\n setState(undefined);\n }\n catch (_a) {\n // If user is in private mode or has storage restriction\n // localStorage can throw.\n }\n }, [key, setState]);\n return [state, set, remove];\n};\nexports.default = useLocalStorage;\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-is.production.min.js');\n} else {\n module.exports = require('./cjs/react-is.development.js');\n}\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar react_1 = require(\"react\");\nfunction usePrevious(state) {\n var ref = react_1.useRef();\n react_1.useEffect(function () {\n ref.current = state;\n });\n return ref.current;\n}\nexports.default = usePrevious;\n","'use strict';\n\nmodule.exports = require('./src/js/main');\n","/**\r\n * A collection of shims that provide minimal functionality of the ES6 collections.\r\n *\r\n * These implementations are not meant to be used outside of the ResizeObserver\r\n * modules as they cover only a limited range of use cases.\r\n */\r\n/* eslint-disable require-jsdoc, valid-jsdoc */\r\nvar MapShim = (function () {\r\n if (typeof Map !== 'undefined') {\r\n return Map;\r\n }\r\n /**\r\n * Returns index in provided array that matches the specified key.\r\n *\r\n * @param {Array} arr\r\n * @param {*} key\r\n * @returns {number}\r\n */\r\n function getIndex(arr, key) {\r\n var result = -1;\r\n arr.some(function (entry, index) {\r\n if (entry[0] === key) {\r\n result = index;\r\n return true;\r\n }\r\n return false;\r\n });\r\n return result;\r\n }\r\n return /** @class */ (function () {\r\n function class_1() {\r\n this.__entries__ = [];\r\n }\r\n Object.defineProperty(class_1.prototype, \"size\", {\r\n /**\r\n * @returns {boolean}\r\n */\r\n get: function () {\r\n return this.__entries__.length;\r\n },\r\n enumerable: true,\r\n configurable: true\r\n });\r\n /**\r\n * @param {*} key\r\n * @returns {*}\r\n */\r\n class_1.prototype.get = function (key) {\r\n var index = getIndex(this.__entries__, key);\r\n var entry = this.__entries__[index];\r\n return entry && entry[1];\r\n };\r\n /**\r\n * @param {*} key\r\n * @param {*} value\r\n * @returns {void}\r\n */\r\n class_1.prototype.set = function (key, value) {\r\n var index = getIndex(this.__entries__, key);\r\n if (~index) {\r\n this.__entries__[index][1] = value;\r\n }\r\n else {\r\n this.__entries__.push([key, value]);\r\n }\r\n };\r\n /**\r\n * @param {*} key\r\n * @returns {void}\r\n */\r\n class_1.prototype.delete = function (key) {\r\n var entries = this.__entries__;\r\n var index = getIndex(entries, key);\r\n if (~index) {\r\n entries.splice(index, 1);\r\n }\r\n };\r\n /**\r\n * @param {*} key\r\n * @returns {void}\r\n */\r\n class_1.prototype.has = function (key) {\r\n return !!~getIndex(this.__entries__, key);\r\n };\r\n /**\r\n * @returns {void}\r\n */\r\n class_1.prototype.clear = function () {\r\n this.__entries__.splice(0);\r\n };\r\n /**\r\n * @param {Function} callback\r\n * @param {*} [ctx=null]\r\n * @returns {void}\r\n */\r\n class_1.prototype.forEach = function (callback, ctx) {\r\n if (ctx === void 0) { ctx = null; }\r\n for (var _i = 0, _a = this.__entries__; _i < _a.length; _i++) {\r\n var entry = _a[_i];\r\n callback.call(ctx, entry[1], entry[0]);\r\n }\r\n };\r\n return class_1;\r\n }());\r\n})();\n\n/**\r\n * Detects whether window and document objects are available in current environment.\r\n */\r\nvar isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' && window.document === document;\n\n// Returns global object of a current environment.\r\nvar global$1 = (function () {\r\n if (typeof global !== 'undefined' && global.Math === Math) {\r\n return global;\r\n }\r\n if (typeof self !== 'undefined' && self.Math === Math) {\r\n return self;\r\n }\r\n if (typeof window !== 'undefined' && window.Math === Math) {\r\n return window;\r\n }\r\n // eslint-disable-next-line no-new-func\r\n return Function('return this')();\r\n})();\n\n/**\r\n * A shim for the requestAnimationFrame which falls back to the setTimeout if\r\n * first one is not supported.\r\n *\r\n * @returns {number} Requests' identifier.\r\n */\r\nvar requestAnimationFrame$1 = (function () {\r\n if (typeof requestAnimationFrame === 'function') {\r\n // It's required to use a bounded function because IE sometimes throws\r\n // an \"Invalid calling object\" error if rAF is invoked without the global\r\n // object on the left hand side.\r\n return requestAnimationFrame.bind(global$1);\r\n }\r\n return function (callback) { return setTimeout(function () { return callback(Date.now()); }, 1000 / 60); };\r\n})();\n\n// Defines minimum timeout before adding a trailing call.\r\nvar trailingTimeout = 2;\r\n/**\r\n * Creates a wrapper function which ensures that provided callback will be\r\n * invoked only once during the specified delay period.\r\n *\r\n * @param {Function} callback - Function to be invoked after the delay period.\r\n * @param {number} delay - Delay after which to invoke callback.\r\n * @returns {Function}\r\n */\r\nfunction throttle (callback, delay) {\r\n var leadingCall = false, trailingCall = false, lastCallTime = 0;\r\n /**\r\n * Invokes the original callback function and schedules new invocation if\r\n * the \"proxy\" was called during current request.\r\n *\r\n * @returns {void}\r\n */\r\n function resolvePending() {\r\n if (leadingCall) {\r\n leadingCall = false;\r\n callback();\r\n }\r\n if (trailingCall) {\r\n proxy();\r\n }\r\n }\r\n /**\r\n * Callback invoked after the specified delay. It will further postpone\r\n * invocation of the original function delegating it to the\r\n * requestAnimationFrame.\r\n *\r\n * @returns {void}\r\n */\r\n function timeoutCallback() {\r\n requestAnimationFrame$1(resolvePending);\r\n }\r\n /**\r\n * Schedules invocation of the original function.\r\n *\r\n * @returns {void}\r\n */\r\n function proxy() {\r\n var timeStamp = Date.now();\r\n if (leadingCall) {\r\n // Reject immediately following calls.\r\n if (timeStamp - lastCallTime < trailingTimeout) {\r\n return;\r\n }\r\n // Schedule new call to be in invoked when the pending one is resolved.\r\n // This is important for \"transitions\" which never actually start\r\n // immediately so there is a chance that we might miss one if change\r\n // happens amids the pending invocation.\r\n trailingCall = true;\r\n }\r\n else {\r\n leadingCall = true;\r\n trailingCall = false;\r\n setTimeout(timeoutCallback, delay);\r\n }\r\n lastCallTime = timeStamp;\r\n }\r\n return proxy;\r\n}\n\n// Minimum delay before invoking the update of observers.\r\nvar REFRESH_DELAY = 20;\r\n// A list of substrings of CSS properties used to find transition events that\r\n// might affect dimensions of observed elements.\r\nvar transitionKeys = ['top', 'right', 'bottom', 'left', 'width', 'height', 'size', 'weight'];\r\n// Check if MutationObserver is available.\r\nvar mutationObserverSupported = typeof MutationObserver !== 'undefined';\r\n/**\r\n * Singleton controller class which handles updates of ResizeObserver instances.\r\n */\r\nvar ResizeObserverController = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of ResizeObserverController.\r\n *\r\n * @private\r\n */\r\n function ResizeObserverController() {\r\n /**\r\n * Indicates whether DOM listeners have been added.\r\n *\r\n * @private {boolean}\r\n */\r\n this.connected_ = false;\r\n /**\r\n * Tells that controller has subscribed for Mutation Events.\r\n *\r\n * @private {boolean}\r\n */\r\n this.mutationEventsAdded_ = false;\r\n /**\r\n * Keeps reference to the instance of MutationObserver.\r\n *\r\n * @private {MutationObserver}\r\n */\r\n this.mutationsObserver_ = null;\r\n /**\r\n * A list of connected observers.\r\n *\r\n * @private {Array}\r\n */\r\n this.observers_ = [];\r\n this.onTransitionEnd_ = this.onTransitionEnd_.bind(this);\r\n this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY);\r\n }\r\n /**\r\n * Adds observer to observers list.\r\n *\r\n * @param {ResizeObserverSPI} observer - Observer to be added.\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.addObserver = function (observer) {\r\n if (!~this.observers_.indexOf(observer)) {\r\n this.observers_.push(observer);\r\n }\r\n // Add listeners if they haven't been added yet.\r\n if (!this.connected_) {\r\n this.connect_();\r\n }\r\n };\r\n /**\r\n * Removes observer from observers list.\r\n *\r\n * @param {ResizeObserverSPI} observer - Observer to be removed.\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.removeObserver = function (observer) {\r\n var observers = this.observers_;\r\n var index = observers.indexOf(observer);\r\n // Remove observer if it's present in registry.\r\n if (~index) {\r\n observers.splice(index, 1);\r\n }\r\n // Remove listeners if controller has no connected observers.\r\n if (!observers.length && this.connected_) {\r\n this.disconnect_();\r\n }\r\n };\r\n /**\r\n * Invokes the update of observers. It will continue running updates insofar\r\n * it detects changes.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.refresh = function () {\r\n var changesDetected = this.updateObservers_();\r\n // Continue running updates if changes have been detected as there might\r\n // be future ones caused by CSS transitions.\r\n if (changesDetected) {\r\n this.refresh();\r\n }\r\n };\r\n /**\r\n * Updates every observer from observers list and notifies them of queued\r\n * entries.\r\n *\r\n * @private\r\n * @returns {boolean} Returns \"true\" if any observer has detected changes in\r\n * dimensions of it's elements.\r\n */\r\n ResizeObserverController.prototype.updateObservers_ = function () {\r\n // Collect observers that have active observations.\r\n var activeObservers = this.observers_.filter(function (observer) {\r\n return observer.gatherActive(), observer.hasActive();\r\n });\r\n // Deliver notifications in a separate cycle in order to avoid any\r\n // collisions between observers, e.g. when multiple instances of\r\n // ResizeObserver are tracking the same element and the callback of one\r\n // of them changes content dimensions of the observed target. Sometimes\r\n // this may result in notifications being blocked for the rest of observers.\r\n activeObservers.forEach(function (observer) { return observer.broadcastActive(); });\r\n return activeObservers.length > 0;\r\n };\r\n /**\r\n * Initializes DOM listeners.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.connect_ = function () {\r\n // Do nothing if running in a non-browser environment or if listeners\r\n // have been already added.\r\n if (!isBrowser || this.connected_) {\r\n return;\r\n }\r\n // Subscription to the \"Transitionend\" event is used as a workaround for\r\n // delayed transitions. This way it's possible to capture at least the\r\n // final state of an element.\r\n document.addEventListener('transitionend', this.onTransitionEnd_);\r\n window.addEventListener('resize', this.refresh);\r\n if (mutationObserverSupported) {\r\n this.mutationsObserver_ = new MutationObserver(this.refresh);\r\n this.mutationsObserver_.observe(document, {\r\n attributes: true,\r\n childList: true,\r\n characterData: true,\r\n subtree: true\r\n });\r\n }\r\n else {\r\n document.addEventListener('DOMSubtreeModified', this.refresh);\r\n this.mutationEventsAdded_ = true;\r\n }\r\n this.connected_ = true;\r\n };\r\n /**\r\n * Removes DOM listeners.\r\n *\r\n * @private\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.disconnect_ = function () {\r\n // Do nothing if running in a non-browser environment or if listeners\r\n // have been already removed.\r\n if (!isBrowser || !this.connected_) {\r\n return;\r\n }\r\n document.removeEventListener('transitionend', this.onTransitionEnd_);\r\n window.removeEventListener('resize', this.refresh);\r\n if (this.mutationsObserver_) {\r\n this.mutationsObserver_.disconnect();\r\n }\r\n if (this.mutationEventsAdded_) {\r\n document.removeEventListener('DOMSubtreeModified', this.refresh);\r\n }\r\n this.mutationsObserver_ = null;\r\n this.mutationEventsAdded_ = false;\r\n this.connected_ = false;\r\n };\r\n /**\r\n * \"Transitionend\" event handler.\r\n *\r\n * @private\r\n * @param {TransitionEvent} event\r\n * @returns {void}\r\n */\r\n ResizeObserverController.prototype.onTransitionEnd_ = function (_a) {\r\n var _b = _a.propertyName, propertyName = _b === void 0 ? '' : _b;\r\n // Detect whether transition may affect dimensions of an element.\r\n var isReflowProperty = transitionKeys.some(function (key) {\r\n return !!~propertyName.indexOf(key);\r\n });\r\n if (isReflowProperty) {\r\n this.refresh();\r\n }\r\n };\r\n /**\r\n * Returns instance of the ResizeObserverController.\r\n *\r\n * @returns {ResizeObserverController}\r\n */\r\n ResizeObserverController.getInstance = function () {\r\n if (!this.instance_) {\r\n this.instance_ = new ResizeObserverController();\r\n }\r\n return this.instance_;\r\n };\r\n /**\r\n * Holds reference to the controller's instance.\r\n *\r\n * @private {ResizeObserverController}\r\n */\r\n ResizeObserverController.instance_ = null;\r\n return ResizeObserverController;\r\n}());\n\n/**\r\n * Defines non-writable/enumerable properties of the provided target object.\r\n *\r\n * @param {Object} target - Object for which to define properties.\r\n * @param {Object} props - Properties to be defined.\r\n * @returns {Object} Target object.\r\n */\r\nvar defineConfigurable = (function (target, props) {\r\n for (var _i = 0, _a = Object.keys(props); _i < _a.length; _i++) {\r\n var key = _a[_i];\r\n Object.defineProperty(target, key, {\r\n value: props[key],\r\n enumerable: false,\r\n writable: false,\r\n configurable: true\r\n });\r\n }\r\n return target;\r\n});\n\n/**\r\n * Returns the global object associated with provided element.\r\n *\r\n * @param {Object} target\r\n * @returns {Object}\r\n */\r\nvar getWindowOf = (function (target) {\r\n // Assume that the element is an instance of Node, which means that it\r\n // has the \"ownerDocument\" property from which we can retrieve a\r\n // corresponding global object.\r\n var ownerGlobal = target && target.ownerDocument && target.ownerDocument.defaultView;\r\n // Return the local global object if it's not possible extract one from\r\n // provided element.\r\n return ownerGlobal || global$1;\r\n});\n\n// Placeholder of an empty content rectangle.\r\nvar emptyRect = createRectInit(0, 0, 0, 0);\r\n/**\r\n * Converts provided string to a number.\r\n *\r\n * @param {number|string} value\r\n * @returns {number}\r\n */\r\nfunction toFloat(value) {\r\n return parseFloat(value) || 0;\r\n}\r\n/**\r\n * Extracts borders size from provided styles.\r\n *\r\n * @param {CSSStyleDeclaration} styles\r\n * @param {...string} positions - Borders positions (top, right, ...)\r\n * @returns {number}\r\n */\r\nfunction getBordersSize(styles) {\r\n var positions = [];\r\n for (var _i = 1; _i < arguments.length; _i++) {\r\n positions[_i - 1] = arguments[_i];\r\n }\r\n return positions.reduce(function (size, position) {\r\n var value = styles['border-' + position + '-width'];\r\n return size + toFloat(value);\r\n }, 0);\r\n}\r\n/**\r\n * Extracts paddings sizes from provided styles.\r\n *\r\n * @param {CSSStyleDeclaration} styles\r\n * @returns {Object} Paddings box.\r\n */\r\nfunction getPaddings(styles) {\r\n var positions = ['top', 'right', 'bottom', 'left'];\r\n var paddings = {};\r\n for (var _i = 0, positions_1 = positions; _i < positions_1.length; _i++) {\r\n var position = positions_1[_i];\r\n var value = styles['padding-' + position];\r\n paddings[position] = toFloat(value);\r\n }\r\n return paddings;\r\n}\r\n/**\r\n * Calculates content rectangle of provided SVG element.\r\n *\r\n * @param {SVGGraphicsElement} target - Element content rectangle of which needs\r\n * to be calculated.\r\n * @returns {DOMRectInit}\r\n */\r\nfunction getSVGContentRect(target) {\r\n var bbox = target.getBBox();\r\n return createRectInit(0, 0, bbox.width, bbox.height);\r\n}\r\n/**\r\n * Calculates content rectangle of provided HTMLElement.\r\n *\r\n * @param {HTMLElement} target - Element for which to calculate the content rectangle.\r\n * @returns {DOMRectInit}\r\n */\r\nfunction getHTMLElementContentRect(target) {\r\n // Client width & height properties can't be\r\n // used exclusively as they provide rounded values.\r\n var clientWidth = target.clientWidth, clientHeight = target.clientHeight;\r\n // By this condition we can catch all non-replaced inline, hidden and\r\n // detached elements. Though elements with width & height properties less\r\n // than 0.5 will be discarded as well.\r\n //\r\n // Without it we would need to implement separate methods for each of\r\n // those cases and it's not possible to perform a precise and performance\r\n // effective test for hidden elements. E.g. even jQuery's ':visible' filter\r\n // gives wrong results for elements with width & height less than 0.5.\r\n if (!clientWidth && !clientHeight) {\r\n return emptyRect;\r\n }\r\n var styles = getWindowOf(target).getComputedStyle(target);\r\n var paddings = getPaddings(styles);\r\n var horizPad = paddings.left + paddings.right;\r\n var vertPad = paddings.top + paddings.bottom;\r\n // Computed styles of width & height are being used because they are the\r\n // only dimensions available to JS that contain non-rounded values. It could\r\n // be possible to utilize the getBoundingClientRect if only it's data wasn't\r\n // affected by CSS transformations let alone paddings, borders and scroll bars.\r\n var width = toFloat(styles.width), height = toFloat(styles.height);\r\n // Width & height include paddings and borders when the 'border-box' box\r\n // model is applied (except for IE).\r\n if (styles.boxSizing === 'border-box') {\r\n // Following conditions are required to handle Internet Explorer which\r\n // doesn't include paddings and borders to computed CSS dimensions.\r\n //\r\n // We can say that if CSS dimensions + paddings are equal to the \"client\"\r\n // properties then it's either IE, and thus we don't need to subtract\r\n // anything, or an element merely doesn't have paddings/borders styles.\r\n if (Math.round(width + horizPad) !== clientWidth) {\r\n width -= getBordersSize(styles, 'left', 'right') + horizPad;\r\n }\r\n if (Math.round(height + vertPad) !== clientHeight) {\r\n height -= getBordersSize(styles, 'top', 'bottom') + vertPad;\r\n }\r\n }\r\n // Following steps can't be applied to the document's root element as its\r\n // client[Width/Height] properties represent viewport area of the window.\r\n // Besides, it's as well not necessary as the itself neither has\r\n // rendered scroll bars nor it can be clipped.\r\n if (!isDocumentElement(target)) {\r\n // In some browsers (only in Firefox, actually) CSS width & height\r\n // include scroll bars size which can be removed at this step as scroll\r\n // bars are the only difference between rounded dimensions + paddings\r\n // and \"client\" properties, though that is not always true in Chrome.\r\n var vertScrollbar = Math.round(width + horizPad) - clientWidth;\r\n var horizScrollbar = Math.round(height + vertPad) - clientHeight;\r\n // Chrome has a rather weird rounding of \"client\" properties.\r\n // E.g. for an element with content width of 314.2px it sometimes gives\r\n // the client width of 315px and for the width of 314.7px it may give\r\n // 314px. And it doesn't happen all the time. So just ignore this delta\r\n // as a non-relevant.\r\n if (Math.abs(vertScrollbar) !== 1) {\r\n width -= vertScrollbar;\r\n }\r\n if (Math.abs(horizScrollbar) !== 1) {\r\n height -= horizScrollbar;\r\n }\r\n }\r\n return createRectInit(paddings.left, paddings.top, width, height);\r\n}\r\n/**\r\n * Checks whether provided element is an instance of the SVGGraphicsElement.\r\n *\r\n * @param {Element} target - Element to be checked.\r\n * @returns {boolean}\r\n */\r\nvar isSVGGraphicsElement = (function () {\r\n // Some browsers, namely IE and Edge, don't have the SVGGraphicsElement\r\n // interface.\r\n if (typeof SVGGraphicsElement !== 'undefined') {\r\n return function (target) { return target instanceof getWindowOf(target).SVGGraphicsElement; };\r\n }\r\n // If it's so, then check that element is at least an instance of the\r\n // SVGElement and that it has the \"getBBox\" method.\r\n // eslint-disable-next-line no-extra-parens\r\n return function (target) { return (target instanceof getWindowOf(target).SVGElement &&\r\n typeof target.getBBox === 'function'); };\r\n})();\r\n/**\r\n * Checks whether provided element is a document element ().\r\n *\r\n * @param {Element} target - Element to be checked.\r\n * @returns {boolean}\r\n */\r\nfunction isDocumentElement(target) {\r\n return target === getWindowOf(target).document.documentElement;\r\n}\r\n/**\r\n * Calculates an appropriate content rectangle for provided html or svg element.\r\n *\r\n * @param {Element} target - Element content rectangle of which needs to be calculated.\r\n * @returns {DOMRectInit}\r\n */\r\nfunction getContentRect(target) {\r\n if (!isBrowser) {\r\n return emptyRect;\r\n }\r\n if (isSVGGraphicsElement(target)) {\r\n return getSVGContentRect(target);\r\n }\r\n return getHTMLElementContentRect(target);\r\n}\r\n/**\r\n * Creates rectangle with an interface of the DOMRectReadOnly.\r\n * Spec: https://drafts.fxtf.org/geometry/#domrectreadonly\r\n *\r\n * @param {DOMRectInit} rectInit - Object with rectangle's x/y coordinates and dimensions.\r\n * @returns {DOMRectReadOnly}\r\n */\r\nfunction createReadOnlyRect(_a) {\r\n var x = _a.x, y = _a.y, width = _a.width, height = _a.height;\r\n // If DOMRectReadOnly is available use it as a prototype for the rectangle.\r\n var Constr = typeof DOMRectReadOnly !== 'undefined' ? DOMRectReadOnly : Object;\r\n var rect = Object.create(Constr.prototype);\r\n // Rectangle's properties are not writable and non-enumerable.\r\n defineConfigurable(rect, {\r\n x: x, y: y, width: width, height: height,\r\n top: y,\r\n right: x + width,\r\n bottom: height + y,\r\n left: x\r\n });\r\n return rect;\r\n}\r\n/**\r\n * Creates DOMRectInit object based on the provided dimensions and the x/y coordinates.\r\n * Spec: https://drafts.fxtf.org/geometry/#dictdef-domrectinit\r\n *\r\n * @param {number} x - X coordinate.\r\n * @param {number} y - Y coordinate.\r\n * @param {number} width - Rectangle's width.\r\n * @param {number} height - Rectangle's height.\r\n * @returns {DOMRectInit}\r\n */\r\nfunction createRectInit(x, y, width, height) {\r\n return { x: x, y: y, width: width, height: height };\r\n}\n\n/**\r\n * Class that is responsible for computations of the content rectangle of\r\n * provided DOM element and for keeping track of it's changes.\r\n */\r\nvar ResizeObservation = /** @class */ (function () {\r\n /**\r\n * Creates an instance of ResizeObservation.\r\n *\r\n * @param {Element} target - Element to be observed.\r\n */\r\n function ResizeObservation(target) {\r\n /**\r\n * Broadcasted width of content rectangle.\r\n *\r\n * @type {number}\r\n */\r\n this.broadcastWidth = 0;\r\n /**\r\n * Broadcasted height of content rectangle.\r\n *\r\n * @type {number}\r\n */\r\n this.broadcastHeight = 0;\r\n /**\r\n * Reference to the last observed content rectangle.\r\n *\r\n * @private {DOMRectInit}\r\n */\r\n this.contentRect_ = createRectInit(0, 0, 0, 0);\r\n this.target = target;\r\n }\r\n /**\r\n * Updates content rectangle and tells whether it's width or height properties\r\n * have changed since the last broadcast.\r\n *\r\n * @returns {boolean}\r\n */\r\n ResizeObservation.prototype.isActive = function () {\r\n var rect = getContentRect(this.target);\r\n this.contentRect_ = rect;\r\n return (rect.width !== this.broadcastWidth ||\r\n rect.height !== this.broadcastHeight);\r\n };\r\n /**\r\n * Updates 'broadcastWidth' and 'broadcastHeight' properties with a data\r\n * from the corresponding properties of the last observed content rectangle.\r\n *\r\n * @returns {DOMRectInit} Last observed content rectangle.\r\n */\r\n ResizeObservation.prototype.broadcastRect = function () {\r\n var rect = this.contentRect_;\r\n this.broadcastWidth = rect.width;\r\n this.broadcastHeight = rect.height;\r\n return rect;\r\n };\r\n return ResizeObservation;\r\n}());\n\nvar ResizeObserverEntry = /** @class */ (function () {\r\n /**\r\n * Creates an instance of ResizeObserverEntry.\r\n *\r\n * @param {Element} target - Element that is being observed.\r\n * @param {DOMRectInit} rectInit - Data of the element's content rectangle.\r\n */\r\n function ResizeObserverEntry(target, rectInit) {\r\n var contentRect = createReadOnlyRect(rectInit);\r\n // According to the specification following properties are not writable\r\n // and are also not enumerable in the native implementation.\r\n //\r\n // Property accessors are not being used as they'd require to define a\r\n // private WeakMap storage which may cause memory leaks in browsers that\r\n // don't support this type of collections.\r\n defineConfigurable(this, { target: target, contentRect: contentRect });\r\n }\r\n return ResizeObserverEntry;\r\n}());\n\nvar ResizeObserverSPI = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of ResizeObserver.\r\n *\r\n * @param {ResizeObserverCallback} callback - Callback function that is invoked\r\n * when one of the observed elements changes it's content dimensions.\r\n * @param {ResizeObserverController} controller - Controller instance which\r\n * is responsible for the updates of observer.\r\n * @param {ResizeObserver} callbackCtx - Reference to the public\r\n * ResizeObserver instance which will be passed to callback function.\r\n */\r\n function ResizeObserverSPI(callback, controller, callbackCtx) {\r\n /**\r\n * Collection of resize observations that have detected changes in dimensions\r\n * of elements.\r\n *\r\n * @private {Array}\r\n */\r\n this.activeObservations_ = [];\r\n /**\r\n * Registry of the ResizeObservation instances.\r\n *\r\n * @private {Map}\r\n */\r\n this.observations_ = new MapShim();\r\n if (typeof callback !== 'function') {\r\n throw new TypeError('The callback provided as parameter 1 is not a function.');\r\n }\r\n this.callback_ = callback;\r\n this.controller_ = controller;\r\n this.callbackCtx_ = callbackCtx;\r\n }\r\n /**\r\n * Starts observing provided element.\r\n *\r\n * @param {Element} target - Element to be observed.\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.observe = function (target) {\r\n if (!arguments.length) {\r\n throw new TypeError('1 argument required, but only 0 present.');\r\n }\r\n // Do nothing if current environment doesn't have the Element interface.\r\n if (typeof Element === 'undefined' || !(Element instanceof Object)) {\r\n return;\r\n }\r\n if (!(target instanceof getWindowOf(target).Element)) {\r\n throw new TypeError('parameter 1 is not of type \"Element\".');\r\n }\r\n var observations = this.observations_;\r\n // Do nothing if element is already being observed.\r\n if (observations.has(target)) {\r\n return;\r\n }\r\n observations.set(target, new ResizeObservation(target));\r\n this.controller_.addObserver(this);\r\n // Force the update of observations.\r\n this.controller_.refresh();\r\n };\r\n /**\r\n * Stops observing provided element.\r\n *\r\n * @param {Element} target - Element to stop observing.\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.unobserve = function (target) {\r\n if (!arguments.length) {\r\n throw new TypeError('1 argument required, but only 0 present.');\r\n }\r\n // Do nothing if current environment doesn't have the Element interface.\r\n if (typeof Element === 'undefined' || !(Element instanceof Object)) {\r\n return;\r\n }\r\n if (!(target instanceof getWindowOf(target).Element)) {\r\n throw new TypeError('parameter 1 is not of type \"Element\".');\r\n }\r\n var observations = this.observations_;\r\n // Do nothing if element is not being observed.\r\n if (!observations.has(target)) {\r\n return;\r\n }\r\n observations.delete(target);\r\n if (!observations.size) {\r\n this.controller_.removeObserver(this);\r\n }\r\n };\r\n /**\r\n * Stops observing all elements.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.disconnect = function () {\r\n this.clearActive();\r\n this.observations_.clear();\r\n this.controller_.removeObserver(this);\r\n };\r\n /**\r\n * Collects observation instances the associated element of which has changed\r\n * it's content rectangle.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.gatherActive = function () {\r\n var _this = this;\r\n this.clearActive();\r\n this.observations_.forEach(function (observation) {\r\n if (observation.isActive()) {\r\n _this.activeObservations_.push(observation);\r\n }\r\n });\r\n };\r\n /**\r\n * Invokes initial callback function with a list of ResizeObserverEntry\r\n * instances collected from active resize observations.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.broadcastActive = function () {\r\n // Do nothing if observer doesn't have active observations.\r\n if (!this.hasActive()) {\r\n return;\r\n }\r\n var ctx = this.callbackCtx_;\r\n // Create ResizeObserverEntry instance for every active observation.\r\n var entries = this.activeObservations_.map(function (observation) {\r\n return new ResizeObserverEntry(observation.target, observation.broadcastRect());\r\n });\r\n this.callback_.call(ctx, entries, ctx);\r\n this.clearActive();\r\n };\r\n /**\r\n * Clears the collection of active observations.\r\n *\r\n * @returns {void}\r\n */\r\n ResizeObserverSPI.prototype.clearActive = function () {\r\n this.activeObservations_.splice(0);\r\n };\r\n /**\r\n * Tells whether observer has active observations.\r\n *\r\n * @returns {boolean}\r\n */\r\n ResizeObserverSPI.prototype.hasActive = function () {\r\n return this.activeObservations_.length > 0;\r\n };\r\n return ResizeObserverSPI;\r\n}());\n\n// Registry of internal observers. If WeakMap is not available use current shim\r\n// for the Map collection as it has all required methods and because WeakMap\r\n// can't be fully polyfilled anyway.\r\nvar observers = typeof WeakMap !== 'undefined' ? new WeakMap() : new MapShim();\r\n/**\r\n * ResizeObserver API. Encapsulates the ResizeObserver SPI implementation\r\n * exposing only those methods and properties that are defined in the spec.\r\n */\r\nvar ResizeObserver = /** @class */ (function () {\r\n /**\r\n * Creates a new instance of ResizeObserver.\r\n *\r\n * @param {ResizeObserverCallback} callback - Callback that is invoked when\r\n * dimensions of the observed elements change.\r\n */\r\n function ResizeObserver(callback) {\r\n if (!(this instanceof ResizeObserver)) {\r\n throw new TypeError('Cannot call a class as a function.');\r\n }\r\n if (!arguments.length) {\r\n throw new TypeError('1 argument required, but only 0 present.');\r\n }\r\n var controller = ResizeObserverController.getInstance();\r\n var observer = new ResizeObserverSPI(callback, controller, this);\r\n observers.set(this, observer);\r\n }\r\n return ResizeObserver;\r\n}());\r\n// Expose public methods of ResizeObserver.\r\n[\r\n 'observe',\r\n 'unobserve',\r\n 'disconnect'\r\n].forEach(function (method) {\r\n ResizeObserver.prototype[method] = function () {\r\n var _a;\r\n return (_a = observers.get(this))[method].apply(_a, arguments);\r\n };\r\n});\n\nvar index = (function () {\r\n // Export existing implementation if available.\r\n if (typeof global$1.ResizeObserver !== 'undefined') {\r\n return global$1.ResizeObserver;\r\n }\r\n return ResizeObserver;\r\n})();\n\nexport default index;\n","import arrayWithHoles from \"./arrayWithHoles\";\nimport iterableToArray from \"./iterableToArray\";\nimport nonIterableRest from \"./nonIterableRest\";\nexport default function _toArray(arr) {\n return arrayWithHoles(arr) || iterableToArray(arr) || nonIterableRest();\n}","import { useRef } from 'react';\nimport useEffectOnce from './useEffectOnce';\nvar useUnmount = function (fn) {\n var fnRef = useRef(fn);\n // update the ref each render so if it change the newest callback will be invoked\n fnRef.current = fn;\n useEffectOnce(function () { return function () { return fnRef.current(); }; });\n};\nexport default useUnmount;\n","/**!\n * easy-pie-chart\n * Lightweight plugin to render simple, animated and retina optimized pie charts\n *\n * @license \n * @author Robert Fleischmann (http://robert-fleischmann.de)\n * @version 2.1.7\n **/\n\n(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD. Register as an anonymous module unless amdModuleId is set\n define([], function () {\n return (root['EasyPieChart'] = factory());\n });\n } else if (typeof exports === 'object') {\n // Node. Does not work with strict CommonJS, but\n // only CommonJS-like environments that support module.exports,\n // like Node.\n module.exports = factory();\n } else {\n root['EasyPieChart'] = factory();\n }\n}(this, function () {\n\n/**\n * Renderer to render the chart on a canvas object\n * @param {DOMElement} el DOM element to host the canvas (root of the plugin)\n * @param {object} options options object of the plugin\n */\nvar CanvasRenderer = function(el, options) {\n\tvar cachedBackground;\n\tvar canvas = document.createElement('canvas');\n\n\tel.appendChild(canvas);\n\n\tif (typeof(G_vmlCanvasManager) === 'object') {\n\t\tG_vmlCanvasManager.initElement(canvas);\n\t}\n\n\tvar ctx = canvas.getContext('2d');\n\n\tcanvas.width = canvas.height = options.size;\n\n\t// canvas on retina devices\n\tvar scaleBy = 1;\n\tif (window.devicePixelRatio > 1) {\n\t\tscaleBy = window.devicePixelRatio;\n\t\tcanvas.style.width = canvas.style.height = [options.size, 'px'].join('');\n\t\tcanvas.width = canvas.height = options.size * scaleBy;\n\t\tctx.scale(scaleBy, scaleBy);\n\t}\n\n\t// move 0,0 coordinates to the center\n\tctx.translate(options.size / 2, options.size / 2);\n\n\t// rotate canvas -90deg\n\tctx.rotate((-1 / 2 + options.rotate / 180) * Math.PI);\n\n\tvar radius = (options.size - options.lineWidth) / 2;\n\tif (options.scaleColor && options.scaleLength) {\n\t\tradius -= options.scaleLength + 2; // 2 is the distance between scale and bar\n\t}\n\n\t// IE polyfill for Date\n\tDate.now = Date.now || function() {\n\t\treturn +(new Date());\n\t};\n\n\t/**\n\t * Draw a circle around the center of the canvas\n\t * @param {strong} color Valid CSS color string\n\t * @param {number} lineWidth Width of the line in px\n\t * @param {number} percent Percentage to draw (float between -1 and 1)\n\t */\n\tvar drawCircle = function(color, lineWidth, percent) {\n\t\tpercent = Math.min(Math.max(-1, percent || 0), 1);\n\t\tvar isNegative = percent <= 0 ? true : false;\n\n\t\tctx.beginPath();\n\t\tctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, isNegative);\n\n\t\tctx.strokeStyle = color;\n\t\tctx.lineWidth = lineWidth;\n\n\t\tctx.stroke();\n\t};\n\n\t/**\n\t * Draw the scale of the chart\n\t */\n\tvar drawScale = function() {\n\t\tvar offset;\n\t\tvar length;\n\n\t\tctx.lineWidth = 1;\n\t\tctx.fillStyle = options.scaleColor;\n\n\t\tctx.save();\n\t\tfor (var i = 24; i > 0; --i) {\n\t\t\tif (i % 6 === 0) {\n\t\t\t\tlength = options.scaleLength;\n\t\t\t\toffset = 0;\n\t\t\t} else {\n\t\t\t\tlength = options.scaleLength * 0.6;\n\t\t\t\toffset = options.scaleLength - length;\n\t\t\t}\n\t\t\tctx.fillRect(-options.size/2 + offset, 0, length, 1);\n\t\t\tctx.rotate(Math.PI / 12);\n\t\t}\n\t\tctx.restore();\n\t};\n\n\t/**\n\t * Request animation frame wrapper with polyfill\n\t * @return {function} Request animation frame method or timeout fallback\n\t */\n\tvar reqAnimationFrame = (function() {\n\t\treturn window.requestAnimationFrame ||\n\t\t\t\twindow.webkitRequestAnimationFrame ||\n\t\t\t\twindow.mozRequestAnimationFrame ||\n\t\t\t\tfunction(callback) {\n\t\t\t\t\twindow.setTimeout(callback, 1000 / 60);\n\t\t\t\t};\n\t}());\n\n\t/**\n\t * Draw the background of the plugin including the scale and the track\n\t */\n\tvar drawBackground = function() {\n\t\tif(options.scaleColor) drawScale();\n\t\tif(options.trackColor) drawCircle(options.trackColor, options.trackWidth || options.lineWidth, 1);\n\t};\n\n /**\n * Canvas accessor\n */\n this.getCanvas = function() {\n return canvas;\n };\n\n /**\n * Canvas 2D context 'ctx' accessor\n */\n this.getCtx = function() {\n return ctx;\n };\n\n\t/**\n\t * Clear the complete canvas\n\t */\n\tthis.clear = function() {\n\t\tctx.clearRect(options.size / -2, options.size / -2, options.size, options.size);\n\t};\n\n\t/**\n\t * Draw the complete chart\n\t * @param {number} percent Percent shown by the chart between -100 and 100\n\t */\n\tthis.draw = function(percent) {\n\t\t// do we need to render a background\n\t\tif (!!options.scaleColor || !!options.trackColor) {\n\t\t\t// getImageData and putImageData are supported\n\t\t\tif (ctx.getImageData && ctx.putImageData) {\n\t\t\t\tif (!cachedBackground) {\n\t\t\t\t\tdrawBackground();\n\t\t\t\t\tcachedBackground = ctx.getImageData(0, 0, options.size * scaleBy, options.size * scaleBy);\n\t\t\t\t} else {\n\t\t\t\t\tctx.putImageData(cachedBackground, 0, 0);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.clear();\n\t\t\t\tdrawBackground();\n\t\t\t}\n\t\t} else {\n\t\t\tthis.clear();\n\t\t}\n\n\t\tctx.lineCap = options.lineCap;\n\n\t\t// if barcolor is a function execute it and pass the percent as a value\n\t\tvar color;\n\t\tif (typeof(options.barColor) === 'function') {\n\t\t\tcolor = options.barColor(percent);\n\t\t} else {\n\t\t\tcolor = options.barColor;\n\t\t}\n\n\t\t// draw bar\n\t\tdrawCircle(color, options.lineWidth, percent / 100);\n\t}.bind(this);\n\n\t/**\n\t * Animate from some percent to some other percentage\n\t * @param {number} from Starting percentage\n\t * @param {number} to Final percentage\n\t */\n\tthis.animate = function(from, to) {\n\t\tvar startTime = Date.now();\n\t\toptions.onStart(from, to);\n\t\tvar animation = function() {\n\t\t\tvar process = Math.min(Date.now() - startTime, options.animate.duration);\n\t\t\tvar currentValue = options.easing(this, process, from, to - from, options.animate.duration);\n\t\t\tthis.draw(currentValue);\n\t\t\toptions.onStep(from, to, currentValue);\n\t\t\tif (process >= options.animate.duration) {\n\t\t\t\toptions.onStop(from, to);\n\t\t\t} else {\n\t\t\t\treqAnimationFrame(animation);\n\t\t\t}\n\t\t}.bind(this);\n\n\t\treqAnimationFrame(animation);\n\t}.bind(this);\n};\n\nvar EasyPieChart = function(el, opts) {\n\tvar defaultOptions = {\n\t\tbarColor: '#ef1e25',\n\t\ttrackColor: '#f9f9f9',\n\t\tscaleColor: '#dfe0e0',\n\t\tscaleLength: 5,\n\t\tlineCap: 'round',\n\t\tlineWidth: 3,\n\t\ttrackWidth: undefined,\n\t\tsize: 110,\n\t\trotate: 0,\n\t\tanimate: {\n\t\t\tduration: 1000,\n\t\t\tenabled: true\n\t\t},\n\t\teasing: function (x, t, b, c, d) { // more can be found here: http://gsgd.co.uk/sandbox/jquery/easing/\n\t\t\tt = t / (d/2);\n\t\t\tif (t < 1) {\n\t\t\t\treturn c / 2 * t * t + b;\n\t\t\t}\n\t\t\treturn -c/2 * ((--t)*(t-2) - 1) + b;\n\t\t},\n\t\tonStart: function(from, to) {\n\t\t\treturn;\n\t\t},\n\t\tonStep: function(from, to, currentValue) {\n\t\t\treturn;\n\t\t},\n\t\tonStop: function(from, to) {\n\t\t\treturn;\n\t\t}\n\t};\n\n\t// detect present renderer\n\tif (typeof(CanvasRenderer) !== 'undefined') {\n\t\tdefaultOptions.renderer = CanvasRenderer;\n\t} else if (typeof(SVGRenderer) !== 'undefined') {\n\t\tdefaultOptions.renderer = SVGRenderer;\n\t} else {\n\t\tthrow new Error('Please load either the SVG- or the CanvasRenderer');\n\t}\n\n\tvar options = {};\n\tvar currentValue = 0;\n\n\t/**\n\t * Initialize the plugin by creating the options object and initialize rendering\n\t */\n\tvar init = function() {\n\t\tthis.el = el;\n\t\tthis.options = options;\n\n\t\t// merge user options into default options\n\t\tfor (var i in defaultOptions) {\n\t\t\tif (defaultOptions.hasOwnProperty(i)) {\n\t\t\t\toptions[i] = opts && typeof(opts[i]) !== 'undefined' ? opts[i] : defaultOptions[i];\n\t\t\t\tif (typeof(options[i]) === 'function') {\n\t\t\t\t\toptions[i] = options[i].bind(this);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// check for jQuery easing\n\t\tif (typeof(options.easing) === 'string' && typeof(jQuery) !== 'undefined' && jQuery.isFunction(jQuery.easing[options.easing])) {\n\t\t\toptions.easing = jQuery.easing[options.easing];\n\t\t} else {\n\t\t\toptions.easing = defaultOptions.easing;\n\t\t}\n\n\t\t// process earlier animate option to avoid bc breaks\n\t\tif (typeof(options.animate) === 'number') {\n\t\t\toptions.animate = {\n\t\t\t\tduration: options.animate,\n\t\t\t\tenabled: true\n\t\t\t};\n\t\t}\n\n\t\tif (typeof(options.animate) === 'boolean' && !options.animate) {\n\t\t\toptions.animate = {\n\t\t\t\tduration: 1000,\n\t\t\t\tenabled: options.animate\n\t\t\t};\n\t\t}\n\n\t\t// create renderer\n\t\tthis.renderer = new options.renderer(el, options);\n\n\t\t// initial draw\n\t\tthis.renderer.draw(currentValue);\n\n\t\t// initial update\n\t\tif (el.dataset && el.dataset.percent) {\n\t\t\tthis.update(parseFloat(el.dataset.percent));\n\t\t} else if (el.getAttribute && el.getAttribute('data-percent')) {\n\t\t\tthis.update(parseFloat(el.getAttribute('data-percent')));\n\t\t}\n\t}.bind(this);\n\n\t/**\n\t * Update the value of the chart\n\t * @param {number} newValue Number between 0 and 100\n\t * @return {object} Instance of the plugin for method chaining\n\t */\n\tthis.update = function(newValue) {\n\t\tnewValue = parseFloat(newValue);\n\t\tif (options.animate.enabled) {\n\t\t\tthis.renderer.animate(currentValue, newValue);\n\t\t} else {\n\t\t\tthis.renderer.draw(newValue);\n\t\t}\n\t\tcurrentValue = newValue;\n\t\treturn this;\n\t}.bind(this);\n\n\t/**\n\t * Disable animation\n\t * @return {object} Instance of the plugin for method chaining\n\t */\n\tthis.disableAnimation = function() {\n\t\toptions.animate.enabled = false;\n\t\treturn this;\n\t};\n\n\t/**\n\t * Enable animation\n\t * @return {object} Instance of the plugin for method chaining\n\t */\n\tthis.enableAnimation = function() {\n\t\toptions.animate.enabled = true;\n\t\treturn this;\n\t};\n\n\tinit();\n};\n\nreturn EasyPieChart;\n\n}));\n","// Generated by CoffeeScript 1.10.0\n(function() {\n var AnimatedText, AnimatedTextFactory, Bar, BaseDonut, BaseGauge, Donut, Gauge, GaugePointer, TextRenderer, ValueUpdater, addCommas, cutHex, formatNumber, mergeObjects, secondsToString,\n slice = [].slice,\n hasProp = {}.hasOwnProperty,\n extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };\n\n (function() {\n var browserRequestAnimationFrame, isCancelled, j, lastId, len, vendor, vendors;\n vendors = ['ms', 'moz', 'webkit', 'o'];\n for (j = 0, len = vendors.length; j < len; j++) {\n vendor = vendors[j];\n if (window.requestAnimationFrame) {\n break;\n }\n window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame'];\n window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] || window[vendor + 'CancelRequestAnimationFrame'];\n }\n browserRequestAnimationFrame = null;\n lastId = 0;\n isCancelled = {};\n if (!requestAnimationFrame) {\n window.requestAnimationFrame = function(callback, element) {\n var currTime, id, lastTime, timeToCall;\n currTime = new Date().getTime();\n timeToCall = Math.max(0, 16 - (currTime - lastTime));\n id = window.setTimeout(function() {\n return callback(currTime + timeToCall);\n }, timeToCall);\n lastTime = currTime + timeToCall;\n return id;\n };\n return window.cancelAnimationFrame = function(id) {\n return clearTimeout(id);\n };\n } else if (!window.cancelAnimationFrame) {\n browserRequestAnimationFrame = window.requestAnimationFrame;\n window.requestAnimationFrame = function(callback, element) {\n var myId;\n myId = ++lastId;\n browserRequestAnimationFrame(function() {\n if (!isCancelled[myId]) {\n return callback();\n }\n }, element);\n return myId;\n };\n return window.cancelAnimationFrame = function(id) {\n return isCancelled[id] = true;\n };\n }\n })();\n\n secondsToString = function(sec) {\n var hr, min;\n hr = Math.floor(sec / 3600);\n min = Math.floor((sec - (hr * 3600)) / 60);\n sec -= (hr * 3600) + (min * 60);\n sec += '';\n min += '';\n while (min.length < 2) {\n min = '0' + min;\n }\n while (sec.length < 2) {\n sec = '0' + sec;\n }\n hr = hr ? hr + ':' : '';\n return hr + min + ':' + sec;\n };\n\n formatNumber = function() {\n var digits, num, value;\n num = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n value = num[0];\n digits = 0 || num[1];\n return addCommas(value.toFixed(digits));\n };\n\n mergeObjects = function(obj1, obj2) {\n var key, out, val;\n out = {};\n for (key in obj1) {\n if (!hasProp.call(obj1, key)) continue;\n val = obj1[key];\n out[key] = val;\n }\n for (key in obj2) {\n if (!hasProp.call(obj2, key)) continue;\n val = obj2[key];\n out[key] = val;\n }\n return out;\n };\n\n addCommas = function(nStr) {\n var rgx, x, x1, x2;\n nStr += '';\n x = nStr.split('.');\n x1 = x[0];\n x2 = '';\n if (x.length > 1) {\n x2 = '.' + x[1];\n }\n rgx = /(\\d+)(\\d{3})/;\n while (rgx.test(x1)) {\n x1 = x1.replace(rgx, '$1' + ',' + '$2');\n }\n return x1 + x2;\n };\n\n cutHex = function(nStr) {\n if (nStr.charAt(0) === \"#\") {\n return nStr.substring(1, 7);\n }\n return nStr;\n };\n\n ValueUpdater = (function() {\n ValueUpdater.prototype.animationSpeed = 32;\n\n function ValueUpdater(addToAnimationQueue, clear) {\n if (addToAnimationQueue == null) {\n addToAnimationQueue = true;\n }\n this.clear = clear != null ? clear : true;\n if (addToAnimationQueue) {\n AnimationUpdater.add(this);\n }\n }\n\n ValueUpdater.prototype.update = function(force) {\n var diff;\n if (force == null) {\n force = false;\n }\n if (force || this.displayedValue !== this.value) {\n if (this.ctx && this.clear) {\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n }\n diff = this.value - this.displayedValue;\n if (Math.abs(diff / this.animationSpeed) <= 0.001) {\n this.displayedValue = this.value;\n } else {\n this.displayedValue = this.displayedValue + diff / this.animationSpeed;\n }\n this.render();\n return true;\n }\n return false;\n };\n\n return ValueUpdater;\n\n })();\n\n BaseGauge = (function(superClass) {\n extend(BaseGauge, superClass);\n\n function BaseGauge() {\n return BaseGauge.__super__.constructor.apply(this, arguments);\n }\n\n BaseGauge.prototype.displayScale = 1;\n\n BaseGauge.prototype.forceUpdate = true;\n\n BaseGauge.prototype.setTextField = function(textField, fractionDigits) {\n return this.textField = textField instanceof TextRenderer ? textField : new TextRenderer(textField, fractionDigits);\n };\n\n BaseGauge.prototype.setMinValue = function(minValue, updateStartValue) {\n var gauge, j, len, ref, results;\n this.minValue = minValue;\n if (updateStartValue == null) {\n updateStartValue = true;\n }\n if (updateStartValue) {\n this.displayedValue = this.minValue;\n ref = this.gp || [];\n results = [];\n for (j = 0, len = ref.length; j < len; j++) {\n gauge = ref[j];\n results.push(gauge.displayedValue = this.minValue);\n }\n return results;\n }\n };\n\n BaseGauge.prototype.setOptions = function(options) {\n if (options == null) {\n options = null;\n }\n this.options = mergeObjects(this.options, options);\n if (this.textField) {\n this.textField.el.style.fontSize = options.fontSize + 'px';\n }\n if (this.options.angle > .5) {\n this.options.angle = .5;\n }\n this.configDisplayScale();\n return this;\n };\n\n BaseGauge.prototype.configDisplayScale = function() {\n var backingStorePixelRatio, devicePixelRatio, height, prevDisplayScale, width;\n prevDisplayScale = this.displayScale;\n if (this.options.highDpiSupport === false) {\n delete this.displayScale;\n } else {\n devicePixelRatio = window.devicePixelRatio || 1;\n backingStorePixelRatio = this.ctx.webkitBackingStorePixelRatio || this.ctx.mozBackingStorePixelRatio || this.ctx.msBackingStorePixelRatio || this.ctx.oBackingStorePixelRatio || this.ctx.backingStorePixelRatio || 1;\n this.displayScale = devicePixelRatio / backingStorePixelRatio;\n }\n if (this.displayScale !== prevDisplayScale) {\n width = this.canvas.G__width || this.canvas.width;\n height = this.canvas.G__height || this.canvas.height;\n this.canvas.width = width * this.displayScale;\n this.canvas.height = height * this.displayScale;\n this.canvas.style.width = width + \"px\";\n this.canvas.style.height = height + \"px\";\n this.canvas.G__width = width;\n this.canvas.G__height = height;\n }\n return this;\n };\n\n BaseGauge.prototype.parseValue = function(value) {\n value = parseFloat(value) || Number(value);\n if (isFinite(value)) {\n return value;\n } else {\n return 0;\n }\n };\n\n return BaseGauge;\n\n })(ValueUpdater);\n\n TextRenderer = (function() {\n function TextRenderer(el, fractionDigits1) {\n this.el = el;\n this.fractionDigits = fractionDigits1;\n }\n\n TextRenderer.prototype.render = function(gauge) {\n return this.el.innerHTML = formatNumber(gauge.displayedValue, this.fractionDigits);\n };\n\n return TextRenderer;\n\n })();\n\n AnimatedText = (function(superClass) {\n extend(AnimatedText, superClass);\n\n AnimatedText.prototype.displayedValue = 0;\n\n AnimatedText.prototype.value = 0;\n\n AnimatedText.prototype.setVal = function(value) {\n return this.value = 1 * value;\n };\n\n function AnimatedText(elem1, text) {\n this.elem = elem1;\n this.text = text != null ? text : false;\n AnimatedText.__super__.constructor.call(this);\n if (this.elem === void 0) {\n throw new Error('The element isn\\'t defined.');\n }\n this.value = 1 * this.elem.innerHTML;\n if (this.text) {\n this.value = 0;\n }\n }\n\n AnimatedText.prototype.render = function() {\n var textVal;\n if (this.text) {\n textVal = secondsToString(this.displayedValue.toFixed(0));\n } else {\n textVal = addCommas(formatNumber(this.displayedValue));\n }\n return this.elem.innerHTML = textVal;\n };\n\n return AnimatedText;\n\n })(ValueUpdater);\n\n AnimatedTextFactory = {\n create: function(objList) {\n var elem, j, len, out;\n out = [];\n for (j = 0, len = objList.length; j < len; j++) {\n elem = objList[j];\n out.push(new AnimatedText(elem));\n }\n return out;\n }\n };\n\n GaugePointer = (function(superClass) {\n extend(GaugePointer, superClass);\n\n GaugePointer.prototype.displayedValue = 0;\n\n GaugePointer.prototype.value = 0;\n\n GaugePointer.prototype.options = {\n strokeWidth: 0.035,\n length: 0.1,\n color: \"#000000\",\n iconPath: null,\n iconScale: 1.0,\n iconAngle: 0\n };\n\n GaugePointer.prototype.img = null;\n\n function GaugePointer(gauge1) {\n this.gauge = gauge1;\n if (this.gauge === void 0) {\n throw new Error('The element isn\\'t defined.');\n }\n this.ctx = this.gauge.ctx;\n this.canvas = this.gauge.canvas;\n GaugePointer.__super__.constructor.call(this, false, false);\n this.setOptions();\n }\n\n GaugePointer.prototype.setOptions = function(options) {\n if (options == null) {\n options = null;\n }\n this.options = mergeObjects(this.options, options);\n this.length = 2 * this.gauge.radius * this.gauge.options.radiusScale * this.options.length;\n this.strokeWidth = this.canvas.height * this.options.strokeWidth;\n this.maxValue = this.gauge.maxValue;\n this.minValue = this.gauge.minValue;\n this.animationSpeed = this.gauge.animationSpeed;\n this.options.angle = this.gauge.options.angle;\n if (this.options.iconPath) {\n this.img = new Image();\n return this.img.src = this.options.iconPath;\n }\n };\n\n GaugePointer.prototype.render = function() {\n var angle, endX, endY, imgX, imgY, startX, startY, x, y;\n angle = this.gauge.getAngle.call(this, this.displayedValue);\n x = Math.round(this.length * Math.cos(angle));\n y = Math.round(this.length * Math.sin(angle));\n startX = Math.round(this.strokeWidth * Math.cos(angle - Math.PI / 2));\n startY = Math.round(this.strokeWidth * Math.sin(angle - Math.PI / 2));\n endX = Math.round(this.strokeWidth * Math.cos(angle + Math.PI / 2));\n endY = Math.round(this.strokeWidth * Math.sin(angle + Math.PI / 2));\n this.ctx.beginPath();\n this.ctx.fillStyle = this.options.color;\n this.ctx.arc(0, 0, this.strokeWidth, 0, Math.PI * 2, false);\n this.ctx.fill();\n this.ctx.beginPath();\n this.ctx.moveTo(startX, startY);\n this.ctx.lineTo(x, y);\n this.ctx.lineTo(endX, endY);\n this.ctx.fill();\n if (this.img) {\n imgX = Math.round(this.img.width * this.options.iconScale);\n imgY = Math.round(this.img.height * this.options.iconScale);\n this.ctx.save();\n this.ctx.translate(x, y);\n this.ctx.rotate(angle + Math.PI / 180.0 * (90 + this.options.iconAngle));\n this.ctx.drawImage(this.img, -imgX / 2, -imgY / 2, imgX, imgY);\n return this.ctx.restore();\n }\n };\n\n return GaugePointer;\n\n })(ValueUpdater);\n\n Bar = (function() {\n function Bar(elem1) {\n this.elem = elem1;\n }\n\n Bar.prototype.updateValues = function(arrValues) {\n this.value = arrValues[0];\n this.maxValue = arrValues[1];\n this.avgValue = arrValues[2];\n return this.render();\n };\n\n Bar.prototype.render = function() {\n var avgPercent, valPercent;\n if (this.textField) {\n this.textField.text(formatNumber(this.value));\n }\n if (this.maxValue === 0) {\n this.maxValue = this.avgValue * 2;\n }\n valPercent = (this.value / this.maxValue) * 100;\n avgPercent = (this.avgValue / this.maxValue) * 100;\n $(\".bar-value\", this.elem).css({\n \"width\": valPercent + \"%\"\n });\n return $(\".typical-value\", this.elem).css({\n \"width\": avgPercent + \"%\"\n });\n };\n\n return Bar;\n\n })();\n\n Gauge = (function(superClass) {\n extend(Gauge, superClass);\n\n Gauge.prototype.elem = null;\n\n Gauge.prototype.value = [20];\n\n Gauge.prototype.maxValue = 80;\n\n Gauge.prototype.minValue = 0;\n\n Gauge.prototype.displayedAngle = 0;\n\n Gauge.prototype.displayedValue = 0;\n\n Gauge.prototype.lineWidth = 40;\n\n Gauge.prototype.paddingTop = 0.1;\n\n Gauge.prototype.paddingBottom = 0.1;\n\n Gauge.prototype.percentColors = null;\n\n Gauge.prototype.options = {\n colorStart: \"#6fadcf\",\n colorStop: void 0,\n gradientType: 0,\n strokeColor: \"#e0e0e0\",\n pointer: {\n length: 0.8,\n strokeWidth: 0.035,\n iconScale: 1.0\n },\n angle: 0.15,\n lineWidth: 0.44,\n radiusScale: 1.0,\n fontSize: 40,\n limitMax: false,\n limitMin: false\n };\n\n function Gauge(canvas) {\n var h, w;\n this.canvas = canvas;\n Gauge.__super__.constructor.call(this);\n this.percentColors = null;\n if (typeof G_vmlCanvasManager !== 'undefined') {\n this.canvas = window.G_vmlCanvasManager.initElement(this.canvas);\n }\n this.ctx = this.canvas.getContext('2d');\n h = this.canvas.clientHeight;\n w = this.canvas.clientWidth;\n this.canvas.height = h;\n this.canvas.width = w;\n this.gp = [new GaugePointer(this)];\n this.setOptions();\n }\n\n Gauge.prototype.setOptions = function(options) {\n var gauge, j, len, phi, ref;\n if (options == null) {\n options = null;\n }\n Gauge.__super__.setOptions.call(this, options);\n this.configPercentColors();\n this.extraPadding = 0;\n if (this.options.angle < 0) {\n phi = Math.PI * (1 + this.options.angle);\n this.extraPadding = Math.sin(phi);\n }\n this.availableHeight = this.canvas.height * (1 - this.paddingTop - this.paddingBottom);\n this.lineWidth = this.availableHeight * this.options.lineWidth;\n this.radius = (this.availableHeight - this.lineWidth / 2) / (1.0 + this.extraPadding);\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n ref = this.gp;\n for (j = 0, len = ref.length; j < len; j++) {\n gauge = ref[j];\n gauge.setOptions(this.options.pointer);\n gauge.render();\n }\n this.render();\n return this;\n };\n\n Gauge.prototype.configPercentColors = function() {\n var bval, gval, i, j, ref, results, rval;\n this.percentColors = null;\n if (this.options.percentColors !== void 0) {\n this.percentColors = new Array();\n results = [];\n for (i = j = 0, ref = this.options.percentColors.length - 1; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) {\n rval = parseInt((cutHex(this.options.percentColors[i][1])).substring(0, 2), 16);\n gval = parseInt((cutHex(this.options.percentColors[i][1])).substring(2, 4), 16);\n bval = parseInt((cutHex(this.options.percentColors[i][1])).substring(4, 6), 16);\n results.push(this.percentColors[i] = {\n pct: this.options.percentColors[i][0],\n color: {\n r: rval,\n g: gval,\n b: bval\n }\n });\n }\n return results;\n }\n };\n\n Gauge.prototype.set = function(value) {\n var gp, i, j, k, l, len, ref, ref1, val;\n if (!(value instanceof Array)) {\n value = [value];\n }\n for (i = j = 0, ref = value.length - 1; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) {\n value[i] = this.parseValue(value[i]);\n }\n if (value.length > this.gp.length) {\n for (i = k = 0, ref1 = value.length - this.gp.length; 0 <= ref1 ? k < ref1 : k > ref1; i = 0 <= ref1 ? ++k : --k) {\n gp = new GaugePointer(this);\n gp.setOptions(this.options.pointer);\n this.gp.push(gp);\n }\n } else if (value.length < this.gp.length) {\n this.gp = this.gp.slice(this.gp.length - value.length);\n }\n i = 0;\n for (l = 0, len = value.length; l < len; l++) {\n val = value[l];\n if (val > this.maxValue) {\n if (this.options.limitMax) {\n val = this.maxValue;\n } else {\n this.maxValue = val + 1;\n }\n } else if (val < this.minValue) {\n if (this.options.limitMin) {\n val = this.minValue;\n } else {\n this.minValue = val - 1;\n }\n }\n this.gp[i].value = val;\n this.gp[i++].setOptions({\n minValue: this.minValue,\n maxValue: this.maxValue,\n angle: this.options.angle\n });\n }\n this.value = Math.max(Math.min(value[value.length - 1], this.maxValue), this.minValue);\n AnimationUpdater.run(this.forceUpdate);\n return this.forceUpdate = false;\n };\n\n Gauge.prototype.getAngle = function(value) {\n return (1 + this.options.angle) * Math.PI + ((value - this.minValue) / (this.maxValue - this.minValue)) * (1 - this.options.angle * 2) * Math.PI;\n };\n\n Gauge.prototype.getColorForPercentage = function(pct, grad) {\n var color, endColor, i, j, rangePct, ref, startColor;\n if (pct === 0) {\n color = this.percentColors[0].color;\n } else {\n color = this.percentColors[this.percentColors.length - 1].color;\n for (i = j = 0, ref = this.percentColors.length - 1; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) {\n if (pct <= this.percentColors[i].pct) {\n if (grad === true) {\n startColor = this.percentColors[i - 1] || this.percentColors[0];\n endColor = this.percentColors[i];\n rangePct = (pct - startColor.pct) / (endColor.pct - startColor.pct);\n color = {\n r: Math.floor(startColor.color.r * (1 - rangePct) + endColor.color.r * rangePct),\n g: Math.floor(startColor.color.g * (1 - rangePct) + endColor.color.g * rangePct),\n b: Math.floor(startColor.color.b * (1 - rangePct) + endColor.color.b * rangePct)\n };\n } else {\n color = this.percentColors[i].color;\n }\n break;\n }\n }\n }\n return 'rgb(' + [color.r, color.g, color.b].join(',') + ')';\n };\n\n Gauge.prototype.getColorForValue = function(val, grad) {\n var pct;\n pct = (val - this.minValue) / (this.maxValue - this.minValue);\n return this.getColorForPercentage(pct, grad);\n };\n\n Gauge.prototype.renderStaticLabels = function(staticLabels, w, h, radius) {\n var font, fontsize, j, len, match, re, ref, rest, rotationAngle, value;\n this.ctx.save();\n this.ctx.translate(w, h);\n font = staticLabels.font || \"10px Times\";\n re = /\\d+\\.?\\d?/;\n match = font.match(re)[0];\n rest = font.slice(match.length);\n fontsize = parseFloat(match) * this.displayScale;\n this.ctx.font = fontsize + rest;\n this.ctx.fillStyle = staticLabels.color || \"#000000\";\n this.ctx.textBaseline = \"bottom\";\n this.ctx.textAlign = \"center\";\n ref = staticLabels.labels;\n for (j = 0, len = ref.length; j < len; j++) {\n value = ref[j];\n if (value.label !== void 0) {\n if ((!this.options.limitMin || value >= this.minValue) && (!this.options.limitMax || value <= this.maxValue)) {\n font = value.font || staticLabels.font;\n match = font.match(re)[0];\n rest = font.slice(match.length);\n fontsize = parseFloat(match) * this.displayScale;\n this.ctx.font = fontsize + rest;\n rotationAngle = this.getAngle(value.label) - 3 * Math.PI / 2;\n this.ctx.rotate(rotationAngle);\n this.ctx.fillText(formatNumber(value.label, staticLabels.fractionDigits), 0, -radius - this.lineWidth / 2);\n this.ctx.rotate(-rotationAngle);\n }\n } else {\n if ((!this.options.limitMin || value >= this.minValue) && (!this.options.limitMax || value <= this.maxValue)) {\n rotationAngle = this.getAngle(value) - 3 * Math.PI / 2;\n this.ctx.rotate(rotationAngle);\n this.ctx.fillText(formatNumber(value, staticLabels.fractionDigits), 0, -radius - this.lineWidth / 2);\n this.ctx.rotate(-rotationAngle);\n }\n }\n }\n return this.ctx.restore();\n };\n\n Gauge.prototype.renderTicks = function(ticksOptions, w, h, radius) {\n var currentDivision, currentSubDivision, divColor, divLength, divWidth, divisionCount, j, lineWidth, range, rangeDivisions, ref, results, scaleMutate, st, subColor, subDivisions, subLength, subWidth, subdivisionCount, t, tmpRadius;\n if (ticksOptions !== {}) {\n divisionCount = ticksOptions.divisions || 0;\n subdivisionCount = ticksOptions.subDivisions || 0;\n divColor = ticksOptions.divColor || '#fff';\n subColor = ticksOptions.subColor || '#fff';\n divLength = ticksOptions.divLength || 0.7;\n subLength = ticksOptions.subLength || 0.2;\n range = parseFloat(this.maxValue) - parseFloat(this.minValue);\n rangeDivisions = parseFloat(range) / parseFloat(ticksOptions.divisions);\n subDivisions = parseFloat(rangeDivisions) / parseFloat(ticksOptions.subDivisions);\n currentDivision = parseFloat(this.minValue);\n currentSubDivision = 0.0 + subDivisions;\n lineWidth = range / 400;\n divWidth = lineWidth * (ticksOptions.divWidth || 1);\n subWidth = lineWidth * (ticksOptions.subWidth || 1);\n results = [];\n for (t = j = 0, ref = divisionCount + 1; j < ref; t = j += 1) {\n this.ctx.lineWidth = this.lineWidth * divLength;\n scaleMutate = (this.lineWidth / 2) * (1 - divLength);\n tmpRadius = (this.radius * this.options.radiusScale) + scaleMutate;\n this.ctx.strokeStyle = divColor;\n this.ctx.beginPath();\n this.ctx.arc(0, 0, tmpRadius, this.getAngle(currentDivision - divWidth), this.getAngle(currentDivision + divWidth), false);\n this.ctx.stroke();\n currentSubDivision = currentDivision + subDivisions;\n currentDivision += rangeDivisions;\n if (t !== ticksOptions.divisions && subdivisionCount > 0) {\n results.push((function() {\n var k, ref1, results1;\n results1 = [];\n for (st = k = 0, ref1 = subdivisionCount - 1; k < ref1; st = k += 1) {\n this.ctx.lineWidth = this.lineWidth * subLength;\n scaleMutate = (this.lineWidth / 2) * (1 - subLength);\n tmpRadius = (this.radius * this.options.radiusScale) + scaleMutate;\n this.ctx.strokeStyle = subColor;\n this.ctx.beginPath();\n this.ctx.arc(0, 0, tmpRadius, this.getAngle(currentSubDivision - subWidth), this.getAngle(currentSubDivision + subWidth), false);\n this.ctx.stroke();\n results1.push(currentSubDivision += subDivisions);\n }\n return results1;\n }).call(this));\n } else {\n results.push(void 0);\n }\n }\n return results;\n }\n };\n\n Gauge.prototype.render = function() {\n var displayedAngle, fillStyle, gauge, h, j, k, len, len1, max, min, radius, ref, ref1, scaleMutate, tmpRadius, w, zone;\n w = this.canvas.width / 2;\n h = (this.canvas.height * this.paddingTop + this.availableHeight) - ((this.radius + this.lineWidth / 2) * this.extraPadding);\n displayedAngle = this.getAngle(this.displayedValue);\n if (this.textField) {\n this.textField.render(this);\n }\n this.ctx.lineCap = \"butt\";\n radius = this.radius * this.options.radiusScale;\n if (this.options.staticLabels) {\n this.renderStaticLabels(this.options.staticLabels, w, h, radius);\n }\n if (this.options.staticZones) {\n this.ctx.save();\n this.ctx.translate(w, h);\n this.ctx.lineWidth = this.lineWidth;\n ref = this.options.staticZones;\n for (j = 0, len = ref.length; j < len; j++) {\n zone = ref[j];\n min = zone.min;\n if (this.options.limitMin && min < this.minValue) {\n min = this.minValue;\n }\n max = zone.max;\n if (this.options.limitMax && max > this.maxValue) {\n max = this.maxValue;\n }\n tmpRadius = this.radius * this.options.radiusScale;\n if (zone.height) {\n this.ctx.lineWidth = this.lineWidth * zone.height;\n scaleMutate = (this.lineWidth / 2) * (zone.offset || 1 - zone.height);\n tmpRadius = (this.radius * this.options.radiusScale) + scaleMutate;\n }\n this.ctx.strokeStyle = zone.strokeStyle;\n this.ctx.beginPath();\n this.ctx.arc(0, 0, tmpRadius, this.getAngle(min), this.getAngle(max), false);\n this.ctx.stroke();\n }\n } else {\n if (this.options.customFillStyle !== void 0) {\n fillStyle = this.options.customFillStyle(this);\n } else if (this.percentColors !== null) {\n fillStyle = this.getColorForValue(this.displayedValue, this.options.generateGradient);\n } else if (this.options.colorStop !== void 0) {\n if (this.options.gradientType === 0) {\n fillStyle = this.ctx.createRadialGradient(w, h, 9, w, h, 70);\n } else {\n fillStyle = this.ctx.createLinearGradient(0, 0, w, 0);\n }\n fillStyle.addColorStop(0, this.options.colorStart);\n fillStyle.addColorStop(1, this.options.colorStop);\n } else {\n fillStyle = this.options.colorStart;\n }\n this.ctx.strokeStyle = fillStyle;\n this.ctx.beginPath();\n this.ctx.arc(w, h, radius, (1 + this.options.angle) * Math.PI, displayedAngle, false);\n this.ctx.lineWidth = this.lineWidth;\n this.ctx.stroke();\n this.ctx.strokeStyle = this.options.strokeColor;\n this.ctx.beginPath();\n this.ctx.arc(w, h, radius, displayedAngle, (2 - this.options.angle) * Math.PI, false);\n this.ctx.stroke();\n this.ctx.save();\n this.ctx.translate(w, h);\n }\n if (this.options.renderTicks) {\n this.renderTicks(this.options.renderTicks, w, h, radius);\n }\n this.ctx.restore();\n this.ctx.translate(w, h);\n ref1 = this.gp;\n for (k = 0, len1 = ref1.length; k < len1; k++) {\n gauge = ref1[k];\n gauge.update(true);\n }\n return this.ctx.translate(-w, -h);\n };\n\n return Gauge;\n\n })(BaseGauge);\n\n BaseDonut = (function(superClass) {\n extend(BaseDonut, superClass);\n\n BaseDonut.prototype.lineWidth = 15;\n\n BaseDonut.prototype.displayedValue = 0;\n\n BaseDonut.prototype.value = 33;\n\n BaseDonut.prototype.maxValue = 80;\n\n BaseDonut.prototype.minValue = 0;\n\n BaseDonut.prototype.options = {\n lineWidth: 0.10,\n colorStart: \"#6f6ea0\",\n colorStop: \"#c0c0db\",\n strokeColor: \"#eeeeee\",\n shadowColor: \"#d5d5d5\",\n angle: 0.35,\n radiusScale: 1.0\n };\n\n function BaseDonut(canvas) {\n this.canvas = canvas;\n BaseDonut.__super__.constructor.call(this);\n if (typeof G_vmlCanvasManager !== 'undefined') {\n this.canvas = window.G_vmlCanvasManager.initElement(this.canvas);\n }\n this.ctx = this.canvas.getContext('2d');\n this.setOptions();\n this.render();\n }\n\n BaseDonut.prototype.getAngle = function(value) {\n return (1 - this.options.angle) * Math.PI + ((value - this.minValue) / (this.maxValue - this.minValue)) * ((2 + this.options.angle) - (1 - this.options.angle)) * Math.PI;\n };\n\n BaseDonut.prototype.setOptions = function(options) {\n if (options == null) {\n options = null;\n }\n BaseDonut.__super__.setOptions.call(this, options);\n this.lineWidth = this.canvas.height * this.options.lineWidth;\n this.radius = this.options.radiusScale * (this.canvas.height / 2 - this.lineWidth / 2);\n return this;\n };\n\n BaseDonut.prototype.set = function(value) {\n this.value = this.parseValue(value);\n if (this.value > this.maxValue) {\n if (this.options.limitMax) {\n this.value = this.maxValue;\n } else {\n this.maxValue = this.value;\n }\n } else if (this.value < this.minValue) {\n if (this.options.limitMin) {\n this.value = this.minValue;\n } else {\n this.minValue = this.value;\n }\n }\n AnimationUpdater.run(this.forceUpdate);\n return this.forceUpdate = false;\n };\n\n BaseDonut.prototype.render = function() {\n var displayedAngle, grdFill, h, start, stop, w;\n displayedAngle = this.getAngle(this.displayedValue);\n w = this.canvas.width / 2;\n h = this.canvas.height / 2;\n if (this.textField) {\n this.textField.render(this);\n }\n grdFill = this.ctx.createRadialGradient(w, h, 39, w, h, 70);\n grdFill.addColorStop(0, this.options.colorStart);\n grdFill.addColorStop(1, this.options.colorStop);\n start = this.radius - this.lineWidth / 2;\n stop = this.radius + this.lineWidth / 2;\n this.ctx.strokeStyle = this.options.strokeColor;\n this.ctx.beginPath();\n this.ctx.arc(w, h, this.radius, (1 - this.options.angle) * Math.PI, (2 + this.options.angle) * Math.PI, false);\n this.ctx.lineWidth = this.lineWidth;\n this.ctx.lineCap = \"round\";\n this.ctx.stroke();\n this.ctx.strokeStyle = grdFill;\n this.ctx.beginPath();\n this.ctx.arc(w, h, this.radius, (1 - this.options.angle) * Math.PI, displayedAngle, false);\n return this.ctx.stroke();\n };\n\n return BaseDonut;\n\n })(BaseGauge);\n\n Donut = (function(superClass) {\n extend(Donut, superClass);\n\n function Donut() {\n return Donut.__super__.constructor.apply(this, arguments);\n }\n\n Donut.prototype.strokeGradient = function(w, h, start, stop) {\n var grd;\n grd = this.ctx.createRadialGradient(w, h, start, w, h, stop);\n grd.addColorStop(0, this.options.shadowColor);\n grd.addColorStop(0.12, this.options._orgStrokeColor);\n grd.addColorStop(0.88, this.options._orgStrokeColor);\n grd.addColorStop(1, this.options.shadowColor);\n return grd;\n };\n\n Donut.prototype.setOptions = function(options) {\n var h, start, stop, w;\n if (options == null) {\n options = null;\n }\n Donut.__super__.setOptions.call(this, options);\n w = this.canvas.width / 2;\n h = this.canvas.height / 2;\n start = this.radius - this.lineWidth / 2;\n stop = this.radius + this.lineWidth / 2;\n this.options._orgStrokeColor = this.options.strokeColor;\n this.options.strokeColor = this.strokeGradient(w, h, start, stop);\n return this;\n };\n\n return Donut;\n\n })(BaseDonut);\n\n window.AnimationUpdater = {\n elements: [],\n animId: null,\n addAll: function(list) {\n var elem, j, len, results;\n results = [];\n for (j = 0, len = list.length; j < len; j++) {\n elem = list[j];\n results.push(AnimationUpdater.elements.push(elem));\n }\n return results;\n },\n add: function(object) {\n return AnimationUpdater.elements.push(object);\n },\n run: function(force) {\n var elem, finished, isCallback, j, len, ref;\n if (force == null) {\n force = false;\n }\n isCallback = isFinite(parseFloat(force));\n if (isCallback || force === true) {\n finished = true;\n ref = AnimationUpdater.elements;\n for (j = 0, len = ref.length; j < len; j++) {\n elem = ref[j];\n if (elem.update(force === true)) {\n finished = false;\n }\n }\n return AnimationUpdater.animId = finished ? null : requestAnimationFrame(AnimationUpdater.run);\n } else if (force === false) {\n if (AnimationUpdater.animId === !null) {\n cancelAnimationFrame(AnimationUpdater.animId);\n }\n return AnimationUpdater.animId = requestAnimationFrame(AnimationUpdater.run);\n }\n }\n };\n\n if (typeof window.define === 'function' && (window.define.amd != null)) {\n define(function() {\n return {\n Gauge: Gauge,\n Donut: Donut,\n BaseDonut: BaseDonut,\n TextRenderer: TextRenderer\n };\n });\n } else if (typeof module !== 'undefined' && (module.exports != null)) {\n module.exports = {\n Gauge: Gauge,\n Donut: Donut,\n BaseDonut: BaseDonut,\n TextRenderer: TextRenderer\n };\n } else {\n window.Gauge = Gauge;\n window.Donut = Donut;\n window.BaseDonut = BaseDonut;\n window.TextRenderer = TextRenderer;\n }\n\n}).call(this);\n","var tabbable = require('tabbable');\nvar xtend = require('xtend');\n\nvar activeFocusDelay;\n\nvar activeFocusTraps = (function() {\n var trapQueue = [];\n return {\n activateTrap: function(trap) {\n if (trapQueue.length > 0) {\n var activeTrap = trapQueue[trapQueue.length - 1];\n if (activeTrap !== trap) {\n activeTrap.pause();\n }\n }\n\n var trapIndex = trapQueue.indexOf(trap);\n if (trapIndex === -1) {\n trapQueue.push(trap);\n } else {\n // move this existing trap to the front of the queue\n trapQueue.splice(trapIndex, 1);\n trapQueue.push(trap);\n }\n },\n\n deactivateTrap: function(trap) {\n var trapIndex = trapQueue.indexOf(trap);\n if (trapIndex !== -1) {\n trapQueue.splice(trapIndex, 1);\n }\n\n if (trapQueue.length > 0) {\n trapQueue[trapQueue.length - 1].unpause();\n }\n }\n };\n})();\n\nfunction focusTrap(element, userOptions) {\n var doc = document;\n var container =\n typeof element === 'string' ? doc.querySelector(element) : element;\n\n var config = xtend(\n {\n returnFocusOnDeactivate: true,\n escapeDeactivates: true\n },\n userOptions\n );\n\n var state = {\n firstTabbableNode: null,\n lastTabbableNode: null,\n nodeFocusedBeforeActivation: null,\n mostRecentlyFocusedNode: null,\n active: false,\n paused: false\n };\n\n var trap = {\n activate: activate,\n deactivate: deactivate,\n pause: pause,\n unpause: unpause\n };\n\n return trap;\n\n function activate(activateOptions) {\n if (state.active) return;\n\n updateTabbableNodes();\n\n state.active = true;\n state.paused = false;\n state.nodeFocusedBeforeActivation = doc.activeElement;\n\n var onActivate =\n activateOptions && activateOptions.onActivate\n ? activateOptions.onActivate\n : config.onActivate;\n if (onActivate) {\n onActivate();\n }\n\n addListeners();\n return trap;\n }\n\n function deactivate(deactivateOptions) {\n if (!state.active) return;\n\n clearTimeout(activeFocusDelay);\n\n removeListeners();\n state.active = false;\n state.paused = false;\n\n activeFocusTraps.deactivateTrap(trap);\n\n var onDeactivate =\n deactivateOptions && deactivateOptions.onDeactivate !== undefined\n ? deactivateOptions.onDeactivate\n : config.onDeactivate;\n if (onDeactivate) {\n onDeactivate();\n }\n\n var returnFocus =\n deactivateOptions && deactivateOptions.returnFocus !== undefined\n ? deactivateOptions.returnFocus\n : config.returnFocusOnDeactivate;\n if (returnFocus) {\n delay(function() {\n tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));\n });\n }\n\n return trap;\n }\n\n function pause() {\n if (state.paused || !state.active) return;\n state.paused = true;\n removeListeners();\n }\n\n function unpause() {\n if (!state.paused || !state.active) return;\n state.paused = false;\n updateTabbableNodes();\n addListeners();\n }\n\n function addListeners() {\n if (!state.active) return;\n\n // There can be only one listening focus trap at a time\n activeFocusTraps.activateTrap(trap);\n\n // Delay ensures that the focused element doesn't capture the event\n // that caused the focus trap activation.\n activeFocusDelay = delay(function() {\n tryFocus(getInitialFocusNode());\n });\n\n doc.addEventListener('focusin', checkFocusIn, true);\n doc.addEventListener('mousedown', checkPointerDown, {\n capture: true,\n passive: false\n });\n doc.addEventListener('touchstart', checkPointerDown, {\n capture: true,\n passive: false\n });\n doc.addEventListener('click', checkClick, {\n capture: true,\n passive: false\n });\n doc.addEventListener('keydown', checkKey, {\n capture: true,\n passive: false\n });\n\n return trap;\n }\n\n function removeListeners() {\n if (!state.active) return;\n\n doc.removeEventListener('focusin', checkFocusIn, true);\n doc.removeEventListener('mousedown', checkPointerDown, true);\n doc.removeEventListener('touchstart', checkPointerDown, true);\n doc.removeEventListener('click', checkClick, true);\n doc.removeEventListener('keydown', checkKey, true);\n\n return trap;\n }\n\n function getNodeForOption(optionName) {\n var optionValue = config[optionName];\n var node = optionValue;\n if (!optionValue) {\n return null;\n }\n if (typeof optionValue === 'string') {\n node = doc.querySelector(optionValue);\n if (!node) {\n throw new Error('`' + optionName + '` refers to no known node');\n }\n }\n if (typeof optionValue === 'function') {\n node = optionValue();\n if (!node) {\n throw new Error('`' + optionName + '` did not return a node');\n }\n }\n return node;\n }\n\n function getInitialFocusNode() {\n var node;\n if (getNodeForOption('initialFocus') !== null) {\n node = getNodeForOption('initialFocus');\n } else if (container.contains(doc.activeElement)) {\n node = doc.activeElement;\n } else {\n node = state.firstTabbableNode || getNodeForOption('fallbackFocus');\n }\n\n if (!node) {\n throw new Error(\n 'Your focus-trap needs to have at least one focusable element'\n );\n }\n\n return node;\n }\n\n function getReturnFocusNode(previousActiveElement) {\n var node = getNodeForOption('setReturnFocus');\n return node ? node : previousActiveElement;\n }\n\n // This needs to be done on mousedown and touchstart instead of click\n // so that it precedes the focus event.\n function checkPointerDown(e) {\n if (container.contains(e.target)) return;\n if (config.clickOutsideDeactivates) {\n deactivate({\n returnFocus: !tabbable.isFocusable(e.target)\n });\n return;\n }\n // This is needed for mobile devices.\n // (If we'll only let `click` events through,\n // then on mobile they will be blocked anyways if `touchstart` is blocked.)\n if (config.allowOutsideClick && config.allowOutsideClick(e)) {\n return;\n }\n e.preventDefault();\n }\n\n // In case focus escapes the trap for some strange reason, pull it back in.\n function checkFocusIn(e) {\n // In Firefox when you Tab out of an iframe the Document is briefly focused.\n if (container.contains(e.target) || e.target instanceof Document) {\n return;\n }\n e.stopImmediatePropagation();\n tryFocus(state.mostRecentlyFocusedNode || getInitialFocusNode());\n }\n\n function checkKey(e) {\n if (config.escapeDeactivates !== false && isEscapeEvent(e)) {\n e.preventDefault();\n deactivate();\n return;\n }\n if (isTabEvent(e)) {\n checkTab(e);\n return;\n }\n }\n\n // Hijack Tab events on the first and last focusable nodes of the trap,\n // in order to prevent focus from escaping. If it escapes for even a\n // moment it can end up scrolling the page and causing confusion so we\n // kind of need to capture the action at the keydown phase.\n function checkTab(e) {\n updateTabbableNodes();\n if (e.shiftKey && e.target === state.firstTabbableNode) {\n e.preventDefault();\n tryFocus(state.lastTabbableNode);\n return;\n }\n if (!e.shiftKey && e.target === state.lastTabbableNode) {\n e.preventDefault();\n tryFocus(state.firstTabbableNode);\n return;\n }\n }\n\n function checkClick(e) {\n if (config.clickOutsideDeactivates) return;\n if (container.contains(e.target)) return;\n if (config.allowOutsideClick && config.allowOutsideClick(e)) {\n return;\n }\n e.preventDefault();\n e.stopImmediatePropagation();\n }\n\n function updateTabbableNodes() {\n var tabbableNodes = tabbable(container);\n state.firstTabbableNode = tabbableNodes[0] || getInitialFocusNode();\n state.lastTabbableNode =\n tabbableNodes[tabbableNodes.length - 1] || getInitialFocusNode();\n }\n\n function tryFocus(node) {\n if (node === doc.activeElement) return;\n if (!node || !node.focus) {\n tryFocus(getInitialFocusNode());\n return;\n }\n node.focus();\n state.mostRecentlyFocusedNode = node;\n if (isSelectableInput(node)) {\n node.select();\n }\n }\n}\n\nfunction isSelectableInput(node) {\n return (\n node.tagName &&\n node.tagName.toLowerCase() === 'input' &&\n typeof node.select === 'function'\n );\n}\n\nfunction isEscapeEvent(e) {\n return e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27;\n}\n\nfunction isTabEvent(e) {\n return e.key === 'Tab' || e.keyCode === 9;\n}\n\nfunction delay(fn) {\n return setTimeout(fn, 0);\n}\n\nmodule.exports = focusTrap;\n","import toDate from \"../toDate/index.js\";\nimport requiredArgs from \"../_lib/requiredArgs/index.js\";\n/**\n * @name getTime\n * @category Timestamp Helpers\n * @summary Get the milliseconds timestamp of the given date.\n *\n * @description\n * Get the milliseconds timestamp of the given date.\n *\n * ### v2.0.0 breaking changes:\n *\n * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes).\n *\n * @param {Date|Number} date - the given date\n * @returns {Number} the timestamp\n * @throws {TypeError} 1 argument required\n *\n * @example\n * // Get the timestamp of 29 February 2012 11:45:05.123:\n * const result = getTime(new Date(2012, 1, 29, 11, 45, 5, 123))\n * //=> 1330515905123\n */\n\nexport default function getTime(dirtyDate) {\n requiredArgs(1, arguments);\n var date = toDate(dirtyDate);\n var timestamp = date.getTime();\n return timestamp;\n}","!function(e,t){\"object\"==typeof exports&&\"undefined\"!=typeof module?t(exports,require(\"react\"),require(\"prop-types\"),require(\"classnames\"),require(\"date-fns/isDate\"),require(\"date-fns/isValid\"),require(\"date-fns/format\"),require(\"date-fns/addMinutes\"),require(\"date-fns/addHours\"),require(\"date-fns/addDays\"),require(\"date-fns/addWeeks\"),require(\"date-fns/addMonths\"),require(\"date-fns/addYears\"),require(\"date-fns/subMinutes\"),require(\"date-fns/subHours\"),require(\"date-fns/subDays\"),require(\"date-fns/subWeeks\"),require(\"date-fns/subMonths\"),require(\"date-fns/subYears\"),require(\"date-fns/getSeconds\"),require(\"date-fns/getMinutes\"),require(\"date-fns/getHours\"),require(\"date-fns/getDay\"),require(\"date-fns/getDate\"),require(\"date-fns/getISOWeek\"),require(\"date-fns/getMonth\"),require(\"date-fns/getQuarter\"),require(\"date-fns/getYear\"),require(\"date-fns/getTime\"),require(\"date-fns/setSeconds\"),require(\"date-fns/setMinutes\"),require(\"date-fns/setHours\"),require(\"date-fns/setMonth\"),require(\"date-fns/setQuarter\"),require(\"date-fns/setYear\"),require(\"date-fns/min\"),require(\"date-fns/max\"),require(\"date-fns/differenceInCalendarDays\"),require(\"date-fns/differenceInCalendarMonths\"),require(\"date-fns/differenceInCalendarWeeks\"),require(\"date-fns/differenceInCalendarYears\"),require(\"date-fns/startOfDay\"),require(\"date-fns/startOfWeek\"),require(\"date-fns/startOfMonth\"),require(\"date-fns/startOfQuarter\"),require(\"date-fns/startOfYear\"),require(\"date-fns/endOfDay\"),require(\"date-fns/endOfWeek\"),require(\"date-fns/endOfMonth\"),require(\"date-fns/isEqual\"),require(\"date-fns/isSameDay\"),require(\"date-fns/isSameMonth\"),require(\"date-fns/isSameYear\"),require(\"date-fns/isSameQuarter\"),require(\"date-fns/isAfter\"),require(\"date-fns/isBefore\"),require(\"date-fns/isWithinInterval\"),require(\"date-fns/toDate\"),require(\"date-fns/parse\"),require(\"date-fns/parseISO\"),require(\"react-onclickoutside\"),require(\"react-dom\"),require(\"react-popper\")):\"function\"==typeof define&&define.amd?define([\"exports\",\"react\",\"prop-types\",\"classnames\",\"date-fns/isDate\",\"date-fns/isValid\",\"date-fns/format\",\"date-fns/addMinutes\",\"date-fns/addHours\",\"date-fns/addDays\",\"date-fns/addWeeks\",\"date-fns/addMonths\",\"date-fns/addYears\",\"date-fns/subMinutes\",\"date-fns/subHours\",\"date-fns/subDays\",\"date-fns/subWeeks\",\"date-fns/subMonths\",\"date-fns/subYears\",\"date-fns/getSeconds\",\"date-fns/getMinutes\",\"date-fns/getHours\",\"date-fns/getDay\",\"date-fns/getDate\",\"date-fns/getISOWeek\",\"date-fns/getMonth\",\"date-fns/getQuarter\",\"date-fns/getYear\",\"date-fns/getTime\",\"date-fns/setSeconds\",\"date-fns/setMinutes\",\"date-fns/setHours\",\"date-fns/setMonth\",\"date-fns/setQuarter\",\"date-fns/setYear\",\"date-fns/min\",\"date-fns/max\",\"date-fns/differenceInCalendarDays\",\"date-fns/differenceInCalendarMonths\",\"date-fns/differenceInCalendarWeeks\",\"date-fns/differenceInCalendarYears\",\"date-fns/startOfDay\",\"date-fns/startOfWeek\",\"date-fns/startOfMonth\",\"date-fns/startOfQuarter\",\"date-fns/startOfYear\",\"date-fns/endOfDay\",\"date-fns/endOfWeek\",\"date-fns/endOfMonth\",\"date-fns/isEqual\",\"date-fns/isSameDay\",\"date-fns/isSameMonth\",\"date-fns/isSameYear\",\"date-fns/isSameQuarter\",\"date-fns/isAfter\",\"date-fns/isBefore\",\"date-fns/isWithinInterval\",\"date-fns/toDate\",\"date-fns/parse\",\"date-fns/parseISO\",\"react-onclickoutside\",\"react-dom\",\"react-popper\"],t):t((e=\"undefined\"!=typeof globalThis?globalThis:e||self).DatePicker={},e.React,e.PropTypes,e.classNames,e.isDate,e.isValidDate,e.format,e.addMinutes,e.addHours,e.addDays,e.addWeeks,e.addMonths,e.addYears,null,null,e.subDays,e.subWeeks,e.subMonths,e.subYears,e.getSeconds,e.getMinutes,e.getHours,e.getDay,e.getDate,e.getISOWeek,e.getMonth,e.getQuarter,e.getYear,e.getTime,e.setSeconds,e.setMinutes,e.setHours,e.setMonth,e.setQuarter,e.setYear,e.min,e.max,e.differenceInCalendarDays,e.differenceInCalendarMonths,null,e.differenceInCalendarYears,e.startOfDay,e.startOfWeek,e.startOfMonth,e.startOfQuarter,e.startOfYear,e.endOfDay,null,null,e.dfIsEqual,e.dfIsSameDay,e.dfIsSameMonth,e.dfIsSameYear,e.dfIsSameQuarter,e.isAfter,e.isBefore,e.isWithinInterval,e.toDate,e.parse,e.parseISO,e.onClickOutside,e.ReactDOM,e.ReactPopper)}(this,(function(e,t,r,a,n,o,s,i,p,l,d,c,u,f,h,m,y,D,v,w,g,k,b,C,S,_,M,P,E,N,O,Y,x,T,I,L,F,R,q,A,W,K,B,j,H,Q,V,U,$,z,G,J,X,Z,ee,te,re,ae,ne,oe,se,ie,pe){\"use strict\";function le(e){return e&&\"object\"==typeof e&&\"default\"in e?e:{default:e}}var de=le(t),ce=le(a),ue=le(n),fe=le(o),he=le(s),me=le(i),ye=le(p),De=le(l),ve=le(d),we=le(c),ge=le(u),ke=le(m),be=le(y),Ce=le(D),Se=le(v),_e=le(w),Me=le(g),Pe=le(k),Ee=le(b),Ne=le(C),Oe=le(S),Ye=le(_),xe=le(M),Te=le(P),Ie=le(E),Le=le(N),Fe=le(O),Re=le(Y),qe=le(x),Ae=le(T),We=le(I),Ke=le(L),Be=le(F),je=le(R),He=le(q),Qe=le(W),Ve=le(K),Ue=le(B),$e=le(j),ze=le(H),Ge=le(Q),Je=le(V),Xe=le(z),Ze=le(G),et=le(J),tt=le(X),rt=le(Z),at=le(ee),nt=le(te),ot=le(re),st=le(ae),it=le(ne),pt=le(oe),lt=le(se),dt=le(ie);function ct(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,a)}return r}function ut(e){for(var t=1;te.length)&&(t=e.length);for(var r=0,a=new Array(t);r0&&(o=it.default(e,t.slice(0,e.length),new Date)),It(o)||(o=new Date(e))),It(o)&&i?o:null)}function It(e,t){return t=t||new Date(\"1/1/1000\"),fe.default(e)&&at.default(e,t)}function Lt(e,t,r){if(\"en\"===r)return he.default(e,t,{awareOfUnicodeTokens:!0});var a=Xt(r);return r&&!a&&console.warn('A locale object was not found for the provided string [\"'.concat(r,'\"].')),!a&&Jt()&&Xt(Jt())&&(a=Xt(Jt())),he.default(e,t,{locale:a||null,awareOfUnicodeTokens:!0})}function Ft(e,t){var r=t.dateFormat,a=t.locale;return e&&Lt(e,Array.isArray(r)?r[0]:r,a)||\"\"}function Rt(e,t){var r=t.hour,a=void 0===r?0:r,n=t.minute,o=void 0===n?0:n,s=t.second,i=void 0===s?0:s;return Re.default(Fe.default(Le.default(e,i),o),a)}function qt(e,t){var r=t&&Xt(t)||Jt()&&Xt(Jt());return Oe.default(e,r?{locale:r}:null)}function At(e,t){return Lt(e,\"ddd\",t)}function Wt(e){return Ve.default(e)}function Kt(e,t,r){var a=Xt(t||Jt());return Ue.default(e,{locale:a,weekStartsOn:r})}function Bt(e){return $e.default(e)}function jt(e){return Ge.default(e)}function Ht(e){return ze.default(e)}function Qt(e,t){return e&&t?tt.default(e,t):!e&&!t}function Vt(e,t){return e&&t?et.default(e,t):!e&&!t}function Ut(e,t){return e&&t?rt.default(e,t):!e&&!t}function $t(e,t){return e&&t?Ze.default(e,t):!e&&!t}function zt(e,t){return e&&t?Xe.default(e,t):!e&&!t}function Gt(e,t,r){var a,n=Ve.default(t),o=Je.default(r);try{a=ot.default(e,{start:n,end:o})}catch(e){a=!1}return a}function Jt(){return(\"undefined\"!=typeof window?window:global).__localeId__}function Xt(e){if(\"string\"==typeof e){var t=\"undefined\"!=typeof window?window:global;return t.__localeData__?t.__localeData__[e]:null}return e}function Zt(e,t){return Lt(qe.default(xt(),e),\"LLLL\",t)}function er(e,t){return Lt(qe.default(xt(),e),\"LLL\",t)}function tr(e,t){return Lt(Ae.default(xt(),e),\"QQQ\",t)}function rr(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.minDate,a=t.maxDate,n=t.excludeDates,o=t.includeDates,s=t.filterDate;return lr(e,{minDate:r,maxDate:a})||n&&n.some((function(t){return $t(e,t)}))||o&&!o.some((function(t){return $t(e,t)}))||s&&!s(xt(e))||!1}function ar(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.excludeDates;return r&&r.some((function(t){return $t(e,t)}))||!1}function nr(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.minDate,a=t.maxDate,n=t.excludeDates,o=t.includeDates,s=t.filterDate;return lr(e,{minDate:r,maxDate:a})||n&&n.some((function(t){return Vt(e,t)}))||o&&!o.some((function(t){return Vt(e,t)}))||s&&!s(xt(e))||!1}function or(e,t,r,a){var n=Te.default(e),o=Ye.default(e),s=Te.default(t),i=Ye.default(t),p=Te.default(a);return n===s&&n===p?o<=r&&r<=i:n=r||pn:void 0}function sr(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.minDate,a=t.maxDate,n=t.excludeDates,o=t.includeDates,s=t.filterDate;return lr(e,{minDate:r,maxDate:a})||n&&n.some((function(t){return Ut(e,t)}))||o&&!o.some((function(t){return Ut(e,t)}))||s&&!s(xt(e))||!1}function ir(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.minDate,a=t.maxDate,n=new Date(e,0,1);return lr(n,{minDate:r,maxDate:a})||!1}function pr(e,t,r,a){var n=Te.default(e),o=xe.default(e),s=Te.default(t),i=xe.default(t),p=Te.default(a);return n===s&&n===p?o<=r&&r<=i:n=r||pn:void 0}function lr(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.minDate,a=t.maxDate;return r&&je.default(e,r)<0||a&&je.default(e,a)>0}function dr(e,t){return t.some((function(t){return Pe.default(t)===Pe.default(e)&&Me.default(t)===Me.default(e)}))}function cr(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.excludeTimes,a=t.includeTimes,n=t.filterTime;return r&&dr(e,r)||a&&!dr(e,a)||n&&!n(e)||!1}function ur(e,t){var r=t.minTime,a=t.maxTime;if(!r||!a)throw new Error(\"Both minTime and maxTime props required\");var n,o=xt(),s=Re.default(Fe.default(o,Me.default(e)),Pe.default(e)),i=Re.default(Fe.default(o,Me.default(r)),Pe.default(r)),p=Re.default(Fe.default(o,Me.default(a)),Pe.default(a));try{n=!ot.default(s,{start:i,end:p})}catch(e){n=!1}return n}function fr(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.minDate,a=t.includeDates,n=Ce.default(e,1);return r&&He.default(r,n)>0||a&&a.every((function(e){return He.default(e,n)>0}))||!1}function hr(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.maxDate,a=t.includeDates,n=we.default(e,1);return r&&He.default(n,r)>0||a&&a.every((function(e){return He.default(n,e)>0}))||!1}function mr(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.minDate,a=t.includeDates,n=Se.default(e,1);return r&&Qe.default(r,n)>0||a&&a.every((function(e){return Qe.default(e,n)>0}))||!1}function yr(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.maxDate,a=t.includeDates,n=ge.default(e,1);return r&&Qe.default(n,r)>0||a&&a.every((function(e){return Qe.default(n,e)>0}))||!1}function Dr(e){var t=e.minDate,r=e.includeDates;if(r&&t){var a=r.filter((function(e){return je.default(e,t)>=0}));return Ke.default(a)}return r?Ke.default(r):t}function vr(e){var t=e.maxDate,r=e.includeDates;if(r&&t){var a=r.filter((function(e){return je.default(e,t)<=0}));return Be.default(a)}return r?Be.default(r):t}function wr(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:\"react-datepicker__day--highlighted\",r=new Map,a=0,n=e.length;a1&&void 0!==arguments[1]?arguments[1]:Ot,r=Math.ceil(Te.default(e)/t)*t,a=r-(t-1);return{startPeriod:a,endPeriod:r}}function Cr(e,t,r,a){for(var n=[],o=0;o<2*t+1;o++){var s=e+t-o,i=!0;r&&(i=Te.default(r)<=s),a&&i&&(i=Te.default(a)>=s),i&&n.push(s)}return n}var Sr=function(e){wt(r,e);var t=St(r);function r(e){var a;ht(this,r),Dt(bt(a=t.call(this,e)),\"renderOptions\",(function(){var e=a.props.year,t=a.state.yearsList.map((function(t){return de.default.createElement(\"div\",{className:e===t?\"react-datepicker__year-option react-datepicker__year-option--selected_year\":\"react-datepicker__year-option\",key:t,onClick:a.onChange.bind(bt(a),t)},e===t?de.default.createElement(\"span\",{className:\"react-datepicker__year-option--selected\"},\"✓\"):\"\",t)})),r=a.props.minDate?Te.default(a.props.minDate):null,n=a.props.maxDate?Te.default(a.props.maxDate):null;return n&&a.state.yearsList.find((function(e){return e===n}))||t.unshift(de.default.createElement(\"div\",{className:\"react-datepicker__year-option\",key:\"upcoming\",onClick:a.incrementYears},de.default.createElement(\"a\",{className:\"react-datepicker__navigation react-datepicker__navigation--years react-datepicker__navigation--years-upcoming\"}))),r&&a.state.yearsList.find((function(e){return e===r}))||t.push(de.default.createElement(\"div\",{className:\"react-datepicker__year-option\",key:\"previous\",onClick:a.decrementYears},de.default.createElement(\"a\",{className:\"react-datepicker__navigation react-datepicker__navigation--years react-datepicker__navigation--years-previous\"}))),t})),Dt(bt(a),\"onChange\",(function(e){a.props.onChange(e)})),Dt(bt(a),\"handleClickOutside\",(function(){a.props.onCancel()})),Dt(bt(a),\"shiftYears\",(function(e){var t=a.state.yearsList.map((function(t){return t+e}));a.setState({yearsList:t})})),Dt(bt(a),\"incrementYears\",(function(){return a.shiftYears(1)})),Dt(bt(a),\"decrementYears\",(function(){return a.shiftYears(-1)}));var n=e.yearDropdownItemNumber,o=e.scrollableYearDropdown,s=n||(o?10:5);return a.state={yearsList:Cr(a.props.year,s,a.props.minDate,a.props.maxDate)},a}return yt(r,[{key:\"render\",value:function(){var e=ce.default({\"react-datepicker__year-dropdown\":!0,\"react-datepicker__year-dropdown--scrollable\":this.props.scrollableYearDropdown});return de.default.createElement(\"div\",{className:e},this.renderOptions())}}]),r}(de.default.Component),_r=lt.default(Sr),Mr=function(e){wt(r,e);var t=St(r);function r(){var e;ht(this,r);for(var a=arguments.length,n=new Array(a),o=0;o0&&void 0!==arguments[0]?arguments[0]:{},r=!1;0===e.getTabIndex()&&!t.isInputFocused&&e.isSameDay(e.props.preSelection)&&(document.activeElement&&document.activeElement!==document.body||(r=!0),e.props.inline&&!e.props.shouldFocusDayInline&&(r=!1),e.props.containerRef&&e.props.containerRef.current&&e.props.containerRef.current.contains(document.activeElement)&&document.activeElement.classList.contains(\"react-datepicker__day\")&&(r=!0)),r&&e.dayEl.current.focus({preventScroll:!0})})),Dt(bt(e),\"renderDayContents\",(function(){if(e.isOutsideMonth()){if(e.props.monthShowsDuplicateDaysEnd&&Ne.default(e.props.day)<10)return null;if(e.props.monthShowsDuplicateDaysStart&&Ne.default(e.props.day)>20)return null}return e.props.renderDayContents?e.props.renderDayContents(Ne.default(e.props.day),e.props.day):Ne.default(e.props.day)})),Dt(bt(e),\"render\",(function(){return de.default.createElement(\"div\",{ref:e.dayEl,className:e.getClassNames(e.props.day),onKeyDown:e.handleOnKeyDown,onClick:e.handleClick,onMouseEnter:e.handleMouseEnter,tabIndex:e.getTabIndex(),\"aria-label\":e.getAriaLabel(),role:\"button\",\"aria-disabled\":e.isDisabled()},e.renderDayContents())})),e}return yt(r,[{key:\"componentDidMount\",value:function(){this.handleFocusDay()}},{key:\"componentDidUpdate\",value:function(e){this.handleFocusDay(e)}}]),r}(de.default.Component),Lr=function(e){wt(r,e);var t=St(r);function r(){var e;ht(this,r);for(var a=arguments.length,n=new Array(a),o=0;o=6,i=!r&&!e.isWeekInMonth(o);if(s||i){if(!e.props.peekNextMonth)break;n=!0}}return t})),Dt(bt(e),\"onMonthClick\",(function(t,r){e.handleDayClick(Bt(qe.default(e.props.day,r)),t)})),Dt(bt(e),\"handleMonthNavigation\",(function(t,r){e.isDisabled(r)||e.isExcluded(r)||(e.props.setPreSelection(r),e.MONTH_REFS[t].current&&e.MONTH_REFS[t].current.focus())})),Dt(bt(e),\"onMonthKeyDown\",(function(t,r){var a=t.key;if(!e.props.disabledKeyboardNavigation)switch(a){case\"Enter\":e.onMonthClick(t,r),e.props.setPreSelection(e.props.selected);break;case\"ArrowRight\":e.handleMonthNavigation(11===r?0:r+1,we.default(e.props.preSelection,1));break;case\"ArrowLeft\":e.handleMonthNavigation(0===r?11:r-1,Ce.default(e.props.preSelection,1))}})),Dt(bt(e),\"onQuarterClick\",(function(t,r){e.handleDayClick(Ht(Ae.default(e.props.day,r)),t)})),Dt(bt(e),\"getMonthClassNames\",(function(t){var r=e.props,a=r.day,n=r.startDate,o=r.endDate,s=r.selected,i=r.minDate,p=r.maxDate,l=r.preSelection,d=r.monthClassName,c=d?d(a):void 0;return ce.default(\"react-datepicker__month-text\",\"react-datepicker__month-\".concat(t),c,{\"react-datepicker__month--disabled\":(i||p)&&nr(qe.default(a,t),e.props),\"react-datepicker__month--selected\":Ye.default(a)===t&&Te.default(a)===Te.default(s),\"react-datepicker__month-text--keyboard-selected\":Ye.default(l)===t,\"react-datepicker__month--in-range\":or(n,o,t,a),\"react-datepicker__month--range-start\":e.isRangeStartMonth(t),\"react-datepicker__month--range-end\":e.isRangeEndMonth(t)})})),Dt(bt(e),\"getTabIndex\",(function(t){var r=Ye.default(e.props.preSelection);return e.props.disabledKeyboardNavigation||t!==r?\"-1\":\"0\"})),Dt(bt(e),\"getAriaLabel\",(function(t){var r=e.props,a=r.ariaLabelPrefix,n=void 0===a?\"Choose\":a,o=r.disabledDayAriaLabelPrefix,s=void 0===o?\"Not available\":o,i=r.day,p=qe.default(i,t),l=e.isDisabled(p)||e.isExcluded(p)?s:n;return\"\".concat(l,\" \").concat(Lt(p,\"MMMM yyyy\"))})),Dt(bt(e),\"getQuarterClassNames\",(function(t){var r=e.props,a=r.day,n=r.startDate,o=r.endDate,s=r.selected,i=r.minDate,p=r.maxDate;return ce.default(\"react-datepicker__quarter-text\",\"react-datepicker__quarter-\".concat(t),{\"react-datepicker__quarter--disabled\":(i||p)&&sr(Ae.default(a,t),e.props),\"react-datepicker__quarter--selected\":xe.default(a)===t&&Te.default(a)===Te.default(s),\"react-datepicker__quarter--in-range\":pr(n,o,t,a),\"react-datepicker__quarter--range-start\":e.isRangeStartQuarter(t),\"react-datepicker__quarter--range-end\":e.isRangeEndQuarter(t)})})),Dt(bt(e),\"renderMonths\",(function(){var t=e.props,r=t.showFullMonthYearPicker,a=t.showTwoColumnMonthYearPicker,n=t.showFourColumnMonthYearPicker,o=t.locale;return(n?[[0,1,2,3],[4,5,6,7],[8,9,10,11]]:a?[[0,1],[2,3],[4,5],[6,7],[8,9],[10,11]]:[[0,1,2],[3,4,5],[6,7,8],[9,10,11]]).map((function(t,a){return de.default.createElement(\"div\",{className:\"react-datepicker__month-wrapper\",key:a},t.map((function(t,a){return de.default.createElement(\"div\",{ref:e.MONTH_REFS[t],key:a,onClick:function(r){e.onMonthClick(r,t)},onKeyDown:function(r){e.onMonthKeyDown(r,t)},tabIndex:e.getTabIndex(t),className:e.getMonthClassNames(t),role:\"button\",\"aria-label\":e.getAriaLabel(t)},r?Zt(t,o):er(t,o))})))}))})),Dt(bt(e),\"renderQuarters\",(function(){return de.default.createElement(\"div\",{className:\"react-datepicker__quarter-wrapper\"},[1,2,3,4].map((function(t,r){return de.default.createElement(\"div\",{key:r,onClick:function(r){e.onQuarterClick(r,t)},className:e.getQuarterClassNames(t)},tr(t,e.props.locale))})))})),Dt(bt(e),\"getClassNames\",(function(){var t=e.props;t.day;var r=t.selectingDate,a=t.selectsStart,n=t.selectsEnd,o=t.showMonthYearPicker,s=t.showQuarterYearPicker;return ce.default(\"react-datepicker__month\",{\"react-datepicker__month--selecting-range\":r&&(a||n)},{\"react-datepicker__monthPicker\":o},{\"react-datepicker__quarterPicker\":s})})),e}return yt(r,[{key:\"render\",value:function(){var e=this.props,t=e.showMonthYearPicker,r=e.showQuarterYearPicker,a=e.day,n=e.ariaLabelPrefix,o=void 0===n?\"month \":n;return de.default.createElement(\"div\",{className:this.getClassNames(),onMouseLeave:this.handleMouseLeave,\"aria-label\":\"\".concat(o,\" \").concat(Lt(a,\"yyyy-MM\"))},t?this.renderMonths():r?this.renderQuarters():this.renderWeeks())}}]),r}(de.default.Component),qr=function(e){wt(r,e);var t=St(r);function r(){var e;ht(this,r);for(var a=arguments.length,n=new Array(a),o=0;o0&&void 0!==arguments[0]?arguments[0]:{}).className||\"\").split(/\\s+/);return Br.some((function(t){return e.indexOf(t)>=0}))})(e.target)&&a.props.onDropdownFocus()})),Dt(bt(a),\"getDateInView\",(function(){var e=a.props,t=e.preSelection,r=e.selected,n=e.openToDate,o=Dr(a.props),s=vr(a.props),i=xt(),p=n||r||t;return p||(o&&nt.default(i,o)?o:s&&at.default(i,s)?s:i)})),Dt(bt(a),\"increaseMonth\",(function(){a.setState((function(e){var t=e.date;return{date:we.default(t,1)}}),(function(){return a.handleMonthChange(a.state.date)}))})),Dt(bt(a),\"decreaseMonth\",(function(){a.setState((function(e){var t=e.date;return{date:Ce.default(t,1)}}),(function(){return a.handleMonthChange(a.state.date)}))})),Dt(bt(a),\"handleDayClick\",(function(e,t,r){a.props.onSelect(e,t,r),a.props.setPreSelection&&a.props.setPreSelection(e)})),Dt(bt(a),\"handleDayMouseEnter\",(function(e){a.setState({selectingDate:e}),a.props.onDayMouseEnter&&a.props.onDayMouseEnter(e)})),Dt(bt(a),\"handleMonthMouseLeave\",(function(){a.setState({selectingDate:null}),a.props.onMonthMouseLeave&&a.props.onMonthMouseLeave()})),Dt(bt(a),\"handleYearChange\",(function(e){a.props.onYearChange&&a.props.onYearChange(e),a.props.adjustDateOnChange&&(a.props.onSelect&&a.props.onSelect(e),a.props.setOpen&&a.props.setOpen(!0)),a.props.setPreSelection&&a.props.setPreSelection(e)})),Dt(bt(a),\"handleMonthChange\",(function(e){a.props.onMonthChange&&a.props.onMonthChange(e),a.props.adjustDateOnChange&&(a.props.onSelect&&a.props.onSelect(e),a.props.setOpen&&a.props.setOpen(!0)),a.props.setPreSelection&&a.props.setPreSelection(e)})),Dt(bt(a),\"handleMonthYearChange\",(function(e){a.handleYearChange(e),a.handleMonthChange(e)})),Dt(bt(a),\"changeYear\",(function(e){a.setState((function(t){var r=t.date;return{date:We.default(r,e)}}),(function(){return a.handleYearChange(a.state.date)}))})),Dt(bt(a),\"changeMonth\",(function(e){a.setState((function(t){var r=t.date;return{date:qe.default(r,e)}}),(function(){return a.handleMonthChange(a.state.date)}))})),Dt(bt(a),\"changeMonthYear\",(function(e){a.setState((function(t){var r=t.date;return{date:We.default(qe.default(r,Ye.default(e)),Te.default(e))}}),(function(){return a.handleMonthYearChange(a.state.date)}))})),Dt(bt(a),\"header\",(function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:a.state.date,t=Kt(e,a.props.locale,a.props.calendarStartDay),r=[];return a.props.showWeekNumbers&&r.push(de.default.createElement(\"div\",{key:\"W\",className:\"react-datepicker__day-name\"},a.props.weekLabel||\"#\")),r.concat([0,1,2,3,4,5,6].map((function(e){var r=De.default(t,e),n=a.formatWeekday(r,a.props.locale),o=a.props.weekDayClassName?a.props.weekDayClassName(r):void 0;return de.default.createElement(\"div\",{key:e,className:ce.default(\"react-datepicker__day-name\",o)},n)})))})),Dt(bt(a),\"formatWeekday\",(function(e,t){return a.props.formatWeekDay?function(e,t,r){return t(Lt(e,\"EEEE\",r))}(e,a.props.formatWeekDay,t):a.props.useWeekdaysShort?function(e,t){return Lt(e,\"EEE\",t)}(e,t):function(e,t){return Lt(e,\"EEEEEE\",t)}(e,t)})),Dt(bt(a),\"decreaseYear\",(function(){a.setState((function(e){var t=e.date;return{date:Se.default(t,a.props.showYearPicker?a.props.yearItemNumber:1)}}),(function(){return a.handleYearChange(a.state.date)}))})),Dt(bt(a),\"renderPreviousButton\",(function(){if(!a.props.renderCustomHeader){var e;switch(!0){case a.props.showMonthYearPicker:e=mr(a.state.date,a.props);break;case a.props.showYearPicker:e=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.minDate,a=t.yearItemNumber,n=void 0===a?Ot:a,o=br(jt(Se.default(e,n)),n).endPeriod,s=r&&Te.default(r);return s&&s>o||!1}(a.state.date,a.props);break;default:e=fr(a.state.date,a.props)}if((a.props.forceShowMonthNavigation||a.props.showDisabledMonthNavigation||!e)&&!a.props.showTimeSelectOnly){var t=[\"react-datepicker__navigation\",\"react-datepicker__navigation--previous\"],r=a.decreaseMonth;(a.props.showMonthYearPicker||a.props.showQuarterYearPicker||a.props.showYearPicker)&&(r=a.decreaseYear),e&&a.props.showDisabledMonthNavigation&&(t.push(\"react-datepicker__navigation--previous--disabled\"),r=null);var n=a.props.showMonthYearPicker||a.props.showQuarterYearPicker||a.props.showYearPicker,o=a.props,s=o.previousMonthAriaLabel,i=void 0===s?\"Previous Month\":s,p=o.previousYearAriaLabel,l=void 0===p?\"Previous Year\":p;return de.default.createElement(\"button\",{type:\"button\",className:t.join(\" \"),onClick:r,onKeyDown:a.props.handleOnKeyDown,\"aria-label\":n?l:i},de.default.createElement(\"span\",{className:[\"react-datepicker__navigation-icon\",\"react-datepicker__navigation-icon--previous\"].join(\" \")},n?a.props.previousYearButtonLabel:a.props.previousMonthButtonLabel))}}})),Dt(bt(a),\"increaseYear\",(function(){a.setState((function(e){var t=e.date;return{date:ge.default(t,a.props.showYearPicker?a.props.yearItemNumber:1)}}),(function(){return a.handleYearChange(a.state.date)}))})),Dt(bt(a),\"renderNextButton\",(function(){if(!a.props.renderCustomHeader){var e;switch(!0){case a.props.showMonthYearPicker:e=yr(a.state.date,a.props);break;case a.props.showYearPicker:e=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.maxDate,a=t.yearItemNumber,n=void 0===a?Ot:a,o=br(ge.default(e,n),n).startPeriod,s=r&&Te.default(r);return s&&s0&&void 0!==arguments[0]?arguments[0]:a.state.date,t=[\"react-datepicker__current-month\"];return a.props.showYearDropdown&&t.push(\"react-datepicker__current-month--hasYearDropdown\"),a.props.showMonthDropdown&&t.push(\"react-datepicker__current-month--hasMonthDropdown\"),a.props.showMonthYearDropdown&&t.push(\"react-datepicker__current-month--hasMonthYearDropdown\"),de.default.createElement(\"div\",{className:t.join(\" \")},Lt(e,a.props.dateFormat,a.props.locale))})),Dt(bt(a),\"renderYearDropdown\",(function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];if(a.props.showYearDropdown&&!e)return de.default.createElement(Mr,{adjustDateOnChange:a.props.adjustDateOnChange,date:a.state.date,onSelect:a.props.onSelect,setOpen:a.props.setOpen,dropdownMode:a.props.dropdownMode,onChange:a.changeYear,minDate:a.props.minDate,maxDate:a.props.maxDate,year:Te.default(a.state.date),scrollableYearDropdown:a.props.scrollableYearDropdown,yearDropdownItemNumber:a.props.yearDropdownItemNumber})})),Dt(bt(a),\"renderMonthDropdown\",(function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];if(a.props.showMonthDropdown&&!e)return de.default.createElement(Nr,{dropdownMode:a.props.dropdownMode,locale:a.props.locale,onChange:a.changeMonth,month:Ye.default(a.state.date),useShortMonthInDropdown:a.props.useShortMonthInDropdown})})),Dt(bt(a),\"renderMonthYearDropdown\",(function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];if(a.props.showMonthYearDropdown&&!e)return de.default.createElement(Tr,{dropdownMode:a.props.dropdownMode,locale:a.props.locale,dateFormat:a.props.dateFormat,onChange:a.changeMonthYear,minDate:a.props.minDate,maxDate:a.props.maxDate,date:a.state.date,scrollableMonthYearDropdown:a.props.scrollableMonthYearDropdown})})),Dt(bt(a),\"renderTodayButton\",(function(){if(a.props.todayButton&&!a.props.showTimeSelectOnly)return de.default.createElement(\"div\",{className:\"react-datepicker__today-button\",onClick:function(e){return a.props.onSelect(Ve.default(xt()),e)}},a.props.todayButton)})),Dt(bt(a),\"renderDefaultHeader\",(function(e){var t=e.monthDate,r=e.i;return de.default.createElement(\"div\",{className:\"react-datepicker__header \".concat(a.props.showTimeSelect?\"react-datepicker__header--has-time-select\":\"\")},a.renderCurrentMonth(t),de.default.createElement(\"div\",{className:\"react-datepicker__header__dropdown react-datepicker__header__dropdown--\".concat(a.props.dropdownMode),onFocus:a.handleDropdownFocus},a.renderMonthDropdown(0!==r),a.renderMonthYearDropdown(0!==r),a.renderYearDropdown(0!==r)),de.default.createElement(\"div\",{className:\"react-datepicker__day-names\"},a.header(t)))})),Dt(bt(a),\"renderCustomHeader\",(function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.monthDate,r=e.i;if(a.props.showTimeSelect&&!a.state.monthContainer||a.props.showTimeSelectOnly)return null;var n=fr(a.state.date,a.props),o=hr(a.state.date,a.props),s=mr(a.state.date,a.props),i=yr(a.state.date,a.props),p=!a.props.showMonthYearPicker&&!a.props.showQuarterYearPicker&&!a.props.showYearPicker;return de.default.createElement(\"div\",{className:\"react-datepicker__header react-datepicker__header--custom\",onFocus:a.props.onDropdownFocus},a.props.renderCustomHeader(ut(ut({},a.state),{},{customHeaderCount:r,monthDate:t,changeMonth:a.changeMonth,changeYear:a.changeYear,decreaseMonth:a.decreaseMonth,increaseMonth:a.increaseMonth,decreaseYear:a.decreaseYear,increaseYear:a.increaseYear,prevMonthButtonDisabled:n,nextMonthButtonDisabled:o,prevYearButtonDisabled:s,nextYearButtonDisabled:i})),p&&de.default.createElement(\"div\",{className:\"react-datepicker__day-names\"},a.header(t)))})),Dt(bt(a),\"renderYearHeader\",(function(){var e=a.state.date,t=a.props,r=t.showYearPicker,n=br(e,t.yearItemNumber),o=n.startPeriod,s=n.endPeriod;return de.default.createElement(\"div\",{className:\"react-datepicker__header react-datepicker-year-header\"},r?\"\".concat(o,\" - \").concat(s):Te.default(e))})),Dt(bt(a),\"renderHeader\",(function(e){switch(!0){case void 0!==a.props.renderCustomHeader:return a.renderCustomHeader(e);case a.props.showMonthYearPicker||a.props.showQuarterYearPicker||a.props.showYearPicker:return a.renderYearHeader(e);default:return a.renderDefaultHeader(e)}})),Dt(bt(a),\"renderMonths\",(function(){if(!a.props.showTimeSelectOnly&&!a.props.showYearPicker){for(var e=[],t=a.props.showPreviousMonths?a.props.monthsShown-1:0,r=Ce.default(a.state.date,t),n=0;n0;e.push(de.default.createElement(\"div\",{key:i,ref:function(e){a.monthContainer=e},className:\"react-datepicker__month-container\"},a.renderHeader({monthDate:s,i:n}),de.default.createElement(Rr,{chooseDayAriaLabelPrefix:a.props.chooseDayAriaLabelPrefix,disabledDayAriaLabelPrefix:a.props.disabledDayAriaLabelPrefix,weekAriaLabelPrefix:a.props.weekAriaLabelPrefix,onChange:a.changeMonthYear,day:s,dayClassName:a.props.dayClassName,calendarStartDay:a.props.calendarStartDay,monthClassName:a.props.monthClassName,onDayClick:a.handleDayClick,handleOnKeyDown:a.props.handleOnDayKeyDown,onDayMouseEnter:a.handleDayMouseEnter,onMouseLeave:a.handleMonthMouseLeave,onWeekSelect:a.props.onWeekSelect,orderInDisplay:n,formatWeekNumber:a.props.formatWeekNumber,locale:a.props.locale,minDate:a.props.minDate,maxDate:a.props.maxDate,excludeDates:a.props.excludeDates,highlightDates:a.props.highlightDates,selectingDate:a.state.selectingDate,includeDates:a.props.includeDates,inline:a.props.inline,shouldFocusDayInline:a.props.shouldFocusDayInline,fixedHeight:a.props.fixedHeight,filterDate:a.props.filterDate,preSelection:a.props.preSelection,setPreSelection:a.props.setPreSelection,selected:a.props.selected,selectsStart:a.props.selectsStart,selectsEnd:a.props.selectsEnd,selectsRange:a.props.selectsRange,showWeekNumbers:a.props.showWeekNumbers,startDate:a.props.startDate,endDate:a.props.endDate,peekNextMonth:a.props.peekNextMonth,setOpen:a.props.setOpen,shouldCloseOnSelect:a.props.shouldCloseOnSelect,renderDayContents:a.props.renderDayContents,disabledKeyboardNavigation:a.props.disabledKeyboardNavigation,showMonthYearPicker:a.props.showMonthYearPicker,showFullMonthYearPicker:a.props.showFullMonthYearPicker,showTwoColumnMonthYearPicker:a.props.showTwoColumnMonthYearPicker,showFourColumnMonthYearPicker:a.props.showFourColumnMonthYearPicker,showYearPicker:a.props.showYearPicker,showQuarterYearPicker:a.props.showQuarterYearPicker,isInputFocused:a.props.isInputFocused,containerRef:a.containerRef,monthShowsDuplicateDaysEnd:p,monthShowsDuplicateDaysStart:l})))}return e}})),Dt(bt(a),\"renderYears\",(function(){if(!a.props.showTimeSelectOnly)return a.props.showYearPicker?de.default.createElement(\"div\",{className:\"react-datepicker__year--container\"},a.renderHeader(),de.default.createElement(Ar,vt({onDayClick:a.handleDayClick,date:a.state.date},a.props))):void 0})),Dt(bt(a),\"renderTimeSection\",(function(){if(a.props.showTimeSelect&&(a.state.monthContainer||a.props.showTimeSelectOnly))return de.default.createElement(qr,{selected:a.props.selected,openToDate:a.props.openToDate,onChange:a.props.onTimeChange,timeClassName:a.props.timeClassName,format:a.props.timeFormat,includeTimes:a.props.includeTimes,intervals:a.props.timeIntervals,minTime:a.props.minTime,maxTime:a.props.maxTime,excludeTimes:a.props.excludeTimes,filterTime:a.props.filterTime,timeCaption:a.props.timeCaption,todayButton:a.props.todayButton,showMonthDropdown:a.props.showMonthDropdown,showMonthYearDropdown:a.props.showMonthYearDropdown,showYearDropdown:a.props.showYearDropdown,withPortal:a.props.withPortal,monthRef:a.state.monthContainer,injectTimes:a.props.injectTimes,locale:a.props.locale,handleOnKeyDown:a.props.handleOnKeyDown,showTimeSelectOnly:a.props.showTimeSelectOnly})})),Dt(bt(a),\"renderInputTimeSection\",(function(){var e=new Date(a.props.selected),t=It(e)&&Boolean(a.props.selected)?\"\".concat(kr(e.getHours()),\":\").concat(kr(e.getMinutes())):\"\";if(a.props.showTimeInput)return de.default.createElement(Wr,{date:e,timeString:t,timeInputLabel:a.props.timeInputLabel,onChange:a.props.onTimeChange,customTimeInput:a.props.customTimeInput})})),a.containerRef=de.default.createRef(),a.state={date:a.getDateInView(),selectingDate:null,monthContainer:null},a}return yt(r,[{key:\"componentDidMount\",value:function(){var e=this;this.props.showTimeSelect&&(this.assignMonthContainer=void e.setState({monthContainer:e.monthContainer}))}},{key:\"componentDidUpdate\",value:function(e){this.props.preSelection&&!$t(this.props.preSelection,e.preSelection)?this.setState({date:this.props.preSelection}):this.props.openToDate&&!$t(this.props.openToDate,e.openToDate)&&this.setState({date:this.props.openToDate})}},{key:\"render\",value:function(){var e=this.props.container||Kr;return de.default.createElement(\"div\",{ref:this.containerRef},de.default.createElement(e,{className:ce.default(\"react-datepicker\",this.props.className,{\"react-datepicker--time-only\":this.props.showTimeSelectOnly}),showPopperArrow:this.props.showPopperArrow,arrowProps:this.props.arrowProps},this.renderPreviousButton(),this.renderNextButton(),this.renderMonths(),this.renderYears(),this.renderTodayButton(),this.renderTimeSection(),this.renderInputTimeSection(),this.props.children))}}],[{key:\"defaultProps\",get:function(){return{onDropdownFocus:function(){},monthsShown:1,monthSelectedIn:0,forceShowMonthNavigation:!1,timeCaption:\"Time\",previousYearButtonLabel:\"Previous Year\",nextYearButtonLabel:\"Next Year\",previousMonthButtonLabel:\"Previous Month\",nextMonthButtonLabel:\"Next Month\",customTimeInput:null,yearItemNumber:Ot}}}]),r}(de.default.Component),Hr=function(e){wt(r,e);var t=St(r);function r(e){var a;return ht(this,r),(a=t.call(this,e)).el=document.createElement(\"div\"),a}return yt(r,[{key:\"componentDidMount\",value:function(){this.portalRoot=document.getElementById(this.props.portalId),this.portalRoot||(this.portalRoot=document.createElement(\"div\"),this.portalRoot.setAttribute(\"id\",this.props.portalId),document.body.appendChild(this.portalRoot)),this.portalRoot.appendChild(this.el)}},{key:\"componentWillUnmount\",value:function(){this.portalRoot.removeChild(this.el)}},{key:\"render\",value:function(){return dt.default.createPortal(this.props.children,this.el)}}]),r}(de.default.Component),Qr=function(e){return!e.disabled&&-1!==e.tabIndex},Vr=function(e){wt(r,e);var t=St(r);function r(e){var a;return ht(this,r),Dt(bt(a=t.call(this,e)),\"getTabChildren\",(function(){return Array.prototype.slice.call(a.tabLoopRef.current.querySelectorAll(\"[tabindex], a, button, input, select, textarea\"),1,-1).filter(Qr)})),Dt(bt(a),\"handleFocusStart\",(function(e){var t=a.getTabChildren();t&&t.length>1&&t[t.length-1].focus()})),Dt(bt(a),\"handleFocusEnd\",(function(e){var t=a.getTabChildren();t&&t.length>1&&t[0].focus()})),a.tabLoopRef=de.default.createRef(),a}return yt(r,[{key:\"render\",value:function(){return this.props.enableTabLoop?de.default.createElement(\"div\",{className:\"react-datepicker__tab-loop\",ref:this.tabLoopRef},de.default.createElement(\"div\",{className:\"react-datepicker__tab-loop__start\",tabIndex:\"0\",onFocus:this.handleFocusStart}),this.props.children,de.default.createElement(\"div\",{className:\"react-datepicker__tab-loop__end\",tabIndex:\"0\",onFocus:this.handleFocusEnd})):this.props.children}}],[{key:\"defaultProps\",get:function(){return{enableTabLoop:!0}}}]),r}(de.default.Component),Ur=function(e){wt(r,e);var t=St(r);function r(){return ht(this,r),t.apply(this,arguments)}return yt(r,[{key:\"render\",value:function(){var e,t=this.props,r=t.className,a=t.wrapperClassName,n=t.hidePopper,o=t.popperComponent,s=t.popperModifiers,i=t.popperPlacement,p=t.popperProps,l=t.targetComponent,d=t.enableTabLoop,c=t.popperOnKeyDown,u=t.portalId;if(!n){var f=ce.default(\"react-datepicker-popper\",r);e=de.default.createElement(pe.Popper,vt({modifiers:s,placement:i},p),(function(e){var t=e.ref,r=e.style,a=e.placement,n=e.arrowProps;return de.default.createElement(Vr,{enableTabLoop:d},de.default.createElement(\"div\",{ref:t,style:r,className:f,\"data-placement\":a,onKeyDown:c},de.default.cloneElement(o,{arrowProps:n})))}))}this.props.popperContainer&&(e=de.default.createElement(this.props.popperContainer,{},e)),u&&!n&&(e=de.default.createElement(Hr,{portalId:u},e));var h=ce.default(\"react-datepicker-wrapper\",a);return de.default.createElement(pe.Manager,{className:\"react-datepicker-manager\"},de.default.createElement(pe.Reference,null,(function(e){var t=e.ref;return de.default.createElement(\"div\",{ref:t,className:h},l)})),e)}}],[{key:\"defaultProps\",get:function(){return{hidePopper:!0,popperModifiers:[],popperProps:{},popperPlacement:\"bottom-start\"}}}]),r}(de.default.Component),$r=\"react-datepicker-ignore-onclickoutside\",zr=lt.default(jr);var Gr=\"Date input not valid.\",Jr=function(e){wt(r,e);var t=St(r);function r(e){var a;return ht(this,r),Dt(bt(a=t.call(this,e)),\"getPreSelection\",(function(){return a.props.openToDate?a.props.openToDate:a.props.selectsEnd&&a.props.startDate?a.props.startDate:a.props.selectsStart&&a.props.endDate?a.props.endDate:xt()})),Dt(bt(a),\"calcInitialState\",(function(){var e,t=a.getPreSelection(),r=Dr(a.props),n=vr(a.props),o=r&&nt.default(t,Ve.default(r))?r:n&&at.default(t,Je.default(n))?n:t;return{open:a.props.startOpen||!1,preventFocus:!1,preSelection:null!==(e=a.props.selectsRange?a.props.startDate:a.props.selected)&&void 0!==e?e:o,highlightDates:wr(a.props.highlightDates),focused:!1,shouldFocusDayInline:!1}})),Dt(bt(a),\"clearPreventFocusTimeout\",(function(){a.preventFocusTimeout&&clearTimeout(a.preventFocusTimeout)})),Dt(bt(a),\"setFocus\",(function(){a.input&&a.input.focus&&a.input.focus({preventScroll:!0})})),Dt(bt(a),\"setBlur\",(function(){a.input&&a.input.blur&&a.input.blur(),a.cancelFocusInput()})),Dt(bt(a),\"setOpen\",(function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];a.setState({open:e,preSelection:e&&a.state.open?a.state.preSelection:a.calcInitialState().preSelection,lastPreSelectChange:Zr},(function(){e||a.setState((function(e){return{focused:!!t&&e.focused}}),(function(){!t&&a.setBlur(),a.setState({inputValue:null})}))}))})),Dt(bt(a),\"inputOk\",(function(){return ue.default(a.state.preSelection)})),Dt(bt(a),\"isCalendarOpen\",(function(){return void 0===a.props.open?a.state.open&&!a.props.disabled&&!a.props.readOnly:a.props.open})),Dt(bt(a),\"handleFocus\",(function(e){a.state.preventFocus||(a.props.onFocus(e),a.props.preventOpenOnFocus||a.props.readOnly||a.setOpen(!0)),a.setState({focused:!0})})),Dt(bt(a),\"cancelFocusInput\",(function(){clearTimeout(a.inputFocusTimeout),a.inputFocusTimeout=null})),Dt(bt(a),\"deferFocusInput\",(function(){a.cancelFocusInput(),a.inputFocusTimeout=setTimeout((function(){return a.setFocus()}),1)})),Dt(bt(a),\"handleDropdownFocus\",(function(){a.cancelFocusInput()})),Dt(bt(a),\"handleBlur\",(function(e){(!a.state.open||a.props.withPortal||a.props.showTimeInput)&&a.props.onBlur(e),a.setState({focused:!1})})),Dt(bt(a),\"handleCalendarClickOutside\",(function(e){a.props.inline||a.setOpen(!1),a.props.onClickOutside(e),a.props.withPortal&&e.preventDefault()})),Dt(bt(a),\"handleChange\",(function(){for(var e=arguments.length,t=new Array(e),r=0;r &&` helpers in initial condition allow es6 code\n // to co-exist with es5.\n // 2. Replace `for of` with es5 compliant iteration using `for`.\n // Basically, take:\n //\n // ```js\n // for (i of a.entries())\n // if (!b.has(i[0])) return false;\n // ```\n //\n // ... and convert to:\n //\n // ```js\n // it = a.entries();\n // while (!(i = it.next()).done)\n // if (!b.has(i.value[0])) return false;\n // ```\n //\n // **Note**: `i` access switches to `i.value`.\n var it;\n if (hasMap && (a instanceof Map) && (b instanceof Map)) {\n if (a.size !== b.size) return false;\n it = a.entries();\n while (!(i = it.next()).done)\n if (!b.has(i.value[0])) return false;\n it = a.entries();\n while (!(i = it.next()).done)\n if (!equal(i.value[1], b.get(i.value[0]))) return false;\n return true;\n }\n\n if (hasSet && (a instanceof Set) && (b instanceof Set)) {\n if (a.size !== b.size) return false;\n it = a.entries();\n while (!(i = it.next()).done)\n if (!b.has(i.value[0])) return false;\n return true;\n }\n // END: Modifications\n\n if (hasArrayBuffer && ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {\n length = a.length;\n if (length != b.length) return false;\n for (i = length; i-- !== 0;)\n if (a[i] !== b[i]) return false;\n return true;\n }\n\n if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;\n if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();\n if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();\n\n keys = Object.keys(a);\n length = keys.length;\n if (length !== Object.keys(b).length) return false;\n\n for (i = length; i-- !== 0;)\n if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;\n // END: fast-deep-equal\n\n // START: react-fast-compare\n // custom handling for DOM elements\n if (hasElementType && a instanceof Element) return false;\n\n // custom handling for React/Preact\n for (i = length; i-- !== 0;) {\n if ((keys[i] === '_owner' || keys[i] === '__v' || keys[i] === '__o') && a.$$typeof) {\n // React-specific: avoid traversing React elements' _owner\n // Preact-specific: avoid traversing Preact elements' __v and __o\n // __v = $_original / $_vnode\n // __o = $_owner\n // These properties contain circular references and are not needed when\n // comparing the actual elements (and not their owners)\n // .$$typeof and ._store on just reasonable markers of elements\n\n continue;\n }\n\n // all other properties should be traversed as usual\n if (!equal(a[keys[i]], b[keys[i]])) return false;\n }\n // END: react-fast-compare\n\n // START: fast-deep-equal\n return true;\n }\n\n return a !== a && b !== b;\n}\n// end fast-deep-equal\n\nmodule.exports = function isEqual(a, b) {\n try {\n return equal(a, b);\n } catch (error) {\n if (((error.message || '').match(/stack|recursion/i))) {\n // warn on circular references, don't crash\n // browsers give this different errors name and messages:\n // chrome/safari: \"RangeError\", \"Maximum call stack size exceeded\"\n // firefox: \"InternalError\", too much recursion\"\n // edge: \"Error\", \"Out of stack space\"\n console.warn('react-fast-compare cannot handle circular refs');\n return false;\n }\n // some other error. we should definitely know about these\n throw error;\n }\n};\n","/**\n * Copyright (c) 2014-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';\n\n/**\n * Similar to invariant but only logs a warning if the condition is not met.\n * This can be used to log issues in development environments in critical\n * paths. Removing the logging code for production environments will keep the\n * same logic and follow the same code paths.\n */\n\nvar __DEV__ = process.env.NODE_ENV !== 'production';\n\nvar warning = function() {};\n\nif (__DEV__) {\n var printWarning = function printWarning(format, args) {\n var len = arguments.length;\n args = new Array(len > 1 ? len - 1 : 0);\n for (var key = 1; key < len; key++) {\n args[key - 1] = arguments[key];\n }\n var argIndex = 0;\n var message = 'Warning: ' +\n format.replace(/%s/g, function() {\n return args[argIndex++];\n });\n if (typeof console !== 'undefined') {\n console.error(message);\n }\n try {\n // --- Welcome to debugging React ---\n // This error was thrown as a convenience so that you can use this stack\n // to find the callsite that caused this warning to fire.\n throw new Error(message);\n } catch (x) {}\n }\n\n warning = function(condition, format, args) {\n var len = arguments.length;\n args = new Array(len > 2 ? len - 2 : 0);\n for (var key = 2; key < len; key++) {\n args[key - 2] = arguments[key];\n }\n if (format === undefined) {\n throw new Error(\n '`warning(condition, format, ...args)` requires a warning ' +\n 'message argument'\n );\n }\n if (!condition) {\n printWarning.apply(null, [format].concat(args));\n }\n };\n}\n\nmodule.exports = warning;\n","import toInteger from \"../toInteger/index.js\";\nimport toDate from \"../../toDate/index.js\";\nimport requiredArgs from \"../requiredArgs/index.js\"; // This function will be a part of public API when UTC function will be implemented.\n// See issue: https://github.com/date-fns/date-fns/issues/376\n\nexport default function setUTCDay(dirtyDate, dirtyDay, dirtyOptions) {\n requiredArgs(2, arguments);\n var options = dirtyOptions || {};\n var locale = options.locale;\n var localeWeekStartsOn = locale && locale.options && locale.options.weekStartsOn;\n var defaultWeekStartsOn = localeWeekStartsOn == null ? 0 : toInteger(localeWeekStartsOn);\n var weekStartsOn = options.weekStartsOn == null ? defaultWeekStartsOn : toInteger(options.weekStartsOn); // Test if weekStartsOn is between 0 and 6 _and_ is not NaN\n\n if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) {\n throw new RangeError('weekStartsOn must be between 0 and 6 inclusively');\n }\n\n var date = toDate(dirtyDate);\n var day = toInteger(dirtyDay);\n var currentDay = date.getUTCDay();\n var remainder = day % 7;\n var dayIndex = (remainder + 7) % 7;\n var diff = (dayIndex < weekStartsOn ? 7 : 0) + day - currentDay;\n date.setUTCDate(date.getUTCDate() + diff);\n return date;\n}","import getUTCWeekYear from \"../../../_lib/getUTCWeekYear/index.js\";\nimport setUTCDay from \"../../../_lib/setUTCDay/index.js\";\nimport setUTCISODay from \"../../../_lib/setUTCISODay/index.js\";\nimport setUTCISOWeek from \"../../../_lib/setUTCISOWeek/index.js\";\nimport setUTCWeek from \"../../../_lib/setUTCWeek/index.js\";\nimport startOfUTCISOWeek from \"../../../_lib/startOfUTCISOWeek/index.js\";\nimport startOfUTCWeek from \"../../../_lib/startOfUTCWeek/index.js\";\nvar MILLISECONDS_IN_HOUR = 3600000;\nvar MILLISECONDS_IN_MINUTE = 60000;\nvar MILLISECONDS_IN_SECOND = 1000;\nvar numericPatterns = {\n month: /^(1[0-2]|0?\\d)/,\n // 0 to 12\n date: /^(3[0-1]|[0-2]?\\d)/,\n // 0 to 31\n dayOfYear: /^(36[0-6]|3[0-5]\\d|[0-2]?\\d?\\d)/,\n // 0 to 366\n week: /^(5[0-3]|[0-4]?\\d)/,\n // 0 to 53\n hour23h: /^(2[0-3]|[0-1]?\\d)/,\n // 0 to 23\n hour24h: /^(2[0-4]|[0-1]?\\d)/,\n // 0 to 24\n hour11h: /^(1[0-1]|0?\\d)/,\n // 0 to 11\n hour12h: /^(1[0-2]|0?\\d)/,\n // 0 to 12\n minute: /^[0-5]?\\d/,\n // 0 to 59\n second: /^[0-5]?\\d/,\n // 0 to 59\n singleDigit: /^\\d/,\n // 0 to 9\n twoDigits: /^\\d{1,2}/,\n // 0 to 99\n threeDigits: /^\\d{1,3}/,\n // 0 to 999\n fourDigits: /^\\d{1,4}/,\n // 0 to 9999\n anyDigitsSigned: /^-?\\d+/,\n singleDigitSigned: /^-?\\d/,\n // 0 to 9, -0 to -9\n twoDigitsSigned: /^-?\\d{1,2}/,\n // 0 to 99, -0 to -99\n threeDigitsSigned: /^-?\\d{1,3}/,\n // 0 to 999, -0 to -999\n fourDigitsSigned: /^-?\\d{1,4}/ // 0 to 9999, -0 to -9999\n\n};\nvar timezonePatterns = {\n basicOptionalMinutes: /^([+-])(\\d{2})(\\d{2})?|Z/,\n basic: /^([+-])(\\d{2})(\\d{2})|Z/,\n basicOptionalSeconds: /^([+-])(\\d{2})(\\d{2})((\\d{2}))?|Z/,\n extended: /^([+-])(\\d{2}):(\\d{2})|Z/,\n extendedOptionalSeconds: /^([+-])(\\d{2}):(\\d{2})(:(\\d{2}))?|Z/\n};\n\nfunction parseNumericPattern(pattern, string, valueCallback) {\n var matchResult = string.match(pattern);\n\n if (!matchResult) {\n return null;\n }\n\n var value = parseInt(matchResult[0], 10);\n return {\n value: valueCallback ? valueCallback(value) : value,\n rest: string.slice(matchResult[0].length)\n };\n}\n\nfunction parseTimezonePattern(pattern, string) {\n var matchResult = string.match(pattern);\n\n if (!matchResult) {\n return null;\n } // Input is 'Z'\n\n\n if (matchResult[0] === 'Z') {\n return {\n value: 0,\n rest: string.slice(1)\n };\n }\n\n var sign = matchResult[1] === '+' ? 1 : -1;\n var hours = matchResult[2] ? parseInt(matchResult[2], 10) : 0;\n var minutes = matchResult[3] ? parseInt(matchResult[3], 10) : 0;\n var seconds = matchResult[5] ? parseInt(matchResult[5], 10) : 0;\n return {\n value: sign * (hours * MILLISECONDS_IN_HOUR + minutes * MILLISECONDS_IN_MINUTE + seconds * MILLISECONDS_IN_SECOND),\n rest: string.slice(matchResult[0].length)\n };\n}\n\nfunction parseAnyDigitsSigned(string, valueCallback) {\n return parseNumericPattern(numericPatterns.anyDigitsSigned, string, valueCallback);\n}\n\nfunction parseNDigits(n, string, valueCallback) {\n switch (n) {\n case 1:\n return parseNumericPattern(numericPatterns.singleDigit, string, valueCallback);\n\n case 2:\n return parseNumericPattern(numericPatterns.twoDigits, string, valueCallback);\n\n case 3:\n return parseNumericPattern(numericPatterns.threeDigits, string, valueCallback);\n\n case 4:\n return parseNumericPattern(numericPatterns.fourDigits, string, valueCallback);\n\n default:\n return parseNumericPattern(new RegExp('^\\\\d{1,' + n + '}'), string, valueCallback);\n }\n}\n\nfunction parseNDigitsSigned(n, string, valueCallback) {\n switch (n) {\n case 1:\n return parseNumericPattern(numericPatterns.singleDigitSigned, string, valueCallback);\n\n case 2:\n return parseNumericPattern(numericPatterns.twoDigitsSigned, string, valueCallback);\n\n case 3:\n return parseNumericPattern(numericPatterns.threeDigitsSigned, string, valueCallback);\n\n case 4:\n return parseNumericPattern(numericPatterns.fourDigitsSigned, string, valueCallback);\n\n default:\n return parseNumericPattern(new RegExp('^-?\\\\d{1,' + n + '}'), string, valueCallback);\n }\n}\n\nfunction dayPeriodEnumToHours(enumValue) {\n switch (enumValue) {\n case 'morning':\n return 4;\n\n case 'evening':\n return 17;\n\n case 'pm':\n case 'noon':\n case 'afternoon':\n return 12;\n\n case 'am':\n case 'midnight':\n case 'night':\n default:\n return 0;\n }\n}\n\nfunction normalizeTwoDigitYear(twoDigitYear, currentYear) {\n var isCommonEra = currentYear > 0; // Absolute number of the current year:\n // 1 -> 1 AC\n // 0 -> 1 BC\n // -1 -> 2 BC\n\n var absCurrentYear = isCommonEra ? currentYear : 1 - currentYear;\n var result;\n\n if (absCurrentYear <= 50) {\n result = twoDigitYear || 100;\n } else {\n var rangeEnd = absCurrentYear + 50;\n var rangeEndCentury = Math.floor(rangeEnd / 100) * 100;\n var isPreviousCentury = twoDigitYear >= rangeEnd % 100;\n result = twoDigitYear + rangeEndCentury - (isPreviousCentury ? 100 : 0);\n }\n\n return isCommonEra ? result : 1 - result;\n}\n\nvar DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\nvar DAYS_IN_MONTH_LEAP_YEAR = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; // User for validation\n\nfunction isLeapYearIndex(year) {\n return year % 400 === 0 || year % 4 === 0 && year % 100 !== 0;\n}\n/*\n * | | Unit | | Unit |\n * |-----|--------------------------------|-----|--------------------------------|\n * | a | AM, PM | A* | Milliseconds in day |\n * | b | AM, PM, noon, midnight | B | Flexible day period |\n * | c | Stand-alone local day of week | C* | Localized hour w/ day period |\n * | d | Day of month | D | Day of year |\n * | e | Local day of week | E | Day of week |\n * | f | | F* | Day of week in month |\n * | g* | Modified Julian day | G | Era |\n * | h | Hour [1-12] | H | Hour [0-23] |\n * | i! | ISO day of week | I! | ISO week of year |\n * | j* | Localized hour w/ day period | J* | Localized hour w/o day period |\n * | k | Hour [1-24] | K | Hour [0-11] |\n * | l* | (deprecated) | L | Stand-alone month |\n * | m | Minute | M | Month |\n * | n | | N | |\n * | o! | Ordinal number modifier | O* | Timezone (GMT) |\n * | p | | P | |\n * | q | Stand-alone quarter | Q | Quarter |\n * | r* | Related Gregorian year | R! | ISO week-numbering year |\n * | s | Second | S | Fraction of second |\n * | t! | Seconds timestamp | T! | Milliseconds timestamp |\n * | u | Extended year | U* | Cyclic year |\n * | v* | Timezone (generic non-locat.) | V* | Timezone (location) |\n * | w | Local week of year | W* | Week of month |\n * | x | Timezone (ISO-8601 w/o Z) | X | Timezone (ISO-8601) |\n * | y | Year (abs) | Y | Local week-numbering year |\n * | z* | Timezone (specific non-locat.) | Z* | Timezone (aliases) |\n *\n * Letters marked by * are not implemented but reserved by Unicode standard.\n *\n * Letters marked by ! are non-standard, but implemented by date-fns:\n * - `o` modifies the previous token to turn it into an ordinal (see `parse` docs)\n * - `i` is ISO day of week. For `i` and `ii` is returns numeric ISO week days,\n * i.e. 7 for Sunday, 1 for Monday, etc.\n * - `I` is ISO week of year, as opposed to `w` which is local week of year.\n * - `R` is ISO week-numbering year, as opposed to `Y` which is local week-numbering year.\n * `R` is supposed to be used in conjunction with `I` and `i`\n * for universal ISO week-numbering date, whereas\n * `Y` is supposed to be used in conjunction with `w` and `e`\n * for week-numbering date specific to the locale.\n */\n\n\nvar parsers = {\n // Era\n G: {\n priority: 140,\n parse: function (string, token, match, _options) {\n switch (token) {\n // AD, BC\n case 'G':\n case 'GG':\n case 'GGG':\n return match.era(string, {\n width: 'abbreviated'\n }) || match.era(string, {\n width: 'narrow'\n });\n // A, B\n\n case 'GGGGG':\n return match.era(string, {\n width: 'narrow'\n });\n // Anno Domini, Before Christ\n\n case 'GGGG':\n default:\n return match.era(string, {\n width: 'wide'\n }) || match.era(string, {\n width: 'abbreviated'\n }) || match.era(string, {\n width: 'narrow'\n });\n }\n },\n set: function (date, flags, value, _options) {\n flags.era = value;\n date.setUTCFullYear(value, 0, 1);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['R', 'u', 't', 'T']\n },\n // Year\n y: {\n // From http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns\n // | Year | y | yy | yyy | yyyy | yyyyy |\n // |----------|-------|----|-------|-------|-------|\n // | AD 1 | 1 | 01 | 001 | 0001 | 00001 |\n // | AD 12 | 12 | 12 | 012 | 0012 | 00012 |\n // | AD 123 | 123 | 23 | 123 | 0123 | 00123 |\n // | AD 1234 | 1234 | 34 | 1234 | 1234 | 01234 |\n // | AD 12345 | 12345 | 45 | 12345 | 12345 | 12345 |\n priority: 130,\n parse: function (string, token, match, _options) {\n var valueCallback = function (year) {\n return {\n year: year,\n isTwoDigitYear: token === 'yy'\n };\n };\n\n switch (token) {\n case 'y':\n return parseNDigits(4, string, valueCallback);\n\n case 'yo':\n return match.ordinalNumber(string, {\n unit: 'year',\n valueCallback: valueCallback\n });\n\n default:\n return parseNDigits(token.length, string, valueCallback);\n }\n },\n validate: function (_date, value, _options) {\n return value.isTwoDigitYear || value.year > 0;\n },\n set: function (date, flags, value, _options) {\n var currentYear = date.getUTCFullYear();\n\n if (value.isTwoDigitYear) {\n var normalizedTwoDigitYear = normalizeTwoDigitYear(value.year, currentYear);\n date.setUTCFullYear(normalizedTwoDigitYear, 0, 1);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n }\n\n var year = !('era' in flags) || flags.era === 1 ? value.year : 1 - value.year;\n date.setUTCFullYear(year, 0, 1);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['Y', 'R', 'u', 'w', 'I', 'i', 'e', 'c', 't', 'T']\n },\n // Local week-numbering year\n Y: {\n priority: 130,\n parse: function (string, token, match, _options) {\n var valueCallback = function (year) {\n return {\n year: year,\n isTwoDigitYear: token === 'YY'\n };\n };\n\n switch (token) {\n case 'Y':\n return parseNDigits(4, string, valueCallback);\n\n case 'Yo':\n return match.ordinalNumber(string, {\n unit: 'year',\n valueCallback: valueCallback\n });\n\n default:\n return parseNDigits(token.length, string, valueCallback);\n }\n },\n validate: function (_date, value, _options) {\n return value.isTwoDigitYear || value.year > 0;\n },\n set: function (date, flags, value, options) {\n var currentYear = getUTCWeekYear(date, options);\n\n if (value.isTwoDigitYear) {\n var normalizedTwoDigitYear = normalizeTwoDigitYear(value.year, currentYear);\n date.setUTCFullYear(normalizedTwoDigitYear, 0, options.firstWeekContainsDate);\n date.setUTCHours(0, 0, 0, 0);\n return startOfUTCWeek(date, options);\n }\n\n var year = !('era' in flags) || flags.era === 1 ? value.year : 1 - value.year;\n date.setUTCFullYear(year, 0, options.firstWeekContainsDate);\n date.setUTCHours(0, 0, 0, 0);\n return startOfUTCWeek(date, options);\n },\n incompatibleTokens: ['y', 'R', 'u', 'Q', 'q', 'M', 'L', 'I', 'd', 'D', 'i', 't', 'T']\n },\n // ISO week-numbering year\n R: {\n priority: 130,\n parse: function (string, token, _match, _options) {\n if (token === 'R') {\n return parseNDigitsSigned(4, string);\n }\n\n return parseNDigitsSigned(token.length, string);\n },\n set: function (_date, _flags, value, _options) {\n var firstWeekOfYear = new Date(0);\n firstWeekOfYear.setUTCFullYear(value, 0, 4);\n firstWeekOfYear.setUTCHours(0, 0, 0, 0);\n return startOfUTCISOWeek(firstWeekOfYear);\n },\n incompatibleTokens: ['G', 'y', 'Y', 'u', 'Q', 'q', 'M', 'L', 'w', 'd', 'D', 'e', 'c', 't', 'T']\n },\n // Extended year\n u: {\n priority: 130,\n parse: function (string, token, _match, _options) {\n if (token === 'u') {\n return parseNDigitsSigned(4, string);\n }\n\n return parseNDigitsSigned(token.length, string);\n },\n set: function (date, _flags, value, _options) {\n date.setUTCFullYear(value, 0, 1);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['G', 'y', 'Y', 'R', 'w', 'I', 'i', 'e', 'c', 't', 'T']\n },\n // Quarter\n Q: {\n priority: 120,\n parse: function (string, token, match, _options) {\n switch (token) {\n // 1, 2, 3, 4\n case 'Q':\n case 'QQ':\n // 01, 02, 03, 04\n return parseNDigits(token.length, string);\n // 1st, 2nd, 3rd, 4th\n\n case 'Qo':\n return match.ordinalNumber(string, {\n unit: 'quarter'\n });\n // Q1, Q2, Q3, Q4\n\n case 'QQQ':\n return match.quarter(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.quarter(string, {\n width: 'narrow',\n context: 'formatting'\n });\n // 1, 2, 3, 4 (narrow quarter; could be not numerical)\n\n case 'QQQQQ':\n return match.quarter(string, {\n width: 'narrow',\n context: 'formatting'\n });\n // 1st quarter, 2nd quarter, ...\n\n case 'QQQQ':\n default:\n return match.quarter(string, {\n width: 'wide',\n context: 'formatting'\n }) || match.quarter(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.quarter(string, {\n width: 'narrow',\n context: 'formatting'\n });\n }\n },\n validate: function (_date, value, _options) {\n return value >= 1 && value <= 4;\n },\n set: function (date, _flags, value, _options) {\n date.setUTCMonth((value - 1) * 3, 1);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['Y', 'R', 'q', 'M', 'L', 'w', 'I', 'd', 'D', 'i', 'e', 'c', 't', 'T']\n },\n // Stand-alone quarter\n q: {\n priority: 120,\n parse: function (string, token, match, _options) {\n switch (token) {\n // 1, 2, 3, 4\n case 'q':\n case 'qq':\n // 01, 02, 03, 04\n return parseNDigits(token.length, string);\n // 1st, 2nd, 3rd, 4th\n\n case 'qo':\n return match.ordinalNumber(string, {\n unit: 'quarter'\n });\n // Q1, Q2, Q3, Q4\n\n case 'qqq':\n return match.quarter(string, {\n width: 'abbreviated',\n context: 'standalone'\n }) || match.quarter(string, {\n width: 'narrow',\n context: 'standalone'\n });\n // 1, 2, 3, 4 (narrow quarter; could be not numerical)\n\n case 'qqqqq':\n return match.quarter(string, {\n width: 'narrow',\n context: 'standalone'\n });\n // 1st quarter, 2nd quarter, ...\n\n case 'qqqq':\n default:\n return match.quarter(string, {\n width: 'wide',\n context: 'standalone'\n }) || match.quarter(string, {\n width: 'abbreviated',\n context: 'standalone'\n }) || match.quarter(string, {\n width: 'narrow',\n context: 'standalone'\n });\n }\n },\n validate: function (_date, value, _options) {\n return value >= 1 && value <= 4;\n },\n set: function (date, _flags, value, _options) {\n date.setUTCMonth((value - 1) * 3, 1);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['Y', 'R', 'Q', 'M', 'L', 'w', 'I', 'd', 'D', 'i', 'e', 'c', 't', 'T']\n },\n // Month\n M: {\n priority: 110,\n parse: function (string, token, match, _options) {\n var valueCallback = function (value) {\n return value - 1;\n };\n\n switch (token) {\n // 1, 2, ..., 12\n case 'M':\n return parseNumericPattern(numericPatterns.month, string, valueCallback);\n // 01, 02, ..., 12\n\n case 'MM':\n return parseNDigits(2, string, valueCallback);\n // 1st, 2nd, ..., 12th\n\n case 'Mo':\n return match.ordinalNumber(string, {\n unit: 'month',\n valueCallback: valueCallback\n });\n // Jan, Feb, ..., Dec\n\n case 'MMM':\n return match.month(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.month(string, {\n width: 'narrow',\n context: 'formatting'\n });\n // J, F, ..., D\n\n case 'MMMMM':\n return match.month(string, {\n width: 'narrow',\n context: 'formatting'\n });\n // January, February, ..., December\n\n case 'MMMM':\n default:\n return match.month(string, {\n width: 'wide',\n context: 'formatting'\n }) || match.month(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.month(string, {\n width: 'narrow',\n context: 'formatting'\n });\n }\n },\n validate: function (_date, value, _options) {\n return value >= 0 && value <= 11;\n },\n set: function (date, _flags, value, _options) {\n date.setUTCMonth(value, 1);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['Y', 'R', 'q', 'Q', 'L', 'w', 'I', 'D', 'i', 'e', 'c', 't', 'T']\n },\n // Stand-alone month\n L: {\n priority: 110,\n parse: function (string, token, match, _options) {\n var valueCallback = function (value) {\n return value - 1;\n };\n\n switch (token) {\n // 1, 2, ..., 12\n case 'L':\n return parseNumericPattern(numericPatterns.month, string, valueCallback);\n // 01, 02, ..., 12\n\n case 'LL':\n return parseNDigits(2, string, valueCallback);\n // 1st, 2nd, ..., 12th\n\n case 'Lo':\n return match.ordinalNumber(string, {\n unit: 'month',\n valueCallback: valueCallback\n });\n // Jan, Feb, ..., Dec\n\n case 'LLL':\n return match.month(string, {\n width: 'abbreviated',\n context: 'standalone'\n }) || match.month(string, {\n width: 'narrow',\n context: 'standalone'\n });\n // J, F, ..., D\n\n case 'LLLLL':\n return match.month(string, {\n width: 'narrow',\n context: 'standalone'\n });\n // January, February, ..., December\n\n case 'LLLL':\n default:\n return match.month(string, {\n width: 'wide',\n context: 'standalone'\n }) || match.month(string, {\n width: 'abbreviated',\n context: 'standalone'\n }) || match.month(string, {\n width: 'narrow',\n context: 'standalone'\n });\n }\n },\n validate: function (_date, value, _options) {\n return value >= 0 && value <= 11;\n },\n set: function (date, _flags, value, _options) {\n date.setUTCMonth(value, 1);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['Y', 'R', 'q', 'Q', 'M', 'w', 'I', 'D', 'i', 'e', 'c', 't', 'T']\n },\n // Local week of year\n w: {\n priority: 100,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 'w':\n return parseNumericPattern(numericPatterns.week, string);\n\n case 'wo':\n return match.ordinalNumber(string, {\n unit: 'week'\n });\n\n default:\n return parseNDigits(token.length, string);\n }\n },\n validate: function (_date, value, _options) {\n return value >= 1 && value <= 53;\n },\n set: function (date, _flags, value, options) {\n return startOfUTCWeek(setUTCWeek(date, value, options), options);\n },\n incompatibleTokens: ['y', 'R', 'u', 'q', 'Q', 'M', 'L', 'I', 'd', 'D', 'i', 't', 'T']\n },\n // ISO week of year\n I: {\n priority: 100,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 'I':\n return parseNumericPattern(numericPatterns.week, string);\n\n case 'Io':\n return match.ordinalNumber(string, {\n unit: 'week'\n });\n\n default:\n return parseNDigits(token.length, string);\n }\n },\n validate: function (_date, value, _options) {\n return value >= 1 && value <= 53;\n },\n set: function (date, _flags, value, options) {\n return startOfUTCISOWeek(setUTCISOWeek(date, value, options), options);\n },\n incompatibleTokens: ['y', 'Y', 'u', 'q', 'Q', 'M', 'L', 'w', 'd', 'D', 'e', 'c', 't', 'T']\n },\n // Day of the month\n d: {\n priority: 90,\n subPriority: 1,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 'd':\n return parseNumericPattern(numericPatterns.date, string);\n\n case 'do':\n return match.ordinalNumber(string, {\n unit: 'date'\n });\n\n default:\n return parseNDigits(token.length, string);\n }\n },\n validate: function (date, value, _options) {\n var year = date.getUTCFullYear();\n var isLeapYear = isLeapYearIndex(year);\n var month = date.getUTCMonth();\n\n if (isLeapYear) {\n return value >= 1 && value <= DAYS_IN_MONTH_LEAP_YEAR[month];\n } else {\n return value >= 1 && value <= DAYS_IN_MONTH[month];\n }\n },\n set: function (date, _flags, value, _options) {\n date.setUTCDate(value);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['Y', 'R', 'q', 'Q', 'w', 'I', 'D', 'i', 'e', 'c', 't', 'T']\n },\n // Day of year\n D: {\n priority: 90,\n subPriority: 1,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 'D':\n case 'DD':\n return parseNumericPattern(numericPatterns.dayOfYear, string);\n\n case 'Do':\n return match.ordinalNumber(string, {\n unit: 'date'\n });\n\n default:\n return parseNDigits(token.length, string);\n }\n },\n validate: function (date, value, _options) {\n var year = date.getUTCFullYear();\n var isLeapYear = isLeapYearIndex(year);\n\n if (isLeapYear) {\n return value >= 1 && value <= 366;\n } else {\n return value >= 1 && value <= 365;\n }\n },\n set: function (date, _flags, value, _options) {\n date.setUTCMonth(0, value);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['Y', 'R', 'q', 'Q', 'M', 'L', 'w', 'I', 'd', 'E', 'i', 'e', 'c', 't', 'T']\n },\n // Day of week\n E: {\n priority: 90,\n parse: function (string, token, match, _options) {\n switch (token) {\n // Tue\n case 'E':\n case 'EE':\n case 'EEE':\n return match.day(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.day(string, {\n width: 'short',\n context: 'formatting'\n }) || match.day(string, {\n width: 'narrow',\n context: 'formatting'\n });\n // T\n\n case 'EEEEE':\n return match.day(string, {\n width: 'narrow',\n context: 'formatting'\n });\n // Tu\n\n case 'EEEEEE':\n return match.day(string, {\n width: 'short',\n context: 'formatting'\n }) || match.day(string, {\n width: 'narrow',\n context: 'formatting'\n });\n // Tuesday\n\n case 'EEEE':\n default:\n return match.day(string, {\n width: 'wide',\n context: 'formatting'\n }) || match.day(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.day(string, {\n width: 'short',\n context: 'formatting'\n }) || match.day(string, {\n width: 'narrow',\n context: 'formatting'\n });\n }\n },\n validate: function (_date, value, _options) {\n return value >= 0 && value <= 6;\n },\n set: function (date, _flags, value, options) {\n date = setUTCDay(date, value, options);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['D', 'i', 'e', 'c', 't', 'T']\n },\n // Local day of week\n e: {\n priority: 90,\n parse: function (string, token, match, options) {\n var valueCallback = function (value) {\n var wholeWeekDays = Math.floor((value - 1) / 7) * 7;\n return (value + options.weekStartsOn + 6) % 7 + wholeWeekDays;\n };\n\n switch (token) {\n // 3\n case 'e':\n case 'ee':\n // 03\n return parseNDigits(token.length, string, valueCallback);\n // 3rd\n\n case 'eo':\n return match.ordinalNumber(string, {\n unit: 'day',\n valueCallback: valueCallback\n });\n // Tue\n\n case 'eee':\n return match.day(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.day(string, {\n width: 'short',\n context: 'formatting'\n }) || match.day(string, {\n width: 'narrow',\n context: 'formatting'\n });\n // T\n\n case 'eeeee':\n return match.day(string, {\n width: 'narrow',\n context: 'formatting'\n });\n // Tu\n\n case 'eeeeee':\n return match.day(string, {\n width: 'short',\n context: 'formatting'\n }) || match.day(string, {\n width: 'narrow',\n context: 'formatting'\n });\n // Tuesday\n\n case 'eeee':\n default:\n return match.day(string, {\n width: 'wide',\n context: 'formatting'\n }) || match.day(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.day(string, {\n width: 'short',\n context: 'formatting'\n }) || match.day(string, {\n width: 'narrow',\n context: 'formatting'\n });\n }\n },\n validate: function (_date, value, _options) {\n return value >= 0 && value <= 6;\n },\n set: function (date, _flags, value, options) {\n date = setUTCDay(date, value, options);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['y', 'R', 'u', 'q', 'Q', 'M', 'L', 'I', 'd', 'D', 'E', 'i', 'c', 't', 'T']\n },\n // Stand-alone local day of week\n c: {\n priority: 90,\n parse: function (string, token, match, options) {\n var valueCallback = function (value) {\n var wholeWeekDays = Math.floor((value - 1) / 7) * 7;\n return (value + options.weekStartsOn + 6) % 7 + wholeWeekDays;\n };\n\n switch (token) {\n // 3\n case 'c':\n case 'cc':\n // 03\n return parseNDigits(token.length, string, valueCallback);\n // 3rd\n\n case 'co':\n return match.ordinalNumber(string, {\n unit: 'day',\n valueCallback: valueCallback\n });\n // Tue\n\n case 'ccc':\n return match.day(string, {\n width: 'abbreviated',\n context: 'standalone'\n }) || match.day(string, {\n width: 'short',\n context: 'standalone'\n }) || match.day(string, {\n width: 'narrow',\n context: 'standalone'\n });\n // T\n\n case 'ccccc':\n return match.day(string, {\n width: 'narrow',\n context: 'standalone'\n });\n // Tu\n\n case 'cccccc':\n return match.day(string, {\n width: 'short',\n context: 'standalone'\n }) || match.day(string, {\n width: 'narrow',\n context: 'standalone'\n });\n // Tuesday\n\n case 'cccc':\n default:\n return match.day(string, {\n width: 'wide',\n context: 'standalone'\n }) || match.day(string, {\n width: 'abbreviated',\n context: 'standalone'\n }) || match.day(string, {\n width: 'short',\n context: 'standalone'\n }) || match.day(string, {\n width: 'narrow',\n context: 'standalone'\n });\n }\n },\n validate: function (_date, value, _options) {\n return value >= 0 && value <= 6;\n },\n set: function (date, _flags, value, options) {\n date = setUTCDay(date, value, options);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['y', 'R', 'u', 'q', 'Q', 'M', 'L', 'I', 'd', 'D', 'E', 'i', 'e', 't', 'T']\n },\n // ISO day of week\n i: {\n priority: 90,\n parse: function (string, token, match, _options) {\n var valueCallback = function (value) {\n if (value === 0) {\n return 7;\n }\n\n return value;\n };\n\n switch (token) {\n // 2\n case 'i':\n case 'ii':\n // 02\n return parseNDigits(token.length, string);\n // 2nd\n\n case 'io':\n return match.ordinalNumber(string, {\n unit: 'day'\n });\n // Tue\n\n case 'iii':\n return match.day(string, {\n width: 'abbreviated',\n context: 'formatting',\n valueCallback: valueCallback\n }) || match.day(string, {\n width: 'short',\n context: 'formatting',\n valueCallback: valueCallback\n }) || match.day(string, {\n width: 'narrow',\n context: 'formatting',\n valueCallback: valueCallback\n });\n // T\n\n case 'iiiii':\n return match.day(string, {\n width: 'narrow',\n context: 'formatting',\n valueCallback: valueCallback\n });\n // Tu\n\n case 'iiiiii':\n return match.day(string, {\n width: 'short',\n context: 'formatting',\n valueCallback: valueCallback\n }) || match.day(string, {\n width: 'narrow',\n context: 'formatting',\n valueCallback: valueCallback\n });\n // Tuesday\n\n case 'iiii':\n default:\n return match.day(string, {\n width: 'wide',\n context: 'formatting',\n valueCallback: valueCallback\n }) || match.day(string, {\n width: 'abbreviated',\n context: 'formatting',\n valueCallback: valueCallback\n }) || match.day(string, {\n width: 'short',\n context: 'formatting',\n valueCallback: valueCallback\n }) || match.day(string, {\n width: 'narrow',\n context: 'formatting',\n valueCallback: valueCallback\n });\n }\n },\n validate: function (_date, value, _options) {\n return value >= 1 && value <= 7;\n },\n set: function (date, _flags, value, options) {\n date = setUTCISODay(date, value, options);\n date.setUTCHours(0, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['y', 'Y', 'u', 'q', 'Q', 'M', 'L', 'w', 'd', 'D', 'E', 'e', 'c', 't', 'T']\n },\n // AM or PM\n a: {\n priority: 80,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 'a':\n case 'aa':\n case 'aaa':\n return match.dayPeriod(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.dayPeriod(string, {\n width: 'narrow',\n context: 'formatting'\n });\n\n case 'aaaaa':\n return match.dayPeriod(string, {\n width: 'narrow',\n context: 'formatting'\n });\n\n case 'aaaa':\n default:\n return match.dayPeriod(string, {\n width: 'wide',\n context: 'formatting'\n }) || match.dayPeriod(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.dayPeriod(string, {\n width: 'narrow',\n context: 'formatting'\n });\n }\n },\n set: function (date, _flags, value, _options) {\n date.setUTCHours(dayPeriodEnumToHours(value), 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['b', 'B', 'H', 'K', 'k', 't', 'T']\n },\n // AM, PM, midnight\n b: {\n priority: 80,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 'b':\n case 'bb':\n case 'bbb':\n return match.dayPeriod(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.dayPeriod(string, {\n width: 'narrow',\n context: 'formatting'\n });\n\n case 'bbbbb':\n return match.dayPeriod(string, {\n width: 'narrow',\n context: 'formatting'\n });\n\n case 'bbbb':\n default:\n return match.dayPeriod(string, {\n width: 'wide',\n context: 'formatting'\n }) || match.dayPeriod(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.dayPeriod(string, {\n width: 'narrow',\n context: 'formatting'\n });\n }\n },\n set: function (date, _flags, value, _options) {\n date.setUTCHours(dayPeriodEnumToHours(value), 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['a', 'B', 'H', 'K', 'k', 't', 'T']\n },\n // in the morning, in the afternoon, in the evening, at night\n B: {\n priority: 80,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 'B':\n case 'BB':\n case 'BBB':\n return match.dayPeriod(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.dayPeriod(string, {\n width: 'narrow',\n context: 'formatting'\n });\n\n case 'BBBBB':\n return match.dayPeriod(string, {\n width: 'narrow',\n context: 'formatting'\n });\n\n case 'BBBB':\n default:\n return match.dayPeriod(string, {\n width: 'wide',\n context: 'formatting'\n }) || match.dayPeriod(string, {\n width: 'abbreviated',\n context: 'formatting'\n }) || match.dayPeriod(string, {\n width: 'narrow',\n context: 'formatting'\n });\n }\n },\n set: function (date, _flags, value, _options) {\n date.setUTCHours(dayPeriodEnumToHours(value), 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['a', 'b', 't', 'T']\n },\n // Hour [1-12]\n h: {\n priority: 70,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 'h':\n return parseNumericPattern(numericPatterns.hour12h, string);\n\n case 'ho':\n return match.ordinalNumber(string, {\n unit: 'hour'\n });\n\n default:\n return parseNDigits(token.length, string);\n }\n },\n validate: function (_date, value, _options) {\n return value >= 1 && value <= 12;\n },\n set: function (date, _flags, value, _options) {\n var isPM = date.getUTCHours() >= 12;\n\n if (isPM && value < 12) {\n date.setUTCHours(value + 12, 0, 0, 0);\n } else if (!isPM && value === 12) {\n date.setUTCHours(0, 0, 0, 0);\n } else {\n date.setUTCHours(value, 0, 0, 0);\n }\n\n return date;\n },\n incompatibleTokens: ['H', 'K', 'k', 't', 'T']\n },\n // Hour [0-23]\n H: {\n priority: 70,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 'H':\n return parseNumericPattern(numericPatterns.hour23h, string);\n\n case 'Ho':\n return match.ordinalNumber(string, {\n unit: 'hour'\n });\n\n default:\n return parseNDigits(token.length, string);\n }\n },\n validate: function (_date, value, _options) {\n return value >= 0 && value <= 23;\n },\n set: function (date, _flags, value, _options) {\n date.setUTCHours(value, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['a', 'b', 'h', 'K', 'k', 't', 'T']\n },\n // Hour [0-11]\n K: {\n priority: 70,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 'K':\n return parseNumericPattern(numericPatterns.hour11h, string);\n\n case 'Ko':\n return match.ordinalNumber(string, {\n unit: 'hour'\n });\n\n default:\n return parseNDigits(token.length, string);\n }\n },\n validate: function (_date, value, _options) {\n return value >= 0 && value <= 11;\n },\n set: function (date, _flags, value, _options) {\n var isPM = date.getUTCHours() >= 12;\n\n if (isPM && value < 12) {\n date.setUTCHours(value + 12, 0, 0, 0);\n } else {\n date.setUTCHours(value, 0, 0, 0);\n }\n\n return date;\n },\n incompatibleTokens: ['a', 'b', 'h', 'H', 'k', 't', 'T']\n },\n // Hour [1-24]\n k: {\n priority: 70,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 'k':\n return parseNumericPattern(numericPatterns.hour24h, string);\n\n case 'ko':\n return match.ordinalNumber(string, {\n unit: 'hour'\n });\n\n default:\n return parseNDigits(token.length, string);\n }\n },\n validate: function (_date, value, _options) {\n return value >= 1 && value <= 24;\n },\n set: function (date, _flags, value, _options) {\n var hours = value <= 24 ? value % 24 : value;\n date.setUTCHours(hours, 0, 0, 0);\n return date;\n },\n incompatibleTokens: ['a', 'b', 'h', 'H', 'K', 't', 'T']\n },\n // Minute\n m: {\n priority: 60,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 'm':\n return parseNumericPattern(numericPatterns.minute, string);\n\n case 'mo':\n return match.ordinalNumber(string, {\n unit: 'minute'\n });\n\n default:\n return parseNDigits(token.length, string);\n }\n },\n validate: function (_date, value, _options) {\n return value >= 0 && value <= 59;\n },\n set: function (date, _flags, value, _options) {\n date.setUTCMinutes(value, 0, 0);\n return date;\n },\n incompatibleTokens: ['t', 'T']\n },\n // Second\n s: {\n priority: 50,\n parse: function (string, token, match, _options) {\n switch (token) {\n case 's':\n return parseNumericPattern(numericPatterns.second, string);\n\n case 'so':\n return match.ordinalNumber(string, {\n unit: 'second'\n });\n\n default:\n return parseNDigits(token.length, string);\n }\n },\n validate: function (_date, value, _options) {\n return value >= 0 && value <= 59;\n },\n set: function (date, _flags, value, _options) {\n date.setUTCSeconds(value, 0);\n return date;\n },\n incompatibleTokens: ['t', 'T']\n },\n // Fraction of second\n S: {\n priority: 30,\n parse: function (string, token, _match, _options) {\n var valueCallback = function (value) {\n return Math.floor(value * Math.pow(10, -token.length + 3));\n };\n\n return parseNDigits(token.length, string, valueCallback);\n },\n set: function (date, _flags, value, _options) {\n date.setUTCMilliseconds(value);\n return date;\n },\n incompatibleTokens: ['t', 'T']\n },\n // Timezone (ISO-8601. +00:00 is `'Z'`)\n X: {\n priority: 10,\n parse: function (string, token, _match, _options) {\n switch (token) {\n case 'X':\n return parseTimezonePattern(timezonePatterns.basicOptionalMinutes, string);\n\n case 'XX':\n return parseTimezonePattern(timezonePatterns.basic, string);\n\n case 'XXXX':\n return parseTimezonePattern(timezonePatterns.basicOptionalSeconds, string);\n\n case 'XXXXX':\n return parseTimezonePattern(timezonePatterns.extendedOptionalSeconds, string);\n\n case 'XXX':\n default:\n return parseTimezonePattern(timezonePatterns.extended, string);\n }\n },\n set: function (date, flags, value, _options) {\n if (flags.timestampIsSet) {\n return date;\n }\n\n return new Date(date.getTime() - value);\n },\n incompatibleTokens: ['t', 'T', 'x']\n },\n // Timezone (ISO-8601)\n x: {\n priority: 10,\n parse: function (string, token, _match, _options) {\n switch (token) {\n case 'x':\n return parseTimezonePattern(timezonePatterns.basicOptionalMinutes, string);\n\n case 'xx':\n return parseTimezonePattern(timezonePatterns.basic, string);\n\n case 'xxxx':\n return parseTimezonePattern(timezonePatterns.basicOptionalSeconds, string);\n\n case 'xxxxx':\n return parseTimezonePattern(timezonePatterns.extendedOptionalSeconds, string);\n\n case 'xxx':\n default:\n return parseTimezonePattern(timezonePatterns.extended, string);\n }\n },\n set: function (date, flags, value, _options) {\n if (flags.timestampIsSet) {\n return date;\n }\n\n return new Date(date.getTime() - value);\n },\n incompatibleTokens: ['t', 'T', 'X']\n },\n // Seconds timestamp\n t: {\n priority: 40,\n parse: function (string, _token, _match, _options) {\n return parseAnyDigitsSigned(string);\n },\n set: function (_date, _flags, value, _options) {\n return [new Date(value * 1000), {\n timestampIsSet: true\n }];\n },\n incompatibleTokens: '*'\n },\n // Milliseconds timestamp\n T: {\n priority: 20,\n parse: function (string, _token, _match, _options) {\n return parseAnyDigitsSigned(string);\n },\n set: function (_date, _flags, value, _options) {\n return [new Date(value), {\n timestampIsSet: true\n }];\n },\n incompatibleTokens: '*'\n }\n};\nexport default parsers;","import toInteger from \"../toInteger/index.js\";\nimport toDate from \"../../toDate/index.js\";\nimport getUTCWeek from \"../getUTCWeek/index.js\";\nimport requiredArgs from \"../requiredArgs/index.js\"; // This function will be a part of public API when UTC function will be implemented.\n// See issue: https://github.com/date-fns/date-fns/issues/376\n\nexport default function setUTCWeek(dirtyDate, dirtyWeek, options) {\n requiredArgs(2, arguments);\n var date = toDate(dirtyDate);\n var week = toInteger(dirtyWeek);\n var diff = getUTCWeek(date, options) - week;\n date.setUTCDate(date.getUTCDate() - diff * 7);\n return date;\n}","import toInteger from \"../toInteger/index.js\";\nimport toDate from \"../../toDate/index.js\";\nimport getUTCISOWeek from \"../getUTCISOWeek/index.js\";\nimport requiredArgs from \"../requiredArgs/index.js\"; // This function will be a part of public API when UTC function will be implemented.\n// See issue: https://github.com/date-fns/date-fns/issues/376\n\nexport default function setUTCISOWeek(dirtyDate, dirtyISOWeek) {\n requiredArgs(2, arguments);\n var date = toDate(dirtyDate);\n var isoWeek = toInteger(dirtyISOWeek);\n var diff = getUTCISOWeek(date) - isoWeek;\n date.setUTCDate(date.getUTCDate() - diff * 7);\n return date;\n}","import toInteger from \"../toInteger/index.js\";\nimport toDate from \"../../toDate/index.js\";\nimport requiredArgs from \"../requiredArgs/index.js\"; // This function will be a part of public API when UTC function will be implemented.\n// See issue: https://github.com/date-fns/date-fns/issues/376\n\nexport default function setUTCISODay(dirtyDate, dirtyDay) {\n requiredArgs(2, arguments);\n var day = toInteger(dirtyDay);\n\n if (day % 7 === 0) {\n day = day - 7;\n }\n\n var weekStartsOn = 1;\n var date = toDate(dirtyDate);\n var currentDay = date.getUTCDay();\n var remainder = day % 7;\n var dayIndex = (remainder + 7) % 7;\n var diff = (dayIndex < weekStartsOn ? 7 : 0) + day - currentDay;\n date.setUTCDate(date.getUTCDate() + diff);\n return date;\n}","import defaultLocale from \"../locale/en-US/index.js\";\nimport subMilliseconds from \"../subMilliseconds/index.js\";\nimport toDate from \"../toDate/index.js\";\nimport assign from \"../_lib/assign/index.js\";\nimport longFormatters from \"../_lib/format/longFormatters/index.js\";\nimport getTimezoneOffsetInMilliseconds from \"../_lib/getTimezoneOffsetInMilliseconds/index.js\";\nimport { isProtectedDayOfYearToken, isProtectedWeekYearToken, throwProtectedError } from \"../_lib/protectedTokens/index.js\";\nimport toInteger from \"../_lib/toInteger/index.js\";\nimport parsers from \"./_lib/parsers/index.js\";\nimport requiredArgs from \"../_lib/requiredArgs/index.js\";\nvar TIMEZONE_UNIT_PRIORITY = 10; // This RegExp consists of three parts separated by `|`:\n// - [yYQqMLwIdDecihHKkms]o matches any available ordinal number token\n// (one of the certain letters followed by `o`)\n// - (\\w)\\1* matches any sequences of the same letter\n// - '' matches two quote characters in a row\n// - '(''|[^'])+('|$) matches anything surrounded by two quote characters ('),\n// except a single quote symbol, which ends the sequence.\n// Two quote characters do not end the sequence.\n// If there is no matching single quote\n// then the sequence will continue until the end of the string.\n// - . matches any single character unmatched by previous parts of the RegExps\n\nvar formattingTokensRegExp = /[yYQqMLwIdDecihHKkms]o|(\\w)\\1*|''|'(''|[^'])+('|$)|./g; // This RegExp catches symbols escaped by quotes, and also\n// sequences of symbols P, p, and the combinations like `PPPPPPPppppp`\n\nvar longFormattingTokensRegExp = /P+p+|P+|p+|''|'(''|[^'])+('|$)|./g;\nvar escapedStringRegExp = /^'([^]*?)'?$/;\nvar doubleQuoteRegExp = /''/g;\nvar notWhitespaceRegExp = /\\S/;\nvar unescapedLatinCharacterRegExp = /[a-zA-Z]/;\n/**\n * @name parse\n * @category Common Helpers\n * @summary Parse the date.\n *\n * @description\n * Return the date parsed from string using the given format string.\n *\n * > ⚠️ Please note that the `format` tokens differ from Moment.js and other libraries.\n * > See: https://git.io/fxCyr\n *\n * The characters in the format string wrapped between two single quotes characters (') are escaped.\n * Two single quotes in a row, whether inside or outside a quoted sequence, represent a 'real' single quote.\n *\n * Format of the format string is based on Unicode Technical Standard #35:\n * https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table\n * with a few additions (see note 5 below the table).\n *\n * Not all tokens are compatible. Combinations that don't make sense or could lead to bugs are prohibited\n * and will throw `RangeError`. For example usage of 24-hour format token with AM/PM token will throw an exception:\n *\n * ```javascript\n * parse('23 AM', 'HH a', new Date())\n * //=> RangeError: The format string mustn't contain `HH` and `a` at the same time\n * ```\n *\n * See the compatibility table: https://docs.google.com/spreadsheets/d/e/2PACX-1vQOPU3xUhplll6dyoMmVUXHKl_8CRDs6_ueLmex3SoqwhuolkuN3O05l4rqx5h1dKX8eb46Ul-CCSrq/pubhtml?gid=0&single=true\n *\n * Accepted format string patterns:\n * | Unit |Prior| Pattern | Result examples | Notes |\n * |---------------------------------|-----|---------|-----------------------------------|-------|\n * | Era | 140 | G..GGG | AD, BC | |\n * | | | GGGG | Anno Domini, Before Christ | 2 |\n * | | | GGGGG | A, B | |\n * | Calendar year | 130 | y | 44, 1, 1900, 2017, 9999 | 4 |\n * | | | yo | 44th, 1st, 1900th, 9999999th | 4,5 |\n * | | | yy | 44, 01, 00, 17 | 4 |\n * | | | yyy | 044, 001, 123, 999 | 4 |\n * | | | yyyy | 0044, 0001, 1900, 2017 | 4 |\n * | | | yyyyy | ... | 2,4 |\n * | Local week-numbering year | 130 | Y | 44, 1, 1900, 2017, 9000 | 4 |\n * | | | Yo | 44th, 1st, 1900th, 9999999th | 4,5 |\n * | | | YY | 44, 01, 00, 17 | 4,6 |\n * | | | YYY | 044, 001, 123, 999 | 4 |\n * | | | YYYY | 0044, 0001, 1900, 2017 | 4,6 |\n * | | | YYYYY | ... | 2,4 |\n * | ISO week-numbering year | 130 | R | -43, 1, 1900, 2017, 9999, -9999 | 4,5 |\n * | | | RR | -43, 01, 00, 17 | 4,5 |\n * | | | RRR | -043, 001, 123, 999, -999 | 4,5 |\n * | | | RRRR | -0043, 0001, 2017, 9999, -9999 | 4,5 |\n * | | | RRRRR | ... | 2,4,5 |\n * | Extended year | 130 | u | -43, 1, 1900, 2017, 9999, -999 | 4 |\n * | | | uu | -43, 01, 99, -99 | 4 |\n * | | | uuu | -043, 001, 123, 999, -999 | 4 |\n * | | | uuuu | -0043, 0001, 2017, 9999, -9999 | 4 |\n * | | | uuuuu | ... | 2,4 |\n * | Quarter (formatting) | 120 | Q | 1, 2, 3, 4 | |\n * | | | Qo | 1st, 2nd, 3rd, 4th | 5 |\n * | | | QQ | 01, 02, 03, 04 | |\n * | | | QQQ | Q1, Q2, Q3, Q4 | |\n * | | | QQQQ | 1st quarter, 2nd quarter, ... | 2 |\n * | | | QQQQQ | 1, 2, 3, 4 | 4 |\n * | Quarter (stand-alone) | 120 | q | 1, 2, 3, 4 | |\n * | | | qo | 1st, 2nd, 3rd, 4th | 5 |\n * | | | qq | 01, 02, 03, 04 | |\n * | | | qqq | Q1, Q2, Q3, Q4 | |\n * | | | qqqq | 1st quarter, 2nd quarter, ... | 2 |\n * | | | qqqqq | 1, 2, 3, 4 | 3 |\n * | Month (formatting) | 110 | M | 1, 2, ..., 12 | |\n * | | | Mo | 1st, 2nd, ..., 12th | 5 |\n * | | | MM | 01, 02, ..., 12 | |\n * | | | MMM | Jan, Feb, ..., Dec | |\n * | | | MMMM | January, February, ..., December | 2 |\n * | | | MMMMM | J, F, ..., D | |\n * | Month (stand-alone) | 110 | L | 1, 2, ..., 12 | |\n * | | | Lo | 1st, 2nd, ..., 12th | 5 |\n * | | | LL | 01, 02, ..., 12 | |\n * | | | LLL | Jan, Feb, ..., Dec | |\n * | | | LLLL | January, February, ..., December | 2 |\n * | | | LLLLL | J, F, ..., D | |\n * | Local week of year | 100 | w | 1, 2, ..., 53 | |\n * | | | wo | 1st, 2nd, ..., 53th | 5 |\n * | | | ww | 01, 02, ..., 53 | |\n * | ISO week of year | 100 | I | 1, 2, ..., 53 | 5 |\n * | | | Io | 1st, 2nd, ..., 53th | 5 |\n * | | | II | 01, 02, ..., 53 | 5 |\n * | Day of month | 90 | d | 1, 2, ..., 31 | |\n * | | | do | 1st, 2nd, ..., 31st | 5 |\n * | | | dd | 01, 02, ..., 31 | |\n * | Day of year | 90 | D | 1, 2, ..., 365, 366 | 7 |\n * | | | Do | 1st, 2nd, ..., 365th, 366th | 5 |\n * | | | DD | 01, 02, ..., 365, 366 | 7 |\n * | | | DDD | 001, 002, ..., 365, 366 | |\n * | | | DDDD | ... | 2 |\n * | Day of week (formatting) | 90 | E..EEE | Mon, Tue, Wed, ..., Sun | |\n * | | | EEEE | Monday, Tuesday, ..., Sunday | 2 |\n * | | | EEEEE | M, T, W, T, F, S, S | |\n * | | | EEEEEE | Mo, Tu, We, Th, Fr, Su, Sa | |\n * | ISO day of week (formatting) | 90 | i | 1, 2, 3, ..., 7 | 5 |\n * | | | io | 1st, 2nd, ..., 7th | 5 |\n * | | | ii | 01, 02, ..., 07 | 5 |\n * | | | iii | Mon, Tue, Wed, ..., Sun | 5 |\n * | | | iiii | Monday, Tuesday, ..., Sunday | 2,5 |\n * | | | iiiii | M, T, W, T, F, S, S | 5 |\n * | | | iiiiii | Mo, Tu, We, Th, Fr, Su, Sa | 5 |\n * | Local day of week (formatting) | 90 | e | 2, 3, 4, ..., 1 | |\n * | | | eo | 2nd, 3rd, ..., 1st | 5 |\n * | | | ee | 02, 03, ..., 01 | |\n * | | | eee | Mon, Tue, Wed, ..., Sun | |\n * | | | eeee | Monday, Tuesday, ..., Sunday | 2 |\n * | | | eeeee | M, T, W, T, F, S, S | |\n * | | | eeeeee | Mo, Tu, We, Th, Fr, Su, Sa | |\n * | Local day of week (stand-alone) | 90 | c | 2, 3, 4, ..., 1 | |\n * | | | co | 2nd, 3rd, ..., 1st | 5 |\n * | | | cc | 02, 03, ..., 01 | |\n * | | | ccc | Mon, Tue, Wed, ..., Sun | |\n * | | | cccc | Monday, Tuesday, ..., Sunday | 2 |\n * | | | ccccc | M, T, W, T, F, S, S | |\n * | | | cccccc | Mo, Tu, We, Th, Fr, Su, Sa | |\n * | AM, PM | 80 | a..aaa | AM, PM | |\n * | | | aaaa | a.m., p.m. | 2 |\n * | | | aaaaa | a, p | |\n * | AM, PM, noon, midnight | 80 | b..bbb | AM, PM, noon, midnight | |\n * | | | bbbb | a.m., p.m., noon, midnight | 2 |\n * | | | bbbbb | a, p, n, mi | |\n * | Flexible day period | 80 | B..BBB | at night, in the morning, ... | |\n * | | | BBBB | at night, in the morning, ... | 2 |\n * | | | BBBBB | at night, in the morning, ... | |\n * | Hour [1-12] | 70 | h | 1, 2, ..., 11, 12 | |\n * | | | ho | 1st, 2nd, ..., 11th, 12th | 5 |\n * | | | hh | 01, 02, ..., 11, 12 | |\n * | Hour [0-23] | 70 | H | 0, 1, 2, ..., 23 | |\n * | | | Ho | 0th, 1st, 2nd, ..., 23rd | 5 |\n * | | | HH | 00, 01, 02, ..., 23 | |\n * | Hour [0-11] | 70 | K | 1, 2, ..., 11, 0 | |\n * | | | Ko | 1st, 2nd, ..., 11th, 0th | 5 |\n * | | | KK | 01, 02, ..., 11, 00 | |\n * | Hour [1-24] | 70 | k | 24, 1, 2, ..., 23 | |\n * | | | ko | 24th, 1st, 2nd, ..., 23rd | 5 |\n * | | | kk | 24, 01, 02, ..., 23 | |\n * | Minute | 60 | m | 0, 1, ..., 59 | |\n * | | | mo | 0th, 1st, ..., 59th | 5 |\n * | | | mm | 00, 01, ..., 59 | |\n * | Second | 50 | s | 0, 1, ..., 59 | |\n * | | | so | 0th, 1st, ..., 59th | 5 |\n * | | | ss | 00, 01, ..., 59 | |\n * | Seconds timestamp | 40 | t | 512969520 | |\n * | | | tt | ... | 2 |\n * | Fraction of second | 30 | S | 0, 1, ..., 9 | |\n * | | | SS | 00, 01, ..., 99 | |\n * | | | SSS | 000, 0001, ..., 999 | |\n * | | | SSSS | ... | 2 |\n * | Milliseconds timestamp | 20 | T | 512969520900 | |\n * | | | TT | ... | 2 |\n * | Timezone (ISO-8601 w/ Z) | 10 | X | -08, +0530, Z | |\n * | | | XX | -0800, +0530, Z | |\n * | | | XXX | -08:00, +05:30, Z | |\n * | | | XXXX | -0800, +0530, Z, +123456 | 2 |\n * | | | XXXXX | -08:00, +05:30, Z, +12:34:56 | |\n * | Timezone (ISO-8601 w/o Z) | 10 | x | -08, +0530, +00 | |\n * | | | xx | -0800, +0530, +0000 | |\n * | | | xxx | -08:00, +05:30, +00:00 | 2 |\n * | | | xxxx | -0800, +0530, +0000, +123456 | |\n * | | | xxxxx | -08:00, +05:30, +00:00, +12:34:56 | |\n * | Long localized date | NA | P | 05/29/1453 | 5,8 |\n * | | | PP | May 29, 1453 | |\n * | | | PPP | May 29th, 1453 | |\n * | | | PPPP | Sunday, May 29th, 1453 | 2,5,8 |\n * | Long localized time | NA | p | 12:00 AM | 5,8 |\n * | | | pp | 12:00:00 AM | |\n * | Combination of date and time | NA | Pp | 05/29/1453, 12:00 AM | |\n * | | | PPpp | May 29, 1453, 12:00:00 AM | |\n * | | | PPPpp | May 29th, 1453 at ... | |\n * | | | PPPPpp | Sunday, May 29th, 1453 at ... | 2,5,8 |\n * Notes:\n * 1. \"Formatting\" units (e.g. formatting quarter) in the default en-US locale\n * are the same as \"stand-alone\" units, but are different in some languages.\n * \"Formatting\" units are declined according to the rules of the language\n * in the context of a date. \"Stand-alone\" units are always nominative singular.\n * In `format` function, they will produce different result:\n *\n * `format(new Date(2017, 10, 6), 'do LLLL', {locale: cs}) //=> '6. listopad'`\n *\n * `format(new Date(2017, 10, 6), 'do MMMM', {locale: cs}) //=> '6. listopadu'`\n *\n * `parse` will try to match both formatting and stand-alone units interchangably.\n *\n * 2. Any sequence of the identical letters is a pattern, unless it is escaped by\n * the single quote characters (see below).\n * If the sequence is longer than listed in table:\n * - for numerical units (`yyyyyyyy`) `parse` will try to match a number\n * as wide as the sequence\n * - for text units (`MMMMMMMM`) `parse` will try to match the widest variation of the unit.\n * These variations are marked with \"2\" in the last column of the table.\n *\n * 3. `QQQQQ` and `qqqqq` could be not strictly numerical in some locales.\n * These tokens represent the shortest form of the quarter.\n *\n * 4. The main difference between `y` and `u` patterns are B.C. years:\n *\n * | Year | `y` | `u` |\n * |------|-----|-----|\n * | AC 1 | 1 | 1 |\n * | BC 1 | 1 | 0 |\n * | BC 2 | 2 | -1 |\n *\n * Also `yy` will try to guess the century of two digit year by proximity with `referenceDate`:\n *\n * `parse('50', 'yy', new Date(2018, 0, 1)) //=> Sat Jan 01 2050 00:00:00`\n *\n * `parse('75', 'yy', new Date(2018, 0, 1)) //=> Wed Jan 01 1975 00:00:00`\n *\n * while `uu` will just assign the year as is:\n *\n * `parse('50', 'uu', new Date(2018, 0, 1)) //=> Sat Jan 01 0050 00:00:00`\n *\n * `parse('75', 'uu', new Date(2018, 0, 1)) //=> Tue Jan 01 0075 00:00:00`\n *\n * The same difference is true for local and ISO week-numbering years (`Y` and `R`),\n * except local week-numbering years are dependent on `options.weekStartsOn`\n * and `options.firstWeekContainsDate` (compare [setISOWeekYear]{@link https://date-fns.org/docs/setISOWeekYear}\n * and [setWeekYear]{@link https://date-fns.org/docs/setWeekYear}).\n *\n * 5. These patterns are not in the Unicode Technical Standard #35:\n * - `i`: ISO day of week\n * - `I`: ISO week of year\n * - `R`: ISO week-numbering year\n * - `o`: ordinal number modifier\n * - `P`: long localized date\n * - `p`: long localized time\n *\n * 6. `YY` and `YYYY` tokens represent week-numbering years but they are often confused with years.\n * You should enable `options.useAdditionalWeekYearTokens` to use them. See: https://git.io/fxCyr\n *\n * 7. `D` and `DD` tokens represent days of the year but they are ofthen confused with days of the month.\n * You should enable `options.useAdditionalDayOfYearTokens` to use them. See: https://git.io/fxCyr\n *\n * 8. `P+` tokens do not have a defined priority since they are merely aliases to other tokens based\n * on the given locale.\n *\n * using `en-US` locale: `P` => `MM/dd/yyyy`\n * using `en-US` locale: `p` => `hh:mm a`\n * using `pt-BR` locale: `P` => `dd/MM/yyyy`\n * using `pt-BR` locale: `p` => `HH:mm`\n *\n * Values will be assigned to the date in the descending order of its unit's priority.\n * Units of an equal priority overwrite each other in the order of appearance.\n *\n * If no values of higher priority are parsed (e.g. when parsing string 'January 1st' without a year),\n * the values will be taken from 3rd argument `referenceDate` which works as a context of parsing.\n *\n * `referenceDate` must be passed for correct work of the function.\n * If you're not sure which `referenceDate` to supply, create a new instance of Date:\n * `parse('02/11/2014', 'MM/dd/yyyy', new Date())`\n * In this case parsing will be done in the context of the current date.\n * If `referenceDate` is `Invalid Date` or a value not convertible to valid `Date`,\n * then `Invalid Date` will be returned.\n *\n * The result may vary by locale.\n *\n * If `formatString` matches with `dateString` but does not provides tokens, `referenceDate` will be returned.\n *\n * If parsing failed, `Invalid Date` will be returned.\n * Invalid Date is a Date, whose time value is NaN.\n * Time value of Date: http://es5.github.io/#x15.9.1.1\n *\n * ### v2.0.0 breaking changes:\n *\n * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes).\n *\n * - Old `parse` was renamed to `toDate`.\n * Now `parse` is a new function which parses a string using a provided format.\n *\n * ```javascript\n * // Before v2.0.0\n * parse('2016-01-01')\n *\n * // v2.0.0 onward (toDate no longer accepts a string)\n * toDate(1392098430000) // Unix to timestamp\n * toDate(new Date(2014, 1, 11, 11, 30, 30)) // Cloning the date\n * parse('2016-01-01', 'yyyy-MM-dd', new Date())\n * ```\n *\n * @param {String} dateString - the string to parse\n * @param {String} formatString - the string of tokens\n * @param {Date|Number} referenceDate - defines values missing from the parsed dateString\n * @param {Object} [options] - an object with options.\n * @param {Locale} [options.locale=defaultLocale] - the locale object. See [Locale]{@link https://date-fns.org/docs/Locale}\n * @param {0|1|2|3|4|5|6} [options.weekStartsOn=0] - the index of the first day of the week (0 - Sunday)\n * @param {1|2|3|4|5|6|7} [options.firstWeekContainsDate=1] - the day of January, which is always in the first week of the year\n * @param {Boolean} [options.useAdditionalWeekYearTokens=false] - if true, allows usage of the week-numbering year tokens `YY` and `YYYY`;\n * see: https://git.io/fxCyr\n * @param {Boolean} [options.useAdditionalDayOfYearTokens=false] - if true, allows usage of the day of year tokens `D` and `DD`;\n * see: https://git.io/fxCyr\n * @returns {Date} the parsed date\n * @throws {TypeError} 3 arguments required\n * @throws {RangeError} `options.weekStartsOn` must be between 0 and 6\n * @throws {RangeError} `options.firstWeekContainsDate` must be between 1 and 7\n * @throws {RangeError} `options.locale` must contain `match` property\n * @throws {RangeError} use `yyyy` instead of `YYYY` for formatting years using [format provided] to the input [input provided]; see: https://git.io/fxCyr\n * @throws {RangeError} use `yy` instead of `YY` for formatting years using [format provided] to the input [input provided]; see: https://git.io/fxCyr\n * @throws {RangeError} use `d` instead of `D` for formatting days of the month using [format provided] to the input [input provided]; see: https://git.io/fxCyr\n * @throws {RangeError} use `dd` instead of `DD` for formatting days of the month using [format provided] to the input [input provided]; see: https://git.io/fxCyr\n * @throws {RangeError} format string contains an unescaped latin alphabet character\n *\n * @example\n * // Parse 11 February 2014 from middle-endian format:\n * var result = parse('02/11/2014', 'MM/dd/yyyy', new Date())\n * //=> Tue Feb 11 2014 00:00:00\n *\n * @example\n * // Parse 28th of February in Esperanto locale in the context of 2010 year:\n * import eo from 'date-fns/locale/eo'\n * var result = parse('28-a de februaro', \"do 'de' MMMM\", new Date(2010, 0, 1), {\n * locale: eo\n * })\n * //=> Sun Feb 28 2010 00:00:00\n */\n\nexport default function parse(dirtyDateString, dirtyFormatString, dirtyReferenceDate, dirtyOptions) {\n requiredArgs(3, arguments);\n var dateString = String(dirtyDateString);\n var formatString = String(dirtyFormatString);\n var options = dirtyOptions || {};\n var locale = options.locale || defaultLocale;\n\n if (!locale.match) {\n throw new RangeError('locale must contain match property');\n }\n\n var localeFirstWeekContainsDate = locale.options && locale.options.firstWeekContainsDate;\n var defaultFirstWeekContainsDate = localeFirstWeekContainsDate == null ? 1 : toInteger(localeFirstWeekContainsDate);\n var firstWeekContainsDate = options.firstWeekContainsDate == null ? defaultFirstWeekContainsDate : toInteger(options.firstWeekContainsDate); // Test if weekStartsOn is between 1 and 7 _and_ is not NaN\n\n if (!(firstWeekContainsDate >= 1 && firstWeekContainsDate <= 7)) {\n throw new RangeError('firstWeekContainsDate must be between 1 and 7 inclusively');\n }\n\n var localeWeekStartsOn = locale.options && locale.options.weekStartsOn;\n var defaultWeekStartsOn = localeWeekStartsOn == null ? 0 : toInteger(localeWeekStartsOn);\n var weekStartsOn = options.weekStartsOn == null ? defaultWeekStartsOn : toInteger(options.weekStartsOn); // Test if weekStartsOn is between 0 and 6 _and_ is not NaN\n\n if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) {\n throw new RangeError('weekStartsOn must be between 0 and 6 inclusively');\n }\n\n if (formatString === '') {\n if (dateString === '') {\n return toDate(dirtyReferenceDate);\n } else {\n return new Date(NaN);\n }\n }\n\n var subFnOptions = {\n firstWeekContainsDate: firstWeekContainsDate,\n weekStartsOn: weekStartsOn,\n locale: locale\n }; // If timezone isn't specified, it will be set to the system timezone\n\n var setters = [{\n priority: TIMEZONE_UNIT_PRIORITY,\n subPriority: -1,\n set: dateToSystemTimezone,\n index: 0\n }];\n var i;\n var tokens = formatString.match(longFormattingTokensRegExp).map(function (substring) {\n var firstCharacter = substring[0];\n\n if (firstCharacter === 'p' || firstCharacter === 'P') {\n var longFormatter = longFormatters[firstCharacter];\n return longFormatter(substring, locale.formatLong, subFnOptions);\n }\n\n return substring;\n }).join('').match(formattingTokensRegExp);\n var usedTokens = [];\n\n for (i = 0; i < tokens.length; i++) {\n var token = tokens[i];\n\n if (!options.useAdditionalWeekYearTokens && isProtectedWeekYearToken(token)) {\n throwProtectedError(token, formatString, dirtyDateString);\n }\n\n if (!options.useAdditionalDayOfYearTokens && isProtectedDayOfYearToken(token)) {\n throwProtectedError(token, formatString, dirtyDateString);\n }\n\n var firstCharacter = token[0];\n var parser = parsers[firstCharacter];\n\n if (parser) {\n var incompatibleTokens = parser.incompatibleTokens;\n\n if (Array.isArray(incompatibleTokens)) {\n var incompatibleToken = void 0;\n\n for (var _i = 0; _i < usedTokens.length; _i++) {\n var usedToken = usedTokens[_i].token;\n\n if (incompatibleTokens.indexOf(usedToken) !== -1 || usedToken === firstCharacter) {\n incompatibleToken = usedTokens[_i];\n break;\n }\n }\n\n if (incompatibleToken) {\n throw new RangeError(\"The format string mustn't contain `\".concat(incompatibleToken.fullToken, \"` and `\").concat(token, \"` at the same time\"));\n }\n } else if (parser.incompatibleTokens === '*' && usedTokens.length) {\n throw new RangeError(\"The format string mustn't contain `\".concat(token, \"` and any other token at the same time\"));\n }\n\n usedTokens.push({\n token: firstCharacter,\n fullToken: token\n });\n var parseResult = parser.parse(dateString, token, locale.match, subFnOptions);\n\n if (!parseResult) {\n return new Date(NaN);\n }\n\n setters.push({\n priority: parser.priority,\n subPriority: parser.subPriority || 0,\n set: parser.set,\n validate: parser.validate,\n value: parseResult.value,\n index: setters.length\n });\n dateString = parseResult.rest;\n } else {\n if (firstCharacter.match(unescapedLatinCharacterRegExp)) {\n throw new RangeError('Format string contains an unescaped latin alphabet character `' + firstCharacter + '`');\n } // Replace two single quote characters with one single quote character\n\n\n if (token === \"''\") {\n token = \"'\";\n } else if (firstCharacter === \"'\") {\n token = cleanEscapedString(token);\n } // Cut token from string, or, if string doesn't match the token, return Invalid Date\n\n\n if (dateString.indexOf(token) === 0) {\n dateString = dateString.slice(token.length);\n } else {\n return new Date(NaN);\n }\n }\n } // Check if the remaining input contains something other than whitespace\n\n\n if (dateString.length > 0 && notWhitespaceRegExp.test(dateString)) {\n return new Date(NaN);\n }\n\n var uniquePrioritySetters = setters.map(function (setter) {\n return setter.priority;\n }).sort(function (a, b) {\n return b - a;\n }).filter(function (priority, index, array) {\n return array.indexOf(priority) === index;\n }).map(function (priority) {\n return setters.filter(function (setter) {\n return setter.priority === priority;\n }).sort(function (a, b) {\n return b.subPriority - a.subPriority;\n });\n }).map(function (setterArray) {\n return setterArray[0];\n });\n var date = toDate(dirtyReferenceDate);\n\n if (isNaN(date)) {\n return new Date(NaN);\n } // Convert the date in system timezone to the same date in UTC+00:00 timezone.\n // This ensures that when UTC functions will be implemented, locales will be compatible with them.\n // See an issue about UTC functions: https://github.com/date-fns/date-fns/issues/37\n\n\n var utcDate = subMilliseconds(date, getTimezoneOffsetInMilliseconds(date));\n var flags = {};\n\n for (i = 0; i < uniquePrioritySetters.length; i++) {\n var setter = uniquePrioritySetters[i];\n\n if (setter.validate && !setter.validate(utcDate, setter.value, subFnOptions)) {\n return new Date(NaN);\n }\n\n var result = setter.set(utcDate, flags, setter.value, subFnOptions); // Result is tuple (date, flags)\n\n if (result[0]) {\n utcDate = result[0];\n assign(flags, result[1]); // Result is date\n } else {\n utcDate = result;\n }\n }\n\n return utcDate;\n}\n\nfunction dateToSystemTimezone(date, flags) {\n if (flags.timestampIsSet) {\n return date;\n }\n\n var convertedDate = new Date(0);\n convertedDate.setFullYear(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());\n convertedDate.setHours(date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds());\n return convertedDate;\n}\n\nfunction cleanEscapedString(input) {\n return input.match(escapedStringRegExp)[1].replace(doubleQuoteRegExp, \"'\");\n}","import _curry2 from './_curry2.js';\nimport _xfBase from './_xfBase.js';\n\nvar XFilter = /*#__PURE__*/function () {\n function XFilter(f, xf) {\n this.xf = xf;\n this.f = f;\n }\n XFilter.prototype['@@transducer/init'] = _xfBase.init;\n XFilter.prototype['@@transducer/result'] = _xfBase.result;\n XFilter.prototype['@@transducer/step'] = function (result, input) {\n return this.f(input) ? this.xf['@@transducer/step'](result, input) : result;\n };\n\n return XFilter;\n}();\n\nvar _xfilter = /*#__PURE__*/_curry2(function _xfilter(f, xf) {\n return new XFilter(f, xf);\n});\nexport default _xfilter;","import _curry2 from './internal/_curry2.js';\nimport _dispatchable from './internal/_dispatchable.js';\nimport _filter from './internal/_filter.js';\nimport _isObject from './internal/_isObject.js';\nimport _reduce from './internal/_reduce.js';\nimport _xfilter from './internal/_xfilter.js';\nimport keys from './keys.js';\n\n/**\n * Takes a predicate and a `Filterable`, and returns a new filterable of the\n * same type containing the members of the given filterable which satisfy the\n * given predicate. Filterable objects include plain objects or any object\n * that has a filter method such as `Array`.\n *\n * Dispatches to the `filter` method of the second argument, if present.\n *\n * Acts as a transducer if a transformer is given in list position.\n *\n * @func\n * @memberOf R\n * @since v0.1.0\n * @category List\n * @sig Filterable f => (a -> Boolean) -> f a -> f a\n * @param {Function} pred\n * @param {Array} filterable\n * @return {Array} Filterable\n * @see R.reject, R.transduce, R.addIndex\n * @example\n *\n * const isEven = n => n % 2 === 0;\n *\n * R.filter(isEven, [1, 2, 3, 4]); //=> [2, 4]\n *\n * R.filter(isEven, {a: 1, b: 2, c: 3, d: 4}); //=> {b: 2, d: 4}\n */\nvar filter = /*#__PURE__*/_curry2( /*#__PURE__*/_dispatchable(['filter'], _xfilter, function (pred, filterable) {\n return _isObject(filterable) ? _reduce(function (acc, key) {\n if (pred(filterable[key])) {\n acc[key] = filterable[key];\n }\n return acc;\n }, {}, keys(filterable)) :\n // else\n _filter(pred, filterable);\n}));\nexport default filter;","export default function _filter(fn, list) {\n var idx = 0;\n var len = list.length;\n var result = [];\n\n while (idx < len) {\n if (fn(list[idx])) {\n result[result.length] = list[idx];\n }\n idx += 1;\n }\n return result;\n}","\n\n/**\n * A function that always returns `true`. Any passed in parameters are ignored.\n *\n * @func\n * @memberOf R\n * @since v0.9.0\n * @category Function\n * @sig * -> Boolean\n * @param {*}\n * @return {Boolean}\n * @see R.F\n * @example\n *\n * R.T(); //=> true\n */\nvar T = function () {\n return true;\n};\nexport default T;","import _curry2 from './internal/_curry2.js';\n\n/**\n * Returns a partial copy of an object omitting the keys specified.\n *\n * @func\n * @memberOf R\n * @since v0.1.0\n * @category Object\n * @sig [String] -> {String: *} -> {String: *}\n * @param {Array} names an array of String property names to omit from the new object\n * @param {Object} obj The object to copy from\n * @return {Object} A new object with properties from `names` not on it.\n * @see R.pick\n * @example\n *\n * R.omit(['a', 'd'], {a: 1, b: 2, c: 3, d: 4}); //=> {b: 2, c: 3}\n */\nvar omit = /*#__PURE__*/_curry2(function omit(names, obj) {\n var result = {};\n var index = {};\n var idx = 0;\n var len = names.length;\n\n while (idx < len) {\n index[names[idx]] = 1;\n idx += 1;\n }\n\n for (var prop in obj) {\n if (!index.hasOwnProperty(prop)) {\n result[prop] = obj[prop];\n }\n }\n return result;\n});\nexport default omit;","import _curry2 from './internal/_curry2.js';\n\n/**\n * Returns the larger of its two arguments.\n *\n * @func\n * @memberOf R\n * @since v0.1.0\n * @category Relation\n * @sig Ord a => a -> a -> a\n * @param {*} a\n * @param {*} b\n * @return {*}\n * @see R.maxBy, R.min\n * @example\n *\n * R.max(789, 123); //=> 789\n * R.max('a', 'b'); //=> 'b'\n */\nvar max = /*#__PURE__*/_curry2(function max(a, b) {\n return b > a ? b : a;\n});\nexport default max;","import _arity from './internal/_arity.js';\nimport _curry1 from './internal/_curry1.js';\nimport map from './map.js';\nimport max from './max.js';\nimport reduce from './reduce.js';\n\n/**\n * Returns a function, `fn`, which encapsulates `if/else, if/else, ...` logic.\n * `R.cond` takes a list of [predicate, transformer] pairs. All of the arguments\n * to `fn` are applied to each of the predicates in turn until one returns a\n * \"truthy\" value, at which point `fn` returns the result of applying its\n * arguments to the corresponding transformer. If none of the predicates\n * matches, `fn` returns undefined.\n *\n * @func\n * @memberOf R\n * @since v0.6.0\n * @category Logic\n * @sig [[(*... -> Boolean),(*... -> *)]] -> (*... -> *)\n * @param {Array} pairs A list of [predicate, transformer]\n * @return {Function}\n * @see R.ifElse, R.unless, R.when\n * @example\n *\n * const fn = R.cond([\n * [R.equals(0), R.always('water freezes at 0°C')],\n * [R.equals(100), R.always('water boils at 100°C')],\n * [R.T, temp => 'nothing special happens at ' + temp + '°C']\n * ]);\n * fn(0); //=> 'water freezes at 0°C'\n * fn(50); //=> 'nothing special happens at 50°C'\n * fn(100); //=> 'water boils at 100°C'\n */\nvar cond = /*#__PURE__*/_curry1(function cond(pairs) {\n var arity = reduce(max, 0, map(function (pair) {\n return pair[0].length;\n }, pairs));\n return _arity(arity, function () {\n var idx = 0;\n while (idx < pairs.length) {\n if (pairs[idx][0].apply(this, arguments)) {\n return pairs[idx][1].apply(this, arguments);\n }\n idx += 1;\n }\n });\n});\nexport default cond;","export default function _pipe(f, g) {\n return function () {\n return g.call(this, f.apply(this, arguments));\n };\n}","import _arity from './internal/_arity.js';\nimport _pipe from './internal/_pipe.js';\nimport reduce from './reduce.js';\nimport tail from './tail.js';\n\n/**\n * Performs left-to-right function composition. The leftmost function may have\n * any arity; the remaining functions must be unary.\n *\n * In some libraries this function is named `sequence`.\n *\n * **Note:** The result of pipe is not automatically curried.\n *\n * @func\n * @memberOf R\n * @since v0.1.0\n * @category Function\n * @sig (((a, b, ..., n) -> o), (o -> p), ..., (x -> y), (y -> z)) -> ((a, b, ..., n) -> z)\n * @param {...Function} functions\n * @return {Function}\n * @see R.compose\n * @example\n *\n * const f = R.pipe(Math.pow, R.negate, R.inc);\n *\n * f(3, 4); // -(3^4) + 1\n * @symb R.pipe(f, g, h)(a, b) = h(g(f(a, b)))\n */\nexport default function pipe() {\n if (arguments.length === 0) {\n throw new Error('pipe requires at least one argument');\n }\n return _arity(arguments[0].length, reduce(_pipe, arguments[0], tail(arguments)));\n}","/** @license React v16.12.0\n * react.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';var h=require(\"object-assign\"),n=\"function\"===typeof Symbol&&Symbol.for,p=n?Symbol.for(\"react.element\"):60103,q=n?Symbol.for(\"react.portal\"):60106,r=n?Symbol.for(\"react.fragment\"):60107,t=n?Symbol.for(\"react.strict_mode\"):60108,u=n?Symbol.for(\"react.profiler\"):60114,v=n?Symbol.for(\"react.provider\"):60109,w=n?Symbol.for(\"react.context\"):60110,x=n?Symbol.for(\"react.forward_ref\"):60112,y=n?Symbol.for(\"react.suspense\"):60113;n&&Symbol.for(\"react.suspense_list\");\nvar z=n?Symbol.for(\"react.memo\"):60115,aa=n?Symbol.for(\"react.lazy\"):60116;n&&Symbol.for(\"react.fundamental\");n&&Symbol.for(\"react.responder\");n&&Symbol.for(\"react.scope\");var A=\"function\"===typeof Symbol&&Symbol.iterator;\nfunction B(a){for(var b=\"https://reactjs.org/docs/error-decoder.html?invariant=\"+a,c=1;cP.length&&P.push(a)}\nfunction S(a,b,c,e){var d=typeof a;if(\"undefined\"===d||\"boolean\"===d)a=null;var g=!1;if(null===a)g=!0;else switch(d){case \"string\":case \"number\":g=!0;break;case \"object\":switch(a.$$typeof){case p:case q:g=!0}}if(g)return c(e,a,\"\"===b?\".\"+T(a,0):b),1;g=0;b=\"\"===b?\".\":b+\":\";if(Array.isArray(a))for(var l=0;lb}return!1}function B(a,b,c,d,e,f){this.acceptsBooleans=2===b||3===b||4===b;this.attributeName=d;this.attributeNamespace=e;this.mustUseProperty=c;this.propertyName=a;this.type=b;this.sanitizeURL=f}var D={};\n\"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style\".split(\" \").forEach(function(a){D[a]=new B(a,0,!1,a,null,!1)});[[\"acceptCharset\",\"accept-charset\"],[\"className\",\"class\"],[\"htmlFor\",\"for\"],[\"httpEquiv\",\"http-equiv\"]].forEach(function(a){var b=a[0];D[b]=new B(b,1,!1,a[1],null,!1)});[\"contentEditable\",\"draggable\",\"spellCheck\",\"value\"].forEach(function(a){D[a]=new B(a,2,!1,a.toLowerCase(),null,!1)});\n[\"autoReverse\",\"externalResourcesRequired\",\"focusable\",\"preserveAlpha\"].forEach(function(a){D[a]=new B(a,2,!1,a,null,!1)});\"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope\".split(\" \").forEach(function(a){D[a]=new B(a,3,!1,a.toLowerCase(),null,!1)});\n[\"checked\",\"multiple\",\"muted\",\"selected\"].forEach(function(a){D[a]=new B(a,3,!0,a,null,!1)});[\"capture\",\"download\"].forEach(function(a){D[a]=new B(a,4,!1,a,null,!1)});[\"cols\",\"rows\",\"size\",\"span\"].forEach(function(a){D[a]=new B(a,6,!1,a,null,!1)});[\"rowSpan\",\"start\"].forEach(function(a){D[a]=new B(a,5,!1,a.toLowerCase(),null,!1)});var sb=/[\\-:]([a-z])/g;function tb(a){return a[1].toUpperCase()}\n\"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height\".split(\" \").forEach(function(a){var b=a.replace(sb,\ntb);D[b]=new B(b,1,!1,a,null,!1)});\"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type\".split(\" \").forEach(function(a){var b=a.replace(sb,tb);D[b]=new B(b,1,!1,a,\"http://www.w3.org/1999/xlink\",!1)});[\"xml:base\",\"xml:lang\",\"xml:space\"].forEach(function(a){var b=a.replace(sb,tb);D[b]=new B(b,1,!1,a,\"http://www.w3.org/XML/1998/namespace\",!1)});[\"tabIndex\",\"crossOrigin\"].forEach(function(a){D[a]=new B(a,1,!1,a.toLowerCase(),null,!1)});\nD.xlinkHref=new B(\"xlinkHref\",1,!1,\"xlink:href\",\"http://www.w3.org/1999/xlink\",!0);[\"src\",\"href\",\"action\",\"formAction\"].forEach(function(a){D[a]=new B(a,1,!1,a.toLowerCase(),null,!0)});function ub(a){switch(typeof a){case \"boolean\":case \"number\":case \"object\":case \"string\":case \"undefined\":return a;default:return\"\"}}\nfunction vb(a,b,c,d){var e=D.hasOwnProperty(b)?D[b]:null;var f=null!==e?0===e.type:d?!1:!(2=b.length))throw Error(u(93));b=b[0]}c=b}null==c&&(c=\"\")}a._wrapperState={initialValue:ub(c)}}\nfunction Mb(a,b){var c=ub(b.value),d=ub(b.defaultValue);null!=c&&(c=\"\"+c,c!==a.value&&(a.value=c),null==b.defaultValue&&a.defaultValue!==c&&(a.defaultValue=c));null!=d&&(a.defaultValue=\"\"+d)}function Nb(a){var b=a.textContent;b===a._wrapperState.initialValue&&\"\"!==b&&null!==b&&(a.value=b)}var Ob={html:\"http://www.w3.org/1999/xhtml\",mathml:\"http://www.w3.org/1998/Math/MathML\",svg:\"http://www.w3.org/2000/svg\"};\nfunction Pb(a){switch(a){case \"svg\":return\"http://www.w3.org/2000/svg\";case \"math\":return\"http://www.w3.org/1998/Math/MathML\";default:return\"http://www.w3.org/1999/xhtml\"}}function Qb(a,b){return null==a||\"http://www.w3.org/1999/xhtml\"===a?Pb(b):\"http://www.w3.org/2000/svg\"===a&&\"foreignObject\"===b?\"http://www.w3.org/1999/xhtml\":a}\nvar Rb,Sb=function(a){return\"undefined\"!==typeof MSApp&&MSApp.execUnsafeLocalFunction?function(b,c,d,e){MSApp.execUnsafeLocalFunction(function(){return a(b,c,d,e)})}:a}(function(a,b){if(a.namespaceURI!==Ob.svg||\"innerHTML\"in a)a.innerHTML=b;else{Rb=Rb||document.createElement(\"div\");Rb.innerHTML=\"\"+b.valueOf().toString()+\"\";for(b=Rb.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;b.firstChild;)a.appendChild(b.firstChild)}});\nfunction Tb(a,b){if(b){var c=a.firstChild;if(c&&c===a.lastChild&&3===c.nodeType){c.nodeValue=b;return}}a.textContent=b}function Ub(a,b){var c={};c[a.toLowerCase()]=b.toLowerCase();c[\"Webkit\"+a]=\"webkit\"+b;c[\"Moz\"+a]=\"moz\"+b;return c}var Vb={animationend:Ub(\"Animation\",\"AnimationEnd\"),animationiteration:Ub(\"Animation\",\"AnimationIteration\"),animationstart:Ub(\"Animation\",\"AnimationStart\"),transitionend:Ub(\"Transition\",\"TransitionEnd\")},Wb={},Xb={};\nYa&&(Xb=document.createElement(\"div\").style,\"AnimationEvent\"in window||(delete Vb.animationend.animation,delete Vb.animationiteration.animation,delete Vb.animationstart.animation),\"TransitionEvent\"in window||delete Vb.transitionend.transition);function Yb(a){if(Wb[a])return Wb[a];if(!Vb[a])return a;var b=Vb[a],c;for(c in b)if(b.hasOwnProperty(c)&&c in Xb)return Wb[a]=b[c];return a}var Zb=Yb(\"animationend\"),$b=Yb(\"animationiteration\"),ac=Yb(\"animationstart\"),bc=Yb(\"transitionend\"),cc=\"abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange waiting\".split(\" \");\nfunction ec(a){var b=a,c=a;if(a.alternate)for(;b.return;)b=b.return;else{a=b;do b=a,0!==(b.effectTag&1026)&&(c=b.return),a=b.return;while(a)}return 3===b.tag?c:null}function fc(a){if(13===a.tag){var b=a.memoizedState;null===b&&(a=a.alternate,null!==a&&(b=a.memoizedState));if(null!==b)return b.dehydrated}return null}function gc(a){if(ec(a)!==a)throw Error(u(188));}\nfunction hc(a){var b=a.alternate;if(!b){b=ec(a);if(null===b)throw Error(u(188));return b!==a?null:a}for(var c=a,d=b;;){var e=c.return;if(null===e)break;var f=e.alternate;if(null===f){d=e.return;if(null!==d){c=d;continue}break}if(e.child===f.child){for(f=e.child;f;){if(f===c)return gc(e),a;if(f===d)return gc(e),b;f=f.sibling}throw Error(u(188));}if(c.return!==d.return)c=e,d=f;else{for(var g=!1,h=e.child;h;){if(h===c){g=!0;c=e;d=f;break}if(h===d){g=!0;d=e;c=f;break}h=h.sibling}if(!g){for(h=f.child;h;){if(h===\nc){g=!0;c=f;d=e;break}if(h===d){g=!0;d=f;c=e;break}h=h.sibling}if(!g)throw Error(u(189));}}if(c.alternate!==d)throw Error(u(190));}if(3!==c.tag)throw Error(u(188));return c.stateNode.current===c?a:b}function ic(a){a=hc(a);if(!a)return null;for(var b=a;;){if(5===b.tag||6===b.tag)return b;if(b.child)b.child.return=b,b=b.child;else{if(b===a)break;for(;!b.sibling;){if(!b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}}return null}\nvar jc,kc,lc,mc=!1,nc=[],oc=null,pc=null,qc=null,rc=new Map,sc=new Map,tc=[],uc=\"mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput close cancel copy cut paste click change contextmenu reset submit\".split(\" \"),vc=\"focus blur dragenter dragleave mouseover mouseout pointerover pointerout gotpointercapture lostpointercapture\".split(\" \");\nfunction wc(a){var b=xc(a);uc.forEach(function(c){yc(c,a,b)});vc.forEach(function(c){yc(c,a,b)})}function zc(a,b,c,d){return{blockedOn:a,topLevelType:b,eventSystemFlags:c|32,nativeEvent:d}}function Ac(a,b){switch(a){case \"focus\":case \"blur\":oc=null;break;case \"dragenter\":case \"dragleave\":pc=null;break;case \"mouseover\":case \"mouseout\":qc=null;break;case \"pointerover\":case \"pointerout\":rc.delete(b.pointerId);break;case \"gotpointercapture\":case \"lostpointercapture\":sc.delete(b.pointerId)}}\nfunction Bc(a,b,c,d,e){if(null===a||a.nativeEvent!==e)return a=zc(b,c,d,e),null!==b&&(b=Cc(b),null!==b&&kc(b)),a;a.eventSystemFlags|=d;return a}function Dc(a,b,c,d){switch(b){case \"focus\":return oc=Bc(oc,a,b,c,d),!0;case \"dragenter\":return pc=Bc(pc,a,b,c,d),!0;case \"mouseover\":return qc=Bc(qc,a,b,c,d),!0;case \"pointerover\":var e=d.pointerId;rc.set(e,Bc(rc.get(e)||null,a,b,c,d));return!0;case \"gotpointercapture\":return e=d.pointerId,sc.set(e,Bc(sc.get(e)||null,a,b,c,d)),!0}return!1}\nfunction Ec(a){var b=Fc(a.target);if(null!==b){var c=ec(b);if(null!==c)if(b=c.tag,13===b){if(b=fc(c),null!==b){a.blockedOn=b;q.unstable_runWithPriority(a.priority,function(){lc(c)});return}}else if(3===b&&c.stateNode.hydrate){a.blockedOn=3===c.tag?c.stateNode.containerInfo:null;return}}a.blockedOn=null}function Gc(a){if(null!==a.blockedOn)return!1;var b=Hc(a.topLevelType,a.eventSystemFlags,a.nativeEvent);if(null!==b){var c=Cc(b);null!==c&&kc(c);a.blockedOn=b;return!1}return!0}\nfunction Ic(a,b,c){Gc(a)&&c.delete(b)}function Jc(){for(mc=!1;0this.eventPool.length&&this.eventPool.push(a)}function Vc(a){a.eventPool=[];a.getPooled=Wc;a.release=Xc}var Yc=E.extend({animationName:null,elapsedTime:null,pseudoElement:null}),Zc=E.extend({clipboardData:function(a){return\"clipboardData\"in a?a.clipboardData:window.clipboardData}}),$c=E.extend({view:null,detail:null}),ad=$c.extend({relatedTarget:null});\nfunction bd(a){var b=a.keyCode;\"charCode\"in a?(a=a.charCode,0===a&&13===b&&(a=13)):a=b;10===a&&(a=13);return 32<=a||13===a?a:0}\nvar cd={Esc:\"Escape\",Spacebar:\" \",Left:\"ArrowLeft\",Up:\"ArrowUp\",Right:\"ArrowRight\",Down:\"ArrowDown\",Del:\"Delete\",Win:\"OS\",Menu:\"ContextMenu\",Apps:\"ContextMenu\",Scroll:\"ScrollLock\",MozPrintableKey:\"Unidentified\"},dd={8:\"Backspace\",9:\"Tab\",12:\"Clear\",13:\"Enter\",16:\"Shift\",17:\"Control\",18:\"Alt\",19:\"Pause\",20:\"CapsLock\",27:\"Escape\",32:\" \",33:\"PageUp\",34:\"PageDown\",35:\"End\",36:\"Home\",37:\"ArrowLeft\",38:\"ArrowUp\",39:\"ArrowRight\",40:\"ArrowDown\",45:\"Insert\",46:\"Delete\",112:\"F1\",113:\"F2\",114:\"F3\",115:\"F4\",\n116:\"F5\",117:\"F6\",118:\"F7\",119:\"F8\",120:\"F9\",121:\"F10\",122:\"F11\",123:\"F12\",144:\"NumLock\",145:\"ScrollLock\",224:\"Meta\"},ed={Alt:\"altKey\",Control:\"ctrlKey\",Meta:\"metaKey\",Shift:\"shiftKey\"};function gd(a){var b=this.nativeEvent;return b.getModifierState?b.getModifierState(a):(a=ed[a])?!!b[a]:!1}function hd(){return gd}\nvar id=$c.extend({key:function(a){if(a.key){var b=cd[a.key]||a.key;if(\"Unidentified\"!==b)return b}return\"keypress\"===a.type?(a=bd(a),13===a?\"Enter\":String.fromCharCode(a)):\"keydown\"===a.type||\"keyup\"===a.type?dd[a.keyCode]||\"Unidentified\":\"\"},location:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,repeat:null,locale:null,getModifierState:hd,charCode:function(a){return\"keypress\"===a.type?bd(a):0},keyCode:function(a){return\"keydown\"===a.type||\"keyup\"===a.type?a.keyCode:0},which:function(a){return\"keypress\"===\na.type?bd(a):\"keydown\"===a.type||\"keyup\"===a.type?a.keyCode:0}}),jd=0,kd=0,ld=!1,md=!1,nd=$c.extend({screenX:null,screenY:null,clientX:null,clientY:null,pageX:null,pageY:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,getModifierState:hd,button:null,buttons:null,relatedTarget:function(a){return a.relatedTarget||(a.fromElement===a.srcElement?a.toElement:a.fromElement)},movementX:function(a){if(\"movementX\"in a)return a.movementX;var b=jd;jd=a.screenX;return ld?\"mousemove\"===a.type?a.screenX-\nb:0:(ld=!0,0)},movementY:function(a){if(\"movementY\"in a)return a.movementY;var b=kd;kd=a.screenY;return md?\"mousemove\"===a.type?a.screenY-b:0:(md=!0,0)}}),od=nd.extend({pointerId:null,width:null,height:null,pressure:null,tangentialPressure:null,tiltX:null,tiltY:null,twist:null,pointerType:null,isPrimary:null}),pd=nd.extend({dataTransfer:null}),qd=$c.extend({touches:null,targetTouches:null,changedTouches:null,altKey:null,metaKey:null,ctrlKey:null,shiftKey:null,getModifierState:hd}),rd=E.extend({propertyName:null,\nelapsedTime:null,pseudoElement:null}),sd=nd.extend({deltaX:function(a){return\"deltaX\"in a?a.deltaX:\"wheelDeltaX\"in a?-a.wheelDeltaX:0},deltaY:function(a){return\"deltaY\"in a?a.deltaY:\"wheelDeltaY\"in a?-a.wheelDeltaY:\"wheelDelta\"in a?-a.wheelDelta:0},deltaZ:null,deltaMode:null}),td=[[\"blur\",\"blur\",0],[\"cancel\",\"cancel\",0],[\"click\",\"click\",0],[\"close\",\"close\",0],[\"contextmenu\",\"contextMenu\",0],[\"copy\",\"copy\",0],[\"cut\",\"cut\",0],[\"auxclick\",\"auxClick\",0],[\"dblclick\",\"doubleClick\",0],[\"dragend\",\"dragEnd\",\n0],[\"dragstart\",\"dragStart\",0],[\"drop\",\"drop\",0],[\"focus\",\"focus\",0],[\"input\",\"input\",0],[\"invalid\",\"invalid\",0],[\"keydown\",\"keyDown\",0],[\"keypress\",\"keyPress\",0],[\"keyup\",\"keyUp\",0],[\"mousedown\",\"mouseDown\",0],[\"mouseup\",\"mouseUp\",0],[\"paste\",\"paste\",0],[\"pause\",\"pause\",0],[\"play\",\"play\",0],[\"pointercancel\",\"pointerCancel\",0],[\"pointerdown\",\"pointerDown\",0],[\"pointerup\",\"pointerUp\",0],[\"ratechange\",\"rateChange\",0],[\"reset\",\"reset\",0],[\"seeked\",\"seeked\",0],[\"submit\",\"submit\",0],[\"touchcancel\",\"touchCancel\",\n0],[\"touchend\",\"touchEnd\",0],[\"touchstart\",\"touchStart\",0],[\"volumechange\",\"volumeChange\",0],[\"drag\",\"drag\",1],[\"dragenter\",\"dragEnter\",1],[\"dragexit\",\"dragExit\",1],[\"dragleave\",\"dragLeave\",1],[\"dragover\",\"dragOver\",1],[\"mousemove\",\"mouseMove\",1],[\"mouseout\",\"mouseOut\",1],[\"mouseover\",\"mouseOver\",1],[\"pointermove\",\"pointerMove\",1],[\"pointerout\",\"pointerOut\",1],[\"pointerover\",\"pointerOver\",1],[\"scroll\",\"scroll\",1],[\"toggle\",\"toggle\",1],[\"touchmove\",\"touchMove\",1],[\"wheel\",\"wheel\",1],[\"abort\",\"abort\",\n2],[Zb,\"animationEnd\",2],[$b,\"animationIteration\",2],[ac,\"animationStart\",2],[\"canplay\",\"canPlay\",2],[\"canplaythrough\",\"canPlayThrough\",2],[\"durationchange\",\"durationChange\",2],[\"emptied\",\"emptied\",2],[\"encrypted\",\"encrypted\",2],[\"ended\",\"ended\",2],[\"error\",\"error\",2],[\"gotpointercapture\",\"gotPointerCapture\",2],[\"load\",\"load\",2],[\"loadeddata\",\"loadedData\",2],[\"loadedmetadata\",\"loadedMetadata\",2],[\"loadstart\",\"loadStart\",2],[\"lostpointercapture\",\"lostPointerCapture\",2],[\"playing\",\"playing\",2],[\"progress\",\n\"progress\",2],[\"seeking\",\"seeking\",2],[\"stalled\",\"stalled\",2],[\"suspend\",\"suspend\",2],[\"timeupdate\",\"timeUpdate\",2],[bc,\"transitionEnd\",2],[\"waiting\",\"waiting\",2]],ud={},vd={},wd=0;for(;wd=b)return{node:c,offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=ce(c)}}\nfunction ee(a,b){return a&&b?a===b?!0:a&&3===a.nodeType?!1:b&&3===b.nodeType?ee(a,b.parentNode):\"contains\"in a?a.contains(b):a.compareDocumentPosition?!!(a.compareDocumentPosition(b)&16):!1:!1}function fe(){for(var a=window,b=be();b instanceof a.HTMLIFrameElement;){try{var c=\"string\"===typeof b.contentWindow.location.href}catch(d){c=!1}if(c)a=b.contentWindow;else break;b=be(a.document)}return b}\nfunction ge(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&(\"input\"===b&&(\"text\"===a.type||\"search\"===a.type||\"tel\"===a.type||\"url\"===a.type||\"password\"===a.type)||\"textarea\"===b||\"true\"===a.contentEditable)}var he=\"$\",ie=\"/$\",je=\"$?\",ke=\"$!\",le=null,me=null;function ne(a,b){switch(a){case \"button\":case \"input\":case \"select\":case \"textarea\":return!!b.autoFocus}return!1}\nfunction oe(a,b){return\"textarea\"===a||\"option\"===a||\"noscript\"===a||\"string\"===typeof b.children||\"number\"===typeof b.children||\"object\"===typeof b.dangerouslySetInnerHTML&&null!==b.dangerouslySetInnerHTML&&null!=b.dangerouslySetInnerHTML.__html}var pe=\"function\"===typeof setTimeout?setTimeout:void 0,qe=\"function\"===typeof clearTimeout?clearTimeout:void 0;function re(a){for(;null!=a;a=a.nextSibling){var b=a.nodeType;if(1===b||3===b)break}return a}\nfunction se(a){a=a.previousSibling;for(var b=0;a;){if(8===a.nodeType){var c=a.data;if(c===he||c===ke||c===je){if(0===b)return a;b--}else c===ie&&b++}a=a.previousSibling}return null}var te=Math.random().toString(36).slice(2),ue=\"__reactInternalInstance$\"+te,ve=\"__reactEventHandlers$\"+te,we=\"__reactContainere$\"+te;\nfunction Fc(a){var b=a[ue];if(b)return b;for(var c=a.parentNode;c;){if(b=c[we]||c[ue]){c=b.alternate;if(null!==b.child||null!==c&&null!==c.child)for(a=se(a);null!==a;){if(c=a[ue])return c;a=se(a)}return b}a=c;c=a.parentNode}return null}function Cc(a){a=a[ue]||a[we];return!a||5!==a.tag&&6!==a.tag&&13!==a.tag&&3!==a.tag?null:a}function xe(a){if(5===a.tag||6===a.tag)return a.stateNode;throw Error(u(33));}function ye(a){return a[ve]||null}var ze=null,Ae=null,Be=null;\nfunction Ce(){if(Be)return Be;var a,b=Ae,c=b.length,d,e=\"value\"in ze?ze.value:ze.textContent,f=e.length;for(a=0;a=He),Ke=String.fromCharCode(32),Le={beforeInput:{phasedRegistrationNames:{bubbled:\"onBeforeInput\",captured:\"onBeforeInputCapture\"},dependencies:[\"compositionend\",\"keypress\",\"textInput\",\"paste\"]},compositionEnd:{phasedRegistrationNames:{bubbled:\"onCompositionEnd\",captured:\"onCompositionEndCapture\"},dependencies:\"blur compositionend keydown keypress keyup mousedown\".split(\" \")},compositionStart:{phasedRegistrationNames:{bubbled:\"onCompositionStart\",\ncaptured:\"onCompositionStartCapture\"},dependencies:\"blur compositionstart keydown keypress keyup mousedown\".split(\" \")},compositionUpdate:{phasedRegistrationNames:{bubbled:\"onCompositionUpdate\",captured:\"onCompositionUpdateCapture\"},dependencies:\"blur compositionupdate keydown keypress keyup mousedown\".split(\" \")}},Me=!1;\nfunction Ne(a,b){switch(a){case \"keyup\":return-1!==Fe.indexOf(b.keyCode);case \"keydown\":return 229!==b.keyCode;case \"keypress\":case \"mousedown\":case \"blur\":return!0;default:return!1}}function Oe(a){a=a.detail;return\"object\"===typeof a&&\"data\"in a?a.data:null}var Pe=!1;function Qe(a,b){switch(a){case \"compositionend\":return Oe(b);case \"keypress\":if(32!==b.which)return null;Me=!0;return Ke;case \"textInput\":return a=b.data,a===Ke&&Me?null:a;default:return null}}\nfunction Re(a,b){if(Pe)return\"compositionend\"===a||!Ge&&Ne(a,b)?(a=Ce(),Be=Ae=ze=null,Pe=!1,a):null;switch(a){case \"paste\":return null;case \"keypress\":if(!(b.ctrlKey||b.altKey||b.metaKey)||b.ctrlKey&&b.altKey){if(b.char&&1=document.documentMode,sf={select:{phasedRegistrationNames:{bubbled:\"onSelect\",captured:\"onSelectCapture\"},dependencies:\"blur contextmenu dragend focus keydown keyup mousedown mouseup selectionchange\".split(\" \")}},tf=null,uf=null,vf=null,wf=!1;\nfunction xf(a,b){var c=b.window===b?b.document:9===b.nodeType?b:b.ownerDocument;if(wf||null==tf||tf!==be(c))return null;c=tf;\"selectionStart\"in c&&ge(c)?c={start:c.selectionStart,end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset});return vf&&qf(vf,c)?null:(vf=c,a=E.getPooled(sf.select,uf,a,b),a.type=\"select\",a.target=tf,Sc(a),a)}\nvar yf={eventTypes:sf,extractEvents:function(a,b,c,d){var e=d.window===d?d.document:9===d.nodeType?d:d.ownerDocument,f;if(!(f=!e)){a:{e=xc(e);f=ja.onSelect;for(var g=0;gBf||(a.current=Af[Bf],Af[Bf]=null,Bf--)}\nfunction I(a,b){Bf++;Af[Bf]=a.current;a.current=b}var Cf={},J={current:Cf},K={current:!1},Df=Cf;function Ef(a,b){var c=a.type.contextTypes;if(!c)return Cf;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=b,a.__reactInternalMemoizedMaskedChildContext=e);return e}function L(a){a=a.childContextTypes;return null!==a&&void 0!==a}\nfunction Ff(a){G(K,a);G(J,a)}function Gf(a){G(K,a);G(J,a)}function Hf(a,b,c){if(J.current!==Cf)throw Error(u(168));I(J,b,a);I(K,c,a)}function If(a,b,c){var d=a.stateNode;a=b.childContextTypes;if(\"function\"!==typeof d.getChildContext)return c;d=d.getChildContext();for(var e in d)if(!(e in a))throw Error(u(108,Wa(b)||\"Unknown\",e));return n({},c,{},d)}function Jf(a){var b=a.stateNode;b=b&&b.__reactInternalMemoizedMergedChildContext||Cf;Df=J.current;I(J,b,a);I(K,K.current,a);return!0}\nfunction Kf(a,b,c){var d=a.stateNode;if(!d)throw Error(u(169));c?(b=If(a,b,Df),d.__reactInternalMemoizedMergedChildContext=b,G(K,a),G(J,a),I(J,b,a)):G(K,a);I(K,c,a)}\nvar Lf=q.unstable_runWithPriority,Mf=q.unstable_scheduleCallback,Nf=q.unstable_cancelCallback,Of=q.unstable_shouldYield,Pf=q.unstable_requestPaint,Qf=q.unstable_now,Rf=q.unstable_getCurrentPriorityLevel,Sf=q.unstable_ImmediatePriority,Tf=q.unstable_UserBlockingPriority,Uf=q.unstable_NormalPriority,Vf=q.unstable_LowPriority,Wf=q.unstable_IdlePriority,Xf={},Yf=void 0!==Pf?Pf:function(){},Zf=null,$f=null,ag=!1,bg=Qf(),cg=1E4>bg?Qf:function(){return Qf()-bg};\nfunction dg(){switch(Rf()){case Sf:return 99;case Tf:return 98;case Uf:return 97;case Vf:return 96;case Wf:return 95;default:throw Error(u(332));}}function eg(a){switch(a){case 99:return Sf;case 98:return Tf;case 97:return Uf;case 96:return Vf;case 95:return Wf;default:throw Error(u(332));}}function fg(a,b){a=eg(a);return Lf(a,b)}function gg(a,b,c){a=eg(a);return Mf(a,b,c)}function hg(a){null===Zf?(Zf=[a],$f=Mf(Sf,ig)):Zf.push(a);return Xf}function jg(){if(null!==$f){var a=$f;$f=null;Nf(a)}ig()}\nfunction ig(){if(!ag&&null!==Zf){ag=!0;var a=0;try{var b=Zf;fg(99,function(){for(;a=b&&(wg=!0),a.firstContext=null)}function xg(a,b){if(qg!==a&&!1!==b&&0!==b){if(\"number\"!==typeof b||1073741823===b)qg=a,b=1073741823;b={context:a,observedBits:b,next:null};if(null===pg){if(null===og)throw Error(u(308));pg=b;og.dependencies={expirationTime:0,firstContext:b,responders:null}}else pg=pg.next=b}return a._currentValue}var yg=!1;\nfunction zg(a){return{baseState:a,firstUpdate:null,lastUpdate:null,firstCapturedUpdate:null,lastCapturedUpdate:null,firstEffect:null,lastEffect:null,firstCapturedEffect:null,lastCapturedEffect:null}}function Ag(a){return{baseState:a.baseState,firstUpdate:a.firstUpdate,lastUpdate:a.lastUpdate,firstCapturedUpdate:null,lastCapturedUpdate:null,firstEffect:null,lastEffect:null,firstCapturedEffect:null,lastCapturedEffect:null}}\nfunction Bg(a,b){return{expirationTime:a,suspenseConfig:b,tag:0,payload:null,callback:null,next:null,nextEffect:null}}function Cg(a,b){null===a.lastUpdate?a.firstUpdate=a.lastUpdate=b:(a.lastUpdate.next=b,a.lastUpdate=b)}\nfunction Dg(a,b){var c=a.alternate;if(null===c){var d=a.updateQueue;var e=null;null===d&&(d=a.updateQueue=zg(a.memoizedState))}else d=a.updateQueue,e=c.updateQueue,null===d?null===e?(d=a.updateQueue=zg(a.memoizedState),e=c.updateQueue=zg(c.memoizedState)):d=a.updateQueue=Ag(e):null===e&&(e=c.updateQueue=Ag(d));null===e||d===e?Cg(d,b):null===d.lastUpdate||null===e.lastUpdate?(Cg(d,b),Cg(e,b)):(Cg(d,b),e.lastUpdate=b)}\nfunction Eg(a,b){var c=a.updateQueue;c=null===c?a.updateQueue=zg(a.memoizedState):Fg(a,c);null===c.lastCapturedUpdate?c.firstCapturedUpdate=c.lastCapturedUpdate=b:(c.lastCapturedUpdate.next=b,c.lastCapturedUpdate=b)}function Fg(a,b){var c=a.alternate;null!==c&&b===c.updateQueue&&(b=a.updateQueue=Ag(b));return b}\nfunction Gg(a,b,c,d,e,f){switch(c.tag){case 1:return a=c.payload,\"function\"===typeof a?a.call(f,d,e):a;case 3:a.effectTag=a.effectTag&-4097|64;case 0:a=c.payload;e=\"function\"===typeof a?a.call(f,d,e):a;if(null===e||void 0===e)break;return n({},d,e);case 2:yg=!0}return d}\nfunction Hg(a,b,c,d,e){yg=!1;b=Fg(a,b);for(var f=b.baseState,g=null,h=0,k=b.firstUpdate,l=f;null!==k;){var m=k.expirationTime;mx?(A=r,r=null):A=r.sibling;var p=y(e,r,h[x],k);if(null===p){null===r&&(r=A);break}a&&\nr&&null===p.alternate&&b(e,r);g=f(p,g,x);null===m?l=p:m.sibling=p;m=p;r=A}if(x===h.length)return c(e,r),l;if(null===r){for(;xx?(A=r,r=null):A=r.sibling;var z=y(e,r,p.value,k);if(null===z){null===r&&(r=A);break}a&&r&&null===z.alternate&&b(e,r);g=f(z,g,x);null===m?l=z:m.sibling=z;m=z;r=A}if(p.done)return c(e,r),l;if(null===r){for(;!p.done;x++,p=h.next())p=C(e,p.value,k),null!==p&&(g=f(p,g,x),null===m?l=p:m.sibling=p,m=p);return l}for(r=d(e,r);!p.done;x++,p=h.next())p=H(r,e,x,p.value,k),null!==p&&(a&&null!==\np.alternate&&r.delete(null===p.key?x:p.key),g=f(p,g,x),null===m?l=p:m.sibling=p,m=p);a&&r.forEach(function(a){return b(e,a)});return l}return function(a,d,f,h){var k=\"object\"===typeof f&&null!==f&&f.type===Ia&&null===f.key;k&&(f=f.props.children);var l=\"object\"===typeof f&&null!==f;if(l)switch(f.$$typeof){case Ga:a:{l=f.key;for(k=d;null!==k;){if(k.key===l)if(7===k.tag?f.type===Ia:k.elementType===f.type){c(a,k.sibling);d=e(k,f.type===Ia?f.props.children:f.props,h);d.ref=Yg(a,k,f);d.return=a;a=d;break a}else{c(a,\nk);break}else b(a,k);k=k.sibling}f.type===Ia?(d=eh(f.props.children,a.mode,h,f.key),d.return=a,a=d):(h=ch(f.type,f.key,f.props,null,a.mode,h),h.ref=Yg(a,d,f),h.return=a,a=h)}return g(a);case Ha:a:{for(k=f.key;null!==d;){if(d.key===k)if(4===d.tag&&d.stateNode.containerInfo===f.containerInfo&&d.stateNode.implementation===f.implementation){c(a,d.sibling);d=e(d,f.children||[],h);d.return=a;a=d;break a}else{c(a,d);break}else b(a,d);d=d.sibling}d=dh(f,a.mode,h);d.return=a;a=d}return g(a)}if(\"string\"===\ntypeof f||\"number\"===typeof f)return f=\"\"+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f,h),d.return=a,a=d):(c(a,d),d=bh(f,a.mode,h),d.return=a,a=d),g(a);if(Xg(f))return z(a,d,f,h);if(Ua(f))return ta(a,d,f,h);l&&Zg(a,f);if(\"undefined\"===typeof f&&!k)switch(a.tag){case 1:case 0:throw a=a.type,Error(u(152,a.displayName||a.name||\"Component\"));}return c(a,d)}}var fh=$g(!0),gh=$g(!1),hh={},ih={current:hh},jh={current:hh},kh={current:hh};function lh(a){if(a===hh)throw Error(u(174));return a}\nfunction mh(a,b){I(kh,b,a);I(jh,a,a);I(ih,hh,a);var c=b.nodeType;switch(c){case 9:case 11:b=(b=b.documentElement)?b.namespaceURI:Qb(null,\"\");break;default:c=8===c?b.parentNode:b,b=c.namespaceURI||null,c=c.tagName,b=Qb(b,c)}G(ih,a);I(ih,b,a)}function nh(a){G(ih,a);G(jh,a);G(kh,a)}function oh(a){lh(kh.current);var b=lh(ih.current);var c=Qb(b,a.type);b!==c&&(I(jh,a,a),I(ih,c,a))}function ph(a){jh.current===a&&(G(ih,a),G(jh,a))}var M={current:0};\nfunction qh(a){for(var b=a;null!==b;){if(13===b.tag){var c=b.memoizedState;if(null!==c&&(c=c.dehydrated,null===c||c.data===je||c.data===ke))return b}else if(19===b.tag&&void 0!==b.memoizedProps.revealOrder){if(0!==(b.effectTag&64))return b}else if(null!==b.child){b.child.return=b;b=b.child;continue}if(b===a)break;for(;null===b.sibling;){if(null===b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}return null}function rh(a,b){return{responder:a,props:b}}\nvar sh=Ea.ReactCurrentDispatcher,N=Ea.ReactCurrentBatchConfig,th=0,uh=null,O=null,vh=null,wh=null,P=null,xh=null,yh=0,zh=null,Ah=0,Bh=!1,Ch=null,Gh=0;function Q(){throw Error(u(321));}function Hh(a,b){if(null===b)return!1;for(var c=0;cyh&&(yh=m,Jg(yh))):(Ig(m,k.suspenseConfig),f=k.eagerReducer===a?k.eagerState:a(f,k.action));g=k;k=k.next}while(null!==k&&k!==d);l||(h=g,e=f);of(f,b.memoizedState)||(wg=!0);b.memoizedState=f;b.baseUpdate=h;b.baseState=e;c.lastRenderedState=f}return[b.memoizedState,c.dispatch]}\nfunction Rh(a){var b=Nh();\"function\"===typeof a&&(a=a());b.memoizedState=b.baseState=a;a=b.queue={last:null,dispatch:null,lastRenderedReducer:Ph,lastRenderedState:a};a=a.dispatch=Sh.bind(null,uh,a);return[b.memoizedState,a]}function Th(a){return Qh(Ph,a)}function Uh(a,b,c,d){a={tag:a,create:b,destroy:c,deps:d,next:null};null===zh?(zh={lastEffect:null},zh.lastEffect=a.next=a):(b=zh.lastEffect,null===b?zh.lastEffect=a.next=a:(c=b.next,b.next=a,a.next=c,zh.lastEffect=a));return a}\nfunction Vh(a,b,c,d){var e=Nh();Ah|=a;e.memoizedState=Uh(b,c,void 0,void 0===d?null:d)}function Wh(a,b,c,d){var e=Oh();d=void 0===d?null:d;var f=void 0;if(null!==O){var g=O.memoizedState;f=g.destroy;if(null!==d&&Hh(d,g.deps)){Uh(0,c,f,d);return}}Ah|=a;e.memoizedState=Uh(b,c,f,d)}function Xh(a,b){return Vh(516,192,a,b)}function Yh(a,b){return Wh(516,192,a,b)}\nfunction Zh(a,b){if(\"function\"===typeof b)return a=a(),b(a),function(){b(null)};if(null!==b&&void 0!==b)return a=a(),b.current=a,function(){b.current=null}}function $h(){}function ai(a,b){Nh().memoizedState=[a,void 0===b?null:b];return a}function bi(a,b){var c=Oh();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&Hh(b,d[1]))return d[0];c.memoizedState=[a,b];return a}\nfunction Sh(a,b,c){if(!(25>Gh))throw Error(u(301));var d=a.alternate;if(a===uh||null!==d&&d===uh)if(Bh=!0,a={expirationTime:th,suspenseConfig:null,action:c,eagerReducer:null,eagerState:null,next:null},null===Ch&&(Ch=new Map),c=Ch.get(b),void 0===c)Ch.set(b,a);else{for(b=c;null!==b.next;)b=b.next;b.next=a}else{var e=Pg(),f=Mg.suspense;e=Qg(e,a,f);f={expirationTime:e,suspenseConfig:f,action:c,eagerReducer:null,eagerState:null,next:null};var g=b.last;if(null===g)f.next=f;else{var h=g.next;null!==h&&\n(f.next=h);g.next=f}b.last=f;if(0===a.expirationTime&&(null===d||0===d.expirationTime)&&(d=b.lastRenderedReducer,null!==d))try{var k=b.lastRenderedState,l=d(k,c);f.eagerReducer=d;f.eagerState=l;if(of(l,k))return}catch(m){}finally{}Rg(a,e)}}\nvar Lh={readContext:xg,useCallback:Q,useContext:Q,useEffect:Q,useImperativeHandle:Q,useLayoutEffect:Q,useMemo:Q,useReducer:Q,useRef:Q,useState:Q,useDebugValue:Q,useResponder:Q,useDeferredValue:Q,useTransition:Q},Jh={readContext:xg,useCallback:ai,useContext:xg,useEffect:Xh,useImperativeHandle:function(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;return Vh(4,36,Zh.bind(null,b,a),c)},useLayoutEffect:function(a,b){return Vh(4,36,a,b)},useMemo:function(a,b){var c=Nh();b=void 0===b?null:b;a=a();c.memoizedState=\n[a,b];return a},useReducer:function(a,b,c){var d=Nh();b=void 0!==c?c(b):b;d.memoizedState=d.baseState=b;a=d.queue={last:null,dispatch:null,lastRenderedReducer:a,lastRenderedState:b};a=a.dispatch=Sh.bind(null,uh,a);return[d.memoizedState,a]},useRef:function(a){var b=Nh();a={current:a};return b.memoizedState=a},useState:Rh,useDebugValue:$h,useResponder:rh,useDeferredValue:function(a,b){var c=Rh(a),d=c[0],e=c[1];Xh(function(){q.unstable_next(function(){var c=N.suspense;N.suspense=void 0===b?null:b;try{e(a)}finally{N.suspense=\nc}})},[a,b]);return d},useTransition:function(a){var b=Rh(!1),c=b[0],d=b[1];return[ai(function(b){d(!0);q.unstable_next(function(){var c=N.suspense;N.suspense=void 0===a?null:a;try{d(!1),b()}finally{N.suspense=c}})},[a,c]),c]}},Kh={readContext:xg,useCallback:bi,useContext:xg,useEffect:Yh,useImperativeHandle:function(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;return Wh(4,36,Zh.bind(null,b,a),c)},useLayoutEffect:function(a,b){return Wh(4,36,a,b)},useMemo:function(a,b){var c=Oh();b=void 0===b?\nnull:b;var d=c.memoizedState;if(null!==d&&null!==b&&Hh(b,d[1]))return d[0];a=a();c.memoizedState=[a,b];return a},useReducer:Qh,useRef:function(){return Oh().memoizedState},useState:Th,useDebugValue:$h,useResponder:rh,useDeferredValue:function(a,b){var c=Th(a),d=c[0],e=c[1];Yh(function(){q.unstable_next(function(){var c=N.suspense;N.suspense=void 0===b?null:b;try{e(a)}finally{N.suspense=c}})},[a,b]);return d},useTransition:function(a){var b=Th(!1),c=b[0],d=b[1];return[bi(function(b){d(!0);q.unstable_next(function(){var c=\nN.suspense;N.suspense=void 0===a?null:a;try{d(!1),b()}finally{N.suspense=c}})},[a,c]),c]}},ci=null,di=null,ei=!1;function fi(a,b){var c=gi(5,null,null,0);c.elementType=\"DELETED\";c.type=\"DELETED\";c.stateNode=b;c.return=a;c.effectTag=8;null!==a.lastEffect?(a.lastEffect.nextEffect=c,a.lastEffect=c):a.firstEffect=a.lastEffect=c}\nfunction hi(a,b){switch(a.tag){case 5:var c=a.type;b=1!==b.nodeType||c.toLowerCase()!==b.nodeName.toLowerCase()?null:b;return null!==b?(a.stateNode=b,!0):!1;case 6:return b=\"\"===a.pendingProps||3!==b.nodeType?null:b,null!==b?(a.stateNode=b,!0):!1;case 13:return!1;default:return!1}}\nfunction ii(a){if(ei){var b=di;if(b){var c=b;if(!hi(a,b)){b=re(c.nextSibling);if(!b||!hi(a,b)){a.effectTag=a.effectTag&-1025|2;ei=!1;ci=a;return}fi(ci,c)}ci=a;di=re(b.firstChild)}else a.effectTag=a.effectTag&-1025|2,ei=!1,ci=a}}function ji(a){for(a=a.return;null!==a&&5!==a.tag&&3!==a.tag&&13!==a.tag;)a=a.return;ci=a}\nfunction ki(a){if(a!==ci)return!1;if(!ei)return ji(a),ei=!0,!1;var b=a.type;if(5!==a.tag||\"head\"!==b&&\"body\"!==b&&!oe(b,a.memoizedProps))for(b=di;b;)fi(a,b),b=re(b.nextSibling);ji(a);if(13===a.tag){a=a.memoizedState;a=null!==a?a.dehydrated:null;if(!a)throw Error(u(317));a:{a=a.nextSibling;for(b=0;a;){if(8===a.nodeType){var c=a.data;if(c===ie){if(0===b){di=re(a.nextSibling);break a}b--}else c!==he&&c!==ke&&c!==je||b++}a=a.nextSibling}di=null}}else di=ci?re(a.stateNode.nextSibling):null;return!0}\nfunction li(){di=ci=null;ei=!1}var mi=Ea.ReactCurrentOwner,wg=!1;function R(a,b,c,d){b.child=null===a?gh(b,null,c,d):fh(b,a.child,c,d)}function ni(a,b,c,d,e){c=c.render;var f=b.ref;vg(b,e);d=Ih(a,b,c,d,f,e);if(null!==a&&!wg)return b.updateQueue=a.updateQueue,b.effectTag&=-517,a.expirationTime<=e&&(a.expirationTime=0),oi(a,b,e);b.effectTag|=1;R(a,b,d,e);return b.child}\nfunction pi(a,b,c,d,e,f){if(null===a){var g=c.type;if(\"function\"===typeof g&&!qi(g)&&void 0===g.defaultProps&&null===c.compare&&void 0===c.defaultProps)return b.tag=15,b.type=g,ri(a,b,g,d,e,f);a=ch(c.type,null,d,null,b.mode,f);a.ref=b.ref;a.return=b;return b.child=a}g=a.child;if(eb)&&Gj.set(a,b)))}}\nfunction Kj(a,b){a.expirationTimea?b:a}\nfunction Z(a){if(0!==a.lastExpiredTime)a.callbackExpirationTime=1073741823,a.callbackPriority=99,a.callbackNode=hg(Lj.bind(null,a));else{var b=Oj(a),c=a.callbackNode;if(0===b)null!==c&&(a.callbackNode=null,a.callbackExpirationTime=0,a.callbackPriority=90);else{var d=Pg();1073741823===b?d=99:1===b||2===b?d=95:(d=10*(1073741821-b)-10*(1073741821-d),d=0>=d?99:250>=d?98:5250>=d?97:95);if(null!==c){var e=a.callbackPriority;if(a.callbackExpirationTime===b&&e>=d)return;c!==Xf&&Nf(c)}a.callbackExpirationTime=\nb;a.callbackPriority=d;b=1073741823===b?hg(Lj.bind(null,a)):gg(d,Qj.bind(null,a),{timeout:10*(1073741821-b)-cg()});a.callbackNode=b}}}\nfunction Qj(a,b){Jj=0;if(b)return b=Pg(),Rj(a,b),Z(a),null;var c=Oj(a);if(0!==c){b=a.callbackNode;if((T&(oj|pj))!==S)throw Error(u(327));Sj();a===U&&c===W||Tj(a,c);if(null!==V){var d=T;T|=oj;var e=Uj(a);do try{Vj();break}catch(h){Wj(a,h)}while(1);rg();T=d;lj.current=e;if(X===rj)throw b=wj,Tj(a,c),Mj(a,c),Z(a),b;if(null===V)switch(e=a.finishedWork=a.current.alternate,a.finishedExpirationTime=c,d=X,U=null,d){case qj:case rj:throw Error(u(345));case sj:Rj(a,2=c){a.lastPingedTime=c;Tj(a,c);break}}f=Oj(a);if(0!==f&&f!==c)break;if(0!==d&&d!==c){a.lastPingedTime=d;break}a.timeoutHandle=pe(Yj.bind(null,a),e);break}Yj(a);break;case uj:Mj(a,c);d=a.lastSuspendedTime;c===d&&(a.nextKnownPendingLevel=Xj(e));if(Bj&&(e=a.lastPingedTime,0===e||e>=c)){a.lastPingedTime=c;Tj(a,c);break}e=Oj(a);if(0!==e&&e!==c)break;if(0!==d&&d!==c){a.lastPingedTime=\nd;break}1073741823!==yj?d=10*(1073741821-yj)-cg():1073741823===xj?d=0:(d=10*(1073741821-xj)-5E3,e=cg(),c=10*(1073741821-c)-e,d=e-d,0>d&&(d=0),d=(120>d?120:480>d?480:1080>d?1080:1920>d?1920:3E3>d?3E3:4320>d?4320:1960*kj(d/1960))-d,c=d?d=0:(e=g.busyDelayMs|0,f=cg()-(10*(1073741821-f)-(g.timeoutMs|0||5E3)),d=f<=e?0:e+d-f);if(10 component higher in the tree to provide a loading indicator or placeholder to display.\"+Xa(e))}X!==vj&&(X=sj);f=Ni(f,e);k=d;do{switch(k.tag){case 3:g=f;k.effectTag|=4096;k.expirationTime=b;var x=fj(k,g,b);Eg(k,x);break a;case 1:g=f;var A=k.type,p=k.stateNode;if(0===(k.effectTag&64)&&(\"function\"===typeof A.getDerivedStateFromError||null!==p&&\"function\"===typeof p.componentDidCatch&&\n(null===jj||!jj.has(p)))){k.effectTag|=4096;k.expirationTime=b;var t=ij(k,g,b);Eg(k,t);break a}}k=k.return}while(null!==k)}V=ek(V)}catch(v){b=v;continue}break}while(1)}function Uj(){var a=lj.current;lj.current=Lh;return null===a?Lh:a}function Ig(a,b){aAj&&(Aj=a)}function Zj(){for(;null!==V;)V=fk(V)}function Vj(){for(;null!==V&&!Of();)V=fk(V)}\nfunction fk(a){var b=gk(a.alternate,a,W);a.memoizedProps=a.pendingProps;null===b&&(b=ek(a));mj.current=null;return b}\nfunction ek(a){V=a;do{var b=V.alternate;a=V.return;if(0===(V.effectTag&2048)){a:{var c=b;b=V;var d=W;var e=b.pendingProps;switch(b.tag){case 2:break;case 16:break;case 15:case 0:break;case 1:L(b.type)&&Ff(b);break;case 3:nh(b);Gf(b);e=b.stateNode;e.pendingContext&&(e.context=e.pendingContext,e.pendingContext=null);(null===c||null===c.child)&&ki(b)&&Ci(b);Ii(b);break;case 5:ph(b);d=lh(kh.current);var f=b.type;if(null!==c&&null!=b.stateNode)Ji(c,b,f,e,d),c.ref!==b.ref&&(b.effectTag|=128);else if(e){var g=\nlh(ih.current);if(ki(b)){e=b;var h=e.stateNode;c=e.type;var k=e.memoizedProps,l=d;h[ue]=e;h[ve]=k;f=void 0;d=h;switch(c){case \"iframe\":case \"object\":case \"embed\":F(\"load\",d);break;case \"video\":case \"audio\":for(h=0;h\\x3c/script>\",h=k.removeChild(k.firstChild)):\"string\"===typeof k.is?h=h.createElement(l,{is:k.is}):(h=h.createElement(l),\"select\"===l&&(l=h,k.multiple?l.multiple=!0:k.size&&(l.size=k.size))):h=h.createElementNS(g,l);k=h;k[ue]=c;k[ve]=e;Hi(k,b,!1,!1);b.stateNode=k;l=f;c=e;var m=d,C=Zd(l,c);switch(l){case \"iframe\":case \"object\":case \"embed\":F(\"load\",\nk);d=c;break;case \"video\":case \"audio\":for(d=0;de.tailExpiration&&1f&&(f=c),k>f&&(f=k),d=d.sibling;e.childExpirationTime=f}if(null!==b)return b;null!==a&&0===(a.effectTag&2048)&&(null===a.firstEffect&&(a.firstEffect=V.firstEffect),null!==V.lastEffect&&(null!==a.lastEffect&&(a.lastEffect.nextEffect=\nV.firstEffect),a.lastEffect=V.lastEffect),1a?b:a}function Yj(a){var b=dg();fg(99,ik.bind(null,a,b));return null}\nfunction ik(a,b){do Sj();while(null!==Ej);if((T&(oj|pj))!==S)throw Error(u(327));var c=a.finishedWork,d=a.finishedExpirationTime;if(null===c)return null;a.finishedWork=null;a.finishedExpirationTime=0;if(c===a.current)throw Error(u(177));a.callbackNode=null;a.callbackExpirationTime=0;a.callbackPriority=90;a.nextKnownPendingLevel=0;var e=Xj(c);a.firstPendingTime=e;d<=a.lastSuspendedTime?a.firstSuspendedTime=a.lastSuspendedTime=a.nextKnownPendingLevel=0:d<=a.firstSuspendedTime&&(a.firstSuspendedTime=\nd-1);d<=a.lastPingedTime&&(a.lastPingedTime=0);d<=a.lastExpiredTime&&(a.lastExpiredTime=0);a===U&&(V=U=null,W=0);1h&&(l=h,h=g,g=l),l=de(p,g),m=de(p,h),l&&m&&(1!==v.rangeCount||v.anchorNode!==l.node||v.anchorOffset!==l.offset||v.focusNode!==m.node||v.focusOffset!==m.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),v.removeAllRanges(),g>h?(v.addRange(t),v.extend(m.node,m.offset)):(t.setEnd(m.node,m.offset),v.addRange(t))))));t=[];for(v=p;v=v.parentNode;)1===v.nodeType&&t.push({element:v,left:v.scrollLeft,top:v.scrollTop});\n\"function\"===typeof p.focus&&p.focus();for(p=0;p=c)return yi(a,b,c);I(M,M.current&\n1,b);b=oi(a,b,c);return null!==b?b.sibling:null}I(M,M.current&1,b);break;case 19:d=b.childExpirationTime>=c;if(0!==(a.effectTag&64)){if(d)return Bi(a,b,c);b.effectTag|=64}e=b.memoizedState;null!==e&&(e.rendering=null,e.tail=null);I(M,M.current,b);if(!d)return null}return oi(a,b,c)}wg=!1}}else wg=!1;b.expirationTime=0;switch(b.tag){case 2:d=b.type;null!==a&&(a.alternate=null,b.alternate=null,b.effectTag|=2);a=b.pendingProps;e=Ef(b,J.current);vg(b,c);e=Ih(null,b,d,a,e,c);b.effectTag|=1;if(\"object\"===\ntypeof e&&null!==e&&\"function\"===typeof e.render&&void 0===e.$$typeof){b.tag=1;Mh();if(L(d)){var f=!0;Jf(b)}else f=!1;b.memoizedState=null!==e.state&&void 0!==e.state?e.state:null;var g=d.getDerivedStateFromProps;\"function\"===typeof g&&Og(b,d,g,a);e.updater=Sg;b.stateNode=e;e._reactInternalFiber=b;Wg(b,d,a,c);b=vi(null,b,d,!0,f,c)}else b.tag=0,R(null,b,e,c),b=b.child;return b;case 16:e=b.elementType;null!==a&&(a.alternate=null,b.alternate=null,b.effectTag|=2);a=b.pendingProps;Va(e);if(1!==e._status)throw e._result;\ne=e._result;b.type=e;f=b.tag=nk(e);a=mg(e,a);switch(f){case 0:b=si(null,b,e,a,c);break;case 1:b=ui(null,b,e,a,c);break;case 11:b=ni(null,b,e,a,c);break;case 14:b=pi(null,b,e,mg(e.type,a),d,c);break;default:throw Error(u(306,e,\"\"));}return b;case 0:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:mg(d,e),si(a,b,d,e,c);case 1:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:mg(d,e),ui(a,b,d,e,c);case 3:wi(b);d=b.updateQueue;if(null===d)throw Error(u(282));e=b.memoizedState;e=null!==e?e.element:\nnull;Hg(b,d,b.pendingProps,null,c);d=b.memoizedState.element;if(d===e)li(),b=oi(a,b,c);else{if(e=b.stateNode.hydrate)di=re(b.stateNode.containerInfo.firstChild),ci=b,e=ei=!0;if(e)for(c=gh(b,null,d,c),b.child=c;c;)c.effectTag=c.effectTag&-3|1024,c=c.sibling;else R(a,b,d,c),li();b=b.child}return b;case 5:return oh(b),null===a&&ii(b),d=b.type,e=b.pendingProps,f=null!==a?a.memoizedProps:null,g=e.children,oe(d,e)?g=null:null!==f&&oe(d,f)&&(b.effectTag|=16),ti(a,b),b.mode&4&&1!==c&&e.hidden?(b.expirationTime=\nb.childExpirationTime=1,b=null):(R(a,b,g,c),b=b.child),b;case 6:return null===a&&ii(b),null;case 13:return yi(a,b,c);case 4:return mh(b,b.stateNode.containerInfo),d=b.pendingProps,null===a?b.child=fh(b,null,d,c):R(a,b,d,c),b.child;case 11:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:mg(d,e),ni(a,b,d,e,c);case 7:return R(a,b,b.pendingProps,c),b.child;case 8:return R(a,b,b.pendingProps.children,c),b.child;case 12:return R(a,b,b.pendingProps.children,c),b.child;case 10:a:{d=b.type._context;\ne=b.pendingProps;g=b.memoizedProps;f=e.value;sg(b,f);if(null!==g){var h=g.value;f=of(h,f)?0:(\"function\"===typeof d._calculateChangedBits?d._calculateChangedBits(h,f):1073741823)|0;if(0===f){if(g.children===e.children&&!K.current){b=oi(a,b,c);break a}}else for(h=b.child,null!==h&&(h.return=b);null!==h;){var k=h.dependencies;if(null!==k){g=h.child;for(var l=k.firstContext;null!==l;){if(l.context===d&&0!==(l.observedBits&f)){1===h.tag&&(l=Bg(c,null),l.tag=2,Dg(h,l));h.expirationTime=b&&a<=b}function Mj(a,b){var c=a.firstSuspendedTime,d=a.lastSuspendedTime;cb||0===c)a.lastSuspendedTime=b;b<=a.lastPingedTime&&(a.lastPingedTime=0);b<=a.lastExpiredTime&&(a.lastExpiredTime=0)}\nfunction Nj(a,b){b>a.firstPendingTime&&(a.firstPendingTime=b);var c=a.firstSuspendedTime;0!==c&&(b>=c?a.firstSuspendedTime=a.lastSuspendedTime=a.nextKnownPendingLevel=0:b>=a.lastSuspendedTime&&(a.lastSuspendedTime=b+1),b>a.nextKnownPendingLevel&&(a.nextKnownPendingLevel=b))}function Rj(a,b){var c=a.lastExpiredTime;if(0===c||c>b)a.lastExpiredTime=b}\nfunction rk(a,b,c,d){var e=b.current,f=Pg(),g=Mg.suspense;f=Qg(f,e,g);a:if(c){c=c._reactInternalFiber;b:{if(ec(c)!==c||1!==c.tag)throw Error(u(170));var h=c;do{switch(h.tag){case 3:h=h.stateNode.context;break b;case 1:if(L(h.type)){h=h.stateNode.__reactInternalMemoizedMergedChildContext;break b}}h=h.return}while(null!==h);throw Error(u(171));}if(1===c.tag){var k=c.type;if(L(k)){c=If(c,k,h);break a}}c=h}else c=Cf;null===b.context?b.context=c:b.pendingContext=c;b=Bg(f,g);b.payload={element:a};d=void 0===\nd?null:d;null!==d&&(b.callback=d);Dg(e,b);Rg(e,f);return f}function sk(a){a=a.current;if(!a.child)return null;switch(a.child.tag){case 5:return a.child.stateNode;default:return a.child.stateNode}}function tk(a,b){a=a.memoizedState;null!==a&&null!==a.dehydrated&&a.retryTime=G};l=function(){};exports.unstable_forceFrameRate=function(a){0>a||125K(n,c))void 0!==r&&0>K(r,n)?(a[d]=r,a[v]=c,d=v):(a[d]=n,a[m]=c,d=m);else if(void 0!==r&&0>K(r,c))a[d]=r,a[v]=c,d=v;else break a}}return b}return null}function K(a,b){var c=a.sortIndex-b.sortIndex;return 0!==c?c:a.id-b.id}var N=[],O=[],P=1,Q=null,R=3,S=!1,T=!1,U=!1;\nfunction V(a){for(var b=L(O);null!==b;){if(null===b.callback)M(O);else if(b.startTime<=a)M(O),b.sortIndex=b.expirationTime,J(N,b);else break;b=L(O)}}function W(a){U=!1;V(a);if(!T)if(null!==L(N))T=!0,f(X);else{var b=L(O);null!==b&&g(W,b.startTime-a)}}\nfunction X(a,b){T=!1;U&&(U=!1,h());S=!0;var c=R;try{V(b);for(Q=L(N);null!==Q&&(!(Q.expirationTime>b)||a&&!k());){var d=Q.callback;if(null!==d){Q.callback=null;R=Q.priorityLevel;var e=d(Q.expirationTime<=b);b=exports.unstable_now();\"function\"===typeof e?Q.callback=e:Q===L(N)&&M(N);V(b)}else M(N);Q=L(N)}if(null!==Q)var m=!0;else{var n=L(O);null!==n&&g(W,n.startTime-b);m=!1}return m}finally{Q=null,R=c,S=!1}}\nfunction Y(a){switch(a){case 1:return-1;case 2:return 250;case 5:return 1073741823;case 4:return 1E4;default:return 5E3}}var Z=l;exports.unstable_ImmediatePriority=1;exports.unstable_UserBlockingPriority=2;exports.unstable_NormalPriority=3;exports.unstable_IdlePriority=5;exports.unstable_LowPriority=4;exports.unstable_runWithPriority=function(a,b){switch(a){case 1:case 2:case 3:case 4:case 5:break;default:a=3}var c=R;R=a;try{return b()}finally{R=c}};\nexports.unstable_next=function(a){switch(R){case 1:case 2:case 3:var b=3;break;default:b=R}var c=R;R=b;try{return a()}finally{R=c}};\nexports.unstable_scheduleCallback=function(a,b,c){var d=exports.unstable_now();if(\"object\"===typeof c&&null!==c){var e=c.delay;e=\"number\"===typeof e&&0d?(a.sortIndex=e,J(O,a),null===L(N)&&a===L(O)&&(U?h():U=!0,g(W,e-d))):(a.sortIndex=c,J(N,a),T||S||(T=!0,f(X)));return a};exports.unstable_cancelCallback=function(a){a.callback=null};\nexports.unstable_wrapCallback=function(a){var b=R;return function(){var c=R;R=b;try{return a.apply(this,arguments)}finally{R=c}}};exports.unstable_getCurrentPriorityLevel=function(){return R};exports.unstable_shouldYield=function(){var a=exports.unstable_now();V(a);var b=L(N);return b!==Q&&null!==Q&&null!==b&&null!==b.callback&&b.startTime<=a&&b.expirationTime 0 && arguments[0] !== undefined ? arguments[0] : {};\n var defaultState = arguments.length > 1 ? arguments[1] : undefined;\n var opts = {\n payload: true,\n fallback: null\n };\n\n var reducer = _extends(reduce, {\n has: has,\n on: on,\n off: off,\n options: options\n });\n\n function has(typeOrActionCreator) {\n return !!handlers[normalizeType(typeOrActionCreator)];\n }\n\n function on(typeOrActionCreator, handler) {\n if (Array.isArray(typeOrActionCreator)) {\n typeOrActionCreator.forEach(function (action) {\n on(action, handler);\n });\n } else {\n handlers[normalizeType(typeOrActionCreator)] = handler;\n }\n\n return reducer;\n }\n\n function off(typeOrActionCreator) {\n if (Array.isArray(typeOrActionCreator)) {\n typeOrActionCreator.forEach(off);\n } else {\n delete handlers[normalizeType(typeOrActionCreator)];\n }\n\n return reducer;\n }\n\n function options(newOpts) {\n Object.keys(newOpts).forEach(function (name) {\n return opts[name] = newOpts[name];\n });\n return reducer;\n }\n\n if (typeof handlers === 'function') {\n var factory = handlers;\n handlers = {};\n factory(on, off);\n }\n\n if (!has(_batch[\"default\"])) {\n on(_batch[\"default\"], function (state, payload) {\n if (opts.payload) {\n return payload.reduce(reduce, state);\n } else {\n return payload.payload.reduce(reduce, state);\n }\n });\n }\n\n function reduce() {\n var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultState;\n var action = arguments.length > 1 ? arguments[1] : undefined;\n\n if (!action || typeof action.type !== 'string') {\n return state;\n }\n\n if (action.type.startsWith('@@redux/')) {\n return state;\n }\n\n var handler = handlers[action.type] || opts.fallback;\n\n if (handler) {\n if (opts.payload) {\n return handler(state, action.payload, action.meta);\n } else {\n return handler(state, action);\n }\n }\n\n return state;\n }\n\n ;\n return reducer;\n}\n\n;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = assignAll;\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction assignAll(actions, stores) {\n if (Array.isArray(actions)) {\n return actions.map(function (action) {\n return action.assignTo(stores);\n });\n }\n\n return Object.keys(actions).reduce(function (assigns, action) {\n return _extends(assigns, _defineProperty({}, action, actions[action].assignTo(stores)));\n }, {});\n}\n\n;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = bindAll;\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction bindAll(actions, stores) {\n if (Array.isArray(actions)) {\n return actions.map(function (action) {\n return action.bindTo(stores);\n });\n }\n\n return Object.keys(actions).reduce(function (binds, action) {\n return _extends(binds, _defineProperty({}, action, actions[action].bindTo(stores)));\n }, {});\n}\n\n;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = disbatch;\n\nvar _batch = _interopRequireDefault(require(\"./batch\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction disbatch(store) {\n for (var _len = arguments.length, actions = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n actions[_key - 1] = arguments[_key];\n }\n\n if (actions && actions.length > 0) {\n if (!store || typeof store !== 'function' && typeof store.dispatch !== 'function') {\n throw new TypeError('disbatch must take either a valid Redux store or a dispatch function as first parameter');\n }\n\n if (typeof store.dispatch === 'function') {\n store = store.dispatch;\n } // store is actually the dispatch function here\n\n\n return store(_batch[\"default\"].apply(void 0, actions));\n } else {\n if (!store || typeof store.dispatch !== 'function') {\n throw new TypeError('disbatch must take a valid Redux store with a dispatch function as first parameter');\n }\n\n return _extends(store, {\n disbatch: disbatch.bind(undefined, store)\n });\n }\n}","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = void 0;\n\nvar reduxLogger = _interopRequireWildcard(require(\"./reduxLogger\"));\n\nfunction _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj[\"default\"] = obj; return newObj; } }\n\nvar _default = {\n reduxLogger: reduxLogger\n};\nexports[\"default\"] = _default;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.actionTransformer = actionTransformer;\nexports.logger = void 0;\n\nvar _batch = _interopRequireDefault(require(\"../batch\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nvar batchType = _batch[\"default\"].getType();\n\nfunction actionTransformer(action) {\n if (action && action.type === batchType) {\n action.payload.type = batchType;\n return action.payload;\n }\n\n return action;\n}\n\nvar logger = {};\nexports.logger = logger;\n\nvar _loop = function _loop(level) {\n if (typeof console[level] === 'function') {\n logger[level] = function levelFn() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n var lastArg = args.pop();\n\n if (Array.isArray(lastArg) && lastArg.type === batchType) {\n lastArg.forEach(function (action) {\n console[level].apply(console, [].concat(args, [action]));\n });\n } else {\n args.push(lastArg);\n console[level].apply(console, args);\n }\n };\n }\n};\n\nfor (var level in console) {\n _loop(level);\n}","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = asError;\n\nfunction _typeof(obj) { if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction asError(action) {\n if (_typeof(action) === 'object' && action !== null) {\n action.error = true;\n }\n\n return action;\n}\n\n;","/**\n * Copyright (c) 2014-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nvar runtime = (function (exports) {\n \"use strict\";\n\n var Op = Object.prototype;\n var hasOwn = Op.hasOwnProperty;\n var undefined; // More compressible than void 0.\n var $Symbol = typeof Symbol === \"function\" ? Symbol : {};\n var iteratorSymbol = $Symbol.iterator || \"@@iterator\";\n var asyncIteratorSymbol = $Symbol.asyncIterator || \"@@asyncIterator\";\n var toStringTagSymbol = $Symbol.toStringTag || \"@@toStringTag\";\n\n function wrap(innerFn, outerFn, self, tryLocsList) {\n // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.\n var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;\n var generator = Object.create(protoGenerator.prototype);\n var context = new Context(tryLocsList || []);\n\n // The ._invoke method unifies the implementations of the .next,\n // .throw, and .return methods.\n generator._invoke = makeInvokeMethod(innerFn, self, context);\n\n return generator;\n }\n exports.wrap = wrap;\n\n // Try/catch helper to minimize deoptimizations. Returns a completion\n // record like context.tryEntries[i].completion. This interface could\n // have been (and was previously) designed to take a closure to be\n // invoked without arguments, but in all the cases we care about we\n // already have an existing method we want to call, so there's no need\n // to create a new function object. We can even get away with assuming\n // the method takes exactly one argument, since that happens to be true\n // in every case, so we don't have to touch the arguments object. The\n // only additional allocation required is the completion record, which\n // has a stable shape and so hopefully should be cheap to allocate.\n function tryCatch(fn, obj, arg) {\n try {\n return { type: \"normal\", arg: fn.call(obj, arg) };\n } catch (err) {\n return { type: \"throw\", arg: err };\n }\n }\n\n var GenStateSuspendedStart = \"suspendedStart\";\n var GenStateSuspendedYield = \"suspendedYield\";\n var GenStateExecuting = \"executing\";\n var GenStateCompleted = \"completed\";\n\n // Returning this object from the innerFn has the same effect as\n // breaking out of the dispatch switch statement.\n var ContinueSentinel = {};\n\n // Dummy constructor functions that we use as the .constructor and\n // .constructor.prototype properties for functions that return Generator\n // objects. For full spec compliance, you may wish to configure your\n // minifier not to mangle the names of these two functions.\n function Generator() {}\n function GeneratorFunction() {}\n function GeneratorFunctionPrototype() {}\n\n // This is a polyfill for %IteratorPrototype% for environments that\n // don't natively support it.\n var IteratorPrototype = {};\n IteratorPrototype[iteratorSymbol] = function () {\n return this;\n };\n\n var getProto = Object.getPrototypeOf;\n var NativeIteratorPrototype = getProto && getProto(getProto(values([])));\n if (NativeIteratorPrototype &&\n NativeIteratorPrototype !== Op &&\n hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {\n // This environment has a native %IteratorPrototype%; use it instead\n // of the polyfill.\n IteratorPrototype = NativeIteratorPrototype;\n }\n\n var Gp = GeneratorFunctionPrototype.prototype =\n Generator.prototype = Object.create(IteratorPrototype);\n GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;\n GeneratorFunctionPrototype.constructor = GeneratorFunction;\n GeneratorFunctionPrototype[toStringTagSymbol] =\n GeneratorFunction.displayName = \"GeneratorFunction\";\n\n // Helper for defining the .next, .throw, and .return methods of the\n // Iterator interface in terms of a single ._invoke method.\n function defineIteratorMethods(prototype) {\n [\"next\", \"throw\", \"return\"].forEach(function(method) {\n prototype[method] = function(arg) {\n return this._invoke(method, arg);\n };\n });\n }\n\n exports.isGeneratorFunction = function(genFun) {\n var ctor = typeof genFun === \"function\" && genFun.constructor;\n return ctor\n ? ctor === GeneratorFunction ||\n // For the native GeneratorFunction constructor, the best we can\n // do is to check its .name property.\n (ctor.displayName || ctor.name) === \"GeneratorFunction\"\n : false;\n };\n\n exports.mark = function(genFun) {\n if (Object.setPrototypeOf) {\n Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);\n } else {\n genFun.__proto__ = GeneratorFunctionPrototype;\n if (!(toStringTagSymbol in genFun)) {\n genFun[toStringTagSymbol] = \"GeneratorFunction\";\n }\n }\n genFun.prototype = Object.create(Gp);\n return genFun;\n };\n\n // Within the body of any async function, `await x` is transformed to\n // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test\n // `hasOwn.call(value, \"__await\")` to determine if the yielded value is\n // meant to be awaited.\n exports.awrap = function(arg) {\n return { __await: arg };\n };\n\n function AsyncIterator(generator) {\n function invoke(method, arg, resolve, reject) {\n var record = tryCatch(generator[method], generator, arg);\n if (record.type === \"throw\") {\n reject(record.arg);\n } else {\n var result = record.arg;\n var value = result.value;\n if (value &&\n typeof value === \"object\" &&\n hasOwn.call(value, \"__await\")) {\n return Promise.resolve(value.__await).then(function(value) {\n invoke(\"next\", value, resolve, reject);\n }, function(err) {\n invoke(\"throw\", err, resolve, reject);\n });\n }\n\n return Promise.resolve(value).then(function(unwrapped) {\n // When a yielded Promise is resolved, its final value becomes\n // the .value of the Promise<{value,done}> result for the\n // current iteration.\n result.value = unwrapped;\n resolve(result);\n }, function(error) {\n // If a rejected Promise was yielded, throw the rejection back\n // into the async generator function so it can be handled there.\n return invoke(\"throw\", error, resolve, reject);\n });\n }\n }\n\n var previousPromise;\n\n function enqueue(method, arg) {\n function callInvokeWithMethodAndArg() {\n return new Promise(function(resolve, reject) {\n invoke(method, arg, resolve, reject);\n });\n }\n\n return previousPromise =\n // If enqueue has been called before, then we want to wait until\n // all previous Promises have been resolved before calling invoke,\n // so that results are always delivered in the correct order. If\n // enqueue has not been called before, then it is important to\n // call invoke immediately, without waiting on a callback to fire,\n // so that the async generator function has the opportunity to do\n // any necessary setup in a predictable way. This predictability\n // is why the Promise constructor synchronously invokes its\n // executor callback, and why async functions synchronously\n // execute code before the first await. Since we implement simple\n // async functions in terms of async generators, it is especially\n // important to get this right, even though it requires care.\n previousPromise ? previousPromise.then(\n callInvokeWithMethodAndArg,\n // Avoid propagating failures to Promises returned by later\n // invocations of the iterator.\n callInvokeWithMethodAndArg\n ) : callInvokeWithMethodAndArg();\n }\n\n // Define the unified helper method that is used to implement .next,\n // .throw, and .return (see defineIteratorMethods).\n this._invoke = enqueue;\n }\n\n defineIteratorMethods(AsyncIterator.prototype);\n AsyncIterator.prototype[asyncIteratorSymbol] = function () {\n return this;\n };\n exports.AsyncIterator = AsyncIterator;\n\n // Note that simple async functions are implemented on top of\n // AsyncIterator objects; they just return a Promise for the value of\n // the final result produced by the iterator.\n exports.async = function(innerFn, outerFn, self, tryLocsList) {\n var iter = new AsyncIterator(\n wrap(innerFn, outerFn, self, tryLocsList)\n );\n\n return exports.isGeneratorFunction(outerFn)\n ? iter // If outerFn is a generator, return the full iterator.\n : iter.next().then(function(result) {\n return result.done ? result.value : iter.next();\n });\n };\n\n function makeInvokeMethod(innerFn, self, context) {\n var state = GenStateSuspendedStart;\n\n return function invoke(method, arg) {\n if (state === GenStateExecuting) {\n throw new Error(\"Generator is already running\");\n }\n\n if (state === GenStateCompleted) {\n if (method === \"throw\") {\n throw arg;\n }\n\n // Be forgiving, per 25.3.3.3.3 of the spec:\n // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume\n return doneResult();\n }\n\n context.method = method;\n context.arg = arg;\n\n while (true) {\n var delegate = context.delegate;\n if (delegate) {\n var delegateResult = maybeInvokeDelegate(delegate, context);\n if (delegateResult) {\n if (delegateResult === ContinueSentinel) continue;\n return delegateResult;\n }\n }\n\n if (context.method === \"next\") {\n // Setting context._sent for legacy support of Babel's\n // function.sent implementation.\n context.sent = context._sent = context.arg;\n\n } else if (context.method === \"throw\") {\n if (state === GenStateSuspendedStart) {\n state = GenStateCompleted;\n throw context.arg;\n }\n\n context.dispatchException(context.arg);\n\n } else if (context.method === \"return\") {\n context.abrupt(\"return\", context.arg);\n }\n\n state = GenStateExecuting;\n\n var record = tryCatch(innerFn, self, context);\n if (record.type === \"normal\") {\n // If an exception is thrown from innerFn, we leave state ===\n // GenStateExecuting and loop back for another invocation.\n state = context.done\n ? GenStateCompleted\n : GenStateSuspendedYield;\n\n if (record.arg === ContinueSentinel) {\n continue;\n }\n\n return {\n value: record.arg,\n done: context.done\n };\n\n } else if (record.type === \"throw\") {\n state = GenStateCompleted;\n // Dispatch the exception by looping back around to the\n // context.dispatchException(context.arg) call above.\n context.method = \"throw\";\n context.arg = record.arg;\n }\n }\n };\n }\n\n // Call delegate.iterator[context.method](context.arg) and handle the\n // result, either by returning a { value, done } result from the\n // delegate iterator, or by modifying context.method and context.arg,\n // setting context.delegate to null, and returning the ContinueSentinel.\n function maybeInvokeDelegate(delegate, context) {\n var method = delegate.iterator[context.method];\n if (method === undefined) {\n // A .throw or .return when the delegate iterator has no .throw\n // method always terminates the yield* loop.\n context.delegate = null;\n\n if (context.method === \"throw\") {\n // Note: [\"return\"] must be used for ES3 parsing compatibility.\n if (delegate.iterator[\"return\"]) {\n // If the delegate iterator has a return method, give it a\n // chance to clean up.\n context.method = \"return\";\n context.arg = undefined;\n maybeInvokeDelegate(delegate, context);\n\n if (context.method === \"throw\") {\n // If maybeInvokeDelegate(context) changed context.method from\n // \"return\" to \"throw\", let that override the TypeError below.\n return ContinueSentinel;\n }\n }\n\n context.method = \"throw\";\n context.arg = new TypeError(\n \"The iterator does not provide a 'throw' method\");\n }\n\n return ContinueSentinel;\n }\n\n var record = tryCatch(method, delegate.iterator, context.arg);\n\n if (record.type === \"throw\") {\n context.method = \"throw\";\n context.arg = record.arg;\n context.delegate = null;\n return ContinueSentinel;\n }\n\n var info = record.arg;\n\n if (! info) {\n context.method = \"throw\";\n context.arg = new TypeError(\"iterator result is not an object\");\n context.delegate = null;\n return ContinueSentinel;\n }\n\n if (info.done) {\n // Assign the result of the finished delegate to the temporary\n // variable specified by delegate.resultName (see delegateYield).\n context[delegate.resultName] = info.value;\n\n // Resume execution at the desired location (see delegateYield).\n context.next = delegate.nextLoc;\n\n // If context.method was \"throw\" but the delegate handled the\n // exception, let the outer generator proceed normally. If\n // context.method was \"next\", forget context.arg since it has been\n // \"consumed\" by the delegate iterator. If context.method was\n // \"return\", allow the original .return call to continue in the\n // outer generator.\n if (context.method !== \"return\") {\n context.method = \"next\";\n context.arg = undefined;\n }\n\n } else {\n // Re-yield the result returned by the delegate method.\n return info;\n }\n\n // The delegate iterator is finished, so forget it and continue with\n // the outer generator.\n context.delegate = null;\n return ContinueSentinel;\n }\n\n // Define Generator.prototype.{next,throw,return} in terms of the\n // unified ._invoke helper method.\n defineIteratorMethods(Gp);\n\n Gp[toStringTagSymbol] = \"Generator\";\n\n // A Generator should always return itself as the iterator object when the\n // @@iterator function is called on it. Some browsers' implementations of the\n // iterator prototype chain incorrectly implement this, causing the Generator\n // object to not be returned from this call. This ensures that doesn't happen.\n // See https://github.com/facebook/regenerator/issues/274 for more details.\n Gp[iteratorSymbol] = function() {\n return this;\n };\n\n Gp.toString = function() {\n return \"[object Generator]\";\n };\n\n function pushTryEntry(locs) {\n var entry = { tryLoc: locs[0] };\n\n if (1 in locs) {\n entry.catchLoc = locs[1];\n }\n\n if (2 in locs) {\n entry.finallyLoc = locs[2];\n entry.afterLoc = locs[3];\n }\n\n this.tryEntries.push(entry);\n }\n\n function resetTryEntry(entry) {\n var record = entry.completion || {};\n record.type = \"normal\";\n delete record.arg;\n entry.completion = record;\n }\n\n function Context(tryLocsList) {\n // The root entry object (effectively a try statement without a catch\n // or a finally block) gives us a place to store values thrown from\n // locations where there is no enclosing try statement.\n this.tryEntries = [{ tryLoc: \"root\" }];\n tryLocsList.forEach(pushTryEntry, this);\n this.reset(true);\n }\n\n exports.keys = function(object) {\n var keys = [];\n for (var key in object) {\n keys.push(key);\n }\n keys.reverse();\n\n // Rather than returning an object with a next method, we keep\n // things simple and return the next function itself.\n return function next() {\n while (keys.length) {\n var key = keys.pop();\n if (key in object) {\n next.value = key;\n next.done = false;\n return next;\n }\n }\n\n // To avoid creating an additional object, we just hang the .value\n // and .done properties off the next function object itself. This\n // also ensures that the minifier will not anonymize the function.\n next.done = true;\n return next;\n };\n };\n\n function values(iterable) {\n if (iterable) {\n var iteratorMethod = iterable[iteratorSymbol];\n if (iteratorMethod) {\n return iteratorMethod.call(iterable);\n }\n\n if (typeof iterable.next === \"function\") {\n return iterable;\n }\n\n if (!isNaN(iterable.length)) {\n var i = -1, next = function next() {\n while (++i < iterable.length) {\n if (hasOwn.call(iterable, i)) {\n next.value = iterable[i];\n next.done = false;\n return next;\n }\n }\n\n next.value = undefined;\n next.done = true;\n\n return next;\n };\n\n return next.next = next;\n }\n }\n\n // Return an iterator with no values.\n return { next: doneResult };\n }\n exports.values = values;\n\n function doneResult() {\n return { value: undefined, done: true };\n }\n\n Context.prototype = {\n constructor: Context,\n\n reset: function(skipTempReset) {\n this.prev = 0;\n this.next = 0;\n // Resetting context._sent for legacy support of Babel's\n // function.sent implementation.\n this.sent = this._sent = undefined;\n this.done = false;\n this.delegate = null;\n\n this.method = \"next\";\n this.arg = undefined;\n\n this.tryEntries.forEach(resetTryEntry);\n\n if (!skipTempReset) {\n for (var name in this) {\n // Not sure about the optimal order of these conditions:\n if (name.charAt(0) === \"t\" &&\n hasOwn.call(this, name) &&\n !isNaN(+name.slice(1))) {\n this[name] = undefined;\n }\n }\n }\n },\n\n stop: function() {\n this.done = true;\n\n var rootEntry = this.tryEntries[0];\n var rootRecord = rootEntry.completion;\n if (rootRecord.type === \"throw\") {\n throw rootRecord.arg;\n }\n\n return this.rval;\n },\n\n dispatchException: function(exception) {\n if (this.done) {\n throw exception;\n }\n\n var context = this;\n function handle(loc, caught) {\n record.type = \"throw\";\n record.arg = exception;\n context.next = loc;\n\n if (caught) {\n // If the dispatched exception was caught by a catch block,\n // then let that catch block handle the exception normally.\n context.method = \"next\";\n context.arg = undefined;\n }\n\n return !! caught;\n }\n\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n var record = entry.completion;\n\n if (entry.tryLoc === \"root\") {\n // Exception thrown outside of any try block that could handle\n // it, so set the completion value of the entire function to\n // throw the exception.\n return handle(\"end\");\n }\n\n if (entry.tryLoc <= this.prev) {\n var hasCatch = hasOwn.call(entry, \"catchLoc\");\n var hasFinally = hasOwn.call(entry, \"finallyLoc\");\n\n if (hasCatch && hasFinally) {\n if (this.prev < entry.catchLoc) {\n return handle(entry.catchLoc, true);\n } else if (this.prev < entry.finallyLoc) {\n return handle(entry.finallyLoc);\n }\n\n } else if (hasCatch) {\n if (this.prev < entry.catchLoc) {\n return handle(entry.catchLoc, true);\n }\n\n } else if (hasFinally) {\n if (this.prev < entry.finallyLoc) {\n return handle(entry.finallyLoc);\n }\n\n } else {\n throw new Error(\"try statement without catch or finally\");\n }\n }\n }\n },\n\n abrupt: function(type, arg) {\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n if (entry.tryLoc <= this.prev &&\n hasOwn.call(entry, \"finallyLoc\") &&\n this.prev < entry.finallyLoc) {\n var finallyEntry = entry;\n break;\n }\n }\n\n if (finallyEntry &&\n (type === \"break\" ||\n type === \"continue\") &&\n finallyEntry.tryLoc <= arg &&\n arg <= finallyEntry.finallyLoc) {\n // Ignore the finally entry if control is not jumping to a\n // location outside the try/catch block.\n finallyEntry = null;\n }\n\n var record = finallyEntry ? finallyEntry.completion : {};\n record.type = type;\n record.arg = arg;\n\n if (finallyEntry) {\n this.method = \"next\";\n this.next = finallyEntry.finallyLoc;\n return ContinueSentinel;\n }\n\n return this.complete(record);\n },\n\n complete: function(record, afterLoc) {\n if (record.type === \"throw\") {\n throw record.arg;\n }\n\n if (record.type === \"break\" ||\n record.type === \"continue\") {\n this.next = record.arg;\n } else if (record.type === \"return\") {\n this.rval = this.arg = record.arg;\n this.method = \"return\";\n this.next = \"end\";\n } else if (record.type === \"normal\" && afterLoc) {\n this.next = afterLoc;\n }\n\n return ContinueSentinel;\n },\n\n finish: function(finallyLoc) {\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n if (entry.finallyLoc === finallyLoc) {\n this.complete(entry.completion, entry.afterLoc);\n resetTryEntry(entry);\n return ContinueSentinel;\n }\n }\n },\n\n \"catch\": function(tryLoc) {\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n if (entry.tryLoc === tryLoc) {\n var record = entry.completion;\n if (record.type === \"throw\") {\n var thrown = record.arg;\n resetTryEntry(entry);\n }\n return thrown;\n }\n }\n\n // The context.catch method must only be called with a location\n // argument that corresponds to a known catch block.\n throw new Error(\"illegal catch attempt\");\n },\n\n delegateYield: function(iterable, resultName, nextLoc) {\n this.delegate = {\n iterator: values(iterable),\n resultName: resultName,\n nextLoc: nextLoc\n };\n\n if (this.method === \"next\") {\n // Deliberately forget the last sent value so that we don't\n // accidentally pass it on to the delegate.\n this.arg = undefined;\n }\n\n return ContinueSentinel;\n }\n };\n\n // Regardless of whether this script is executing as a CommonJS module\n // or not, return the runtime object so that we can declare the variable\n // regeneratorRuntime in the outer scope, which allows this module to be\n // injected easily by `bin/regenerator --include-runtime script.js`.\n return exports;\n\n}(\n // If this script is executing as a CommonJS module, use module.exports\n // as the regeneratorRuntime namespace. Otherwise create a new empty\n // object. Either way, the resulting object will be used to initialize\n // the regeneratorRuntime variable at the top of this file.\n typeof module === \"object\" ? module.exports : {}\n));\n\ntry {\n regeneratorRuntime = runtime;\n} catch (accidentalStrictMode) {\n // This module should not be running in strict mode, so the above\n // assignment should always work unless something is misconfigured. Just\n // in case runtime.js accidentally runs in strict mode, we can escape\n // strict mode using a global Function call. This could conceivably fail\n // if a Content Security Policy forbids using Function, but in that case\n // the proper solution is to fix the accidental strict mode problem. If\n // you've misconfigured your bundler to force strict mode and applied a\n // CSP to forbid Function, and you're not willing to fix either of those\n // problems, please detail your unique predicament in a GitHub issue.\n Function(\"r\", \"regeneratorRuntime = r\")(runtime);\n}\n","'use strict';\n\nvar utils = require('./utils');\nvar bind = require('./helpers/bind');\nvar Axios = require('./core/Axios');\nvar mergeConfig = require('./core/mergeConfig');\nvar defaults = require('./defaults');\n\n/**\n * Create an instance of Axios\n *\n * @param {Object} defaultConfig The default config for the instance\n * @return {Axios} A new instance of Axios\n */\nfunction createInstance(defaultConfig) {\n var context = new Axios(defaultConfig);\n var instance = bind(Axios.prototype.request, context);\n\n // Copy axios.prototype to instance\n utils.extend(instance, Axios.prototype, context);\n\n // Copy context to instance\n utils.extend(instance, context);\n\n return instance;\n}\n\n// Create the default instance to be exported\nvar axios = createInstance(defaults);\n\n// Expose Axios class to allow class inheritance\naxios.Axios = Axios;\n\n// Factory for creating new instances\naxios.create = function create(instanceConfig) {\n return createInstance(mergeConfig(axios.defaults, instanceConfig));\n};\n\n// Expose Cancel & CancelToken\naxios.Cancel = require('./cancel/Cancel');\naxios.CancelToken = require('./cancel/CancelToken');\naxios.isCancel = require('./cancel/isCancel');\n\n// Expose all/spread\naxios.all = function all(promises) {\n return Promise.all(promises);\n};\naxios.spread = require('./helpers/spread');\n\n// Expose isAxiosError\naxios.isAxiosError = require('./helpers/isAxiosError');\n\nmodule.exports = axios;\n\n// Allow use of default import syntax in TypeScript\nmodule.exports.default = axios;\n","'use strict';\n\nvar utils = require('./../utils');\nvar buildURL = require('../helpers/buildURL');\nvar InterceptorManager = require('./InterceptorManager');\nvar dispatchRequest = require('./dispatchRequest');\nvar mergeConfig = require('./mergeConfig');\nvar validator = require('../helpers/validator');\n\nvar validators = validator.validators;\n/**\n * Create a new instance of Axios\n *\n * @param {Object} instanceConfig The default config for the instance\n */\nfunction Axios(instanceConfig) {\n this.defaults = instanceConfig;\n this.interceptors = {\n request: new InterceptorManager(),\n response: new InterceptorManager()\n };\n}\n\n/**\n * Dispatch a request\n *\n * @param {Object} config The config specific for this request (merged with this.defaults)\n */\nAxios.prototype.request = function request(config) {\n /*eslint no-param-reassign:0*/\n // Allow for axios('example/url'[, config]) a la fetch API\n if (typeof config === 'string') {\n config = arguments[1] || {};\n config.url = arguments[0];\n } else {\n config = config || {};\n }\n\n config = mergeConfig(this.defaults, config);\n\n // Set config.method\n if (config.method) {\n config.method = config.method.toLowerCase();\n } else if (this.defaults.method) {\n config.method = this.defaults.method.toLowerCase();\n } else {\n config.method = 'get';\n }\n\n var transitional = config.transitional;\n\n if (transitional !== undefined) {\n validator.assertOptions(transitional, {\n silentJSONParsing: validators.transitional(validators.boolean, '1.0.0'),\n forcedJSONParsing: validators.transitional(validators.boolean, '1.0.0'),\n clarifyTimeoutError: validators.transitional(validators.boolean, '1.0.0')\n }, false);\n }\n\n // filter out skipped interceptors\n var requestInterceptorChain = [];\n var synchronousRequestInterceptors = true;\n this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {\n if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {\n return;\n }\n\n synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;\n\n requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);\n });\n\n var responseInterceptorChain = [];\n this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {\n responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);\n });\n\n var promise;\n\n if (!synchronousRequestInterceptors) {\n var chain = [dispatchRequest, undefined];\n\n Array.prototype.unshift.apply(chain, requestInterceptorChain);\n chain.concat(responseInterceptorChain);\n\n promise = Promise.resolve(config);\n while (chain.length) {\n promise = promise.then(chain.shift(), chain.shift());\n }\n\n return promise;\n }\n\n\n var newConfig = config;\n while (requestInterceptorChain.length) {\n var onFulfilled = requestInterceptorChain.shift();\n var onRejected = requestInterceptorChain.shift();\n try {\n newConfig = onFulfilled(newConfig);\n } catch (error) {\n onRejected(error);\n break;\n }\n }\n\n try {\n promise = dispatchRequest(newConfig);\n } catch (error) {\n return Promise.reject(error);\n }\n\n while (responseInterceptorChain.length) {\n promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());\n }\n\n return promise;\n};\n\nAxios.prototype.getUri = function getUri(config) {\n config = mergeConfig(this.defaults, config);\n return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\\?/, '');\n};\n\n// Provide aliases for supported request methods\nutils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {\n /*eslint func-names:0*/\n Axios.prototype[method] = function(url, config) {\n return this.request(mergeConfig(config || {}, {\n method: method,\n url: url,\n data: (config || {}).data\n }));\n };\n});\n\nutils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {\n /*eslint func-names:0*/\n Axios.prototype[method] = function(url, data, config) {\n return this.request(mergeConfig(config || {}, {\n method: method,\n url: url,\n data: data\n }));\n };\n});\n\nmodule.exports = Axios;\n","'use strict';\n\nvar utils = require('./../utils');\n\nfunction InterceptorManager() {\n this.handlers = [];\n}\n\n/**\n * Add a new interceptor to the stack\n *\n * @param {Function} fulfilled The function to handle `then` for a `Promise`\n * @param {Function} rejected The function to handle `reject` for a `Promise`\n *\n * @return {Number} An ID used to remove interceptor later\n */\nInterceptorManager.prototype.use = function use(fulfilled, rejected, options) {\n this.handlers.push({\n fulfilled: fulfilled,\n rejected: rejected,\n synchronous: options ? options.synchronous : false,\n runWhen: options ? options.runWhen : null\n });\n return this.handlers.length - 1;\n};\n\n/**\n * Remove an interceptor from the stack\n *\n * @param {Number} id The ID that was returned by `use`\n */\nInterceptorManager.prototype.eject = function eject(id) {\n if (this.handlers[id]) {\n this.handlers[id] = null;\n }\n};\n\n/**\n * Iterate over all the registered interceptors\n *\n * This method is particularly useful for skipping over any\n * interceptors that may have become `null` calling `eject`.\n *\n * @param {Function} fn The function to call for each interceptor\n */\nInterceptorManager.prototype.forEach = function forEach(fn) {\n utils.forEach(this.handlers, function forEachHandler(h) {\n if (h !== null) {\n fn(h);\n }\n });\n};\n\nmodule.exports = InterceptorManager;\n","'use strict';\n\nvar utils = require('./../utils');\nvar transformData = require('./transformData');\nvar isCancel = require('../cancel/isCancel');\nvar defaults = require('../defaults');\n\n/**\n * Throws a `Cancel` if cancellation has been requested.\n */\nfunction throwIfCancellationRequested(config) {\n if (config.cancelToken) {\n config.cancelToken.throwIfRequested();\n }\n}\n\n/**\n * Dispatch a request to the server using the configured adapter.\n *\n * @param {object} config The config that is to be used for the request\n * @returns {Promise} The Promise to be fulfilled\n */\nmodule.exports = function dispatchRequest(config) {\n throwIfCancellationRequested(config);\n\n // Ensure headers exist\n config.headers = config.headers || {};\n\n // Transform request data\n config.data = transformData.call(\n config,\n config.data,\n config.headers,\n config.transformRequest\n );\n\n // Flatten headers\n config.headers = utils.merge(\n config.headers.common || {},\n config.headers[config.method] || {},\n config.headers\n );\n\n utils.forEach(\n ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],\n function cleanHeaderConfig(method) {\n delete config.headers[method];\n }\n );\n\n var adapter = config.adapter || defaults.adapter;\n\n return adapter(config).then(function onAdapterResolution(response) {\n throwIfCancellationRequested(config);\n\n // Transform response data\n response.data = transformData.call(\n config,\n response.data,\n response.headers,\n config.transformResponse\n );\n\n return response;\n }, function onAdapterRejection(reason) {\n if (!isCancel(reason)) {\n throwIfCancellationRequested(config);\n\n // Transform response data\n if (reason && reason.response) {\n reason.response.data = transformData.call(\n config,\n reason.response.data,\n reason.response.headers,\n config.transformResponse\n );\n }\n }\n\n return Promise.reject(reason);\n });\n};\n","'use strict';\n\nvar utils = require('./../utils');\nvar defaults = require('./../defaults');\n\n/**\n * Transform the data for a request or a response\n *\n * @param {Object|String} data The data to be transformed\n * @param {Array} headers The headers for the request or response\n * @param {Array|Function} fns A single function or Array of functions\n * @returns {*} The resulting transformed data\n */\nmodule.exports = function transformData(data, headers, fns) {\n var context = this || defaults;\n /*eslint no-param-reassign:0*/\n utils.forEach(fns, function transform(fn) {\n data = fn.call(context, data, headers);\n });\n\n return data;\n};\n","'use strict';\n\nvar utils = require('../utils');\n\nmodule.exports = function normalizeHeaderName(headers, normalizedName) {\n utils.forEach(headers, function processHeader(value, name) {\n if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {\n headers[normalizedName] = value;\n delete headers[name];\n }\n });\n};\n","'use strict';\n\nvar createError = require('./createError');\n\n/**\n * Resolve or reject a Promise based on response status.\n *\n * @param {Function} resolve A function that resolves the promise.\n * @param {Function} reject A function that rejects the promise.\n * @param {object} response The response.\n */\nmodule.exports = function settle(resolve, reject, response) {\n var validateStatus = response.config.validateStatus;\n if (!response.status || !validateStatus || validateStatus(response.status)) {\n resolve(response);\n } else {\n reject(createError(\n 'Request failed with status code ' + response.status,\n response.config,\n null,\n response.request,\n response\n ));\n }\n};\n","'use strict';\n\nvar utils = require('./../utils');\n\nmodule.exports = (\n utils.isStandardBrowserEnv() ?\n\n // Standard browser envs support document.cookie\n (function standardBrowserEnv() {\n return {\n write: function write(name, value, expires, path, domain, secure) {\n var cookie = [];\n cookie.push(name + '=' + encodeURIComponent(value));\n\n if (utils.isNumber(expires)) {\n cookie.push('expires=' + new Date(expires).toGMTString());\n }\n\n if (utils.isString(path)) {\n cookie.push('path=' + path);\n }\n\n if (utils.isString(domain)) {\n cookie.push('domain=' + domain);\n }\n\n if (secure === true) {\n cookie.push('secure');\n }\n\n document.cookie = cookie.join('; ');\n },\n\n read: function read(name) {\n var match = document.cookie.match(new RegExp('(^|;\\\\s*)(' + name + ')=([^;]*)'));\n return (match ? decodeURIComponent(match[3]) : null);\n },\n\n remove: function remove(name) {\n this.write(name, '', Date.now() - 86400000);\n }\n };\n })() :\n\n // Non standard browser env (web workers, react-native) lack needed support.\n (function nonStandardBrowserEnv() {\n return {\n write: function write() {},\n read: function read() { return null; },\n remove: function remove() {}\n };\n })()\n);\n","'use strict';\n\nvar isAbsoluteURL = require('../helpers/isAbsoluteURL');\nvar combineURLs = require('../helpers/combineURLs');\n\n/**\n * Creates a new URL by combining the baseURL with the requestedURL,\n * only when the requestedURL is not already an absolute URL.\n * If the requestURL is absolute, this function returns the requestedURL untouched.\n *\n * @param {string} baseURL The base URL\n * @param {string} requestedURL Absolute or relative URL to combine\n * @returns {string} The combined full path\n */\nmodule.exports = function buildFullPath(baseURL, requestedURL) {\n if (baseURL && !isAbsoluteURL(requestedURL)) {\n return combineURLs(baseURL, requestedURL);\n }\n return requestedURL;\n};\n","'use strict';\n\n/**\n * Determines whether the specified URL is absolute\n *\n * @param {string} url The URL to test\n * @returns {boolean} True if the specified URL is absolute, otherwise false\n */\nmodule.exports = function isAbsoluteURL(url) {\n // A URL is considered absolute if it begins with \"://\" or \"//\" (protocol-relative URL).\n // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed\n // by any combination of letters, digits, plus, period, or hyphen.\n return /^([a-z][a-z\\d\\+\\-\\.]*:)?\\/\\//i.test(url);\n};\n","'use strict';\n\n/**\n * Creates a new URL by combining the specified URLs\n *\n * @param {string} baseURL The base URL\n * @param {string} relativeURL The relative URL\n * @returns {string} The combined URL\n */\nmodule.exports = function combineURLs(baseURL, relativeURL) {\n return relativeURL\n ? baseURL.replace(/\\/+$/, '') + '/' + relativeURL.replace(/^\\/+/, '')\n : baseURL;\n};\n","'use strict';\n\nvar utils = require('./../utils');\n\n// Headers whose duplicates are ignored by node\n// c.f. https://nodejs.org/api/http.html#http_message_headers\nvar ignoreDuplicateOf = [\n 'age', 'authorization', 'content-length', 'content-type', 'etag',\n 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since',\n 'last-modified', 'location', 'max-forwards', 'proxy-authorization',\n 'referer', 'retry-after', 'user-agent'\n];\n\n/**\n * Parse headers into an object\n *\n * ```\n * Date: Wed, 27 Aug 2014 08:58:49 GMT\n * Content-Type: application/json\n * Connection: keep-alive\n * Transfer-Encoding: chunked\n * ```\n *\n * @param {String} headers Headers needing to be parsed\n * @returns {Object} Headers parsed into an object\n */\nmodule.exports = function parseHeaders(headers) {\n var parsed = {};\n var key;\n var val;\n var i;\n\n if (!headers) { return parsed; }\n\n utils.forEach(headers.split('\\n'), function parser(line) {\n i = line.indexOf(':');\n key = utils.trim(line.substr(0, i)).toLowerCase();\n val = utils.trim(line.substr(i + 1));\n\n if (key) {\n if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) {\n return;\n }\n if (key === 'set-cookie') {\n parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]);\n } else {\n parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;\n }\n }\n });\n\n return parsed;\n};\n","'use strict';\n\nvar utils = require('./../utils');\n\nmodule.exports = (\n utils.isStandardBrowserEnv() ?\n\n // Standard browser envs have full support of the APIs needed to test\n // whether the request URL is of the same origin as current location.\n (function standardBrowserEnv() {\n var msie = /(msie|trident)/i.test(navigator.userAgent);\n var urlParsingNode = document.createElement('a');\n var originURL;\n\n /**\n * Parse a URL to discover it's components\n *\n * @param {String} url The URL to be parsed\n * @returns {Object}\n */\n function resolveURL(url) {\n var href = url;\n\n if (msie) {\n // IE needs attribute set twice to normalize properties\n urlParsingNode.setAttribute('href', href);\n href = urlParsingNode.href;\n }\n\n urlParsingNode.setAttribute('href', href);\n\n // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils\n return {\n href: urlParsingNode.href,\n protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',\n host: urlParsingNode.host,\n search: urlParsingNode.search ? urlParsingNode.search.replace(/^\\?/, '') : '',\n hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',\n hostname: urlParsingNode.hostname,\n port: urlParsingNode.port,\n pathname: (urlParsingNode.pathname.charAt(0) === '/') ?\n urlParsingNode.pathname :\n '/' + urlParsingNode.pathname\n };\n }\n\n originURL = resolveURL(window.location.href);\n\n /**\n * Determine if a URL shares the same origin as the current location\n *\n * @param {String} requestURL The URL to test\n * @returns {boolean} True if URL shares the same origin, otherwise false\n */\n return function isURLSameOrigin(requestURL) {\n var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL;\n return (parsed.protocol === originURL.protocol &&\n parsed.host === originURL.host);\n };\n })() :\n\n // Non standard browser envs (web workers, react-native) lack needed support.\n (function nonStandardBrowserEnv() {\n return function isURLSameOrigin() {\n return true;\n };\n })()\n);\n","'use strict';\n\nvar pkg = require('./../../package.json');\n\nvar validators = {};\n\n// eslint-disable-next-line func-names\n['object', 'boolean', 'number', 'function', 'string', 'symbol'].forEach(function(type, i) {\n validators[type] = function validator(thing) {\n return typeof thing === type || 'a' + (i < 1 ? 'n ' : ' ') + type;\n };\n});\n\nvar deprecatedWarnings = {};\nvar currentVerArr = pkg.version.split('.');\n\n/**\n * Compare package versions\n * @param {string} version\n * @param {string?} thanVersion\n * @returns {boolean}\n */\nfunction isOlderVersion(version, thanVersion) {\n var pkgVersionArr = thanVersion ? thanVersion.split('.') : currentVerArr;\n var destVer = version.split('.');\n for (var i = 0; i < 3; i++) {\n if (pkgVersionArr[i] > destVer[i]) {\n return true;\n } else if (pkgVersionArr[i] < destVer[i]) {\n return false;\n }\n }\n return false;\n}\n\n/**\n * Transitional option validator\n * @param {function|boolean?} validator\n * @param {string?} version\n * @param {string} message\n * @returns {function}\n */\nvalidators.transitional = function transitional(validator, version, message) {\n var isDeprecated = version && isOlderVersion(version);\n\n function formatMessage(opt, desc) {\n return '[Axios v' + pkg.version + '] Transitional option \\'' + opt + '\\'' + desc + (message ? '. ' + message : '');\n }\n\n // eslint-disable-next-line func-names\n return function(value, opt, opts) {\n if (validator === false) {\n throw new Error(formatMessage(opt, ' has been removed in ' + version));\n }\n\n if (isDeprecated && !deprecatedWarnings[opt]) {\n deprecatedWarnings[opt] = true;\n // eslint-disable-next-line no-console\n console.warn(\n formatMessage(\n opt,\n ' has been deprecated since v' + version + ' and will be removed in the near future'\n )\n );\n }\n\n return validator ? validator(value, opt, opts) : true;\n };\n};\n\n/**\n * Assert object's properties type\n * @param {object} options\n * @param {object} schema\n * @param {boolean?} allowUnknown\n */\n\nfunction assertOptions(options, schema, allowUnknown) {\n if (typeof options !== 'object') {\n throw new TypeError('options must be an object');\n }\n var keys = Object.keys(options);\n var i = keys.length;\n while (i-- > 0) {\n var opt = keys[i];\n var validator = schema[opt];\n if (validator) {\n var value = options[opt];\n var result = value === undefined || validator(value, opt, options);\n if (result !== true) {\n throw new TypeError('option ' + opt + ' must be ' + result);\n }\n continue;\n }\n if (allowUnknown !== true) {\n throw Error('Unknown option ' + opt);\n }\n }\n}\n\nmodule.exports = {\n isOlderVersion: isOlderVersion,\n assertOptions: assertOptions,\n validators: validators\n};\n","'use strict';\n\nvar Cancel = require('./Cancel');\n\n/**\n * A `CancelToken` is an object that can be used to request cancellation of an operation.\n *\n * @class\n * @param {Function} executor The executor function.\n */\nfunction CancelToken(executor) {\n if (typeof executor !== 'function') {\n throw new TypeError('executor must be a function.');\n }\n\n var resolvePromise;\n this.promise = new Promise(function promiseExecutor(resolve) {\n resolvePromise = resolve;\n });\n\n var token = this;\n executor(function cancel(message) {\n if (token.reason) {\n // Cancellation has already been requested\n return;\n }\n\n token.reason = new Cancel(message);\n resolvePromise(token.reason);\n });\n}\n\n/**\n * Throws a `Cancel` if cancellation has been requested.\n */\nCancelToken.prototype.throwIfRequested = function throwIfRequested() {\n if (this.reason) {\n throw this.reason;\n }\n};\n\n/**\n * Returns an object that contains a new `CancelToken` and a function that, when called,\n * cancels the `CancelToken`.\n */\nCancelToken.source = function source() {\n var cancel;\n var token = new CancelToken(function executor(c) {\n cancel = c;\n });\n return {\n token: token,\n cancel: cancel\n };\n};\n\nmodule.exports = CancelToken;\n","'use strict';\n\n/**\n * Syntactic sugar for invoking a function and expanding an array for arguments.\n *\n * Common use case would be to use `Function.prototype.apply`.\n *\n * ```js\n * function f(x, y, z) {}\n * var args = [1, 2, 3];\n * f.apply(null, args);\n * ```\n *\n * With `spread` this example can be re-written.\n *\n * ```js\n * spread(function(x, y, z) {})([1, 2, 3]);\n * ```\n *\n * @param {Function} callback\n * @returns {Function}\n */\nmodule.exports = function spread(callback) {\n return function wrap(arr) {\n return callback.apply(null, arr);\n };\n};\n","'use strict';\n\n/**\n * Determines whether the payload is an error thrown by Axios\n *\n * @param {*} payload The value to test\n * @returns {boolean} True if the payload is an error thrown by Axios, otherwise false\n */\nmodule.exports = function isAxiosError(payload) {\n return (typeof payload === 'object') && (payload.isAxiosError === true);\n};\n","\"use strict\";\n\nexports.__esModule = true;\nexports[\"default\"] = void 0;\n\nfunction last() {\n var _ref;\n\n return _ref = arguments.length - 1, _ref < 0 || arguments.length <= _ref ? undefined : arguments[_ref];\n}\n\nfunction negation(a) {\n return -a;\n}\n\nfunction addition(a, b) {\n return a + b;\n}\n\nfunction subtraction(a, b) {\n return a - b;\n}\n\nfunction multiplication(a, b) {\n return a * b;\n}\n\nfunction division(a, b) {\n return a / b;\n}\n\nfunction max() {\n return Math.max.apply(Math, arguments);\n}\n\nfunction min() {\n return Math.min.apply(Math, arguments);\n}\n\nfunction comma() {\n return Array.of.apply(Array, arguments);\n}\n\nvar defaultSymbols = {\n symbols: {\n '*': {\n infix: {\n symbol: '*',\n f: multiplication,\n notation: 'infix',\n precedence: 4,\n rightToLeft: 0,\n argCount: 2\n },\n symbol: '*',\n regSymbol: '\\\\*'\n },\n '/': {\n infix: {\n symbol: '/',\n f: division,\n notation: 'infix',\n precedence: 4,\n rightToLeft: 0,\n argCount: 2\n },\n symbol: '/',\n regSymbol: '/'\n },\n '+': {\n infix: {\n symbol: '+',\n f: addition,\n notation: 'infix',\n precedence: 2,\n rightToLeft: 0,\n argCount: 2\n },\n prefix: {\n symbol: '+',\n f: last,\n notation: 'prefix',\n precedence: 3,\n rightToLeft: 0,\n argCount: 1\n },\n symbol: '+',\n regSymbol: '\\\\+'\n },\n '-': {\n infix: {\n symbol: '-',\n f: subtraction,\n notation: 'infix',\n precedence: 2,\n rightToLeft: 0,\n argCount: 2\n },\n prefix: {\n symbol: '-',\n f: negation,\n notation: 'prefix',\n precedence: 3,\n rightToLeft: 0,\n argCount: 1\n },\n symbol: '-',\n regSymbol: '-'\n },\n ',': {\n infix: {\n symbol: ',',\n f: comma,\n notation: 'infix',\n precedence: 1,\n rightToLeft: 0,\n argCount: 2\n },\n symbol: ',',\n regSymbol: ','\n },\n '(': {\n prefix: {\n symbol: '(',\n f: last,\n notation: 'prefix',\n precedence: 0,\n rightToLeft: 0,\n argCount: 1\n },\n symbol: '(',\n regSymbol: '\\\\('\n },\n ')': {\n postfix: {\n symbol: ')',\n f: undefined,\n notation: 'postfix',\n precedence: 0,\n rightToLeft: 0,\n argCount: 1\n },\n symbol: ')',\n regSymbol: '\\\\)'\n },\n min: {\n func: {\n symbol: 'min',\n f: min,\n notation: 'func',\n precedence: 0,\n rightToLeft: 0,\n argCount: 1\n },\n symbol: 'min',\n regSymbol: 'min\\\\b'\n },\n max: {\n func: {\n symbol: 'max',\n f: max,\n notation: 'func',\n precedence: 0,\n rightToLeft: 0,\n argCount: 1\n },\n symbol: 'max',\n regSymbol: 'max\\\\b'\n }\n }\n};\nvar _default = defaultSymbols;\nexports[\"default\"] = _default;\nmodule.exports = exports.default;","\"use strict\";\n\nexports.__esModule = true;\nexports[\"default\"] = void 0;\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }\n\nfunction _wrapNativeSuper(Class) { var _cache = typeof Map === \"function\" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null || !_isNativeFunction(Class)) return Class; if (typeof Class !== \"function\") { throw new TypeError(\"Super expression must either be null or a function\"); } if (typeof _cache !== \"undefined\") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); }\n\nfunction _construct(Parent, args, Class) { if (_isNativeReflectConstruct()) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _isNativeFunction(fn) { return Function.toString.call(fn).indexOf(\"[native code]\") !== -1; }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n// based on https://github.com/styled-components/styled-components/blob/fcf6f3804c57a14dd7984dfab7bc06ee2edca044/src/utils/error.js\n\n/**\n * Parse errors.md and turn it into a simple hash of code: message\n * @private\n */\nvar ERRORS = {\n \"1\": \"Passed invalid arguments to hsl, please pass multiple numbers e.g. hsl(360, 0.75, 0.4) or an object e.g. rgb({ hue: 255, saturation: 0.4, lightness: 0.75 }).\\n\\n\",\n \"2\": \"Passed invalid arguments to hsla, please pass multiple numbers e.g. hsla(360, 0.75, 0.4, 0.7) or an object e.g. rgb({ hue: 255, saturation: 0.4, lightness: 0.75, alpha: 0.7 }).\\n\\n\",\n \"3\": \"Passed an incorrect argument to a color function, please pass a string representation of a color.\\n\\n\",\n \"4\": \"Couldn't generate valid rgb string from %s, it returned %s.\\n\\n\",\n \"5\": \"Couldn't parse the color string. Please provide the color as a string in hex, rgb, rgba, hsl or hsla notation.\\n\\n\",\n \"6\": \"Passed invalid arguments to rgb, please pass multiple numbers e.g. rgb(255, 205, 100) or an object e.g. rgb({ red: 255, green: 205, blue: 100 }).\\n\\n\",\n \"7\": \"Passed invalid arguments to rgba, please pass multiple numbers e.g. rgb(255, 205, 100, 0.75) or an object e.g. rgb({ red: 255, green: 205, blue: 100, alpha: 0.75 }).\\n\\n\",\n \"8\": \"Passed invalid argument to toColorString, please pass a RgbColor, RgbaColor, HslColor or HslaColor object.\\n\\n\",\n \"9\": \"Please provide a number of steps to the modularScale helper.\\n\\n\",\n \"10\": \"Please pass a number or one of the predefined scales to the modularScale helper as the ratio.\\n\\n\",\n \"11\": \"Invalid value passed as base to modularScale, expected number or em string but got \\\"%s\\\"\\n\\n\",\n \"12\": \"Expected a string ending in \\\"px\\\" or a number passed as the first argument to %s(), got \\\"%s\\\" instead.\\n\\n\",\n \"13\": \"Expected a string ending in \\\"px\\\" or a number passed as the second argument to %s(), got \\\"%s\\\" instead.\\n\\n\",\n \"14\": \"Passed invalid pixel value (\\\"%s\\\") to %s(), please pass a value like \\\"12px\\\" or 12.\\n\\n\",\n \"15\": \"Passed invalid base value (\\\"%s\\\") to %s(), please pass a value like \\\"12px\\\" or 12.\\n\\n\",\n \"16\": \"You must provide a template to this method.\\n\\n\",\n \"17\": \"You passed an unsupported selector state to this method.\\n\\n\",\n \"18\": \"minScreen and maxScreen must be provided as stringified numbers with the same units.\\n\\n\",\n \"19\": \"fromSize and toSize must be provided as stringified numbers with the same units.\\n\\n\",\n \"20\": \"expects either an array of objects or a single object with the properties prop, fromSize, and toSize.\\n\\n\",\n \"21\": \"expects the objects in the first argument array to have the properties `prop`, `fromSize`, and `toSize`.\\n\\n\",\n \"22\": \"expects the first argument object to have the properties `prop`, `fromSize`, and `toSize`.\\n\\n\",\n \"23\": \"fontFace expects a name of a font-family.\\n\\n\",\n \"24\": \"fontFace expects either the path to the font file(s) or a name of a local copy.\\n\\n\",\n \"25\": \"fontFace expects localFonts to be an array.\\n\\n\",\n \"26\": \"fontFace expects fileFormats to be an array.\\n\\n\",\n \"27\": \"radialGradient requries at least 2 color-stops to properly render.\\n\\n\",\n \"28\": \"Please supply a filename to retinaImage() as the first argument.\\n\\n\",\n \"29\": \"Passed invalid argument to triangle, please pass correct pointingDirection e.g. 'right'.\\n\\n\",\n \"30\": \"Passed an invalid value to `height` or `width`. Please provide a pixel based unit.\\n\\n\",\n \"31\": \"The animation shorthand only takes 8 arguments. See the specification for more information: http://mdn.io/animation\\n\\n\",\n \"32\": \"To pass multiple animations please supply them in arrays, e.g. animation(['rotate', '2s'], ['move', '1s'])\\nTo pass a single animation please supply them in simple values, e.g. animation('rotate', '2s')\\n\\n\",\n \"33\": \"The animation shorthand arrays can only have 8 elements. See the specification for more information: http://mdn.io/animation\\n\\n\",\n \"34\": \"borderRadius expects a radius value as a string or number as the second argument.\\n\\n\",\n \"35\": \"borderRadius expects one of \\\"top\\\", \\\"bottom\\\", \\\"left\\\" or \\\"right\\\" as the first argument.\\n\\n\",\n \"36\": \"Property must be a string value.\\n\\n\",\n \"37\": \"Syntax Error at %s.\\n\\n\",\n \"38\": \"Formula contains a function that needs parentheses at %s.\\n\\n\",\n \"39\": \"Formula is missing closing parenthesis at %s.\\n\\n\",\n \"40\": \"Formula has too many closing parentheses at %s.\\n\\n\",\n \"41\": \"All values in a formula must have the same unit or be unitless.\\n\\n\",\n \"42\": \"Please provide a number of steps to the modularScale helper.\\n\\n\",\n \"43\": \"Please pass a number or one of the predefined scales to the modularScale helper as the ratio.\\n\\n\",\n \"44\": \"Invalid value passed as base to modularScale, expected number or em/rem string but got %s.\\n\\n\",\n \"45\": \"Passed invalid argument to hslToColorString, please pass a HslColor or HslaColor object.\\n\\n\",\n \"46\": \"Passed invalid argument to rgbToColorString, please pass a RgbColor or RgbaColor object.\\n\\n\",\n \"47\": \"minScreen and maxScreen must be provided as stringified numbers with the same units.\\n\\n\",\n \"48\": \"fromSize and toSize must be provided as stringified numbers with the same units.\\n\\n\",\n \"49\": \"Expects either an array of objects or a single object with the properties prop, fromSize, and toSize.\\n\\n\",\n \"50\": \"Expects the objects in the first argument array to have the properties prop, fromSize, and toSize.\\n\\n\",\n \"51\": \"Expects the first argument object to have the properties prop, fromSize, and toSize.\\n\\n\",\n \"52\": \"fontFace expects either the path to the font file(s) or a name of a local copy.\\n\\n\",\n \"53\": \"fontFace expects localFonts to be an array.\\n\\n\",\n \"54\": \"fontFace expects fileFormats to be an array.\\n\\n\",\n \"55\": \"fontFace expects a name of a font-family.\\n\\n\",\n \"56\": \"linearGradient requries at least 2 color-stops to properly render.\\n\\n\",\n \"57\": \"radialGradient requries at least 2 color-stops to properly render.\\n\\n\",\n \"58\": \"Please supply a filename to retinaImage() as the first argument.\\n\\n\",\n \"59\": \"Passed invalid argument to triangle, please pass correct pointingDirection e.g. 'right'.\\n\\n\",\n \"60\": \"Passed an invalid value to `height` or `width`. Please provide a pixel based unit.\\n\\n\",\n \"61\": \"Property must be a string value.\\n\\n\",\n \"62\": \"borderRadius expects a radius value as a string or number as the second argument.\\n\\n\",\n \"63\": \"borderRadius expects one of \\\"top\\\", \\\"bottom\\\", \\\"left\\\" or \\\"right\\\" as the first argument.\\n\\n\",\n \"64\": \"The animation shorthand only takes 8 arguments. See the specification for more information: http://mdn.io/animation.\\n\\n\",\n \"65\": \"To pass multiple animations please supply them in arrays, e.g. animation(['rotate', '2s'], ['move', '1s'])\\\\nTo pass a single animation please supply them in simple values, e.g. animation('rotate', '2s').\\n\\n\",\n \"66\": \"The animation shorthand arrays can only have 8 elements. See the specification for more information: http://mdn.io/animation.\\n\\n\",\n \"67\": \"You must provide a template to this method.\\n\\n\",\n \"68\": \"You passed an unsupported selector state to this method.\\n\\n\",\n \"69\": \"Expected a string ending in \\\"px\\\" or a number passed as the first argument to %s(), got %s instead.\\n\\n\",\n \"70\": \"Expected a string ending in \\\"px\\\" or a number passed as the second argument to %s(), got %s instead.\\n\\n\",\n \"71\": \"Passed invalid pixel value %s to %s(), please pass a value like \\\"12px\\\" or 12.\\n\\n\",\n \"72\": \"Passed invalid base value %s to %s(), please pass a value like \\\"12px\\\" or 12.\\n\\n\",\n \"73\": \"Please provide a valid CSS variable.\\n\\n\",\n \"74\": \"CSS variable not found and no default was provided.\\n\\n\",\n \"75\": \"important requires a valid style object, got a %s instead.\\n\\n\",\n \"76\": \"fromSize and toSize must be provided as stringified numbers with the same units as minScreen and maxScreen.\\n\\n\",\n \"77\": \"remToPx expects a value in \\\"rem\\\" but you provided it in \\\"%s\\\".\\n\\n\",\n \"78\": \"base must be set in \\\"px\\\" or \\\"%\\\" but you set it in \\\"%s\\\".\\n\"\n};\n/**\n * super basic version of sprintf\n * @private\n */\n\nfunction format() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n var a = args[0];\n var b = [];\n var c;\n\n for (c = 1; c < args.length; c += 1) {\n b.push(args[c]);\n }\n\n b.forEach(function (d) {\n a = a.replace(/%[a-z]/, d);\n });\n return a;\n}\n/**\n * Create an error file out of errors.md for development and a simple web link to the full errors\n * in production mode.\n * @private\n */\n\n\nvar PolishedError = /*#__PURE__*/function (_Error) {\n _inheritsLoose(PolishedError, _Error);\n\n function PolishedError(code) {\n var _this;\n\n if (process.env.NODE_ENV === 'production') {\n _this = _Error.call(this, \"An error occurred. See https://github.com/styled-components/polished/blob/main/src/internalHelpers/errors.md#\" + code + \" for more information.\") || this;\n } else {\n for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n args[_key2 - 1] = arguments[_key2];\n }\n\n _this = _Error.call(this, format.apply(void 0, [ERRORS[code]].concat(args))) || this;\n }\n\n return _assertThisInitialized(_this);\n }\n\n return PolishedError;\n}( /*#__PURE__*/_wrapNativeSuper(Error));\n\nexports[\"default\"] = PolishedError;\nmodule.exports = exports.default;","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar react_1 = require(\"react\");\nvar useEffectOnce = function (effect) {\n react_1.useEffect(effect, []);\n};\nexports.default = useEffectOnce;\n","import React from 'react'\n\nlet renderErr = 'Renderer Error ☝️'\n\nexport const actions = {\n init: 'init',\n}\n\nexport const defaultRenderer = ({ value = '' }) => value;\nexport const emptyRenderer = () => <> ;\n\nexport const defaultColumn = {\n Cell: defaultRenderer,\n width: 150,\n minWidth: 0,\n maxWidth: Number.MAX_SAFE_INTEGER,\n}\n\nfunction mergeProps(...propList) {\n return propList.reduce((props, next) => {\n const { style, className, ...rest } = next\n\n props = {\n ...props,\n ...rest,\n }\n\n if (style) {\n props.style = props.style\n ? { ...(props.style || {}), ...(style || {}) }\n : style\n }\n\n if (className) {\n props.className = props.className\n ? props.className + ' ' + className\n : className\n }\n\n if (props.className === '') {\n delete props.className\n }\n\n return props\n }, {})\n}\n\nfunction handlePropGetter(prevProps, userProps, meta) {\n // Handle a lambda, pass it the previous props\n if (typeof userProps === 'function') {\n return handlePropGetter({}, userProps(prevProps, meta))\n }\n\n // Handle an array, merge each item as separate props\n if (Array.isArray(userProps)) {\n return mergeProps(prevProps, ...userProps)\n }\n\n // Handle an object by default, merge the two objects\n return mergeProps(prevProps, userProps)\n}\n\nexport const makePropGetter = (hooks, meta = {}) => {\n return (userProps = {}) =>\n [...hooks, userProps].reduce(\n (prev, next) =>\n handlePropGetter(prev, next, {\n ...meta,\n userProps,\n }),\n {}\n )\n}\n\nexport const reduceHooks = (hooks, initial, meta = {}, allowUndefined) =>\n hooks.reduce((prev, next) => {\n const nextValue = next(prev, meta)\n if (process.env.NODE_ENV !== 'production') {\n if (!allowUndefined && typeof nextValue === 'undefined') {\n console.info(next)\n throw new Error(\n 'React Table: A reducer hook ☝️ just returned undefined! This is not allowed.'\n )\n }\n }\n return nextValue\n }, initial)\n\nexport const loopHooks = (hooks, context, meta = {}) =>\n hooks.forEach(hook => {\n const nextValue = hook(context, meta)\n if (process.env.NODE_ENV !== 'production') {\n if (typeof nextValue !== 'undefined') {\n console.info(hook, nextValue)\n throw new Error(\n 'React Table: A loop-type hook ☝️ just returned a value! This is not allowed.'\n )\n }\n }\n })\n\nexport function ensurePluginOrder(plugins, befores, pluginName, afters) {\n if (process.env.NODE_ENV !== 'production' && afters) {\n throw new Error(\n `Defining plugins in the \"after\" section of ensurePluginOrder is no longer supported (see plugin ${pluginName})`\n )\n }\n const pluginIndex = plugins.findIndex(\n plugin => plugin.pluginName === pluginName\n )\n\n if (pluginIndex === -1) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(`The plugin \"${pluginName}\" was not found in the plugin list!\nThis usually means you need to need to name your plugin hook by setting the 'pluginName' property of the hook function, eg:\n\n ${pluginName}.pluginName = '${pluginName}'\n`)\n }\n }\n\n befores.forEach(before => {\n const beforeIndex = plugins.findIndex(\n plugin => plugin.pluginName === before\n )\n if (beforeIndex > -1 && beforeIndex > pluginIndex) {\n if (process.env.NODE_ENV !== 'production') {\n throw new Error(\n `React Table: The ${pluginName} plugin hook must be placed after the ${before} plugin hook!`\n )\n }\n }\n })\n}\n\nexport function functionalUpdate(updater, old) {\n return typeof updater === 'function' ? updater(old) : updater\n}\n\nexport function useGetLatest(obj) {\n const ref = React.useRef()\n ref.current = obj\n\n return React.useCallback(() => ref.current, [])\n}\n\n// SSR has issues with useLayoutEffect still, so use useEffect during SSR\nexport const safeUseLayoutEffect =\n typeof document !== 'undefined' ? React.useLayoutEffect : React.useEffect\n\nexport function useMountedLayoutEffect(fn, deps) {\n const mountedRef = React.useRef(false)\n\n safeUseLayoutEffect(() => {\n if (mountedRef.current) {\n fn()\n }\n mountedRef.current = true\n // eslint-disable-next-line\n }, deps)\n}\n\nexport function useAsyncDebounce(defaultFn, defaultWait = 0) {\n const debounceRef = React.useRef({})\n\n const getDefaultFn = useGetLatest(defaultFn)\n const getDefaultWait = useGetLatest(defaultWait)\n\n return React.useCallback(\n async (...args) => {\n if (!debounceRef.current.promise) {\n debounceRef.current.promise = new Promise((resolve, reject) => {\n debounceRef.current.resolve = resolve\n debounceRef.current.reject = reject\n })\n }\n\n if (debounceRef.current.timeout) {\n clearTimeout(debounceRef.current.timeout)\n }\n\n debounceRef.current.timeout = setTimeout(async () => {\n delete debounceRef.current.timeout\n try {\n debounceRef.current.resolve(await getDefaultFn()(...args))\n } catch (err) {\n debounceRef.current.reject(err)\n } finally {\n delete debounceRef.current.promise\n }\n }, getDefaultWait())\n\n return debounceRef.current.promise\n },\n [getDefaultFn, getDefaultWait]\n )\n}\n\nexport function makeRenderer(instance, column, meta = {}) {\n return (type, userProps = {}) => {\n const Comp = typeof type === 'string' ? column[type] : type\n\n if (typeof Comp === 'undefined') {\n console.info(column)\n throw new Error(renderErr)\n }\n\n return flexRender(Comp, { ...instance, column, ...meta, ...userProps })\n }\n}\n\nexport function flexRender(Comp, props) {\n return isReactComponent(Comp) ? : Comp\n}\n\nfunction isReactComponent(component) {\n return (\n isClassComponent(component) ||\n typeof component === 'function' ||\n isExoticComponent(component)\n )\n}\n\nfunction isClassComponent(component) {\n return (\n typeof component === 'function' &&\n (() => {\n const proto = Object.getPrototypeOf(component)\n return proto.prototype && proto.prototype.isReactComponent\n })()\n )\n}\n\nfunction isExoticComponent(component) {\n return (\n typeof component === 'object' &&\n typeof component.$$typeof === 'symbol' &&\n ['react.memo', 'react.forward_ref'].includes(component.$$typeof.description)\n )\n}\n","import { defaultColumn, emptyRenderer } from './publicUtils'\n\n// Find the depth of the columns\nexport function findMaxDepth(columns, depth = 0) {\n return columns.reduce((prev, curr) => {\n if (curr.columns) {\n return Math.max(prev, findMaxDepth(curr.columns, depth + 1))\n }\n return depth\n }, 0)\n}\n\n// Build the visible columns, headers and flat column list\nexport function linkColumnStructure(columns, parent, depth = 0) {\n return columns.map(column => {\n column = {\n ...column,\n parent,\n depth,\n }\n\n assignColumnAccessor(column)\n\n if (column.columns) {\n column.columns = linkColumnStructure(column.columns, column, depth + 1)\n }\n return column\n })\n}\n\nexport function flattenColumns(columns) {\n return flattenBy(columns, 'columns')\n}\n\nexport function assignColumnAccessor(column) {\n // First check for string accessor\n let { id, accessor, Header } = column\n\n if (typeof accessor === 'string') {\n id = id || accessor\n const accessorPath = accessor.split('.')\n accessor = row => getBy(row, accessorPath)\n }\n\n if (!id && typeof Header === 'string' && Header) {\n id = Header\n }\n\n if (!id && column.columns) {\n console.error(column)\n throw new Error('A column ID (or unique \"Header\" value) is required!')\n }\n\n if (!id) {\n console.error(column)\n throw new Error('A column ID (or string accessor) is required!')\n }\n\n Object.assign(column, {\n id,\n accessor,\n })\n\n return column\n}\n\nexport function decorateColumn(column, userDefaultColumn) {\n if (!userDefaultColumn) {\n throw new Error()\n }\n Object.assign(column, {\n // Make sure there is a fallback header, just in case\n Header: emptyRenderer,\n Footer: emptyRenderer,\n ...defaultColumn,\n ...userDefaultColumn,\n ...column,\n })\n\n Object.assign(column, {\n originalWidth: column.width,\n })\n\n return column\n}\n\n// Build the header groups from the bottom up\nexport function makeHeaderGroups(\n allColumns,\n defaultColumn,\n additionalHeaderProperties = () => ({})\n) {\n const headerGroups = []\n\n let scanColumns = allColumns\n\n let uid = 0\n const getUID = () => uid++\n\n while (scanColumns.length) {\n // The header group we are creating\n const headerGroup = {\n headers: [],\n }\n\n // The parent columns we're going to scan next\n const parentColumns = []\n\n const hasParents = scanColumns.some(d => d.parent)\n\n // Scan each column for parents\n scanColumns.forEach(column => {\n // What is the latest (last) parent column?\n let latestParentColumn = [...parentColumns].reverse()[0]\n\n let newParent\n\n if (hasParents) {\n // If the column has a parent, add it if necessary\n if (column.parent) {\n newParent = {\n ...column.parent,\n originalId: column.parent.id,\n id: `${column.parent.id}_${getUID()}`,\n headers: [column],\n ...additionalHeaderProperties(column),\n }\n } else {\n // If other columns have parents, we'll need to add a place holder if necessary\n const originalId = `${column.id}_placeholder`\n newParent = decorateColumn(\n {\n originalId,\n id: `${column.id}_placeholder_${getUID()}`,\n placeholderOf: column,\n headers: [column],\n ...additionalHeaderProperties(column),\n },\n defaultColumn\n )\n }\n\n // If the resulting parent columns are the same, just add\n // the column and increment the header span\n if (\n latestParentColumn &&\n latestParentColumn.originalId === newParent.originalId\n ) {\n latestParentColumn.headers.push(column)\n } else {\n parentColumns.push(newParent)\n }\n }\n\n headerGroup.headers.push(column)\n })\n\n headerGroups.push(headerGroup)\n\n // Start scanning the parent columns\n scanColumns = parentColumns\n }\n\n return headerGroups.reverse()\n}\n\nconst pathObjCache = new Map()\n\nexport function getBy(obj, path, def) {\n if (!path) {\n return obj\n }\n const cacheKey = typeof path === 'function' ? path : JSON.stringify(path)\n\n const pathObj =\n pathObjCache.get(cacheKey) ||\n (() => {\n const pathObj = makePathArray(path)\n pathObjCache.set(cacheKey, pathObj)\n return pathObj\n })()\n\n let val\n\n try {\n val = pathObj.reduce((cursor, pathPart) => cursor[pathPart], obj)\n } catch (e) {\n // continue regardless of error\n }\n return typeof val !== 'undefined' ? val : def\n}\n\nexport function getFirstDefined(...args) {\n for (let i = 0; i < args.length; i += 1) {\n if (typeof args[i] !== 'undefined') {\n return args[i]\n }\n }\n}\n\nexport function getElementDimensions(element) {\n const rect = element.getBoundingClientRect()\n const style = window.getComputedStyle(element)\n const margins = {\n left: parseInt(style.marginLeft),\n right: parseInt(style.marginRight),\n }\n const padding = {\n left: parseInt(style.paddingLeft),\n right: parseInt(style.paddingRight),\n }\n return {\n left: Math.ceil(rect.left),\n width: Math.ceil(rect.width),\n outerWidth: Math.ceil(\n rect.width + margins.left + margins.right + padding.left + padding.right\n ),\n marginLeft: margins.left,\n marginRight: margins.right,\n paddingLeft: padding.left,\n paddingRight: padding.right,\n scrollWidth: element.scrollWidth,\n }\n}\n\nexport function isFunction(a) {\n if (typeof a === 'function') {\n return a\n }\n}\n\nexport function flattenBy(arr, key) {\n const flat = []\n\n const recurse = arr => {\n arr.forEach(d => {\n if (!d[key]) {\n flat.push(d)\n } else {\n recurse(d[key])\n }\n })\n }\n\n recurse(arr)\n\n return flat\n}\n\nexport function expandRows(\n rows,\n { manualExpandedKey, expanded, expandSubRows = true }\n) {\n const expandedRows = []\n\n const handleRow = (row, addToExpandedRows = true) => {\n row.isExpanded =\n (row.original && row.original[manualExpandedKey]) || expanded[row.id]\n\n row.canExpand = row.subRows && !!row.subRows.length\n\n if (addToExpandedRows) {\n expandedRows.push(row)\n }\n\n if (row.subRows && row.subRows.length && row.isExpanded) {\n row.subRows.forEach(row => handleRow(row, expandSubRows))\n }\n }\n\n rows.forEach(row => handleRow(row))\n\n return expandedRows\n}\n\nexport function getFilterMethod(filter, userFilterTypes, filterTypes) {\n return (\n isFunction(filter) ||\n userFilterTypes[filter] ||\n filterTypes[filter] ||\n filterTypes.text\n )\n}\n\nexport function shouldAutoRemoveFilter(autoRemove, value, column) {\n return autoRemove ? autoRemove(value, column) : typeof value === 'undefined'\n}\n\nexport function unpreparedAccessWarning() {\n throw new Error(\n 'React-Table: You have not called prepareRow(row) one or more rows you are attempting to render.'\n )\n}\n\nlet passiveSupported = null\nexport function passiveEventSupported() {\n // memoize support to avoid adding multiple test events\n if (typeof passiveSupported === 'boolean') return passiveSupported\n\n let supported = false\n try {\n const options = {\n get passive() {\n supported = true\n return false\n },\n }\n\n window.addEventListener('test', null, options)\n window.removeEventListener('test', null, options)\n } catch (err) {\n supported = false\n }\n passiveSupported = supported\n return passiveSupported\n}\n\n//\n\nconst reOpenBracket = /\\[/g\nconst reCloseBracket = /\\]/g\n\nfunction makePathArray(obj) {\n return (\n flattenDeep(obj)\n // remove all periods in parts\n .map(d => String(d).replace('.', '_'))\n // join parts using period\n .join('.')\n // replace brackets with periods\n .replace(reOpenBracket, '.')\n .replace(reCloseBracket, '')\n // split it back out on periods\n .split('.')\n )\n}\n\nfunction flattenDeep(arr, newArr = []) {\n if (!Array.isArray(arr)) {\n newArr.push(arr)\n } else {\n for (let i = 0; i < arr.length; i += 1) {\n flattenDeep(arr[i], newArr)\n }\n }\n return newArr\n}\n","const defaultGetTableProps = props => ({\n role: 'table',\n ...props,\n})\n\nconst defaultGetTableBodyProps = props => ({\n role: 'rowgroup',\n ...props,\n})\n\nconst defaultGetHeaderProps = (props, { column }) => ({\n key: `header_${column.id}`,\n colSpan: column.totalVisibleHeaderCount,\n role: 'columnheader',\n ...props,\n})\n\nconst defaultGetFooterProps = (props, { column }) => ({\n key: `footer_${column.id}`,\n colSpan: column.totalVisibleHeaderCount,\n ...props,\n})\n\nconst defaultGetHeaderGroupProps = (props, { index }) => ({\n key: `headerGroup_${index}`,\n role: 'row',\n ...props,\n})\n\nconst defaultGetFooterGroupProps = (props, { index }) => ({\n key: `footerGroup_${index}`,\n ...props,\n})\n\nconst defaultGetRowProps = (props, { row }) => ({\n key: `row_${row.id}`,\n role: 'row',\n ...props,\n})\n\nconst defaultGetCellProps = (props, { cell }) => ({\n key: `cell_${cell.row.id}_${cell.column.id}`,\n role: 'cell',\n ...props,\n})\n\nexport default function makeDefaultPluginHooks() {\n return {\n useOptions: [],\n stateReducers: [],\n useControlledState: [],\n columns: [],\n columnsDeps: [],\n allColumns: [],\n allColumnsDeps: [],\n accessValue: [],\n materializedColumns: [],\n materializedColumnsDeps: [],\n useInstanceAfterData: [],\n visibleColumns: [],\n visibleColumnsDeps: [],\n headerGroups: [],\n headerGroupsDeps: [],\n useInstanceBeforeDimensions: [],\n useInstance: [],\n prepareRow: [],\n getTableProps: [defaultGetTableProps],\n getTableBodyProps: [defaultGetTableBodyProps],\n getHeaderGroupProps: [defaultGetHeaderGroupProps],\n getFooterGroupProps: [defaultGetFooterGroupProps],\n getHeaderProps: [defaultGetHeaderProps],\n getFooterProps: [defaultGetFooterProps],\n getRowProps: [defaultGetRowProps],\n getCellProps: [defaultGetCellProps],\n useFinalInstance: [],\n }\n}\n","import React from 'react'\n\nimport {\n actions,\n functionalUpdate,\n useGetLatest,\n makePropGetter,\n useMountedLayoutEffect,\n} from '../publicUtils'\n\nactions.resetHiddenColumns = 'resetHiddenColumns'\nactions.toggleHideColumn = 'toggleHideColumn'\nactions.setHiddenColumns = 'setHiddenColumns'\nactions.toggleHideAllColumns = 'toggleHideAllColumns'\n\nexport const useColumnVisibility = hooks => {\n hooks.getToggleHiddenProps = [defaultGetToggleHiddenProps]\n hooks.getToggleHideAllColumnsProps = [defaultGetToggleHideAllColumnsProps]\n\n hooks.stateReducers.push(reducer)\n hooks.useInstanceBeforeDimensions.push(useInstanceBeforeDimensions)\n hooks.headerGroupsDeps.push((deps, { instance }) => [\n ...deps,\n instance.state.hiddenColumns,\n ])\n hooks.useInstance.push(useInstance)\n}\n\nuseColumnVisibility.pluginName = 'useColumnVisibility'\n\nconst defaultGetToggleHiddenProps = (props, { column }) => [\n props,\n {\n onChange: e => {\n column.toggleHidden(!e.target.checked)\n },\n style: {\n cursor: 'pointer',\n },\n checked: column.isVisible,\n title: 'Toggle Column Visible',\n },\n]\n\nconst defaultGetToggleHideAllColumnsProps = (props, { instance }) => [\n props,\n {\n onChange: e => {\n instance.toggleHideAllColumns(!e.target.checked)\n },\n style: {\n cursor: 'pointer',\n },\n checked: !instance.allColumnsHidden && !instance.state.hiddenColumns.length,\n title: 'Toggle All Columns Hidden',\n indeterminate:\n !instance.allColumnsHidden && instance.state.hiddenColumns.length,\n },\n]\n\nfunction reducer(state, action, previousState, instance) {\n if (action.type === actions.init) {\n return {\n hiddenColumns: [],\n ...state,\n }\n }\n\n if (action.type === actions.resetHiddenColumns) {\n return {\n ...state,\n hiddenColumns: instance.initialState.hiddenColumns || [],\n }\n }\n\n if (action.type === actions.toggleHideColumn) {\n const should =\n typeof action.value !== 'undefined'\n ? action.value\n : !state.hiddenColumns.includes(action.columnId)\n\n const hiddenColumns = should\n ? [...state.hiddenColumns, action.columnId]\n : state.hiddenColumns.filter(d => d !== action.columnId)\n\n return {\n ...state,\n hiddenColumns,\n }\n }\n\n if (action.type === actions.setHiddenColumns) {\n return {\n ...state,\n hiddenColumns: functionalUpdate(action.value, state.hiddenColumns),\n }\n }\n\n if (action.type === actions.toggleHideAllColumns) {\n const shouldAll =\n typeof action.value !== 'undefined'\n ? action.value\n : !state.hiddenColumns.length\n\n return {\n ...state,\n hiddenColumns: shouldAll ? instance.allColumns.map(d => d.id) : [],\n }\n }\n}\n\nfunction useInstanceBeforeDimensions(instance) {\n const {\n headers,\n state: { hiddenColumns },\n } = instance\n\n const isMountedRef = React.useRef(false)\n\n if (!isMountedRef.current) {\n }\n\n const handleColumn = (column, parentVisible) => {\n column.isVisible = parentVisible && !hiddenColumns.includes(column.id)\n\n let totalVisibleHeaderCount = 0\n\n if (column.headers && column.headers.length) {\n column.headers.forEach(\n subColumn =>\n (totalVisibleHeaderCount += handleColumn(subColumn, column.isVisible))\n )\n } else {\n totalVisibleHeaderCount = column.isVisible ? 1 : 0\n }\n\n column.totalVisibleHeaderCount = totalVisibleHeaderCount\n\n return totalVisibleHeaderCount\n }\n\n let totalVisibleHeaderCount = 0\n\n headers.forEach(\n subHeader => (totalVisibleHeaderCount += handleColumn(subHeader, true))\n )\n}\n\nfunction useInstance(instance) {\n const {\n columns,\n flatHeaders,\n dispatch,\n allColumns,\n getHooks,\n state: { hiddenColumns },\n autoResetHiddenColumns = true,\n } = instance\n\n const getInstance = useGetLatest(instance)\n\n const allColumnsHidden = allColumns.length === hiddenColumns.length\n\n const toggleHideColumn = React.useCallback(\n (columnId, value) =>\n dispatch({ type: actions.toggleHideColumn, columnId, value }),\n [dispatch]\n )\n\n const setHiddenColumns = React.useCallback(\n value => dispatch({ type: actions.setHiddenColumns, value }),\n [dispatch]\n )\n\n const toggleHideAllColumns = React.useCallback(\n value => dispatch({ type: actions.toggleHideAllColumns, value }),\n [dispatch]\n )\n\n const getToggleHideAllColumnsProps = makePropGetter(\n getHooks().getToggleHideAllColumnsProps,\n { instance: getInstance() }\n )\n\n flatHeaders.forEach(column => {\n column.toggleHidden = value => {\n dispatch({\n type: actions.toggleHideColumn,\n columnId: column.id,\n value,\n })\n }\n\n column.getToggleHiddenProps = makePropGetter(\n getHooks().getToggleHiddenProps,\n {\n instance: getInstance(),\n column,\n }\n )\n })\n\n const getAutoResetHiddenColumns = useGetLatest(autoResetHiddenColumns)\n\n useMountedLayoutEffect(() => {\n if (getAutoResetHiddenColumns()) {\n dispatch({ type: actions.resetHiddenColumns })\n }\n }, [dispatch, columns])\n\n Object.assign(instance, {\n allColumnsHidden,\n toggleHideColumn,\n setHiddenColumns,\n toggleHideAllColumns,\n getToggleHideAllColumnsProps,\n })\n}\n","import React from 'react'\n\n//\n\nimport {\n linkColumnStructure,\n flattenColumns,\n assignColumnAccessor,\n unpreparedAccessWarning,\n makeHeaderGroups,\n decorateColumn,\n} from '../utils'\n\nimport {\n useGetLatest,\n reduceHooks,\n actions,\n loopHooks,\n makePropGetter,\n makeRenderer,\n} from '../publicUtils'\n\nimport makeDefaultPluginHooks from '../makeDefaultPluginHooks'\n\nimport { useColumnVisibility } from './useColumnVisibility'\n\nconst defaultInitialState = {}\nconst defaultColumnInstance = {}\nconst defaultReducer = (state, action, prevState) => state\nconst defaultGetSubRows = (row, index) => row.subRows || []\nconst defaultGetRowId = (row, index, parent) =>\n `${parent ? [parent.id, index].join('.') : index}`\nconst defaultUseControlledState = d => d\n\nfunction applyDefaults(props) {\n const {\n initialState = defaultInitialState,\n defaultColumn = defaultColumnInstance,\n getSubRows = defaultGetSubRows,\n getRowId = defaultGetRowId,\n stateReducer = defaultReducer,\n useControlledState = defaultUseControlledState,\n ...rest\n } = props\n\n return {\n ...rest,\n initialState,\n defaultColumn,\n getSubRows,\n getRowId,\n stateReducer,\n useControlledState,\n }\n}\n\nexport const useTable = (props, ...plugins) => {\n // Apply default props\n props = applyDefaults(props)\n\n // Add core plugins\n plugins = [useColumnVisibility, ...plugins]\n\n // Create the table instance\n let instanceRef = React.useRef({})\n\n // Create a getter for the instance (helps avoid a lot of potential memory leaks)\n const getInstance = useGetLatest(instanceRef.current)\n\n // Assign the props, plugins and hooks to the instance\n Object.assign(getInstance(), {\n ...props,\n plugins,\n hooks: makeDefaultPluginHooks(),\n })\n\n // Allow plugins to register hooks as early as possible\n plugins.filter(Boolean).forEach(plugin => {\n plugin(getInstance().hooks)\n })\n\n // Consume all hooks and make a getter for them\n const getHooks = useGetLatest(getInstance().hooks)\n getInstance().getHooks = getHooks\n delete getInstance().hooks\n\n // Allow useOptions hooks to modify the options coming into the table\n Object.assign(\n getInstance(),\n reduceHooks(getHooks().useOptions, applyDefaults(props))\n )\n\n const {\n data,\n columns: userColumns,\n initialState,\n defaultColumn,\n getSubRows,\n getRowId,\n stateReducer,\n useControlledState,\n } = getInstance()\n\n // Setup user reducer ref\n const getStateReducer = useGetLatest(stateReducer)\n\n // Build the reducer\n const reducer = React.useCallback(\n (state, action) => {\n // Detect invalid actions\n if (!action.type) {\n console.info({ action })\n throw new Error('Unknown Action 👆')\n }\n\n // Reduce the state from all plugin reducers\n return [\n ...getHooks().stateReducers,\n // Allow the user to add their own state reducer(s)\n ...(Array.isArray(getStateReducer())\n ? getStateReducer()\n : [getStateReducer()]),\n ].reduce(\n (s, handler) => handler(s, action, state, getInstance()) || s,\n state\n )\n },\n [getHooks, getStateReducer, getInstance]\n )\n\n // Start the reducer\n const [reducerState, dispatch] = React.useReducer(reducer, undefined, () =>\n reducer(initialState, { type: actions.init })\n )\n\n // Allow the user to control the final state with hooks\n const state = reduceHooks(\n [...getHooks().useControlledState, useControlledState],\n reducerState,\n { instance: getInstance() }\n )\n\n Object.assign(getInstance(), {\n state,\n dispatch,\n })\n\n // Decorate All the columns\n const columns = React.useMemo(\n () =>\n linkColumnStructure(\n reduceHooks(getHooks().columns, userColumns, {\n instance: getInstance(),\n })\n ),\n [\n getHooks,\n getInstance,\n userColumns,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n ...reduceHooks(getHooks().columnsDeps, [], { instance: getInstance() }),\n ]\n )\n getInstance().columns = columns\n\n // Get the flat list of all columns and allow hooks to decorate\n // those columns (and trigger this memoization via deps)\n let allColumns = React.useMemo(\n () =>\n reduceHooks(getHooks().allColumns, flattenColumns(columns), {\n instance: getInstance(),\n }).map(assignColumnAccessor),\n [\n columns,\n getHooks,\n getInstance,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n ...reduceHooks(getHooks().allColumnsDeps, [], {\n instance: getInstance(),\n }),\n ]\n )\n getInstance().allColumns = allColumns\n\n // Access the row model using initial columns\n const [rows, flatRows, rowsById] = React.useMemo(() => {\n let rows = []\n let flatRows = []\n const rowsById = {}\n\n const allColumnsQueue = [...allColumns]\n\n while (allColumnsQueue.length) {\n const column = allColumnsQueue.shift()\n accessRowsForColumn({\n data,\n rows,\n flatRows,\n rowsById,\n column,\n getRowId,\n getSubRows,\n accessValueHooks: getHooks().accessValue,\n getInstance,\n })\n }\n\n return [rows, flatRows, rowsById]\n }, [allColumns, data, getRowId, getSubRows, getHooks, getInstance])\n\n Object.assign(getInstance(), {\n rows,\n initialRows: [...rows],\n flatRows,\n rowsById,\n // materializedColumns,\n })\n\n loopHooks(getHooks().useInstanceAfterData, getInstance())\n\n // Get the flat list of all columns AFTER the rows\n // have been access, and allow hooks to decorate\n // those columns (and trigger this memoization via deps)\n let visibleColumns = React.useMemo(\n () =>\n reduceHooks(getHooks().visibleColumns, allColumns, {\n instance: getInstance(),\n }).map(d => decorateColumn(d, defaultColumn)),\n [\n getHooks,\n allColumns,\n getInstance,\n defaultColumn,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n ...reduceHooks(getHooks().visibleColumnsDeps, [], {\n instance: getInstance(),\n }),\n ]\n )\n\n // Combine new visible columns with all columns\n allColumns = React.useMemo(() => {\n const columns = [...visibleColumns]\n\n allColumns.forEach(column => {\n if (!columns.find(d => d.id === column.id)) {\n columns.push(column)\n }\n })\n\n return columns\n }, [allColumns, visibleColumns])\n getInstance().allColumns = allColumns\n\n if (process.env.NODE_ENV !== 'production') {\n const duplicateColumns = allColumns.filter((column, i) => {\n return allColumns.findIndex(d => d.id === column.id) !== i\n })\n\n if (duplicateColumns.length) {\n console.info(allColumns)\n throw new Error(\n `Duplicate columns were found with ids: \"${duplicateColumns\n .map(d => d.id)\n .join(', ')}\" in the columns array above`\n )\n }\n }\n\n // Make the headerGroups\n const headerGroups = React.useMemo(\n () =>\n reduceHooks(\n getHooks().headerGroups,\n makeHeaderGroups(visibleColumns, defaultColumn),\n getInstance()\n ),\n [\n getHooks,\n visibleColumns,\n defaultColumn,\n getInstance,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n ...reduceHooks(getHooks().headerGroupsDeps, [], {\n instance: getInstance(),\n }),\n ]\n )\n getInstance().headerGroups = headerGroups\n\n // Get the first level of headers\n const headers = React.useMemo(\n () => (headerGroups.length ? headerGroups[0].headers : []),\n [headerGroups]\n )\n getInstance().headers = headers\n\n // Provide a flat header list for utilities\n getInstance().flatHeaders = headerGroups.reduce(\n (all, headerGroup) => [...all, ...headerGroup.headers],\n []\n )\n\n loopHooks(getHooks().useInstanceBeforeDimensions, getInstance())\n\n // Filter columns down to visible ones\n const visibleColumnsDep = visibleColumns\n .filter(d => d.isVisible)\n .map(d => d.id)\n .sort()\n .join('_')\n\n visibleColumns = React.useMemo(\n () => visibleColumns.filter(d => d.isVisible),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [visibleColumns, visibleColumnsDep]\n )\n getInstance().visibleColumns = visibleColumns\n\n // Header Visibility is needed by this point\n const [\n totalColumnsMinWidth,\n totalColumnsWidth,\n totalColumnsMaxWidth,\n ] = calculateHeaderWidths(headers)\n\n getInstance().totalColumnsMinWidth = totalColumnsMinWidth\n getInstance().totalColumnsWidth = totalColumnsWidth\n getInstance().totalColumnsMaxWidth = totalColumnsMaxWidth\n\n loopHooks(getHooks().useInstance, getInstance())\n\n // Each materialized header needs to be assigned a render function and other\n // prop getter properties here.\n ;[...getInstance().flatHeaders, ...getInstance().allColumns].forEach(\n column => {\n // Give columns/headers rendering power\n column.render = makeRenderer(getInstance(), column)\n\n // Give columns/headers a default getHeaderProps\n column.getHeaderProps = makePropGetter(getHooks().getHeaderProps, {\n instance: getInstance(),\n column,\n })\n\n // Give columns/headers a default getFooterProps\n column.getFooterProps = makePropGetter(getHooks().getFooterProps, {\n instance: getInstance(),\n column,\n })\n }\n )\n\n getInstance().headerGroups = React.useMemo(\n () =>\n headerGroups.filter((headerGroup, i) => {\n // Filter out any headers and headerGroups that don't have visible columns\n headerGroup.headers = headerGroup.headers.filter(column => {\n const recurse = headers =>\n headers.filter(column => {\n if (column.headers) {\n return recurse(column.headers)\n }\n return column.isVisible\n }).length\n if (column.headers) {\n return recurse(column.headers)\n }\n return column.isVisible\n })\n\n // Give headerGroups getRowProps\n if (headerGroup.headers.length) {\n headerGroup.getHeaderGroupProps = makePropGetter(\n getHooks().getHeaderGroupProps,\n { instance: getInstance(), headerGroup, index: i }\n )\n\n headerGroup.getFooterGroupProps = makePropGetter(\n getHooks().getFooterGroupProps,\n { instance: getInstance(), headerGroup, index: i }\n )\n\n return true\n }\n\n return false\n }),\n [headerGroups, getInstance, getHooks]\n )\n\n getInstance().footerGroups = [...getInstance().headerGroups].reverse()\n\n // The prepareRow function is absolutely necessary and MUST be called on\n // any rows the user wishes to be displayed.\n\n getInstance().prepareRow = React.useCallback(\n row => {\n row.getRowProps = makePropGetter(getHooks().getRowProps, {\n instance: getInstance(),\n row,\n })\n\n // Build the visible cells for each row\n row.allCells = allColumns.map(column => {\n const value = row.values[column.id]\n\n const cell = {\n column,\n row,\n value,\n }\n\n // Give each cell a getCellProps base\n cell.getCellProps = makePropGetter(getHooks().getCellProps, {\n instance: getInstance(),\n cell,\n })\n\n // Give each cell a renderer function (supports multiple renderers)\n cell.render = makeRenderer(getInstance(), column, {\n row,\n cell,\n value,\n })\n\n return cell\n })\n\n row.cells = visibleColumns.map(column =>\n row.allCells.find(cell => cell.column.id === column.id)\n )\n\n // need to apply any row specific hooks (useExpanded requires this)\n loopHooks(getHooks().prepareRow, row, { instance: getInstance() })\n },\n [getHooks, getInstance, allColumns, visibleColumns]\n )\n\n getInstance().getTableProps = makePropGetter(getHooks().getTableProps, {\n instance: getInstance(),\n })\n\n getInstance().getTableBodyProps = makePropGetter(\n getHooks().getTableBodyProps,\n {\n instance: getInstance(),\n }\n )\n\n loopHooks(getHooks().useFinalInstance, getInstance())\n\n return getInstance()\n}\n\nfunction calculateHeaderWidths(headers, left = 0) {\n let sumTotalMinWidth = 0\n let sumTotalWidth = 0\n let sumTotalMaxWidth = 0\n let sumTotalFlexWidth = 0\n\n headers.forEach(header => {\n let { headers: subHeaders } = header\n\n header.totalLeft = left\n\n if (subHeaders && subHeaders.length) {\n const [\n totalMinWidth,\n totalWidth,\n totalMaxWidth,\n totalFlexWidth,\n ] = calculateHeaderWidths(subHeaders, left)\n header.totalMinWidth = totalMinWidth\n header.totalWidth = totalWidth\n header.totalMaxWidth = totalMaxWidth\n header.totalFlexWidth = totalFlexWidth\n } else {\n header.totalMinWidth = header.minWidth\n header.totalWidth = Math.min(\n Math.max(header.minWidth, header.width),\n header.maxWidth\n )\n header.totalMaxWidth = header.maxWidth\n header.totalFlexWidth = header.canResize ? header.totalWidth : 0\n }\n if (header.isVisible) {\n left += header.totalWidth\n sumTotalMinWidth += header.totalMinWidth\n sumTotalWidth += header.totalWidth\n sumTotalMaxWidth += header.totalMaxWidth\n sumTotalFlexWidth += header.totalFlexWidth\n }\n })\n\n return [sumTotalMinWidth, sumTotalWidth, sumTotalMaxWidth, sumTotalFlexWidth]\n}\n\nfunction accessRowsForColumn({\n data,\n rows,\n flatRows,\n rowsById,\n column,\n getRowId,\n getSubRows,\n accessValueHooks,\n getInstance,\n}) {\n // Access the row's data column-by-column\n // We do it this way so we can incrementally add materialized\n // columns after the first pass and avoid excessive looping\n const accessRow = (originalRow, rowIndex, depth = 0, parent, parentRows) => {\n // Keep the original reference around\n const original = originalRow\n\n const id = getRowId(originalRow, rowIndex, parent)\n\n let row = rowsById[id]\n\n // If the row hasn't been created, let's make it\n if (!row) {\n row = {\n id,\n original,\n index: rowIndex,\n depth,\n cells: [{}], // This is a dummy cell\n }\n\n // Override common array functions (and the dummy cell's getCellProps function)\n // to show an error if it is accessed without calling prepareRow\n row.cells.map = unpreparedAccessWarning\n row.cells.filter = unpreparedAccessWarning\n row.cells.forEach = unpreparedAccessWarning\n row.cells[0].getCellProps = unpreparedAccessWarning\n\n // Create the cells and values\n row.values = {}\n\n // Push this row into the parentRows array\n parentRows.push(row)\n // Keep track of every row in a flat array\n flatRows.push(row)\n // Also keep track of every row by its ID\n rowsById[id] = row\n\n // Get the original subrows\n row.originalSubRows = getSubRows(originalRow, rowIndex)\n\n // Then recursively access them\n if (row.originalSubRows) {\n const subRows = []\n row.originalSubRows.forEach((d, i) =>\n accessRow(d, i, depth + 1, row, subRows)\n )\n // Keep the new subRows array on the row\n row.subRows = subRows\n }\n } else if (row.subRows) {\n // If the row exists, then it's already been accessed\n // Keep recursing, but don't worry about passing the\n // accumlator array (those rows already exist)\n row.originalSubRows.forEach((d, i) => accessRow(d, i, depth + 1, row))\n }\n\n // If the column has an accessor, use it to get a value\n if (column.accessor) {\n row.values[column.id] = column.accessor(\n originalRow,\n rowIndex,\n row,\n parentRows,\n data\n )\n }\n\n // Allow plugins to manipulate the column value\n row.values[column.id] = reduceHooks(\n accessValueHooks,\n row.values[column.id],\n {\n row,\n column,\n instance: getInstance(),\n },\n true\n )\n }\n\n data.forEach((originalRow, rowIndex) =>\n accessRow(originalRow, rowIndex, 0, undefined, rows)\n )\n}\n","import React from 'react'\n\nimport { expandRows } from '../utils'\n\nimport {\n useGetLatest,\n actions,\n useMountedLayoutEffect,\n makePropGetter,\n ensurePluginOrder,\n} from '../publicUtils'\n\n// Actions\nactions.resetExpanded = 'resetExpanded'\nactions.toggleRowExpanded = 'toggleRowExpanded'\nactions.toggleAllRowsExpanded = 'toggleAllRowsExpanded'\n\nexport const useExpanded = hooks => {\n hooks.getToggleAllRowsExpandedProps = [defaultGetToggleAllRowsExpandedProps]\n hooks.getToggleRowExpandedProps = [defaultGetToggleRowExpandedProps]\n hooks.stateReducers.push(reducer)\n hooks.useInstance.push(useInstance)\n hooks.prepareRow.push(prepareRow)\n}\n\nuseExpanded.pluginName = 'useExpanded'\n\nconst defaultGetToggleAllRowsExpandedProps = (props, { instance }) => [\n props,\n {\n onClick: e => {\n instance.toggleAllRowsExpanded()\n },\n style: {\n cursor: 'pointer',\n },\n title: 'Toggle All Rows Expanded',\n },\n]\n\nconst defaultGetToggleRowExpandedProps = (props, { row }) => [\n props,\n {\n onClick: () => {\n row.toggleRowExpanded()\n },\n style: {\n cursor: 'pointer',\n },\n title: 'Toggle Row Expanded',\n },\n]\n\n// Reducer\nfunction reducer(state, action, previousState, instance) {\n if (action.type === actions.init) {\n return {\n expanded: {},\n ...state,\n }\n }\n\n if (action.type === actions.resetExpanded) {\n return {\n ...state,\n expanded: instance.initialState.expanded || {},\n }\n }\n\n if (action.type === actions.toggleAllRowsExpanded) {\n const { value } = action\n const { rowsById } = instance\n\n const isAllRowsExpanded =\n Object.keys(rowsById).length === Object.keys(state.expanded).length\n\n const expandAll = typeof value !== 'undefined' ? value : !isAllRowsExpanded\n\n if (expandAll) {\n const expanded = {}\n\n Object.keys(rowsById).forEach(rowId => {\n expanded[rowId] = true\n })\n\n return {\n ...state,\n expanded,\n }\n }\n\n return {\n ...state,\n expanded: {},\n }\n }\n\n if (action.type === actions.toggleRowExpanded) {\n const { id, value: setExpanded } = action\n const exists = state.expanded[id]\n\n const shouldExist =\n typeof setExpanded !== 'undefined' ? setExpanded : !exists\n\n if (!exists && shouldExist) {\n return {\n ...state,\n expanded: {\n ...state.expanded,\n [id]: true,\n },\n }\n } else if (exists && !shouldExist) {\n const { [id]: _, ...rest } = state.expanded\n return {\n ...state,\n expanded: rest,\n }\n } else {\n return state\n }\n }\n}\n\nfunction useInstance(instance) {\n const {\n data,\n rows,\n rowsById,\n manualExpandedKey = 'expanded',\n paginateExpandedRows = true,\n expandSubRows = true,\n autoResetExpanded = true,\n getHooks,\n plugins,\n state: { expanded },\n dispatch,\n } = instance\n\n ensurePluginOrder(\n plugins,\n ['useSortBy', 'useGroupBy', 'usePivotColumns', 'useGlobalFilter'],\n 'useExpanded'\n )\n\n const getAutoResetExpanded = useGetLatest(autoResetExpanded)\n\n let isAllRowsExpanded = Boolean(\n Object.keys(rowsById).length && Object.keys(expanded).length\n )\n\n if (isAllRowsExpanded) {\n if (Object.keys(rowsById).some(id => !expanded[id])) {\n isAllRowsExpanded = false\n }\n }\n\n // Bypass any effects from firing when this changes\n useMountedLayoutEffect(() => {\n if (getAutoResetExpanded()) {\n dispatch({ type: actions.resetExpanded })\n }\n }, [dispatch, data])\n\n const toggleRowExpanded = React.useCallback(\n (id, value) => {\n dispatch({ type: actions.toggleRowExpanded, id, value })\n },\n [dispatch]\n )\n\n const toggleAllRowsExpanded = React.useCallback(\n value => dispatch({ type: actions.toggleAllRowsExpanded, value }),\n [dispatch]\n )\n\n const expandedRows = React.useMemo(() => {\n if (paginateExpandedRows) {\n return expandRows(rows, { manualExpandedKey, expanded, expandSubRows })\n }\n\n return rows\n }, [paginateExpandedRows, rows, manualExpandedKey, expanded, expandSubRows])\n\n const expandedDepth = React.useMemo(() => findExpandedDepth(expanded), [\n expanded,\n ])\n\n const getInstance = useGetLatest(instance)\n\n const getToggleAllRowsExpandedProps = makePropGetter(\n getHooks().getToggleAllRowsExpandedProps,\n { instance: getInstance() }\n )\n\n Object.assign(instance, {\n preExpandedRows: rows,\n expandedRows,\n rows: expandedRows,\n expandedDepth,\n isAllRowsExpanded,\n toggleRowExpanded,\n toggleAllRowsExpanded,\n getToggleAllRowsExpandedProps,\n })\n}\n\nfunction prepareRow(row, { instance: { getHooks }, instance }) {\n row.toggleRowExpanded = set => instance.toggleRowExpanded(row.id, set)\n\n row.getToggleRowExpandedProps = makePropGetter(\n getHooks().getToggleRowExpandedProps,\n {\n instance,\n row,\n }\n )\n}\n\nfunction findExpandedDepth(expanded) {\n let maxDepth = 0\n\n Object.keys(expanded).forEach(id => {\n const splitId = id.split('.')\n maxDepth = Math.max(maxDepth, splitId.length)\n })\n\n return maxDepth\n}\n","export const text = (rows, ids, filterValue) => {\n rows = rows.filter(row => {\n return ids.some(id => {\n const rowValue = row.values[id]\n return String(rowValue)\n .toLowerCase()\n .includes(String(filterValue).toLowerCase())\n })\n })\n return rows\n}\n\ntext.autoRemove = val => !val\n\nexport const exactText = (rows, ids, filterValue) => {\n return rows.filter(row => {\n return ids.some(id => {\n const rowValue = row.values[id]\n return rowValue !== undefined\n ? String(rowValue).toLowerCase() === String(filterValue).toLowerCase()\n : true\n })\n })\n}\n\nexactText.autoRemove = val => !val\n\nexport const exactTextCase = (rows, ids, filterValue) => {\n return rows.filter(row => {\n return ids.some(id => {\n const rowValue = row.values[id]\n return rowValue !== undefined\n ? String(rowValue) === String(filterValue)\n : true\n })\n })\n}\n\nexactTextCase.autoRemove = val => !val\n\nexport const includes = (rows, ids, filterValue) => {\n return rows.filter(row => {\n return ids.some(id => {\n const rowValue = row.values[id]\n return rowValue.includes(filterValue)\n })\n })\n}\n\nincludes.autoRemove = val => !val || !val.length\n\nexport const includesAll = (rows, ids, filterValue) => {\n return rows.filter(row => {\n return ids.some(id => {\n const rowValue = row.values[id]\n return (\n rowValue &&\n rowValue.length &&\n filterValue.every(val => rowValue.includes(val))\n )\n })\n })\n}\n\nincludesAll.autoRemove = val => !val || !val.length\n\nexport const includesSome = (rows, ids, filterValue) => {\n return rows.filter(row => {\n return ids.some(id => {\n const rowValue = row.values[id]\n return (\n rowValue &&\n rowValue.length &&\n filterValue.some(val => rowValue.includes(val))\n )\n })\n })\n}\n\nincludesSome.autoRemove = val => !val || !val.length\n\nexport const includesValue = (rows, ids, filterValue) => {\n return rows.filter(row => {\n return ids.some(id => {\n const rowValue = row.values[id]\n return filterValue.includes(rowValue)\n })\n })\n}\n\nincludesValue.autoRemove = val => !val || !val.length\n\nexport const exact = (rows, ids, filterValue) => {\n return rows.filter(row => {\n return ids.some(id => {\n const rowValue = row.values[id]\n return rowValue === filterValue\n })\n })\n}\n\nexact.autoRemove = val => typeof val === 'undefined'\n\nexport const equals = (rows, ids, filterValue) => {\n return rows.filter(row => {\n return ids.some(id => {\n const rowValue = row.values[id]\n // eslint-disable-next-line eqeqeq\n return rowValue == filterValue\n })\n })\n}\n\nequals.autoRemove = val => val == null\n\nexport const between = (rows, ids, filterValue) => {\n let [min, max] = filterValue || []\n\n min = typeof min === 'number' ? min : -Infinity\n max = typeof max === 'number' ? max : Infinity\n\n if (min > max) {\n const temp = min\n min = max\n max = temp\n }\n\n return rows.filter(row => {\n return ids.some(id => {\n const rowValue = row.values[id]\n return rowValue >= min && rowValue <= max\n })\n })\n}\n\nbetween.autoRemove = val =>\n !val || (typeof val[0] !== 'number' && typeof val[1] !== 'number')\n","import React from 'react'\n\nimport {\n getFirstDefined,\n getFilterMethod,\n shouldAutoRemoveFilter,\n} from '../utils'\n\nimport {\n actions,\n useGetLatest,\n functionalUpdate,\n useMountedLayoutEffect,\n} from '../publicUtils'\n\nimport * as filterTypes from '../filterTypes'\n\n// Actions\nactions.resetFilters = 'resetFilters'\nactions.setFilter = 'setFilter'\nactions.setAllFilters = 'setAllFilters'\n\nexport const useFilters = hooks => {\n hooks.stateReducers.push(reducer)\n hooks.useInstance.push(useInstance)\n}\n\nuseFilters.pluginName = 'useFilters'\n\nfunction reducer(state, action, previousState, instance) {\n if (action.type === actions.init) {\n return {\n filters: [],\n ...state,\n }\n }\n\n if (action.type === actions.resetFilters) {\n return {\n ...state,\n filters: instance.initialState.filters || [],\n }\n }\n\n if (action.type === actions.setFilter) {\n const { columnId, filterValue } = action\n const { allColumns, filterTypes: userFilterTypes } = instance\n\n const column = allColumns.find(d => d.id === columnId)\n\n if (!column) {\n throw new Error(\n `React-Table: Could not find a column with id: ${columnId}`\n )\n }\n\n const filterMethod = getFilterMethod(\n column.filter,\n userFilterTypes || {},\n filterTypes\n )\n\n const previousfilter = state.filters.find(d => d.id === columnId)\n\n const newFilter = functionalUpdate(\n filterValue,\n previousfilter && previousfilter.value\n )\n\n //\n if (shouldAutoRemoveFilter(filterMethod.autoRemove, newFilter, column)) {\n return {\n ...state,\n filters: state.filters.filter(d => d.id !== columnId),\n }\n }\n\n if (previousfilter) {\n return {\n ...state,\n filters: state.filters.map(d => {\n if (d.id === columnId) {\n return { id: columnId, value: newFilter }\n }\n return d\n }),\n }\n }\n\n return {\n ...state,\n filters: [...state.filters, { id: columnId, value: newFilter }],\n }\n }\n\n if (action.type === actions.setAllFilters) {\n const { filters } = action\n const { allColumns, filterTypes: userFilterTypes } = instance\n\n return {\n ...state,\n // Filter out undefined values\n filters: functionalUpdate(filters, state.filters).filter(filter => {\n const column = allColumns.find(d => d.id === filter.id)\n const filterMethod = getFilterMethod(\n column.filter,\n userFilterTypes || {},\n filterTypes\n )\n\n if (\n shouldAutoRemoveFilter(filterMethod.autoRemove, filter.value, column)\n ) {\n return false\n }\n return true\n }),\n }\n }\n}\n\nfunction useInstance(instance) {\n const {\n data,\n rows,\n flatRows,\n rowsById,\n allColumns,\n filterTypes: userFilterTypes,\n manualFilters,\n defaultCanFilter = false,\n disableFilters,\n state: { filters },\n dispatch,\n autoResetFilters = true,\n } = instance\n\n const setFilter = React.useCallback(\n (columnId, filterValue) => {\n dispatch({ type: actions.setFilter, columnId, filterValue })\n },\n [dispatch]\n )\n\n const setAllFilters = React.useCallback(\n filters => {\n dispatch({\n type: actions.setAllFilters,\n filters,\n })\n },\n [dispatch]\n )\n\n allColumns.forEach(column => {\n const {\n id,\n accessor,\n defaultCanFilter: columnDefaultCanFilter,\n disableFilters: columnDisableFilters,\n } = column\n\n // Determine if a column is filterable\n column.canFilter = accessor\n ? getFirstDefined(\n columnDisableFilters === true ? false : undefined,\n disableFilters === true ? false : undefined,\n true\n )\n : getFirstDefined(columnDefaultCanFilter, defaultCanFilter, false)\n\n // Provide the column a way of updating the filter value\n column.setFilter = val => setFilter(column.id, val)\n\n // Provide the current filter value to the column for\n // convenience\n const found = filters.find(d => d.id === id)\n column.filterValue = found && found.value\n })\n\n const [\n filteredRows,\n filteredFlatRows,\n filteredRowsById,\n ] = React.useMemo(() => {\n if (manualFilters || !filters.length) {\n return [rows, flatRows, rowsById]\n }\n\n const filteredFlatRows = []\n const filteredRowsById = {}\n\n // Filters top level and nested rows\n const filterRows = (rows, depth = 0) => {\n let filteredRows = rows\n\n filteredRows = filters.reduce(\n (filteredSoFar, { id: columnId, value: filterValue }) => {\n // Find the filters column\n const column = allColumns.find(d => d.id === columnId)\n\n if (!column) {\n return filteredSoFar\n }\n\n if (depth === 0) {\n column.preFilteredRows = filteredSoFar\n }\n\n const filterMethod = getFilterMethod(\n column.filter,\n userFilterTypes || {},\n filterTypes\n )\n\n if (!filterMethod) {\n console.warn(\n `Could not find a valid 'column.filter' for column with the ID: ${column.id}.`\n )\n return filteredSoFar\n }\n\n // Pass the rows, id, filterValue and column to the filterMethod\n // to get the filtered rows back\n column.filteredRows = filterMethod(\n filteredSoFar,\n [columnId],\n filterValue\n )\n\n return column.filteredRows\n },\n rows\n )\n\n // Apply the filter to any subRows\n // We technically could do this recursively in the above loop,\n // but that would severely hinder the API for the user, since they\n // would be required to do that recursion in some scenarios\n filteredRows.forEach(row => {\n filteredFlatRows.push(row)\n filteredRowsById[row.id] = row\n if (!row.subRows) {\n return\n }\n\n row.subRows =\n row.subRows && row.subRows.length > 0\n ? filterRows(row.subRows, depth + 1)\n : row.subRows\n })\n\n return filteredRows\n }\n\n return [filterRows(rows), filteredFlatRows, filteredRowsById]\n }, [\n manualFilters,\n filters,\n rows,\n flatRows,\n rowsById,\n allColumns,\n userFilterTypes,\n ])\n\n React.useMemo(() => {\n // Now that each filtered column has it's partially filtered rows,\n // lets assign the final filtered rows to all of the other columns\n const nonFilteredColumns = allColumns.filter(\n column => !filters.find(d => d.id === column.id)\n )\n\n // This essentially enables faceted filter options to be built easily\n // using every column's preFilteredRows value\n nonFilteredColumns.forEach(column => {\n column.preFilteredRows = filteredRows\n column.filteredRows = filteredRows\n })\n }, [filteredRows, filters, allColumns])\n\n const getAutoResetFilters = useGetLatest(autoResetFilters)\n\n useMountedLayoutEffect(() => {\n if (getAutoResetFilters()) {\n dispatch({ type: actions.resetFilters })\n }\n }, [dispatch, manualFilters ? null : data])\n\n Object.assign(instance, {\n preFilteredRows: rows,\n preFilteredFlatRows: flatRows,\n preFilteredRowsById: rowsById,\n filteredRows,\n filteredFlatRows,\n filteredRowsById,\n rows: filteredRows,\n flatRows: filteredFlatRows,\n rowsById: filteredRowsById,\n setFilter,\n setAllFilters,\n })\n}\n","import React from 'react'\n\nimport {\n getFilterMethod,\n shouldAutoRemoveFilter,\n getFirstDefined,\n} from '../utils'\n\nimport {\n actions,\n useMountedLayoutEffect,\n functionalUpdate,\n useGetLatest,\n} from '../publicUtils'\n\nimport * as filterTypes from '../filterTypes'\n\n// Actions\nactions.resetGlobalFilter = 'resetGlobalFilter'\nactions.setGlobalFilter = 'setGlobalFilter'\n\nexport const useGlobalFilter = hooks => {\n hooks.stateReducers.push(reducer)\n hooks.useInstance.push(useInstance)\n}\n\nuseGlobalFilter.pluginName = 'useGlobalFilter'\n\nfunction reducer(state, action, previousState, instance) {\n if (action.type === actions.resetGlobalFilter) {\n return {\n ...state,\n globalFilter: instance.initialState.globalFilter || undefined,\n }\n }\n\n if (action.type === actions.setGlobalFilter) {\n const { filterValue } = action\n const { userFilterTypes } = instance\n\n const filterMethod = getFilterMethod(\n instance.globalFilter,\n userFilterTypes || {},\n filterTypes\n )\n\n const newFilter = functionalUpdate(filterValue, state.globalFilter)\n\n //\n if (shouldAutoRemoveFilter(filterMethod.autoRemove, newFilter)) {\n const { globalFilter, ...stateWithoutGlobalFilter } = state\n return stateWithoutGlobalFilter\n }\n\n return {\n ...state,\n globalFilter: newFilter,\n }\n }\n}\n\nfunction useInstance(instance) {\n const {\n data,\n rows,\n flatRows,\n rowsById,\n allColumns,\n filterTypes: userFilterTypes,\n globalFilter,\n manualGlobalFilter,\n state: { globalFilter: globalFilterValue },\n dispatch,\n autoResetGlobalFilter = true,\n disableGlobalFilter,\n } = instance\n\n const setGlobalFilter = React.useCallback(\n filterValue => {\n dispatch({ type: actions.setGlobalFilter, filterValue })\n },\n [dispatch]\n )\n\n // TODO: Create a filter cache for incremental high speed multi-filtering\n // This gets pretty complicated pretty fast, since you have to maintain a\n // cache for each row group (top-level rows, and each row's recursive subrows)\n // This would make multi-filtering a lot faster though. Too far?\n\n const [\n globalFilteredRows,\n globalFilteredFlatRows,\n globalFilteredRowsById,\n ] = React.useMemo(() => {\n if (manualGlobalFilter || typeof globalFilterValue === 'undefined') {\n return [rows, flatRows, rowsById]\n }\n\n const filteredFlatRows = []\n const filteredRowsById = {}\n\n const filterMethod = getFilterMethod(\n globalFilter,\n userFilterTypes || {},\n filterTypes\n )\n\n if (!filterMethod) {\n console.warn(`Could not find a valid 'globalFilter' option.`)\n return rows\n }\n\n allColumns.forEach(column => {\n const { disableGlobalFilter: columnDisableGlobalFilter } = column\n\n column.canFilter = getFirstDefined(\n columnDisableGlobalFilter === true ? false : undefined,\n disableGlobalFilter === true ? false : undefined,\n true\n )\n })\n\n const filterableColumns = allColumns.filter(c => c.canFilter === true)\n\n // Filters top level and nested rows\n const filterRows = filteredRows => {\n filteredRows = filterMethod(\n filteredRows,\n filterableColumns.map(d => d.id),\n globalFilterValue\n )\n\n filteredRows.forEach(row => {\n filteredFlatRows.push(row)\n filteredRowsById[row.id] = row\n\n row.subRows =\n row.subRows && row.subRows.length\n ? filterRows(row.subRows)\n : row.subRows\n })\n\n return filteredRows\n }\n\n return [filterRows(rows), filteredFlatRows, filteredRowsById]\n }, [\n manualGlobalFilter,\n globalFilterValue,\n globalFilter,\n userFilterTypes,\n allColumns,\n rows,\n flatRows,\n rowsById,\n disableGlobalFilter,\n ])\n\n const getAutoResetGlobalFilter = useGetLatest(autoResetGlobalFilter)\n\n useMountedLayoutEffect(() => {\n if (getAutoResetGlobalFilter()) {\n dispatch({ type: actions.resetGlobalFilter })\n }\n }, [dispatch, manualGlobalFilter ? null : data])\n\n Object.assign(instance, {\n preGlobalFilteredRows: rows,\n preGlobalFilteredFlatRows: flatRows,\n preGlobalFilteredRowsById: rowsById,\n globalFilteredRows,\n globalFilteredFlatRows,\n globalFilteredRowsById,\n rows: globalFilteredRows,\n flatRows: globalFilteredFlatRows,\n rowsById: globalFilteredRowsById,\n setGlobalFilter,\n disableGlobalFilter,\n })\n}\n","export function sum(values, aggregatedValues) {\n // It's faster to just add the aggregations together instead of\n // process leaf nodes individually\n return aggregatedValues.reduce(\n (sum, next) => sum + (typeof next === 'number' ? next : 0),\n 0\n )\n}\n\nexport function min(values) {\n let min = values[0] || 0\n\n values.forEach(value => {\n if (typeof value === 'number') {\n min = Math.min(min, value)\n }\n })\n\n return min\n}\n\nexport function max(values) {\n let max = values[0] || 0\n\n values.forEach(value => {\n if (typeof value === 'number') {\n max = Math.max(max, value)\n }\n })\n\n return max\n}\n\nexport function minMax(values) {\n let min = values[0] || 0\n let max = values[0] || 0\n\n values.forEach(value => {\n if (typeof value === 'number') {\n min = Math.min(min, value)\n max = Math.max(max, value)\n }\n })\n\n return `${min}..${max}`\n}\n\nexport function average(values) {\n return sum(null, values) / values.length\n}\n\nexport function median(values) {\n if (!values.length) {\n return null\n }\n\n const mid = Math.floor(values.length / 2)\n const nums = [...values].sort((a, b) => a - b)\n return values.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2\n}\n\nexport function unique(values) {\n return Array.from(new Set(values).values())\n}\n\nexport function uniqueCount(values) {\n return new Set(values).size\n}\n\nexport function count(values) {\n return values.length\n}\n","import React from 'react'\n\nimport * as aggregations from '../aggregations'\n\nimport { getFirstDefined, flattenBy } from '../utils'\n\nimport {\n actions,\n makePropGetter,\n ensurePluginOrder,\n useMountedLayoutEffect,\n useGetLatest,\n} from '../publicUtils'\n\nconst emptyArray = []\nconst emptyObject = {}\n\n// Actions\nactions.resetGroupBy = 'resetGroupBy'\nactions.setGroupBy = 'setGroupBy'\nactions.toggleGroupBy = 'toggleGroupBy'\n\nexport const useGroupBy = hooks => {\n hooks.getGroupByToggleProps = [defaultGetGroupByToggleProps]\n hooks.stateReducers.push(reducer)\n hooks.visibleColumnsDeps.push((deps, { instance }) => [\n ...deps,\n instance.state.groupBy,\n ])\n hooks.visibleColumns.push(visibleColumns)\n hooks.useInstance.push(useInstance)\n hooks.prepareRow.push(prepareRow)\n}\n\nuseGroupBy.pluginName = 'useGroupBy'\n\nconst defaultGetGroupByToggleProps = (props, { header }) => [\n props,\n {\n onClick: header.canGroupBy\n ? e => {\n e.persist()\n header.toggleGroupBy()\n }\n : undefined,\n style: {\n cursor: header.canGroupBy ? 'pointer' : undefined,\n },\n title: 'Toggle GroupBy',\n },\n]\n\n// Reducer\nfunction reducer(state, action, previousState, instance) {\n if (action.type === actions.init) {\n return {\n groupBy: [],\n ...state,\n }\n }\n\n if (action.type === actions.resetGroupBy) {\n return {\n ...state,\n groupBy: instance.initialState.groupBy || [],\n }\n }\n\n if (action.type === actions.setGroupBy) {\n const { value } = action\n return {\n ...state,\n groupBy: value,\n }\n }\n\n if (action.type === actions.toggleGroupBy) {\n const { columnId, value: setGroupBy } = action\n\n const resolvedGroupBy =\n typeof setGroupBy !== 'undefined'\n ? setGroupBy\n : !state.groupBy.includes(columnId)\n\n if (resolvedGroupBy) {\n return {\n ...state,\n groupBy: [...state.groupBy, columnId],\n }\n }\n\n return {\n ...state,\n groupBy: state.groupBy.filter(d => d !== columnId),\n }\n }\n}\n\nfunction visibleColumns(\n columns,\n {\n instance: {\n state: { groupBy },\n },\n }\n) {\n // Sort grouped columns to the start of the column list\n // before the headers are built\n\n const groupByColumns = groupBy\n .map(g => columns.find(col => col.id === g))\n .filter(Boolean)\n\n const nonGroupByColumns = columns.filter(col => !groupBy.includes(col.id))\n\n columns = [...groupByColumns, ...nonGroupByColumns]\n\n columns.forEach(column => {\n column.isGrouped = groupBy.includes(column.id)\n column.groupedIndex = groupBy.indexOf(column.id)\n })\n\n return columns\n}\n\nconst defaultUserAggregations = {}\n\nfunction useInstance(instance) {\n const {\n data,\n rows,\n flatRows,\n rowsById,\n allColumns,\n flatHeaders,\n groupByFn = defaultGroupByFn,\n manualGroupBy,\n aggregations: userAggregations = defaultUserAggregations,\n plugins,\n state: { groupBy },\n dispatch,\n autoResetGroupBy = true,\n disableGroupBy,\n defaultCanGroupBy,\n getHooks,\n } = instance\n\n ensurePluginOrder(plugins, ['useColumnOrder', 'useFilters'], 'useGroupBy')\n\n const getInstance = useGetLatest(instance)\n\n allColumns.forEach(column => {\n const {\n accessor,\n defaultGroupBy: defaultColumnGroupBy,\n disableGroupBy: columnDisableGroupBy,\n } = column\n\n column.canGroupBy = accessor\n ? getFirstDefined(\n column.canGroupBy,\n columnDisableGroupBy === true ? false : undefined,\n disableGroupBy === true ? false : undefined,\n true\n )\n : getFirstDefined(\n column.canGroupBy,\n defaultColumnGroupBy,\n defaultCanGroupBy,\n false\n )\n\n if (column.canGroupBy) {\n column.toggleGroupBy = () => instance.toggleGroupBy(column.id)\n }\n\n column.Aggregated = column.Aggregated || column.Cell\n })\n\n const toggleGroupBy = React.useCallback(\n (columnId, value) => {\n dispatch({ type: actions.toggleGroupBy, columnId, value })\n },\n [dispatch]\n )\n\n const setGroupBy = React.useCallback(\n value => {\n dispatch({ type: actions.setGroupBy, value })\n },\n [dispatch]\n )\n\n flatHeaders.forEach(header => {\n header.getGroupByToggleProps = makePropGetter(\n getHooks().getGroupByToggleProps,\n { instance: getInstance(), header }\n )\n })\n\n const [\n groupedRows,\n groupedFlatRows,\n groupedRowsById,\n onlyGroupedFlatRows,\n onlyGroupedRowsById,\n nonGroupedFlatRows,\n nonGroupedRowsById,\n ] = React.useMemo(() => {\n if (manualGroupBy || !groupBy.length) {\n return [\n rows,\n flatRows,\n rowsById,\n emptyArray,\n emptyObject,\n flatRows,\n rowsById,\n ]\n }\n\n // Ensure that the list of filtered columns exist\n const existingGroupBy = groupBy.filter(g =>\n allColumns.find(col => col.id === g)\n )\n\n // Find the columns that can or are aggregating\n // Uses each column to aggregate rows into a single value\n const aggregateRowsToValues = (leafRows, groupedRows, depth) => {\n const values = {}\n\n allColumns.forEach(column => {\n // Don't aggregate columns that are in the groupBy\n if (existingGroupBy.includes(column.id)) {\n values[column.id] = groupedRows[0]\n ? groupedRows[0].values[column.id]\n : null\n return\n }\n\n // Aggregate the values\n let aggregateFn =\n typeof column.aggregate === 'function'\n ? column.aggregate\n : userAggregations[column.aggregate] ||\n aggregations[column.aggregate]\n\n if (aggregateFn) {\n // Get the columnValues to aggregate\n const groupedValues = groupedRows.map(row => row.values[column.id])\n\n // Get the columnValues to aggregate\n const leafValues = leafRows.map(row => {\n let columnValue = row.values[column.id]\n\n if (!depth && column.aggregateValue) {\n const aggregateValueFn =\n typeof column.aggregateValue === 'function'\n ? column.aggregateValue\n : userAggregations[column.aggregateValue] ||\n aggregations[column.aggregateValue]\n\n if (!aggregateValueFn) {\n console.info({ column })\n throw new Error(\n `React Table: Invalid column.aggregateValue option for column listed above`\n )\n }\n\n columnValue = aggregateValueFn(columnValue, row, column)\n }\n return columnValue\n })\n\n values[column.id] = aggregateFn(leafValues, groupedValues)\n } else if (column.aggregate) {\n console.info({ column })\n throw new Error(\n `React Table: Invalid column.aggregate option for column listed above`\n )\n } else {\n values[column.id] = null\n }\n })\n\n return values\n }\n\n let groupedFlatRows = []\n const groupedRowsById = {}\n const onlyGroupedFlatRows = []\n const onlyGroupedRowsById = {}\n const nonGroupedFlatRows = []\n const nonGroupedRowsById = {}\n\n // Recursively group the data\n const groupUpRecursively = (rows, depth = 0, parentId) => {\n // This is the last level, just return the rows\n if (depth === existingGroupBy.length) {\n return rows.map((row) => ({ ...row, depth }))\n }\n\n const columnId = existingGroupBy[depth]\n\n // Group the rows together for this level\n let rowGroupsMap = groupByFn(rows, columnId)\n\n // Peform aggregations for each group\n const aggregatedGroupedRows = Object.entries(rowGroupsMap).map(\n ([groupByVal, groupedRows], index) => {\n let id = `${columnId}:${groupByVal}`\n id = parentId ? `${parentId}>${id}` : id\n\n // First, Recurse to group sub rows before aggregation\n const subRows = groupUpRecursively(groupedRows, depth + 1, id)\n\n // Flatten the leaf rows of the rows in this group\n const leafRows = depth\n ? flattenBy(groupedRows, 'leafRows')\n : groupedRows\n\n const values = aggregateRowsToValues(leafRows, groupedRows, depth)\n\n const row = {\n id,\n isGrouped: true,\n groupByID: columnId,\n groupByVal,\n values,\n subRows,\n leafRows,\n depth,\n index,\n }\n\n subRows.forEach(subRow => {\n groupedFlatRows.push(subRow)\n groupedRowsById[subRow.id] = subRow\n if (subRow.isGrouped) {\n onlyGroupedFlatRows.push(subRow)\n onlyGroupedRowsById[subRow.id] = subRow\n } else {\n nonGroupedFlatRows.push(subRow)\n nonGroupedRowsById[subRow.id] = subRow\n }\n })\n\n return row\n }\n )\n\n return aggregatedGroupedRows\n }\n\n const groupedRows = groupUpRecursively(rows)\n\n groupedRows.forEach(subRow => {\n groupedFlatRows.push(subRow)\n groupedRowsById[subRow.id] = subRow\n if (subRow.isGrouped) {\n onlyGroupedFlatRows.push(subRow)\n onlyGroupedRowsById[subRow.id] = subRow\n } else {\n nonGroupedFlatRows.push(subRow)\n nonGroupedRowsById[subRow.id] = subRow\n }\n })\n\n // Assign the new data\n return [\n groupedRows,\n groupedFlatRows,\n groupedRowsById,\n onlyGroupedFlatRows,\n onlyGroupedRowsById,\n nonGroupedFlatRows,\n nonGroupedRowsById,\n ]\n }, [\n manualGroupBy,\n groupBy,\n rows,\n flatRows,\n rowsById,\n allColumns,\n userAggregations,\n groupByFn,\n ])\n\n const getAutoResetGroupBy = useGetLatest(autoResetGroupBy)\n\n useMountedLayoutEffect(() => {\n if (getAutoResetGroupBy()) {\n dispatch({ type: actions.resetGroupBy })\n }\n }, [dispatch, manualGroupBy ? null : data])\n\n Object.assign(instance, {\n preGroupedRows: rows,\n preGroupedFlatRow: flatRows,\n preGroupedRowsById: rowsById,\n groupedRows,\n groupedFlatRows,\n groupedRowsById,\n onlyGroupedFlatRows,\n onlyGroupedRowsById,\n nonGroupedFlatRows,\n nonGroupedRowsById,\n rows: groupedRows,\n flatRows: groupedFlatRows,\n rowsById: groupedRowsById,\n toggleGroupBy,\n setGroupBy,\n })\n}\n\nfunction prepareRow(row) {\n row.allCells.forEach(cell => {\n // Grouped cells are in the groupBy and the pivot cell for the row\n cell.isGrouped = cell.column.isGrouped && cell.column.id === row.groupByID\n // Placeholder cells are any columns in the groupBy that are not grouped\n cell.isPlaceholder = !cell.isGrouped && cell.column.isGrouped\n // Aggregated cells are not grouped, not repeated, but still have subRows\n cell.isAggregated =\n !cell.isGrouped && !cell.isPlaceholder && row.subRows?.length\n })\n}\n\nexport function defaultGroupByFn(rows, columnId) {\n return rows.reduce((prev, row, i) => {\n // TODO: Might want to implement a key serializer here so\n // irregular column values can still be grouped if needed?\n const resKey = `${row.values[columnId]}`\n prev[resKey] = Array.isArray(prev[resKey]) ? prev[resKey] : []\n prev[resKey].push(row)\n return prev\n }, {})\n}\n","const reSplitAlphaNumeric = /([0-9]+)/gm\n\n// Mixed sorting is slow, but very inclusive of many edge cases.\n// It handles numbers, mixed alphanumeric combinations, and even\n// null, undefined, and Infinity\nexport const alphanumeric = (rowA, rowB, columnId) => {\n let [a, b] = getRowValuesByColumnID(rowA, rowB, columnId)\n\n // Force to strings (or \"\" for unsupported types)\n a = toString(a)\n b = toString(b)\n\n // Split on number groups, but keep the delimiter\n // Then remove falsey split values\n a = a.split(reSplitAlphaNumeric).filter(Boolean)\n b = b.split(reSplitAlphaNumeric).filter(Boolean)\n\n // While\n while (a.length && b.length) {\n let aa = a.shift()\n let bb = b.shift()\n\n const an = parseInt(aa, 10)\n const bn = parseInt(bb, 10)\n\n const combo = [an, bn].sort()\n\n // Both are string\n if (isNaN(combo[0])) {\n if (aa > bb) {\n return 1\n }\n if (bb > aa) {\n return -1\n }\n continue\n }\n\n // One is a string, one is a number\n if (isNaN(combo[1])) {\n return isNaN(an) ? -1 : 1\n }\n\n // Both are numbers\n if (an > bn) {\n return 1\n }\n if (bn > an) {\n return -1\n }\n }\n\n return a.length - b.length\n}\nexport function datetime(rowA, rowB, columnId) {\n let [a, b] = getRowValuesByColumnID(rowA, rowB, columnId)\n\n a = a.getTime()\n b = b.getTime()\n\n return compareBasic(a, b)\n}\n\nexport function basic(rowA, rowB, columnId) {\n let [a, b] = getRowValuesByColumnID(rowA, rowB, columnId)\n\n return compareBasic(a, b)\n}\n\nexport function string(rowA, rowB, columnId) {\n let [a, b] = getRowValuesByColumnID(rowA, rowB, columnId)\n\n a = a.split('').filter(Boolean)\n b = b.split('').filter(Boolean)\n\n while (a.length && b.length) {\n let aa = a.shift()\n let bb = b.shift()\n\n let alower = aa.toLowerCase()\n let blower = bb.toLowerCase()\n\n // Case insensitive comparison until characters match\n if (alower > blower) {\n return 1\n }\n if (blower > alower) {\n return -1\n }\n // If lowercase characters are identical\n if (aa > bb) {\n return 1\n }\n if (bb > aa) {\n return -1\n }\n continue\n }\n\n return a.length - b.length\n}\n\nexport function number(rowA, rowB, columnId) {\n let [a, b] = getRowValuesByColumnID(rowA, rowB, columnId)\n\n const replaceNonNumeric = /[^0-9.]/gi\n\n a = Number(String(a).replace(replaceNonNumeric, ''))\n b = Number(String(b).replace(replaceNonNumeric, ''))\n\n return compareBasic(a, b)\n}\n\n// Utils\n\nfunction compareBasic(a, b) {\n return a === b ? 0 : a > b ? 1 : -1\n}\n\nfunction getRowValuesByColumnID(row1, row2, columnId) {\n return [row1.values[columnId], row2.values[columnId]]\n}\n\nfunction toString(a) {\n if (typeof a === 'number') {\n if (isNaN(a) || a === Infinity || a === -Infinity) {\n return ''\n }\n return String(a)\n }\n if (typeof a === 'string') {\n return a\n }\n return ''\n}\n","import React from 'react'\n\nimport {\n actions,\n ensurePluginOrder,\n defaultColumn,\n makePropGetter,\n useGetLatest,\n useMountedLayoutEffect,\n} from '../publicUtils'\n\nimport { getFirstDefined, isFunction } from '../utils'\n\nimport * as sortTypes from '../sortTypes'\n\n// Actions\nactions.resetSortBy = 'resetSortBy'\nactions.setSortBy = 'setSortBy'\nactions.toggleSortBy = 'toggleSortBy'\nactions.clearSortBy = 'clearSortBy'\n\ndefaultColumn.sortType = 'alphanumeric'\ndefaultColumn.sortDescFirst = false\n\nexport const useSortBy = hooks => {\n hooks.getSortByToggleProps = [defaultGetSortByToggleProps]\n hooks.stateReducers.push(reducer)\n hooks.useInstance.push(useInstance)\n}\n\nuseSortBy.pluginName = 'useSortBy'\n\nconst defaultGetSortByToggleProps = (props, { instance, column }) => {\n const { isMultiSortEvent = e => e.shiftKey } = instance\n\n return [\n props,\n {\n onClick: column.canSort\n ? e => {\n e.persist()\n column.toggleSortBy(\n undefined,\n !instance.disableMultiSort && isMultiSortEvent(e)\n )\n }\n : undefined,\n style: {\n cursor: column.canSort ? 'pointer' : undefined,\n },\n title: column.canSort ? 'Toggle SortBy' : undefined,\n },\n ]\n}\n\n// Reducer\nfunction reducer(state, action, previousState, instance) {\n if (action.type === actions.init) {\n return {\n sortBy: [],\n ...state,\n }\n }\n\n if (action.type === actions.resetSortBy) {\n return {\n ...state,\n sortBy: instance.initialState.sortBy || [],\n }\n }\n\n if (action.type === actions.clearSortBy) {\n const { sortBy } = state\n const newSortBy = sortBy.filter(d => d.id !== action.columnId)\n\n return {\n ...state,\n sortBy: newSortBy,\n }\n }\n\n if (action.type === actions.setSortBy) {\n const { sortBy } = action\n return {\n ...state,\n sortBy,\n }\n }\n\n if (action.type === actions.toggleSortBy) {\n const { columnId, desc, multi } = action\n\n const {\n allColumns,\n disableMultiSort,\n disableSortRemove,\n disableMultiRemove,\n maxMultiSortColCount = Number.MAX_SAFE_INTEGER,\n } = instance\n\n const { sortBy } = state\n\n // Find the column for this columnId\n const column = allColumns.find(d => d.id === columnId)\n const { sortDescFirst } = column\n\n // Find any existing sortBy for this column\n const existingSortBy = sortBy.find(d => d.id === columnId)\n const existingIndex = sortBy.findIndex(d => d.id === columnId)\n const hasDescDefined = typeof desc !== 'undefined' && desc !== null\n\n let newSortBy = []\n\n // What should we do with this sort action?\n let sortAction\n\n if (!disableMultiSort && multi) {\n if (existingSortBy) {\n sortAction = 'toggle'\n } else {\n sortAction = 'add'\n }\n } else {\n // Normal mode\n if (existingIndex !== sortBy.length - 1 || sortBy.length !== 1) {\n sortAction = 'replace'\n } else if (existingSortBy) {\n sortAction = 'toggle'\n } else {\n sortAction = 'replace'\n }\n }\n\n // Handle toggle states that will remove the sortBy\n if (\n sortAction === 'toggle' && // Must be toggling\n !disableSortRemove && // If disableSortRemove, disable in general\n !hasDescDefined && // Must not be setting desc\n (multi ? !disableMultiRemove : true) && // If multi, don't allow if disableMultiRemove\n ((existingSortBy && // Finally, detect if it should indeed be removed\n existingSortBy.desc &&\n !sortDescFirst) ||\n (!existingSortBy.desc && sortDescFirst))\n ) {\n sortAction = 'remove'\n }\n\n if (sortAction === 'replace') {\n newSortBy = [\n {\n id: columnId,\n desc: hasDescDefined ? desc : sortDescFirst,\n },\n ]\n } else if (sortAction === 'add') {\n newSortBy = [\n ...sortBy,\n {\n id: columnId,\n desc: hasDescDefined ? desc : sortDescFirst,\n },\n ]\n // Take latest n columns\n newSortBy.splice(0, newSortBy.length - maxMultiSortColCount)\n } else if (sortAction === 'toggle') {\n // This flips (or sets) the\n newSortBy = sortBy.map(d => {\n if (d.id === columnId) {\n return {\n ...d,\n desc: hasDescDefined ? desc : !existingSortBy.desc,\n }\n }\n return d\n })\n } else if (sortAction === 'remove') {\n newSortBy = sortBy.filter(d => d.id !== columnId)\n }\n\n return {\n ...state,\n sortBy: newSortBy,\n }\n }\n}\n\nfunction useInstance(instance) {\n const {\n data,\n rows,\n flatRows,\n allColumns,\n orderByFn = defaultOrderByFn,\n sortTypes: userSortTypes,\n manualSortBy,\n defaultCanSort,\n disableSortBy,\n flatHeaders,\n state: { sortBy },\n dispatch,\n plugins,\n getHooks,\n autoResetSortBy = true,\n } = instance\n\n ensurePluginOrder(\n plugins,\n ['useFilters', 'useGlobalFilter', 'useGroupBy', 'usePivotColumns'],\n 'useSortBy'\n )\n\n const setSortBy = React.useCallback(\n sortBy => {\n dispatch({ type: actions.setSortBy, sortBy })\n },\n [dispatch]\n )\n\n // Updates sorting based on a columnId, desc flag and multi flag\n const toggleSortBy = React.useCallback(\n (columnId, desc, multi) => {\n dispatch({ type: actions.toggleSortBy, columnId, desc, multi })\n },\n [dispatch]\n )\n\n // use reference to avoid memory leak in #1608\n const getInstance = useGetLatest(instance)\n\n // Add the getSortByToggleProps method to columns and headers\n flatHeaders.forEach(column => {\n const {\n accessor,\n canSort: defaultColumnCanSort,\n disableSortBy: columnDisableSortBy,\n id,\n } = column\n\n const canSort = accessor\n ? getFirstDefined(\n columnDisableSortBy === true ? false : undefined,\n disableSortBy === true ? false : undefined,\n true\n )\n : getFirstDefined(defaultCanSort, defaultColumnCanSort, false)\n\n column.canSort = canSort\n\n if (column.canSort) {\n column.toggleSortBy = (desc, multi) =>\n toggleSortBy(column.id, desc, multi)\n\n column.clearSortBy = () => {\n dispatch({ type: actions.clearSortBy, columnId: column.id })\n }\n }\n\n column.getSortByToggleProps = makePropGetter(\n getHooks().getSortByToggleProps,\n {\n instance: getInstance(),\n column,\n }\n )\n\n const columnSort = sortBy.find(d => d.id === id)\n column.isSorted = !!columnSort\n column.sortedIndex = sortBy.findIndex(d => d.id === id)\n column.isSortedDesc = column.isSorted ? columnSort.desc : undefined\n })\n\n const [sortedRows, sortedFlatRows] = React.useMemo(() => {\n if (manualSortBy || !sortBy.length) {\n return [rows, flatRows]\n }\n\n const sortedFlatRows = []\n\n // Filter out sortBys that correspond to non existing columns\n const availableSortBy = sortBy.filter(sort =>\n allColumns.find(col => col.id === sort.id)\n )\n\n const sortData = rows => {\n // Use the orderByFn to compose multiple sortBy's together.\n // This will also perform a stable sorting using the row index\n // if needed.\n const sortedData = orderByFn(\n rows,\n availableSortBy.map(sort => {\n // Support custom sorting methods for each column\n const column = allColumns.find(d => d.id === sort.id)\n\n if (!column) {\n throw new Error(\n `React-Table: Could not find a column with id: ${sort.id} while sorting`\n )\n }\n\n const { sortType } = column\n\n // Look up sortBy functions in this order:\n // column function\n // column string lookup on user sortType\n // column string lookup on built-in sortType\n // default function\n // default string lookup on user sortType\n // default string lookup on built-in sortType\n const sortMethod =\n isFunction(sortType) ||\n (userSortTypes || {})[sortType] ||\n sortTypes[sortType]\n\n if (!sortMethod) {\n throw new Error(\n `React-Table: Could not find a valid sortType of '${sortType}' for column '${sort.id}'.`\n )\n }\n\n // Return the correct sortFn.\n // This function should always return in ascending order\n return (a, b) => sortMethod(a, b, sort.id, sort.desc)\n }),\n // Map the directions\n availableSortBy.map(sort => {\n // Detect and use the sortInverted option\n const column = allColumns.find(d => d.id === sort.id)\n\n if (column && column.sortInverted) {\n return sort.desc\n }\n\n return !sort.desc\n })\n )\n\n // If there are sub-rows, sort them\n sortedData.forEach(row => {\n sortedFlatRows.push(row)\n if (!row.subRows || row.subRows.length === 0) {\n return\n }\n row.subRows = sortData(row.subRows)\n })\n\n return sortedData\n }\n\n return [sortData(rows), sortedFlatRows]\n }, [\n manualSortBy,\n sortBy,\n rows,\n flatRows,\n allColumns,\n orderByFn,\n userSortTypes,\n ])\n\n const getAutoResetSortBy = useGetLatest(autoResetSortBy)\n\n useMountedLayoutEffect(() => {\n if (getAutoResetSortBy()) {\n dispatch({ type: actions.resetSortBy })\n }\n }, [manualSortBy ? null : data])\n\n Object.assign(instance, {\n preSortedRows: rows,\n preSortedFlatRows: flatRows,\n sortedRows,\n sortedFlatRows,\n rows: sortedRows,\n flatRows: sortedFlatRows,\n setSortBy,\n toggleSortBy,\n })\n}\n\nexport function defaultOrderByFn(arr, funcs, dirs) {\n return [...arr].sort((rowA, rowB) => {\n for (let i = 0; i < funcs.length; i += 1) {\n const sortFn = funcs[i]\n const desc = dirs[i] === false || dirs[i] === 'desc'\n const sortInt = sortFn(rowA, rowB)\n if (sortInt !== 0) {\n return desc ? -sortInt : sortInt\n }\n }\n return dirs[0] ? rowA.index - rowB.index : rowB.index - rowA.index\n })\n}\n","import React from 'react'\n\n//\n\nimport {\n actions,\n ensurePluginOrder,\n functionalUpdate,\n useMountedLayoutEffect,\n useGetLatest,\n} from '../publicUtils'\n\nimport { expandRows } from '../utils'\n\nconst pluginName = 'usePagination'\n\n// Actions\nactions.resetPage = 'resetPage'\nactions.gotoPage = 'gotoPage'\nactions.setPageSize = 'setPageSize'\n\nexport const usePagination = hooks => {\n hooks.stateReducers.push(reducer)\n hooks.useInstance.push(useInstance)\n}\n\nusePagination.pluginName = pluginName\n\nfunction reducer(state, action, previousState, instance) {\n if (action.type === actions.init) {\n return {\n pageSize: 10,\n pageIndex: 0,\n ...state,\n }\n }\n\n if (action.type === actions.resetPage) {\n return {\n ...state,\n pageIndex: instance.initialState.pageIndex || 0,\n }\n }\n\n if (action.type === actions.gotoPage) {\n const { pageCount, page } = instance\n const newPageIndex = functionalUpdate(action.pageIndex, state.pageIndex)\n let canNavigate = false\n\n if (newPageIndex > state.pageIndex) {\n // next page\n canNavigate =\n pageCount === -1\n ? page.length >= state.pageSize\n : newPageIndex < pageCount\n } else if (newPageIndex < state.pageIndex) {\n // prev page\n canNavigate = newPageIndex > -1\n }\n\n if (!canNavigate) {\n return state\n }\n\n return {\n ...state,\n pageIndex: newPageIndex,\n }\n }\n\n if (action.type === actions.setPageSize) {\n const { pageSize } = action\n const topRowIndex = state.pageSize * state.pageIndex\n const pageIndex = Math.floor(topRowIndex / pageSize)\n\n return {\n ...state,\n pageIndex,\n pageSize,\n }\n }\n}\n\nfunction useInstance(instance) {\n const {\n rows,\n autoResetPage = true,\n manualExpandedKey = 'expanded',\n plugins,\n pageCount: userPageCount,\n paginateExpandedRows = true,\n expandSubRows = true,\n state: {\n pageSize,\n pageIndex,\n expanded,\n globalFilter,\n filters,\n groupBy,\n sortBy,\n },\n dispatch,\n data,\n manualPagination,\n } = instance\n\n ensurePluginOrder(\n plugins,\n ['useGlobalFilter', 'useFilters', 'useGroupBy', 'useSortBy', 'useExpanded'],\n 'usePagination'\n )\n\n const getAutoResetPage = useGetLatest(autoResetPage)\n\n useMountedLayoutEffect(() => {\n if (getAutoResetPage()) {\n dispatch({ type: actions.resetPage })\n }\n }, [\n dispatch,\n manualPagination ? null : data,\n globalFilter,\n filters,\n groupBy,\n sortBy,\n ])\n\n const pageCount = manualPagination\n ? userPageCount\n : Math.ceil(rows.length / pageSize)\n\n const pageOptions = React.useMemo(\n () =>\n pageCount > 0\n ? [...new Array(pageCount)].fill(null).map((d, i) => i)\n : [],\n [pageCount]\n )\n\n const page = React.useMemo(() => {\n let page\n\n if (manualPagination) {\n page = rows\n } else {\n const pageStart = pageSize * pageIndex\n const pageEnd = pageStart + pageSize\n\n page = rows.slice(pageStart, pageEnd)\n }\n\n if (paginateExpandedRows) {\n return page\n }\n\n return expandRows(page, { manualExpandedKey, expanded, expandSubRows })\n }, [\n expandSubRows,\n expanded,\n manualExpandedKey,\n manualPagination,\n pageIndex,\n pageSize,\n paginateExpandedRows,\n rows,\n ])\n\n const canPreviousPage = pageIndex > 0\n const canNextPage =\n pageCount === -1 ? page.length >= pageSize : pageIndex < pageCount - 1\n\n const gotoPage = React.useCallback(\n pageIndex => {\n dispatch({ type: actions.gotoPage, pageIndex })\n },\n [dispatch]\n )\n\n const previousPage = React.useCallback(() => {\n return gotoPage(old => old - 1)\n }, [gotoPage])\n\n const nextPage = React.useCallback(() => {\n return gotoPage(old => old + 1)\n }, [gotoPage])\n\n const setPageSize = React.useCallback(\n pageSize => {\n dispatch({ type: actions.setPageSize, pageSize })\n },\n [dispatch]\n )\n\n Object.assign(instance, {\n pageOptions,\n pageCount,\n page,\n canPreviousPage,\n canNextPage,\n gotoPage,\n previousPage,\n nextPage,\n setPageSize,\n })\n}\n","/* istanbul ignore file */\n\nimport {\n actions,\n makePropGetter,\n ensurePluginOrder,\n useMountedLayoutEffect,\n useGetLatest,\n} from '../publicUtils'\n\nimport { flattenColumns, getFirstDefined } from '../utils'\n\n// Actions\nactions.resetPivot = 'resetPivot'\nactions.togglePivot = 'togglePivot'\n\nexport const _UNSTABLE_usePivotColumns = hooks => {\n hooks.getPivotToggleProps = [defaultGetPivotToggleProps]\n hooks.stateReducers.push(reducer)\n hooks.useInstanceAfterData.push(useInstanceAfterData)\n hooks.allColumns.push(allColumns)\n hooks.accessValue.push(accessValue)\n hooks.materializedColumns.push(materializedColumns)\n hooks.materializedColumnsDeps.push(materializedColumnsDeps)\n hooks.visibleColumns.push(visibleColumns)\n hooks.visibleColumnsDeps.push(visibleColumnsDeps)\n hooks.useInstance.push(useInstance)\n hooks.prepareRow.push(prepareRow)\n}\n\n_UNSTABLE_usePivotColumns.pluginName = 'usePivotColumns'\n\nconst defaultPivotColumns = []\n\nconst defaultGetPivotToggleProps = (props, { header }) => [\n props,\n {\n onClick: header.canPivot\n ? e => {\n e.persist()\n header.togglePivot()\n }\n : undefined,\n style: {\n cursor: header.canPivot ? 'pointer' : undefined,\n },\n title: 'Toggle Pivot',\n },\n]\n\n// Reducer\nfunction reducer(state, action, previousState, instance) {\n if (action.type === actions.init) {\n return {\n pivotColumns: defaultPivotColumns,\n ...state,\n }\n }\n\n if (action.type === actions.resetPivot) {\n return {\n ...state,\n pivotColumns: instance.initialState.pivotColumns || defaultPivotColumns,\n }\n }\n\n if (action.type === actions.togglePivot) {\n const { columnId, value: setPivot } = action\n\n const resolvedPivot =\n typeof setPivot !== 'undefined'\n ? setPivot\n : !state.pivotColumns.includes(columnId)\n\n if (resolvedPivot) {\n return {\n ...state,\n pivotColumns: [...state.pivotColumns, columnId],\n }\n }\n\n return {\n ...state,\n pivotColumns: state.pivotColumns.filter(d => d !== columnId),\n }\n }\n}\n\nfunction useInstanceAfterData(instance) {\n instance.allColumns.forEach(column => {\n column.isPivotSource = instance.state.pivotColumns.includes(column.id)\n })\n}\n\nfunction allColumns(columns, { instance }) {\n columns.forEach(column => {\n column.isPivotSource = instance.state.pivotColumns.includes(column.id)\n column.uniqueValues = new Set()\n })\n return columns\n}\n\nfunction accessValue(value, { column }) {\n if (column.uniqueValues && typeof value !== 'undefined') {\n column.uniqueValues.add(value)\n }\n return value\n}\n\nfunction materializedColumns(materialized, { instance }) {\n const { allColumns, state } = instance\n\n if (!state.pivotColumns.length || !state.groupBy || !state.groupBy.length) {\n return materialized\n }\n\n const pivotColumns = state.pivotColumns\n .map(id => allColumns.find(d => d.id === id))\n .filter(Boolean)\n\n const sourceColumns = allColumns.filter(\n d =>\n !d.isPivotSource &&\n !state.groupBy.includes(d.id) &&\n !state.pivotColumns.includes(d.id)\n )\n\n const buildPivotColumns = (depth = 0, parent, pivotFilters = []) => {\n const pivotColumn = pivotColumns[depth]\n\n if (!pivotColumn) {\n return sourceColumns.map(sourceColumn => {\n // TODO: We could offer support here for renesting pivoted\n // columns inside copies of their header groups. For now,\n // that seems like it would be (1) overkill on nesting, considering\n // you already get nesting for every pivot level and (2)\n // really hard. :)\n\n return {\n ...sourceColumn,\n canPivot: false,\n isPivoted: true,\n parent,\n depth: depth,\n id: `${parent ? `${parent.id}.${sourceColumn.id}` : sourceColumn.id}`,\n accessor: (originalRow, i, row) => {\n if (pivotFilters.every(filter => filter(row))) {\n return row.values[sourceColumn.id]\n }\n },\n }\n })\n }\n\n const uniqueValues = Array.from(pivotColumn.uniqueValues).sort()\n\n return uniqueValues.map(uniqueValue => {\n const columnGroup = {\n ...pivotColumn,\n Header:\n pivotColumn.PivotHeader || typeof pivotColumn.header === 'string'\n ? `${pivotColumn.Header}: ${uniqueValue}`\n : uniqueValue,\n isPivotGroup: true,\n parent,\n depth,\n id: parent\n ? `${parent.id}.${pivotColumn.id}.${uniqueValue}`\n : `${pivotColumn.id}.${uniqueValue}`,\n pivotValue: uniqueValue,\n }\n\n columnGroup.columns = buildPivotColumns(depth + 1, columnGroup, [\n ...pivotFilters,\n row => row.values[pivotColumn.id] === uniqueValue,\n ])\n\n return columnGroup\n })\n }\n\n const newMaterialized = flattenColumns(buildPivotColumns())\n\n return [...materialized, ...newMaterialized]\n}\n\nfunction materializedColumnsDeps(\n deps,\n {\n instance: {\n state: { pivotColumns, groupBy },\n },\n }\n) {\n return [...deps, pivotColumns, groupBy]\n}\n\nfunction visibleColumns(visibleColumns, { instance: { state } }) {\n visibleColumns = visibleColumns.filter(d => !d.isPivotSource)\n\n if (state.pivotColumns.length && state.groupBy && state.groupBy.length) {\n visibleColumns = visibleColumns.filter(\n column => column.isGrouped || column.isPivoted\n )\n }\n\n return visibleColumns\n}\n\nfunction visibleColumnsDeps(deps, { instance }) {\n return [...deps, instance.state.pivotColumns, instance.state.groupBy]\n}\n\nfunction useInstance(instance) {\n const {\n columns,\n allColumns,\n flatHeaders,\n // pivotFn = defaultPivotFn,\n // manualPivot,\n getHooks,\n plugins,\n dispatch,\n autoResetPivot = true,\n manaulPivot,\n disablePivot,\n defaultCanPivot,\n } = instance\n\n ensurePluginOrder(plugins, ['useGroupBy'], 'usePivotColumns')\n\n const getInstance = useGetLatest(instance)\n\n allColumns.forEach(column => {\n const {\n accessor,\n defaultPivot: defaultColumnPivot,\n disablePivot: columnDisablePivot,\n } = column\n\n column.canPivot = accessor\n ? getFirstDefined(\n column.canPivot,\n columnDisablePivot === true ? false : undefined,\n disablePivot === true ? false : undefined,\n true\n )\n : getFirstDefined(\n column.canPivot,\n defaultColumnPivot,\n defaultCanPivot,\n false\n )\n\n if (column.canPivot) {\n column.togglePivot = () => instance.togglePivot(column.id)\n }\n\n column.Aggregated = column.Aggregated || column.Cell\n })\n\n const togglePivot = (columnId, value) => {\n dispatch({ type: actions.togglePivot, columnId, value })\n }\n\n flatHeaders.forEach(header => {\n header.getPivotToggleProps = makePropGetter(\n getHooks().getPivotToggleProps,\n {\n instance: getInstance(),\n header,\n }\n )\n })\n\n const getAutoResetPivot = useGetLatest(autoResetPivot)\n\n useMountedLayoutEffect(() => {\n if (getAutoResetPivot()) {\n dispatch({ type: actions.resetPivot })\n }\n }, [dispatch, manaulPivot ? null : columns])\n\n Object.assign(instance, {\n togglePivot,\n })\n}\n\nfunction prepareRow(row) {\n row.allCells.forEach(cell => {\n // Grouped cells are in the pivotColumns and the pivot cell for the row\n cell.isPivoted = cell.column.isPivoted\n })\n}\n","import React from 'react'\n\nimport {\n actions,\n makePropGetter,\n ensurePluginOrder,\n useGetLatest,\n useMountedLayoutEffect,\n} from '../publicUtils'\n\nconst pluginName = 'useRowSelect'\n\n// Actions\nactions.resetSelectedRows = 'resetSelectedRows'\nactions.toggleAllRowsSelected = 'toggleAllRowsSelected'\nactions.toggleRowSelected = 'toggleRowSelected'\nactions.toggleAllPageRowsSelected = 'toggleAllPageRowsSelected'\n\nexport const useRowSelect = hooks => {\n hooks.getToggleRowSelectedProps = [defaultGetToggleRowSelectedProps]\n hooks.getToggleAllRowsSelectedProps = [defaultGetToggleAllRowsSelectedProps]\n hooks.getToggleAllPageRowsSelectedProps = [\n defaultGetToggleAllPageRowsSelectedProps,\n ]\n hooks.stateReducers.push(reducer)\n hooks.useInstance.push(useInstance)\n hooks.prepareRow.push(prepareRow)\n}\n\nuseRowSelect.pluginName = pluginName\n\nconst defaultGetToggleRowSelectedProps = (props, { instance, row }) => {\n const { manualRowSelectedKey = 'isSelected' } = instance\n let checked = false\n\n if (row.original && row.original[manualRowSelectedKey]) {\n checked = true\n } else {\n checked = row.isSelected\n }\n\n return [\n props,\n {\n onChange: e => {\n row.toggleRowSelected(e.target.checked)\n },\n style: {\n cursor: 'pointer',\n },\n checked,\n title: 'Toggle Row Selected',\n indeterminate: row.isSomeSelected,\n },\n ]\n}\n\nconst defaultGetToggleAllRowsSelectedProps = (props, { instance }) => [\n props,\n {\n onChange: e => {\n instance.toggleAllRowsSelected(e.target.checked)\n },\n style: {\n cursor: 'pointer',\n },\n checked: instance.isAllRowsSelected,\n title: 'Toggle All Rows Selected',\n indeterminate: Boolean(\n !instance.isAllRowsSelected &&\n Object.keys(instance.state.selectedRowIds).length\n ),\n },\n]\n\nconst defaultGetToggleAllPageRowsSelectedProps = (props, { instance }) => [\n props,\n {\n onChange(e) {\n instance.toggleAllPageRowsSelected(e.target.checked)\n },\n style: {\n cursor: 'pointer',\n },\n checked: instance.isAllPageRowsSelected,\n title: 'Toggle All Current Page Rows Selected',\n indeterminate: Boolean(\n !instance.isAllPageRowsSelected &&\n instance.page.some(({ id }) => instance.state.selectedRowIds[id])\n ),\n },\n]\n\n// eslint-disable-next-line max-params\nfunction reducer(state, action, previousState, instance) {\n if (action.type === actions.init) {\n return {\n selectedRowIds: {},\n ...state,\n }\n }\n\n if (action.type === actions.resetSelectedRows) {\n return {\n ...state,\n selectedRowIds: instance.initialState.selectedRowIds || {},\n }\n }\n\n if (action.type === actions.toggleAllRowsSelected) {\n const { value: setSelected } = action\n const {\n isAllRowsSelected,\n rowsById,\n nonGroupedRowsById = rowsById,\n } = instance\n\n const selectAll =\n typeof setSelected !== 'undefined' ? setSelected : !isAllRowsSelected\n\n // Only remove/add the rows that are visible on the screen\n // Leave all the other rows that are selected alone.\n const selectedRowIds = Object.assign({}, state.selectedRowIds)\n\n if (selectAll) {\n Object.keys(nonGroupedRowsById).forEach(rowId => {\n selectedRowIds[rowId] = true\n })\n } else {\n Object.keys(nonGroupedRowsById).forEach(rowId => {\n delete selectedRowIds[rowId]\n })\n }\n\n return {\n ...state,\n selectedRowIds,\n }\n }\n\n if (action.type === actions.toggleRowSelected) {\n const { id, value: setSelected } = action\n const { rowsById, selectSubRows = true, getSubRows } = instance\n const isSelected = state.selectedRowIds[id]\n const shouldExist =\n typeof setSelected !== 'undefined' ? setSelected : !isSelected\n\n if (isSelected === shouldExist) {\n return state\n }\n\n const newSelectedRowIds = { ...state.selectedRowIds }\n\n const handleRowById = id => {\n const row = rowsById[id]\n\n if (row) {\n if (!row.isGrouped) {\n if (shouldExist) {\n newSelectedRowIds[id] = true\n } else {\n delete newSelectedRowIds[id]\n }\n }\n\n if (selectSubRows && getSubRows(row)) {\n return getSubRows(row).forEach(row => handleRowById(row.id))\n }\n }\n }\n\n handleRowById(id)\n\n return {\n ...state,\n selectedRowIds: newSelectedRowIds,\n }\n }\n\n if (action.type === actions.toggleAllPageRowsSelected) {\n const { value: setSelected } = action\n const {\n page,\n rowsById,\n selectSubRows = true,\n isAllPageRowsSelected,\n getSubRows,\n } = instance\n\n const selectAll =\n typeof setSelected !== 'undefined' ? setSelected : !isAllPageRowsSelected\n\n const newSelectedRowIds = { ...state.selectedRowIds }\n\n const handleRowById = id => {\n const row = rowsById[id]\n\n if (!row.isGrouped) {\n if (selectAll) {\n newSelectedRowIds[id] = true\n } else {\n delete newSelectedRowIds[id]\n }\n }\n\n if (selectSubRows && getSubRows(row)) {\n return getSubRows(row).forEach(row => handleRowById(row.id))\n }\n }\n\n page.forEach(row => handleRowById(row.id))\n\n return {\n ...state,\n selectedRowIds: newSelectedRowIds,\n }\n }\n return state\n}\n\nfunction useInstance(instance) {\n const {\n data,\n rows,\n getHooks,\n plugins,\n rowsById,\n nonGroupedRowsById = rowsById,\n autoResetSelectedRows = true,\n state: { selectedRowIds },\n selectSubRows = true,\n dispatch,\n page,\n getSubRows,\n } = instance\n\n ensurePluginOrder(\n plugins,\n ['useFilters', 'useGroupBy', 'useSortBy', 'useExpanded', 'usePagination'],\n 'useRowSelect'\n )\n\n const selectedFlatRows = React.useMemo(() => {\n const selectedFlatRows = []\n\n rows.forEach(row => {\n const isSelected = selectSubRows\n ? getRowIsSelected(row, selectedRowIds, getSubRows)\n : !!selectedRowIds[row.id]\n row.isSelected = !!isSelected\n row.isSomeSelected = isSelected === null\n\n if (isSelected) {\n selectedFlatRows.push(row)\n }\n })\n\n return selectedFlatRows\n }, [rows, selectSubRows, selectedRowIds, getSubRows])\n\n let isAllRowsSelected = Boolean(\n Object.keys(nonGroupedRowsById).length && Object.keys(selectedRowIds).length\n )\n\n let isAllPageRowsSelected = isAllRowsSelected\n\n if (isAllRowsSelected) {\n if (Object.keys(nonGroupedRowsById).some(id => !selectedRowIds[id])) {\n isAllRowsSelected = false\n }\n }\n\n if (!isAllRowsSelected) {\n if (page && page.length && page.some(({ id }) => !selectedRowIds[id])) {\n isAllPageRowsSelected = false\n }\n }\n\n const getAutoResetSelectedRows = useGetLatest(autoResetSelectedRows)\n\n useMountedLayoutEffect(() => {\n if (getAutoResetSelectedRows()) {\n dispatch({ type: actions.resetSelectedRows })\n }\n }, [dispatch, data])\n\n const toggleAllRowsSelected = React.useCallback(\n value => dispatch({ type: actions.toggleAllRowsSelected, value }),\n [dispatch]\n )\n\n const toggleAllPageRowsSelected = React.useCallback(\n value => dispatch({ type: actions.toggleAllPageRowsSelected, value }),\n [dispatch]\n )\n\n const toggleRowSelected = React.useCallback(\n (id, value) => dispatch({ type: actions.toggleRowSelected, id, value }),\n [dispatch]\n )\n\n const getInstance = useGetLatest(instance)\n\n const getToggleAllRowsSelectedProps = makePropGetter(\n getHooks().getToggleAllRowsSelectedProps,\n { instance: getInstance() }\n )\n\n const getToggleAllPageRowsSelectedProps = makePropGetter(\n getHooks().getToggleAllPageRowsSelectedProps,\n { instance: getInstance() }\n )\n\n Object.assign(instance, {\n selectedFlatRows,\n isAllRowsSelected,\n isAllPageRowsSelected,\n toggleRowSelected,\n toggleAllRowsSelected,\n getToggleAllRowsSelectedProps,\n getToggleAllPageRowsSelectedProps,\n toggleAllPageRowsSelected,\n })\n}\n\nfunction prepareRow(row, { instance }) {\n row.toggleRowSelected = set => instance.toggleRowSelected(row.id, set)\n\n row.getToggleRowSelectedProps = makePropGetter(\n instance.getHooks().getToggleRowSelectedProps,\n { instance: instance, row }\n )\n}\n\nfunction getRowIsSelected(row, selectedRowIds, getSubRows) {\n if (selectedRowIds[row.id]) {\n return true\n }\n\n const subRows = getSubRows(row)\n\n if (subRows && subRows.length) {\n let allChildrenSelected = true\n let someSelected = false\n\n subRows.forEach(subRow => {\n // Bail out early if we know both of these\n if (someSelected && !allChildrenSelected) {\n return\n }\n\n if (getRowIsSelected(subRow, selectedRowIds, getSubRows)) {\n someSelected = true\n } else {\n allChildrenSelected = false\n }\n })\n return allChildrenSelected ? true : someSelected ? null : false\n }\n\n return false\n}\n","import React from 'react'\n\nimport {\n actions,\n functionalUpdate,\n useMountedLayoutEffect,\n useGetLatest,\n} from '../publicUtils'\n\nconst defaultInitialRowStateAccessor = row => ({})\nconst defaultInitialCellStateAccessor = cell => ({})\n\n// Actions\nactions.setRowState = 'setRowState'\nactions.setCellState = 'setCellState'\nactions.resetRowState = 'resetRowState'\n\nexport const useRowState = hooks => {\n hooks.stateReducers.push(reducer)\n hooks.useInstance.push(useInstance)\n hooks.prepareRow.push(prepareRow)\n}\n\nuseRowState.pluginName = 'useRowState'\n\nfunction reducer(state, action, previousState, instance) {\n const {\n initialRowStateAccessor = defaultInitialRowStateAccessor,\n initialCellStateAccessor = defaultInitialCellStateAccessor,\n rowsById,\n } = instance\n\n if (action.type === actions.init) {\n return {\n rowState: {},\n ...state,\n }\n }\n\n if (action.type === actions.resetRowState) {\n return {\n ...state,\n rowState: instance.initialState.rowState || {},\n }\n }\n\n if (action.type === actions.setRowState) {\n const { rowId, value } = action\n\n const oldRowState =\n typeof state.rowState[rowId] !== 'undefined'\n ? state.rowState[rowId]\n : initialRowStateAccessor(rowsById[rowId])\n\n return {\n ...state,\n rowState: {\n ...state.rowState,\n [rowId]: functionalUpdate(value, oldRowState),\n },\n }\n }\n\n if (action.type === actions.setCellState) {\n const { rowId, columnId, value } = action\n\n const oldRowState =\n typeof state.rowState[rowId] !== 'undefined'\n ? state.rowState[rowId]\n : initialRowStateAccessor(rowsById[rowId])\n\n const oldCellState =\n typeof oldRowState?.cellState?.[columnId] !== 'undefined'\n ? oldRowState.cellState[columnId]\n : initialCellStateAccessor(\n rowsById[rowId]?.cells?.find(cell => cell.column.id === columnId)\n )\n\n return {\n ...state,\n rowState: {\n ...state.rowState,\n [rowId]: {\n ...oldRowState,\n cellState: {\n ...(oldRowState.cellState || {}),\n [columnId]: functionalUpdate(value, oldCellState),\n },\n },\n },\n }\n }\n}\n\nfunction useInstance(instance) {\n const { autoResetRowState = true, data, dispatch } = instance\n\n const setRowState = React.useCallback(\n (rowId, value) =>\n dispatch({\n type: actions.setRowState,\n rowId,\n value,\n }),\n [dispatch]\n )\n\n const setCellState = React.useCallback(\n (rowId, columnId, value) =>\n dispatch({\n type: actions.setCellState,\n rowId,\n columnId,\n value,\n }),\n [dispatch]\n )\n\n const getAutoResetRowState = useGetLatest(autoResetRowState)\n\n useMountedLayoutEffect(() => {\n if (getAutoResetRowState()) {\n dispatch({ type: actions.resetRowState })\n }\n }, [data])\n\n Object.assign(instance, {\n setRowState,\n setCellState,\n })\n}\n\nfunction prepareRow(row, { instance }) {\n const {\n initialRowStateAccessor = defaultInitialRowStateAccessor,\n initialCellStateAccessor = defaultInitialCellStateAccessor,\n state: { rowState },\n } = instance\n\n if (row) {\n row.state =\n typeof rowState[row.id] !== 'undefined'\n ? rowState[row.id]\n : initialRowStateAccessor(row)\n\n row.setState = updater => {\n return instance.setRowState(row.id, updater)\n }\n\n row.cells.forEach(cell => {\n if (!row.state.cellState) {\n row.state.cellState = {}\n }\n\n cell.state =\n typeof row.state.cellState[cell.column.id] !== 'undefined'\n ? row.state.cellState[cell.column.id]\n : initialCellStateAccessor(cell)\n\n cell.setState = updater => {\n return instance.setCellState(row.id, cell.column.id, updater)\n }\n })\n }\n}\n","import React from 'react'\n\nimport { functionalUpdate, actions } from '../publicUtils'\n\n// Actions\nactions.resetColumnOrder = 'resetColumnOrder'\nactions.setColumnOrder = 'setColumnOrder'\n\nexport const useColumnOrder = hooks => {\n hooks.stateReducers.push(reducer)\n hooks.visibleColumnsDeps.push((deps, { instance }) => {\n return [...deps, instance.state.columnOrder]\n })\n hooks.visibleColumns.push(visibleColumns)\n hooks.useInstance.push(useInstance)\n}\n\nuseColumnOrder.pluginName = 'useColumnOrder'\n\nfunction reducer(state, action, previousState, instance) {\n if (action.type === actions.init) {\n return {\n columnOrder: [],\n ...state,\n }\n }\n\n if (action.type === actions.resetColumnOrder) {\n return {\n ...state,\n columnOrder: instance.initialState.columnOrder || [],\n }\n }\n\n if (action.type === actions.setColumnOrder) {\n return {\n ...state,\n columnOrder: functionalUpdate(action.columnOrder, state.columnOrder),\n }\n }\n}\n\nfunction visibleColumns(\n columns,\n {\n instance: {\n state: { columnOrder },\n },\n }\n) {\n // If there is no order, return the normal columns\n if (!columnOrder || !columnOrder.length) {\n return columns\n }\n\n const columnOrderCopy = [...columnOrder]\n\n // If there is an order, make a copy of the columns\n const columnsCopy = [...columns]\n\n // And make a new ordered array of the columns\n const columnsInOrder = []\n\n // Loop over the columns and place them in order into the new array\n while (columnsCopy.length && columnOrderCopy.length) {\n const targetColumnId = columnOrderCopy.shift()\n const foundIndex = columnsCopy.findIndex(d => d.id === targetColumnId)\n if (foundIndex > -1) {\n columnsInOrder.push(columnsCopy.splice(foundIndex, 1)[0])\n }\n }\n\n // If there are any columns left, add them to the end\n return [...columnsInOrder, ...columnsCopy]\n}\n\nfunction useInstance(instance) {\n const { dispatch } = instance\n\n instance.setColumnOrder = React.useCallback(\n columnOrder => {\n return dispatch({ type: actions.setColumnOrder, columnOrder })\n },\n [dispatch]\n )\n}\n","import React from 'react'\n\nimport {\n actions,\n defaultColumn,\n makePropGetter,\n useGetLatest,\n ensurePluginOrder,\n useMountedLayoutEffect,\n} from '../publicUtils'\n\nimport { getFirstDefined, passiveEventSupported } from '../utils'\n\n// Default Column\ndefaultColumn.canResize = true\n\n// Actions\nactions.columnStartResizing = 'columnStartResizing'\nactions.columnResizing = 'columnResizing'\nactions.columnDoneResizing = 'columnDoneResizing'\nactions.resetResize = 'resetResize'\n\nexport const useResizeColumns = hooks => {\n hooks.getResizerProps = [defaultGetResizerProps]\n hooks.getHeaderProps.push({\n style: {\n position: 'relative',\n },\n })\n hooks.stateReducers.push(reducer)\n hooks.useInstance.push(useInstance)\n hooks.useInstanceBeforeDimensions.push(useInstanceBeforeDimensions)\n}\n\nconst defaultGetResizerProps = (props, { instance, header }) => {\n const { dispatch } = instance\n\n const onResizeStart = (e, header) => {\n let isTouchEvent = false\n if (e.type === 'touchstart') {\n // lets not respond to multiple touches (e.g. 2 or 3 fingers)\n if (e.touches && e.touches.length > 1) {\n return\n }\n isTouchEvent = true\n }\n const headersToResize = getLeafHeaders(header)\n const headerIdWidths = headersToResize.map(d => [d.id, d.totalWidth])\n\n const clientX = isTouchEvent ? Math.round(e.touches[0].clientX) : e.clientX\n\n let raf\n let mostRecentClientX\n\n const dispatchEnd = () => {\n window.cancelAnimationFrame(raf)\n raf = null\n dispatch({ type: actions.columnDoneResizing })\n }\n const dispatchMove = () => {\n window.cancelAnimationFrame(raf)\n raf = null\n dispatch({ type: actions.columnResizing, clientX: mostRecentClientX })\n }\n\n const scheduleDispatchMoveOnNextAnimationFrame = clientXPos => {\n mostRecentClientX = clientXPos\n if (!raf) {\n raf = window.requestAnimationFrame(dispatchMove)\n }\n }\n\n const handlersAndEvents = {\n mouse: {\n moveEvent: 'mousemove',\n moveHandler: e => scheduleDispatchMoveOnNextAnimationFrame(e.clientX),\n upEvent: 'mouseup',\n upHandler: e => {\n document.removeEventListener(\n 'mousemove',\n handlersAndEvents.mouse.moveHandler\n )\n document.removeEventListener(\n 'mouseup',\n handlersAndEvents.mouse.upHandler\n )\n dispatchEnd()\n },\n },\n touch: {\n moveEvent: 'touchmove',\n moveHandler: e => {\n if (e.cancelable) {\n e.preventDefault()\n e.stopPropagation()\n }\n scheduleDispatchMoveOnNextAnimationFrame(e.touches[0].clientX)\n return false\n },\n upEvent: 'touchend',\n upHandler: e => {\n document.removeEventListener(\n handlersAndEvents.touch.moveEvent,\n handlersAndEvents.touch.moveHandler\n )\n document.removeEventListener(\n handlersAndEvents.touch.upEvent,\n handlersAndEvents.touch.moveHandler\n )\n dispatchEnd()\n },\n },\n }\n\n const events = isTouchEvent\n ? handlersAndEvents.touch\n : handlersAndEvents.mouse\n const passiveIfSupported = passiveEventSupported()\n ? { passive: false }\n : false\n document.addEventListener(\n events.moveEvent,\n events.moveHandler,\n passiveIfSupported\n )\n document.addEventListener(\n events.upEvent,\n events.upHandler,\n passiveIfSupported\n )\n\n dispatch({\n type: actions.columnStartResizing,\n columnId: header.id,\n columnWidth: header.totalWidth,\n headerIdWidths,\n clientX,\n })\n }\n\n return [\n props,\n {\n onMouseDown: e => e.persist() || onResizeStart(e, header),\n onTouchStart: e => e.persist() || onResizeStart(e, header),\n style: {\n cursor: 'col-resize',\n },\n draggable: false,\n role: 'separator',\n },\n ]\n}\n\nuseResizeColumns.pluginName = 'useResizeColumns'\n\nfunction reducer(state, action) {\n if (action.type === actions.init) {\n return {\n columnResizing: {\n columnWidths: {},\n },\n ...state,\n }\n }\n\n if (action.type === actions.resetResize) {\n return {\n ...state,\n columnResizing: {\n columnWidths: {},\n },\n }\n }\n\n if (action.type === actions.columnStartResizing) {\n const { clientX, columnId, columnWidth, headerIdWidths } = action\n\n return {\n ...state,\n columnResizing: {\n ...state.columnResizing,\n startX: clientX,\n headerIdWidths,\n columnWidth,\n isResizingColumn: columnId,\n },\n }\n }\n\n if (action.type === actions.columnResizing) {\n const { clientX } = action\n const { startX, columnWidth, headerIdWidths = [] } = state.columnResizing\n\n const deltaX = clientX - startX\n const percentageDeltaX = deltaX / columnWidth\n\n const newColumnWidths = {}\n\n headerIdWidths.forEach(([headerId, headerWidth]) => {\n newColumnWidths[headerId] = Math.max(\n headerWidth + headerWidth * percentageDeltaX,\n 0\n )\n })\n\n return {\n ...state,\n columnResizing: {\n ...state.columnResizing,\n columnWidths: {\n ...state.columnResizing.columnWidths,\n ...newColumnWidths,\n },\n },\n }\n }\n\n if (action.type === actions.columnDoneResizing) {\n return {\n ...state,\n columnResizing: {\n ...state.columnResizing,\n startX: null,\n isResizingColumn: null,\n },\n }\n }\n}\n\nconst useInstanceBeforeDimensions = instance => {\n const {\n flatHeaders,\n disableResizing,\n getHooks,\n state: { columnResizing },\n } = instance\n\n const getInstance = useGetLatest(instance)\n\n flatHeaders.forEach(header => {\n const canResize = getFirstDefined(\n header.disableResizing === true ? false : undefined,\n disableResizing === true ? false : undefined,\n true\n )\n\n header.canResize = canResize\n header.width =\n columnResizing.columnWidths[header.id] ||\n header.originalWidth ||\n header.width\n header.isResizing = columnResizing.isResizingColumn === header.id\n\n if (canResize) {\n header.getResizerProps = makePropGetter(getHooks().getResizerProps, {\n instance: getInstance(),\n header,\n })\n }\n })\n}\n\nfunction useInstance(instance) {\n const { plugins, dispatch, autoResetResize = true, columns } = instance\n\n ensurePluginOrder(plugins, ['useAbsoluteLayout'], 'useResizeColumns')\n\n const getAutoResetResize = useGetLatest(autoResetResize)\n useMountedLayoutEffect(() => {\n if (getAutoResetResize()) {\n dispatch({ type: actions.resetResize })\n }\n }, [columns])\n\n const resetResizing = React.useCallback(\n () => dispatch({ type: actions.resetResize }),\n [dispatch]\n )\n\n Object.assign(instance, {\n resetResizing,\n })\n}\n\nfunction getLeafHeaders(header) {\n const leafHeaders = []\n const recurseHeader = header => {\n if (header.columns && header.columns.length) {\n header.columns.map(recurseHeader)\n }\n leafHeaders.push(header)\n }\n recurseHeader(header)\n return leafHeaders\n}\n","const cellStyles = {\n position: 'absolute',\n top: 0,\n}\n\nexport const useAbsoluteLayout = hooks => {\n hooks.getTableBodyProps.push(getRowStyles)\n hooks.getRowProps.push(getRowStyles)\n hooks.getHeaderGroupProps.push(getRowStyles)\n hooks.getFooterGroupProps.push(getRowStyles)\n\n hooks.getHeaderProps.push((props, { column }) => [\n props,\n {\n style: {\n ...cellStyles,\n left: `${column.totalLeft}px`,\n width: `${column.totalWidth}px`,\n },\n },\n ])\n\n hooks.getCellProps.push((props, { cell }) => [\n props,\n {\n style: {\n ...cellStyles,\n left: `${cell.column.totalLeft}px`,\n width: `${cell.column.totalWidth}px`,\n },\n },\n ])\n\n hooks.getFooterProps.push((props, { column }) => [\n props,\n {\n style: {\n ...cellStyles,\n left: `${column.totalLeft}px`,\n width: `${column.totalWidth}px`,\n },\n },\n ])\n}\n\nuseAbsoluteLayout.pluginName = 'useAbsoluteLayout'\n\nconst getRowStyles = (props, { instance }) => [\n props,\n {\n style: {\n position: 'relative',\n width: `${instance.totalColumnsWidth}px`,\n },\n },\n]\n","const cellStyles = {\n display: 'inline-block',\n boxSizing: 'border-box',\n}\n\nconst getRowStyles = (props, { instance }) => [\n props,\n {\n style: {\n display: 'flex',\n width: `${instance.totalColumnsWidth}px`,\n },\n },\n]\n\nexport const useBlockLayout = hooks => {\n hooks.getRowProps.push(getRowStyles)\n hooks.getHeaderGroupProps.push(getRowStyles)\n hooks.getFooterGroupProps.push(getRowStyles)\n\n hooks.getHeaderProps.push((props, { column }) => [\n props,\n {\n style: {\n ...cellStyles,\n width: `${column.totalWidth}px`,\n },\n },\n ])\n\n hooks.getCellProps.push((props, { cell }) => [\n props,\n {\n style: {\n ...cellStyles,\n width: `${cell.column.totalWidth}px`,\n },\n },\n ])\n\n hooks.getFooterProps.push((props, { column }) => [\n props,\n {\n style: {\n ...cellStyles,\n width: `${column.totalWidth}px`,\n },\n },\n ])\n}\n\nuseBlockLayout.pluginName = 'useBlockLayout'\n","export function useFlexLayout(hooks) {\n hooks.getTableProps.push(getTableProps)\n hooks.getRowProps.push(getRowStyles)\n hooks.getHeaderGroupProps.push(getRowStyles)\n hooks.getFooterGroupProps.push(getRowStyles)\n hooks.getHeaderProps.push(getHeaderProps)\n hooks.getCellProps.push(getCellProps)\n hooks.getFooterProps.push(getFooterProps)\n}\n\nuseFlexLayout.pluginName = 'useFlexLayout'\n\nconst getTableProps = (props, { instance }) => [\n props,\n {\n style: {\n minWidth: `${instance.totalColumnsMinWidth}px`,\n },\n },\n]\n\nconst getRowStyles = (props, { instance }) => [\n props,\n {\n style: {\n display: 'flex',\n flex: '1 0 auto',\n minWidth: `${instance.totalColumnsMinWidth}px`,\n },\n },\n]\n\nconst getHeaderProps = (props, { column }) => [\n props,\n {\n style: {\n boxSizing: 'border-box',\n flex: column.totalFlexWidth\n ? `${column.totalFlexWidth} 0 auto`\n : undefined,\n minWidth: `${column.totalMinWidth}px`,\n width: `${column.totalWidth}px`,\n },\n },\n]\n\nconst getCellProps = (props, { cell }) => [\n props,\n {\n style: {\n boxSizing: 'border-box',\n flex: `${cell.column.totalFlexWidth} 0 auto`,\n minWidth: `${cell.column.totalMinWidth}px`,\n width: `${cell.column.totalWidth}px`,\n },\n },\n]\n\nconst getFooterProps = (props, { column }) => [\n props,\n {\n style: {\n boxSizing: 'border-box',\n flex: column.totalFlexWidth\n ? `${column.totalFlexWidth} 0 auto`\n : undefined,\n minWidth: `${column.totalMinWidth}px`,\n width: `${column.totalWidth}px`,\n },\n },\n]\n","import { actions } from '../publicUtils'\n\n// Actions\nactions.columnStartResizing = 'columnStartResizing'\nactions.columnResizing = 'columnResizing'\nactions.columnDoneResizing = 'columnDoneResizing'\nactions.resetResize = 'resetResize'\n\nexport function useGridLayout(hooks) {\n hooks.stateReducers.push(reducer)\n hooks.getTableProps.push(getTableProps)\n hooks.getHeaderProps.push(getHeaderProps)\n hooks.getRowProps.push(getRowProps)\n}\n\nuseGridLayout.pluginName = 'useGridLayout'\n\nconst getTableProps = (props, { instance }) => {\n const gridTemplateColumns = instance.visibleColumns.map(column => {\n if (instance.state.gridLayout.columnWidths[column.id])\n return `${instance.state.gridLayout.columnWidths[column.id]}px`\n // When resizing, lock the width of all unset columns\n // instead of using user-provided width or defaultColumn width,\n // which could potentially be 'auto' or 'fr' units that don't scale linearly\n if (instance.state.columnResizing?.isResizingColumn)\n return `${instance.state.gridLayout.startWidths[column.id]}px`\n if (typeof column.width === 'number') return `${column.width}px`\n return column.width\n })\n return [\n props,\n {\n style: {\n display: `grid`,\n gridTemplateColumns: gridTemplateColumns.join(` `),\n },\n },\n ]\n}\n\nconst getHeaderProps = (props, { column }) => [\n props,\n {\n id: `header-cell-${column.id}`,\n style: {\n position: `sticky`, //enables a scroll wrapper to be placed around the table and have sticky headers\n gridColumn: `span ${column.totalVisibleHeaderCount}`,\n },\n },\n]\n\nconst getRowProps = (props, { row }) => {\n if (row.isExpanded) {\n return [\n props,\n {\n style: {\n gridColumn: `1 / ${row.cells.length + 1}`,\n },\n },\n ]\n }\n return [props, {}]\n}\n\nfunction reducer(state, action, previousState, instance) {\n if (action.type === actions.init) {\n return {\n gridLayout: {\n columnWidths: {},\n },\n ...state,\n }\n }\n\n if (action.type === actions.resetResize) {\n return {\n ...state,\n gridLayout: {\n columnWidths: {},\n },\n }\n }\n\n if (action.type === actions.columnStartResizing) {\n const { columnId, headerIdWidths } = action\n const columnWidth = getElementWidth(columnId)\n\n if (columnWidth !== undefined) {\n const startWidths = instance.visibleColumns.reduce(\n (acc, column) => ({\n ...acc,\n [column.id]: getElementWidth(column.id),\n }),\n {}\n )\n const minWidths = instance.visibleColumns.reduce(\n (acc, column) => ({\n ...acc,\n [column.id]: column.minWidth,\n }),\n {}\n )\n const maxWidths = instance.visibleColumns.reduce(\n (acc, column) => ({\n ...acc,\n [column.id]: column.maxWidth,\n }),\n {}\n )\n\n const headerIdGridWidths = headerIdWidths.map(([headerId]) => [\n headerId,\n getElementWidth(headerId),\n ])\n\n return {\n ...state,\n gridLayout: {\n ...state.gridLayout,\n startWidths,\n minWidths,\n maxWidths,\n headerIdGridWidths,\n columnWidth,\n },\n }\n } else {\n return state\n }\n }\n\n if (action.type === actions.columnResizing) {\n const { clientX } = action\n const { startX } = state.columnResizing\n const {\n columnWidth,\n minWidths,\n maxWidths,\n headerIdGridWidths = [],\n } = state.gridLayout\n\n const deltaX = clientX - startX\n const percentageDeltaX = deltaX / columnWidth\n\n const newColumnWidths = {}\n\n headerIdGridWidths.forEach(([headerId, headerWidth]) => {\n newColumnWidths[headerId] = Math.min(\n Math.max(\n minWidths[headerId],\n headerWidth + headerWidth * percentageDeltaX\n ),\n maxWidths[headerId]\n )\n })\n\n return {\n ...state,\n gridLayout: {\n ...state.gridLayout,\n columnWidths: {\n ...state.gridLayout.columnWidths,\n ...newColumnWidths,\n },\n },\n }\n }\n\n if (action.type === actions.columnDoneResizing) {\n return {\n ...state,\n gridLayout: {\n ...state.gridLayout,\n startWidths: {},\n minWidths: {},\n maxWidths: {},\n },\n }\n }\n}\n\nfunction getElementWidth(columnId) {\n const width = document.getElementById(`header-cell-${columnId}`)?.offsetWidth\n\n if (width !== undefined) {\n return width\n }\n}\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar react_1 = require(\"react\");\nfunction useTimeoutFn(fn, ms) {\n if (ms === void 0) { ms = 0; }\n var ready = react_1.useRef(false);\n var timeout = react_1.useRef();\n var callback = react_1.useRef(fn);\n var isReady = react_1.useCallback(function () { return ready.current; }, []);\n var set = react_1.useCallback(function () {\n ready.current = false;\n timeout.current && clearTimeout(timeout.current);\n timeout.current = setTimeout(function () {\n ready.current = true;\n callback.current();\n }, ms);\n }, [ms]);\n var clear = react_1.useCallback(function () {\n ready.current = null;\n timeout.current && clearTimeout(timeout.current);\n }, []);\n // update ref when function changes\n react_1.useEffect(function () {\n callback.current = fn;\n }, [fn]);\n // set on mount, clear on unmount\n react_1.useEffect(function () {\n set();\n return clear;\n }, [ms]);\n return [isReady, clear, set];\n}\nexports.default = useTimeoutFn;\n","'use strict';\n\nvar utils = require('./utils');\nvar bind = require('./helpers/bind');\nvar Axios = require('./core/Axios');\nvar mergeConfig = require('./core/mergeConfig');\nvar defaults = require('./defaults');\n\n/**\n * Create an instance of Axios\n *\n * @param {Object} defaultConfig The default config for the instance\n * @return {Axios} A new instance of Axios\n */\nfunction createInstance(defaultConfig) {\n var context = new Axios(defaultConfig);\n var instance = bind(Axios.prototype.request, context);\n\n // Copy axios.prototype to instance\n utils.extend(instance, Axios.prototype, context);\n\n // Copy context to instance\n utils.extend(instance, context);\n\n // Factory for creating new instances\n instance.create = function create(instanceConfig) {\n return createInstance(mergeConfig(defaultConfig, instanceConfig));\n };\n\n return instance;\n}\n\n// Create the default instance to be exported\nvar axios = createInstance(defaults);\n\n// Expose Axios class to allow class inheritance\naxios.Axios = Axios;\n\n// Expose Cancel & CancelToken\naxios.CanceledError = require('./cancel/CanceledError');\naxios.CancelToken = require('./cancel/CancelToken');\naxios.isCancel = require('./cancel/isCancel');\naxios.VERSION = require('./env/data').version;\naxios.toFormData = require('./helpers/toFormData');\n\n// Expose AxiosError class\naxios.AxiosError = require('../lib/core/AxiosError');\n\n// alias for CanceledError for backward compatibility\naxios.Cancel = axios.CanceledError;\n\n// Expose all/spread\naxios.all = function all(promises) {\n return Promise.all(promises);\n};\naxios.spread = require('./helpers/spread');\n\n// Expose isAxiosError\naxios.isAxiosError = require('./helpers/isAxiosError');\n\nmodule.exports = axios;\n\n// Allow use of default import syntax in TypeScript\nmodule.exports.default = axios;\n","'use strict';\n\nvar utils = require('./../utils');\nvar buildURL = require('../helpers/buildURL');\nvar InterceptorManager = require('./InterceptorManager');\nvar dispatchRequest = require('./dispatchRequest');\nvar mergeConfig = require('./mergeConfig');\nvar buildFullPath = require('./buildFullPath');\nvar validator = require('../helpers/validator');\n\nvar validators = validator.validators;\n/**\n * Create a new instance of Axios\n *\n * @param {Object} instanceConfig The default config for the instance\n */\nfunction Axios(instanceConfig) {\n this.defaults = instanceConfig;\n this.interceptors = {\n request: new InterceptorManager(),\n response: new InterceptorManager()\n };\n}\n\n/**\n * Dispatch a request\n *\n * @param {Object} config The config specific for this request (merged with this.defaults)\n */\nAxios.prototype.request = function request(configOrUrl, config) {\n /*eslint no-param-reassign:0*/\n // Allow for axios('example/url'[, config]) a la fetch API\n if (typeof configOrUrl === 'string') {\n config = config || {};\n config.url = configOrUrl;\n } else {\n config = configOrUrl || {};\n }\n\n config = mergeConfig(this.defaults, config);\n\n // Set config.method\n if (config.method) {\n config.method = config.method.toLowerCase();\n } else if (this.defaults.method) {\n config.method = this.defaults.method.toLowerCase();\n } else {\n config.method = 'get';\n }\n\n var transitional = config.transitional;\n\n if (transitional !== undefined) {\n validator.assertOptions(transitional, {\n silentJSONParsing: validators.transitional(validators.boolean),\n forcedJSONParsing: validators.transitional(validators.boolean),\n clarifyTimeoutError: validators.transitional(validators.boolean)\n }, false);\n }\n\n // filter out skipped interceptors\n var requestInterceptorChain = [];\n var synchronousRequestInterceptors = true;\n this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {\n if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {\n return;\n }\n\n synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;\n\n requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);\n });\n\n var responseInterceptorChain = [];\n this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {\n responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);\n });\n\n var promise;\n\n if (!synchronousRequestInterceptors) {\n var chain = [dispatchRequest, undefined];\n\n Array.prototype.unshift.apply(chain, requestInterceptorChain);\n chain = chain.concat(responseInterceptorChain);\n\n promise = Promise.resolve(config);\n while (chain.length) {\n promise = promise.then(chain.shift(), chain.shift());\n }\n\n return promise;\n }\n\n\n var newConfig = config;\n while (requestInterceptorChain.length) {\n var onFulfilled = requestInterceptorChain.shift();\n var onRejected = requestInterceptorChain.shift();\n try {\n newConfig = onFulfilled(newConfig);\n } catch (error) {\n onRejected(error);\n break;\n }\n }\n\n try {\n promise = dispatchRequest(newConfig);\n } catch (error) {\n return Promise.reject(error);\n }\n\n while (responseInterceptorChain.length) {\n promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());\n }\n\n return promise;\n};\n\nAxios.prototype.getUri = function getUri(config) {\n config = mergeConfig(this.defaults, config);\n var fullPath = buildFullPath(config.baseURL, config.url);\n return buildURL(fullPath, config.params, config.paramsSerializer);\n};\n\n// Provide aliases for supported request methods\nutils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {\n /*eslint func-names:0*/\n Axios.prototype[method] = function(url, config) {\n return this.request(mergeConfig(config || {}, {\n method: method,\n url: url,\n data: (config || {}).data\n }));\n };\n});\n\nutils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {\n /*eslint func-names:0*/\n\n function generateHTTPMethod(isForm) {\n return function httpMethod(url, data, config) {\n return this.request(mergeConfig(config || {}, {\n method: method,\n headers: isForm ? {\n 'Content-Type': 'multipart/form-data'\n } : {},\n url: url,\n data: data\n }));\n };\n }\n\n Axios.prototype[method] = generateHTTPMethod();\n\n Axios.prototype[method + 'Form'] = generateHTTPMethod(true);\n});\n\nmodule.exports = Axios;\n","'use strict';\n\nvar utils = require('./../utils');\n\nfunction InterceptorManager() {\n this.handlers = [];\n}\n\n/**\n * Add a new interceptor to the stack\n *\n * @param {Function} fulfilled The function to handle `then` for a `Promise`\n * @param {Function} rejected The function to handle `reject` for a `Promise`\n *\n * @return {Number} An ID used to remove interceptor later\n */\nInterceptorManager.prototype.use = function use(fulfilled, rejected, options) {\n this.handlers.push({\n fulfilled: fulfilled,\n rejected: rejected,\n synchronous: options ? options.synchronous : false,\n runWhen: options ? options.runWhen : null\n });\n return this.handlers.length - 1;\n};\n\n/**\n * Remove an interceptor from the stack\n *\n * @param {Number} id The ID that was returned by `use`\n */\nInterceptorManager.prototype.eject = function eject(id) {\n if (this.handlers[id]) {\n this.handlers[id] = null;\n }\n};\n\n/**\n * Iterate over all the registered interceptors\n *\n * This method is particularly useful for skipping over any\n * interceptors that may have become `null` calling `eject`.\n *\n * @param {Function} fn The function to call for each interceptor\n */\nInterceptorManager.prototype.forEach = function forEach(fn) {\n utils.forEach(this.handlers, function forEachHandler(h) {\n if (h !== null) {\n fn(h);\n }\n });\n};\n\nmodule.exports = InterceptorManager;\n","'use strict';\n\nvar utils = require('./../utils');\nvar transformData = require('./transformData');\nvar isCancel = require('../cancel/isCancel');\nvar defaults = require('../defaults');\nvar CanceledError = require('../cancel/CanceledError');\n\n/**\n * Throws a `CanceledError` if cancellation has been requested.\n */\nfunction throwIfCancellationRequested(config) {\n if (config.cancelToken) {\n config.cancelToken.throwIfRequested();\n }\n\n if (config.signal && config.signal.aborted) {\n throw new CanceledError();\n }\n}\n\n/**\n * Dispatch a request to the server using the configured adapter.\n *\n * @param {object} config The config that is to be used for the request\n * @returns {Promise} The Promise to be fulfilled\n */\nmodule.exports = function dispatchRequest(config) {\n throwIfCancellationRequested(config);\n\n // Ensure headers exist\n config.headers = config.headers || {};\n\n // Transform request data\n config.data = transformData.call(\n config,\n config.data,\n config.headers,\n config.transformRequest\n );\n\n // Flatten headers\n config.headers = utils.merge(\n config.headers.common || {},\n config.headers[config.method] || {},\n config.headers\n );\n\n utils.forEach(\n ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],\n function cleanHeaderConfig(method) {\n delete config.headers[method];\n }\n );\n\n var adapter = config.adapter || defaults.adapter;\n\n return adapter(config).then(function onAdapterResolution(response) {\n throwIfCancellationRequested(config);\n\n // Transform response data\n response.data = transformData.call(\n config,\n response.data,\n response.headers,\n config.transformResponse\n );\n\n return response;\n }, function onAdapterRejection(reason) {\n if (!isCancel(reason)) {\n throwIfCancellationRequested(config);\n\n // Transform response data\n if (reason && reason.response) {\n reason.response.data = transformData.call(\n config,\n reason.response.data,\n reason.response.headers,\n config.transformResponse\n );\n }\n }\n\n return Promise.reject(reason);\n });\n};\n","'use strict';\n\nvar utils = require('./../utils');\nvar defaults = require('../defaults');\n\n/**\n * Transform the data for a request or a response\n *\n * @param {Object|String} data The data to be transformed\n * @param {Array} headers The headers for the request or response\n * @param {Array|Function} fns A single function or Array of functions\n * @returns {*} The resulting transformed data\n */\nmodule.exports = function transformData(data, headers, fns) {\n var context = this || defaults;\n /*eslint no-param-reassign:0*/\n utils.forEach(fns, function transform(fn) {\n data = fn.call(context, data, headers);\n });\n\n return data;\n};\n","'use strict';\n\nvar utils = require('../utils');\n\nmodule.exports = function normalizeHeaderName(headers, normalizedName) {\n utils.forEach(headers, function processHeader(value, name) {\n if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {\n headers[normalizedName] = value;\n delete headers[name];\n }\n });\n};\n","/*!\n * The buffer module from node.js, for the browser.\n *\n * @author Feross Aboukhadijeh \n * @license MIT\n */\n/* eslint-disable no-proto */\n\n'use strict'\n\nvar base64 = require('base64-js')\nvar ieee754 = require('ieee754')\nvar isArray = require('isarray')\n\nexports.Buffer = Buffer\nexports.SlowBuffer = SlowBuffer\nexports.INSPECT_MAX_BYTES = 50\n\n/**\n * If `Buffer.TYPED_ARRAY_SUPPORT`:\n * === true Use Uint8Array implementation (fastest)\n * === false Use Object implementation (most compatible, even IE6)\n *\n * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,\n * Opera 11.6+, iOS 4.2+.\n *\n * Due to various browser bugs, sometimes the Object implementation will be used even\n * when the browser supports typed arrays.\n *\n * Note:\n *\n * - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,\n * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.\n *\n * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.\n *\n * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of\n * incorrect length in some situations.\n\n * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they\n * get the Object implementation, which is slower but behaves correctly.\n */\nBuffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined\n ? global.TYPED_ARRAY_SUPPORT\n : typedArraySupport()\n\n/*\n * Export kMaxLength after typed array support is determined.\n */\nexports.kMaxLength = kMaxLength()\n\nfunction typedArraySupport () {\n try {\n var arr = new Uint8Array(1)\n arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }}\n return arr.foo() === 42 && // typed array instances can be augmented\n typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`\n arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`\n } catch (e) {\n return false\n }\n}\n\nfunction kMaxLength () {\n return Buffer.TYPED_ARRAY_SUPPORT\n ? 0x7fffffff\n : 0x3fffffff\n}\n\nfunction createBuffer (that, length) {\n if (kMaxLength() < length) {\n throw new RangeError('Invalid typed array length')\n }\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n // Return an augmented `Uint8Array` instance, for best performance\n that = new Uint8Array(length)\n that.__proto__ = Buffer.prototype\n } else {\n // Fallback: Return an object instance of the Buffer class\n if (that === null) {\n that = new Buffer(length)\n }\n that.length = length\n }\n\n return that\n}\n\n/**\n * The Buffer constructor returns instances of `Uint8Array` that have their\n * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of\n * `Uint8Array`, so the returned instances will have all the node `Buffer` methods\n * and the `Uint8Array` methods. Square bracket notation works as expected -- it\n * returns a single octet.\n *\n * The `Uint8Array` prototype remains unmodified.\n */\n\nfunction Buffer (arg, encodingOrOffset, length) {\n if (!Buffer.TYPED_ARRAY_SUPPORT && !(this instanceof Buffer)) {\n return new Buffer(arg, encodingOrOffset, length)\n }\n\n // Common case.\n if (typeof arg === 'number') {\n if (typeof encodingOrOffset === 'string') {\n throw new Error(\n 'If encoding is specified then the first argument must be a string'\n )\n }\n return allocUnsafe(this, arg)\n }\n return from(this, arg, encodingOrOffset, length)\n}\n\nBuffer.poolSize = 8192 // not used by this implementation\n\n// TODO: Legacy, not needed anymore. Remove in next major version.\nBuffer._augment = function (arr) {\n arr.__proto__ = Buffer.prototype\n return arr\n}\n\nfunction from (that, value, encodingOrOffset, length) {\n if (typeof value === 'number') {\n throw new TypeError('\"value\" argument must not be a number')\n }\n\n if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {\n return fromArrayBuffer(that, value, encodingOrOffset, length)\n }\n\n if (typeof value === 'string') {\n return fromString(that, value, encodingOrOffset)\n }\n\n return fromObject(that, value)\n}\n\n/**\n * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError\n * if value is a number.\n * Buffer.from(str[, encoding])\n * Buffer.from(array)\n * Buffer.from(buffer)\n * Buffer.from(arrayBuffer[, byteOffset[, length]])\n **/\nBuffer.from = function (value, encodingOrOffset, length) {\n return from(null, value, encodingOrOffset, length)\n}\n\nif (Buffer.TYPED_ARRAY_SUPPORT) {\n Buffer.prototype.__proto__ = Uint8Array.prototype\n Buffer.__proto__ = Uint8Array\n if (typeof Symbol !== 'undefined' && Symbol.species &&\n Buffer[Symbol.species] === Buffer) {\n // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97\n Object.defineProperty(Buffer, Symbol.species, {\n value: null,\n configurable: true\n })\n }\n}\n\nfunction assertSize (size) {\n if (typeof size !== 'number') {\n throw new TypeError('\"size\" argument must be a number')\n } else if (size < 0) {\n throw new RangeError('\"size\" argument must not be negative')\n }\n}\n\nfunction alloc (that, size, fill, encoding) {\n assertSize(size)\n if (size <= 0) {\n return createBuffer(that, size)\n }\n if (fill !== undefined) {\n // Only pay attention to encoding if it's a string. This\n // prevents accidentally sending in a number that would\n // be interpretted as a start offset.\n return typeof encoding === 'string'\n ? createBuffer(that, size).fill(fill, encoding)\n : createBuffer(that, size).fill(fill)\n }\n return createBuffer(that, size)\n}\n\n/**\n * Creates a new filled Buffer instance.\n * alloc(size[, fill[, encoding]])\n **/\nBuffer.alloc = function (size, fill, encoding) {\n return alloc(null, size, fill, encoding)\n}\n\nfunction allocUnsafe (that, size) {\n assertSize(size)\n that = createBuffer(that, size < 0 ? 0 : checked(size) | 0)\n if (!Buffer.TYPED_ARRAY_SUPPORT) {\n for (var i = 0; i < size; ++i) {\n that[i] = 0\n }\n }\n return that\n}\n\n/**\n * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.\n * */\nBuffer.allocUnsafe = function (size) {\n return allocUnsafe(null, size)\n}\n/**\n * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.\n */\nBuffer.allocUnsafeSlow = function (size) {\n return allocUnsafe(null, size)\n}\n\nfunction fromString (that, string, encoding) {\n if (typeof encoding !== 'string' || encoding === '') {\n encoding = 'utf8'\n }\n\n if (!Buffer.isEncoding(encoding)) {\n throw new TypeError('\"encoding\" must be a valid string encoding')\n }\n\n var length = byteLength(string, encoding) | 0\n that = createBuffer(that, length)\n\n var actual = that.write(string, encoding)\n\n if (actual !== length) {\n // Writing a hex string, for example, that contains invalid characters will\n // cause everything after the first invalid character to be ignored. (e.g.\n // 'abxxcd' will be treated as 'ab')\n that = that.slice(0, actual)\n }\n\n return that\n}\n\nfunction fromArrayLike (that, array) {\n var length = array.length < 0 ? 0 : checked(array.length) | 0\n that = createBuffer(that, length)\n for (var i = 0; i < length; i += 1) {\n that[i] = array[i] & 255\n }\n return that\n}\n\nfunction fromArrayBuffer (that, array, byteOffset, length) {\n array.byteLength // this throws if `array` is not a valid ArrayBuffer\n\n if (byteOffset < 0 || array.byteLength < byteOffset) {\n throw new RangeError('\\'offset\\' is out of bounds')\n }\n\n if (array.byteLength < byteOffset + (length || 0)) {\n throw new RangeError('\\'length\\' is out of bounds')\n }\n\n if (byteOffset === undefined && length === undefined) {\n array = new Uint8Array(array)\n } else if (length === undefined) {\n array = new Uint8Array(array, byteOffset)\n } else {\n array = new Uint8Array(array, byteOffset, length)\n }\n\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n // Return an augmented `Uint8Array` instance, for best performance\n that = array\n that.__proto__ = Buffer.prototype\n } else {\n // Fallback: Return an object instance of the Buffer class\n that = fromArrayLike(that, array)\n }\n return that\n}\n\nfunction fromObject (that, obj) {\n if (Buffer.isBuffer(obj)) {\n var len = checked(obj.length) | 0\n that = createBuffer(that, len)\n\n if (that.length === 0) {\n return that\n }\n\n obj.copy(that, 0, 0, len)\n return that\n }\n\n if (obj) {\n if ((typeof ArrayBuffer !== 'undefined' &&\n obj.buffer instanceof ArrayBuffer) || 'length' in obj) {\n if (typeof obj.length !== 'number' || isnan(obj.length)) {\n return createBuffer(that, 0)\n }\n return fromArrayLike(that, obj)\n }\n\n if (obj.type === 'Buffer' && isArray(obj.data)) {\n return fromArrayLike(that, obj.data)\n }\n }\n\n throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')\n}\n\nfunction checked (length) {\n // Note: cannot use `length < kMaxLength()` here because that fails when\n // length is NaN (which is otherwise coerced to zero.)\n if (length >= kMaxLength()) {\n throw new RangeError('Attempt to allocate Buffer larger than maximum ' +\n 'size: 0x' + kMaxLength().toString(16) + ' bytes')\n }\n return length | 0\n}\n\nfunction SlowBuffer (length) {\n if (+length != length) { // eslint-disable-line eqeqeq\n length = 0\n }\n return Buffer.alloc(+length)\n}\n\nBuffer.isBuffer = function isBuffer (b) {\n return !!(b != null && b._isBuffer)\n}\n\nBuffer.compare = function compare (a, b) {\n if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {\n throw new TypeError('Arguments must be Buffers')\n }\n\n if (a === b) return 0\n\n var x = a.length\n var y = b.length\n\n for (var i = 0, len = Math.min(x, y); i < len; ++i) {\n if (a[i] !== b[i]) {\n x = a[i]\n y = b[i]\n break\n }\n }\n\n if (x < y) return -1\n if (y < x) return 1\n return 0\n}\n\nBuffer.isEncoding = function isEncoding (encoding) {\n switch (String(encoding).toLowerCase()) {\n case 'hex':\n case 'utf8':\n case 'utf-8':\n case 'ascii':\n case 'latin1':\n case 'binary':\n case 'base64':\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return true\n default:\n return false\n }\n}\n\nBuffer.concat = function concat (list, length) {\n if (!isArray(list)) {\n throw new TypeError('\"list\" argument must be an Array of Buffers')\n }\n\n if (list.length === 0) {\n return Buffer.alloc(0)\n }\n\n var i\n if (length === undefined) {\n length = 0\n for (i = 0; i < list.length; ++i) {\n length += list[i].length\n }\n }\n\n var buffer = Buffer.allocUnsafe(length)\n var pos = 0\n for (i = 0; i < list.length; ++i) {\n var buf = list[i]\n if (!Buffer.isBuffer(buf)) {\n throw new TypeError('\"list\" argument must be an Array of Buffers')\n }\n buf.copy(buffer, pos)\n pos += buf.length\n }\n return buffer\n}\n\nfunction byteLength (string, encoding) {\n if (Buffer.isBuffer(string)) {\n return string.length\n }\n if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' &&\n (ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) {\n return string.byteLength\n }\n if (typeof string !== 'string') {\n string = '' + string\n }\n\n var len = string.length\n if (len === 0) return 0\n\n // Use a for loop to avoid recursion\n var loweredCase = false\n for (;;) {\n switch (encoding) {\n case 'ascii':\n case 'latin1':\n case 'binary':\n return len\n case 'utf8':\n case 'utf-8':\n case undefined:\n return utf8ToBytes(string).length\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return len * 2\n case 'hex':\n return len >>> 1\n case 'base64':\n return base64ToBytes(string).length\n default:\n if (loweredCase) return utf8ToBytes(string).length // assume utf8\n encoding = ('' + encoding).toLowerCase()\n loweredCase = true\n }\n }\n}\nBuffer.byteLength = byteLength\n\nfunction slowToString (encoding, start, end) {\n var loweredCase = false\n\n // No need to verify that \"this.length <= MAX_UINT32\" since it's a read-only\n // property of a typed array.\n\n // This behaves neither like String nor Uint8Array in that we set start/end\n // to their upper/lower bounds if the value passed is out of range.\n // undefined is handled specially as per ECMA-262 6th Edition,\n // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.\n if (start === undefined || start < 0) {\n start = 0\n }\n // Return early if start > this.length. Done here to prevent potential uint32\n // coercion fail below.\n if (start > this.length) {\n return ''\n }\n\n if (end === undefined || end > this.length) {\n end = this.length\n }\n\n if (end <= 0) {\n return ''\n }\n\n // Force coersion to uint32. This will also coerce falsey/NaN values to 0.\n end >>>= 0\n start >>>= 0\n\n if (end <= start) {\n return ''\n }\n\n if (!encoding) encoding = 'utf8'\n\n while (true) {\n switch (encoding) {\n case 'hex':\n return hexSlice(this, start, end)\n\n case 'utf8':\n case 'utf-8':\n return utf8Slice(this, start, end)\n\n case 'ascii':\n return asciiSlice(this, start, end)\n\n case 'latin1':\n case 'binary':\n return latin1Slice(this, start, end)\n\n case 'base64':\n return base64Slice(this, start, end)\n\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return utf16leSlice(this, start, end)\n\n default:\n if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)\n encoding = (encoding + '').toLowerCase()\n loweredCase = true\n }\n }\n}\n\n// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect\n// Buffer instances.\nBuffer.prototype._isBuffer = true\n\nfunction swap (b, n, m) {\n var i = b[n]\n b[n] = b[m]\n b[m] = i\n}\n\nBuffer.prototype.swap16 = function swap16 () {\n var len = this.length\n if (len % 2 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 16-bits')\n }\n for (var i = 0; i < len; i += 2) {\n swap(this, i, i + 1)\n }\n return this\n}\n\nBuffer.prototype.swap32 = function swap32 () {\n var len = this.length\n if (len % 4 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 32-bits')\n }\n for (var i = 0; i < len; i += 4) {\n swap(this, i, i + 3)\n swap(this, i + 1, i + 2)\n }\n return this\n}\n\nBuffer.prototype.swap64 = function swap64 () {\n var len = this.length\n if (len % 8 !== 0) {\n throw new RangeError('Buffer size must be a multiple of 64-bits')\n }\n for (var i = 0; i < len; i += 8) {\n swap(this, i, i + 7)\n swap(this, i + 1, i + 6)\n swap(this, i + 2, i + 5)\n swap(this, i + 3, i + 4)\n }\n return this\n}\n\nBuffer.prototype.toString = function toString () {\n var length = this.length | 0\n if (length === 0) return ''\n if (arguments.length === 0) return utf8Slice(this, 0, length)\n return slowToString.apply(this, arguments)\n}\n\nBuffer.prototype.equals = function equals (b) {\n if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')\n if (this === b) return true\n return Buffer.compare(this, b) === 0\n}\n\nBuffer.prototype.inspect = function inspect () {\n var str = ''\n var max = exports.INSPECT_MAX_BYTES\n if (this.length > 0) {\n str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')\n if (this.length > max) str += ' ... '\n }\n return ''\n}\n\nBuffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {\n if (!Buffer.isBuffer(target)) {\n throw new TypeError('Argument must be a Buffer')\n }\n\n if (start === undefined) {\n start = 0\n }\n if (end === undefined) {\n end = target ? target.length : 0\n }\n if (thisStart === undefined) {\n thisStart = 0\n }\n if (thisEnd === undefined) {\n thisEnd = this.length\n }\n\n if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {\n throw new RangeError('out of range index')\n }\n\n if (thisStart >= thisEnd && start >= end) {\n return 0\n }\n if (thisStart >= thisEnd) {\n return -1\n }\n if (start >= end) {\n return 1\n }\n\n start >>>= 0\n end >>>= 0\n thisStart >>>= 0\n thisEnd >>>= 0\n\n if (this === target) return 0\n\n var x = thisEnd - thisStart\n var y = end - start\n var len = Math.min(x, y)\n\n var thisCopy = this.slice(thisStart, thisEnd)\n var targetCopy = target.slice(start, end)\n\n for (var i = 0; i < len; ++i) {\n if (thisCopy[i] !== targetCopy[i]) {\n x = thisCopy[i]\n y = targetCopy[i]\n break\n }\n }\n\n if (x < y) return -1\n if (y < x) return 1\n return 0\n}\n\n// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,\n// OR the last index of `val` in `buffer` at offset <= `byteOffset`.\n//\n// Arguments:\n// - buffer - a Buffer to search\n// - val - a string, Buffer, or number\n// - byteOffset - an index into `buffer`; will be clamped to an int32\n// - encoding - an optional encoding, relevant is val is a string\n// - dir - true for indexOf, false for lastIndexOf\nfunction bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {\n // Empty buffer means no match\n if (buffer.length === 0) return -1\n\n // Normalize byteOffset\n if (typeof byteOffset === 'string') {\n encoding = byteOffset\n byteOffset = 0\n } else if (byteOffset > 0x7fffffff) {\n byteOffset = 0x7fffffff\n } else if (byteOffset < -0x80000000) {\n byteOffset = -0x80000000\n }\n byteOffset = +byteOffset // Coerce to Number.\n if (isNaN(byteOffset)) {\n // byteOffset: it it's undefined, null, NaN, \"foo\", etc, search whole buffer\n byteOffset = dir ? 0 : (buffer.length - 1)\n }\n\n // Normalize byteOffset: negative offsets start from the end of the buffer\n if (byteOffset < 0) byteOffset = buffer.length + byteOffset\n if (byteOffset >= buffer.length) {\n if (dir) return -1\n else byteOffset = buffer.length - 1\n } else if (byteOffset < 0) {\n if (dir) byteOffset = 0\n else return -1\n }\n\n // Normalize val\n if (typeof val === 'string') {\n val = Buffer.from(val, encoding)\n }\n\n // Finally, search either indexOf (if dir is true) or lastIndexOf\n if (Buffer.isBuffer(val)) {\n // Special case: looking for empty string/buffer always fails\n if (val.length === 0) {\n return -1\n }\n return arrayIndexOf(buffer, val, byteOffset, encoding, dir)\n } else if (typeof val === 'number') {\n val = val & 0xFF // Search for a byte value [0-255]\n if (Buffer.TYPED_ARRAY_SUPPORT &&\n typeof Uint8Array.prototype.indexOf === 'function') {\n if (dir) {\n return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset)\n } else {\n return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset)\n }\n }\n return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir)\n }\n\n throw new TypeError('val must be string, number or Buffer')\n}\n\nfunction arrayIndexOf (arr, val, byteOffset, encoding, dir) {\n var indexSize = 1\n var arrLength = arr.length\n var valLength = val.length\n\n if (encoding !== undefined) {\n encoding = String(encoding).toLowerCase()\n if (encoding === 'ucs2' || encoding === 'ucs-2' ||\n encoding === 'utf16le' || encoding === 'utf-16le') {\n if (arr.length < 2 || val.length < 2) {\n return -1\n }\n indexSize = 2\n arrLength /= 2\n valLength /= 2\n byteOffset /= 2\n }\n }\n\n function read (buf, i) {\n if (indexSize === 1) {\n return buf[i]\n } else {\n return buf.readUInt16BE(i * indexSize)\n }\n }\n\n var i\n if (dir) {\n var foundIndex = -1\n for (i = byteOffset; i < arrLength; i++) {\n if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {\n if (foundIndex === -1) foundIndex = i\n if (i - foundIndex + 1 === valLength) return foundIndex * indexSize\n } else {\n if (foundIndex !== -1) i -= i - foundIndex\n foundIndex = -1\n }\n }\n } else {\n if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength\n for (i = byteOffset; i >= 0; i--) {\n var found = true\n for (var j = 0; j < valLength; j++) {\n if (read(arr, i + j) !== read(val, j)) {\n found = false\n break\n }\n }\n if (found) return i\n }\n }\n\n return -1\n}\n\nBuffer.prototype.includes = function includes (val, byteOffset, encoding) {\n return this.indexOf(val, byteOffset, encoding) !== -1\n}\n\nBuffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {\n return bidirectionalIndexOf(this, val, byteOffset, encoding, true)\n}\n\nBuffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) {\n return bidirectionalIndexOf(this, val, byteOffset, encoding, false)\n}\n\nfunction hexWrite (buf, string, offset, length) {\n offset = Number(offset) || 0\n var remaining = buf.length - offset\n if (!length) {\n length = remaining\n } else {\n length = Number(length)\n if (length > remaining) {\n length = remaining\n }\n }\n\n // must be an even number of digits\n var strLen = string.length\n if (strLen % 2 !== 0) throw new TypeError('Invalid hex string')\n\n if (length > strLen / 2) {\n length = strLen / 2\n }\n for (var i = 0; i < length; ++i) {\n var parsed = parseInt(string.substr(i * 2, 2), 16)\n if (isNaN(parsed)) return i\n buf[offset + i] = parsed\n }\n return i\n}\n\nfunction utf8Write (buf, string, offset, length) {\n return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)\n}\n\nfunction asciiWrite (buf, string, offset, length) {\n return blitBuffer(asciiToBytes(string), buf, offset, length)\n}\n\nfunction latin1Write (buf, string, offset, length) {\n return asciiWrite(buf, string, offset, length)\n}\n\nfunction base64Write (buf, string, offset, length) {\n return blitBuffer(base64ToBytes(string), buf, offset, length)\n}\n\nfunction ucs2Write (buf, string, offset, length) {\n return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)\n}\n\nBuffer.prototype.write = function write (string, offset, length, encoding) {\n // Buffer#write(string)\n if (offset === undefined) {\n encoding = 'utf8'\n length = this.length\n offset = 0\n // Buffer#write(string, encoding)\n } else if (length === undefined && typeof offset === 'string') {\n encoding = offset\n length = this.length\n offset = 0\n // Buffer#write(string, offset[, length][, encoding])\n } else if (isFinite(offset)) {\n offset = offset | 0\n if (isFinite(length)) {\n length = length | 0\n if (encoding === undefined) encoding = 'utf8'\n } else {\n encoding = length\n length = undefined\n }\n // legacy write(string, encoding, offset, length) - remove in v0.13\n } else {\n throw new Error(\n 'Buffer.write(string, encoding, offset[, length]) is no longer supported'\n )\n }\n\n var remaining = this.length - offset\n if (length === undefined || length > remaining) length = remaining\n\n if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {\n throw new RangeError('Attempt to write outside buffer bounds')\n }\n\n if (!encoding) encoding = 'utf8'\n\n var loweredCase = false\n for (;;) {\n switch (encoding) {\n case 'hex':\n return hexWrite(this, string, offset, length)\n\n case 'utf8':\n case 'utf-8':\n return utf8Write(this, string, offset, length)\n\n case 'ascii':\n return asciiWrite(this, string, offset, length)\n\n case 'latin1':\n case 'binary':\n return latin1Write(this, string, offset, length)\n\n case 'base64':\n // Warning: maxLength not taken into account in base64Write\n return base64Write(this, string, offset, length)\n\n case 'ucs2':\n case 'ucs-2':\n case 'utf16le':\n case 'utf-16le':\n return ucs2Write(this, string, offset, length)\n\n default:\n if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)\n encoding = ('' + encoding).toLowerCase()\n loweredCase = true\n }\n }\n}\n\nBuffer.prototype.toJSON = function toJSON () {\n return {\n type: 'Buffer',\n data: Array.prototype.slice.call(this._arr || this, 0)\n }\n}\n\nfunction base64Slice (buf, start, end) {\n if (start === 0 && end === buf.length) {\n return base64.fromByteArray(buf)\n } else {\n return base64.fromByteArray(buf.slice(start, end))\n }\n}\n\nfunction utf8Slice (buf, start, end) {\n end = Math.min(buf.length, end)\n var res = []\n\n var i = start\n while (i < end) {\n var firstByte = buf[i]\n var codePoint = null\n var bytesPerSequence = (firstByte > 0xEF) ? 4\n : (firstByte > 0xDF) ? 3\n : (firstByte > 0xBF) ? 2\n : 1\n\n if (i + bytesPerSequence <= end) {\n var secondByte, thirdByte, fourthByte, tempCodePoint\n\n switch (bytesPerSequence) {\n case 1:\n if (firstByte < 0x80) {\n codePoint = firstByte\n }\n break\n case 2:\n secondByte = buf[i + 1]\n if ((secondByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)\n if (tempCodePoint > 0x7F) {\n codePoint = tempCodePoint\n }\n }\n break\n case 3:\n secondByte = buf[i + 1]\n thirdByte = buf[i + 2]\n if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)\n if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {\n codePoint = tempCodePoint\n }\n }\n break\n case 4:\n secondByte = buf[i + 1]\n thirdByte = buf[i + 2]\n fourthByte = buf[i + 3]\n if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {\n tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)\n if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {\n codePoint = tempCodePoint\n }\n }\n }\n }\n\n if (codePoint === null) {\n // we did not generate a valid codePoint so insert a\n // replacement char (U+FFFD) and advance only 1 byte\n codePoint = 0xFFFD\n bytesPerSequence = 1\n } else if (codePoint > 0xFFFF) {\n // encode to utf16 (surrogate pair dance)\n codePoint -= 0x10000\n res.push(codePoint >>> 10 & 0x3FF | 0xD800)\n codePoint = 0xDC00 | codePoint & 0x3FF\n }\n\n res.push(codePoint)\n i += bytesPerSequence\n }\n\n return decodeCodePointsArray(res)\n}\n\n// Based on http://stackoverflow.com/a/22747272/680742, the browser with\n// the lowest limit is Chrome, with 0x10000 args.\n// We go 1 magnitude less, for safety\nvar MAX_ARGUMENTS_LENGTH = 0x1000\n\nfunction decodeCodePointsArray (codePoints) {\n var len = codePoints.length\n if (len <= MAX_ARGUMENTS_LENGTH) {\n return String.fromCharCode.apply(String, codePoints) // avoid extra slice()\n }\n\n // Decode in chunks to avoid \"call stack size exceeded\".\n var res = ''\n var i = 0\n while (i < len) {\n res += String.fromCharCode.apply(\n String,\n codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)\n )\n }\n return res\n}\n\nfunction asciiSlice (buf, start, end) {\n var ret = ''\n end = Math.min(buf.length, end)\n\n for (var i = start; i < end; ++i) {\n ret += String.fromCharCode(buf[i] & 0x7F)\n }\n return ret\n}\n\nfunction latin1Slice (buf, start, end) {\n var ret = ''\n end = Math.min(buf.length, end)\n\n for (var i = start; i < end; ++i) {\n ret += String.fromCharCode(buf[i])\n }\n return ret\n}\n\nfunction hexSlice (buf, start, end) {\n var len = buf.length\n\n if (!start || start < 0) start = 0\n if (!end || end < 0 || end > len) end = len\n\n var out = ''\n for (var i = start; i < end; ++i) {\n out += toHex(buf[i])\n }\n return out\n}\n\nfunction utf16leSlice (buf, start, end) {\n var bytes = buf.slice(start, end)\n var res = ''\n for (var i = 0; i < bytes.length; i += 2) {\n res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)\n }\n return res\n}\n\nBuffer.prototype.slice = function slice (start, end) {\n var len = this.length\n start = ~~start\n end = end === undefined ? len : ~~end\n\n if (start < 0) {\n start += len\n if (start < 0) start = 0\n } else if (start > len) {\n start = len\n }\n\n if (end < 0) {\n end += len\n if (end < 0) end = 0\n } else if (end > len) {\n end = len\n }\n\n if (end < start) end = start\n\n var newBuf\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n newBuf = this.subarray(start, end)\n newBuf.__proto__ = Buffer.prototype\n } else {\n var sliceLen = end - start\n newBuf = new Buffer(sliceLen, undefined)\n for (var i = 0; i < sliceLen; ++i) {\n newBuf[i] = this[i + start]\n }\n }\n\n return newBuf\n}\n\n/*\n * Need to make sure that buffer isn't trying to write out of bounds.\n */\nfunction checkOffset (offset, ext, length) {\n if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')\n if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')\n}\n\nBuffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n var val = this[offset]\n var mul = 1\n var i = 0\n while (++i < byteLength && (mul *= 0x100)) {\n val += this[offset + i] * mul\n }\n\n return val\n}\n\nBuffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) {\n checkOffset(offset, byteLength, this.length)\n }\n\n var val = this[offset + --byteLength]\n var mul = 1\n while (byteLength > 0 && (mul *= 0x100)) {\n val += this[offset + --byteLength] * mul\n }\n\n return val\n}\n\nBuffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 1, this.length)\n return this[offset]\n}\n\nBuffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 2, this.length)\n return this[offset] | (this[offset + 1] << 8)\n}\n\nBuffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 2, this.length)\n return (this[offset] << 8) | this[offset + 1]\n}\n\nBuffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return ((this[offset]) |\n (this[offset + 1] << 8) |\n (this[offset + 2] << 16)) +\n (this[offset + 3] * 0x1000000)\n}\n\nBuffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset] * 0x1000000) +\n ((this[offset + 1] << 16) |\n (this[offset + 2] << 8) |\n this[offset + 3])\n}\n\nBuffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n var val = this[offset]\n var mul = 1\n var i = 0\n while (++i < byteLength && (mul *= 0x100)) {\n val += this[offset + i] * mul\n }\n mul *= 0x80\n\n if (val >= mul) val -= Math.pow(2, 8 * byteLength)\n\n return val\n}\n\nBuffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) checkOffset(offset, byteLength, this.length)\n\n var i = byteLength\n var mul = 1\n var val = this[offset + --i]\n while (i > 0 && (mul *= 0x100)) {\n val += this[offset + --i] * mul\n }\n mul *= 0x80\n\n if (val >= mul) val -= Math.pow(2, 8 * byteLength)\n\n return val\n}\n\nBuffer.prototype.readInt8 = function readInt8 (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 1, this.length)\n if (!(this[offset] & 0x80)) return (this[offset])\n return ((0xff - this[offset] + 1) * -1)\n}\n\nBuffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 2, this.length)\n var val = this[offset] | (this[offset + 1] << 8)\n return (val & 0x8000) ? val | 0xFFFF0000 : val\n}\n\nBuffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 2, this.length)\n var val = this[offset + 1] | (this[offset] << 8)\n return (val & 0x8000) ? val | 0xFFFF0000 : val\n}\n\nBuffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset]) |\n (this[offset + 1] << 8) |\n (this[offset + 2] << 16) |\n (this[offset + 3] << 24)\n}\n\nBuffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n\n return (this[offset] << 24) |\n (this[offset + 1] << 16) |\n (this[offset + 2] << 8) |\n (this[offset + 3])\n}\n\nBuffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n return ieee754.read(this, offset, true, 23, 4)\n}\n\nBuffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 4, this.length)\n return ieee754.read(this, offset, false, 23, 4)\n}\n\nBuffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 8, this.length)\n return ieee754.read(this, offset, true, 52, 8)\n}\n\nBuffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {\n if (!noAssert) checkOffset(offset, 8, this.length)\n return ieee754.read(this, offset, false, 52, 8)\n}\n\nfunction checkInt (buf, value, offset, ext, max, min) {\n if (!Buffer.isBuffer(buf)) throw new TypeError('\"buffer\" argument must be a Buffer instance')\n if (value > max || value < min) throw new RangeError('\"value\" argument is out of bounds')\n if (offset + ext > buf.length) throw new RangeError('Index out of range')\n}\n\nBuffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) {\n var maxBytes = Math.pow(2, 8 * byteLength) - 1\n checkInt(this, value, offset, byteLength, maxBytes, 0)\n }\n\n var mul = 1\n var i = 0\n this[offset] = value & 0xFF\n while (++i < byteLength && (mul *= 0x100)) {\n this[offset + i] = (value / mul) & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset | 0\n byteLength = byteLength | 0\n if (!noAssert) {\n var maxBytes = Math.pow(2, 8 * byteLength) - 1\n checkInt(this, value, offset, byteLength, maxBytes, 0)\n }\n\n var i = byteLength - 1\n var mul = 1\n this[offset + i] = value & 0xFF\n while (--i >= 0 && (mul *= 0x100)) {\n this[offset + i] = (value / mul) & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)\n if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)\n this[offset] = (value & 0xff)\n return offset + 1\n}\n\nfunction objectWriteUInt16 (buf, value, offset, littleEndian) {\n if (value < 0) value = 0xffff + value + 1\n for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; ++i) {\n buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>\n (littleEndian ? i : 1 - i) * 8\n }\n}\n\nBuffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n } else {\n objectWriteUInt16(this, value, offset, true)\n }\n return offset + 2\n}\n\nBuffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value >>> 8)\n this[offset + 1] = (value & 0xff)\n } else {\n objectWriteUInt16(this, value, offset, false)\n }\n return offset + 2\n}\n\nfunction objectWriteUInt32 (buf, value, offset, littleEndian) {\n if (value < 0) value = 0xffffffff + value + 1\n for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; ++i) {\n buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff\n }\n}\n\nBuffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset + 3] = (value >>> 24)\n this[offset + 2] = (value >>> 16)\n this[offset + 1] = (value >>> 8)\n this[offset] = (value & 0xff)\n } else {\n objectWriteUInt32(this, value, offset, true)\n }\n return offset + 4\n}\n\nBuffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value >>> 24)\n this[offset + 1] = (value >>> 16)\n this[offset + 2] = (value >>> 8)\n this[offset + 3] = (value & 0xff)\n } else {\n objectWriteUInt32(this, value, offset, false)\n }\n return offset + 4\n}\n\nBuffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) {\n var limit = Math.pow(2, 8 * byteLength - 1)\n\n checkInt(this, value, offset, byteLength, limit - 1, -limit)\n }\n\n var i = 0\n var mul = 1\n var sub = 0\n this[offset] = value & 0xFF\n while (++i < byteLength && (mul *= 0x100)) {\n if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {\n sub = 1\n }\n this[offset + i] = ((value / mul) >> 0) - sub & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) {\n var limit = Math.pow(2, 8 * byteLength - 1)\n\n checkInt(this, value, offset, byteLength, limit - 1, -limit)\n }\n\n var i = byteLength - 1\n var mul = 1\n var sub = 0\n this[offset + i] = value & 0xFF\n while (--i >= 0 && (mul *= 0x100)) {\n if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {\n sub = 1\n }\n this[offset + i] = ((value / mul) >> 0) - sub & 0xFF\n }\n\n return offset + byteLength\n}\n\nBuffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)\n if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)\n if (value < 0) value = 0xff + value + 1\n this[offset] = (value & 0xff)\n return offset + 1\n}\n\nBuffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n } else {\n objectWriteUInt16(this, value, offset, true)\n }\n return offset + 2\n}\n\nBuffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value >>> 8)\n this[offset + 1] = (value & 0xff)\n } else {\n objectWriteUInt16(this, value, offset, false)\n }\n return offset + 2\n}\n\nBuffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value & 0xff)\n this[offset + 1] = (value >>> 8)\n this[offset + 2] = (value >>> 16)\n this[offset + 3] = (value >>> 24)\n } else {\n objectWriteUInt32(this, value, offset, true)\n }\n return offset + 4\n}\n\nBuffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {\n value = +value\n offset = offset | 0\n if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)\n if (value < 0) value = 0xffffffff + value + 1\n if (Buffer.TYPED_ARRAY_SUPPORT) {\n this[offset] = (value >>> 24)\n this[offset + 1] = (value >>> 16)\n this[offset + 2] = (value >>> 8)\n this[offset + 3] = (value & 0xff)\n } else {\n objectWriteUInt32(this, value, offset, false)\n }\n return offset + 4\n}\n\nfunction checkIEEE754 (buf, value, offset, ext, max, min) {\n if (offset + ext > buf.length) throw new RangeError('Index out of range')\n if (offset < 0) throw new RangeError('Index out of range')\n}\n\nfunction writeFloat (buf, value, offset, littleEndian, noAssert) {\n if (!noAssert) {\n checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)\n }\n ieee754.write(buf, value, offset, littleEndian, 23, 4)\n return offset + 4\n}\n\nBuffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {\n return writeFloat(this, value, offset, true, noAssert)\n}\n\nBuffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {\n return writeFloat(this, value, offset, false, noAssert)\n}\n\nfunction writeDouble (buf, value, offset, littleEndian, noAssert) {\n if (!noAssert) {\n checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)\n }\n ieee754.write(buf, value, offset, littleEndian, 52, 8)\n return offset + 8\n}\n\nBuffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {\n return writeDouble(this, value, offset, true, noAssert)\n}\n\nBuffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {\n return writeDouble(this, value, offset, false, noAssert)\n}\n\n// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)\nBuffer.prototype.copy = function copy (target, targetStart, start, end) {\n if (!start) start = 0\n if (!end && end !== 0) end = this.length\n if (targetStart >= target.length) targetStart = target.length\n if (!targetStart) targetStart = 0\n if (end > 0 && end < start) end = start\n\n // Copy 0 bytes; we're done\n if (end === start) return 0\n if (target.length === 0 || this.length === 0) return 0\n\n // Fatal error conditions\n if (targetStart < 0) {\n throw new RangeError('targetStart out of bounds')\n }\n if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds')\n if (end < 0) throw new RangeError('sourceEnd out of bounds')\n\n // Are we oob?\n if (end > this.length) end = this.length\n if (target.length - targetStart < end - start) {\n end = target.length - targetStart + start\n }\n\n var len = end - start\n var i\n\n if (this === target && start < targetStart && targetStart < end) {\n // descending copy from end\n for (i = len - 1; i >= 0; --i) {\n target[i + targetStart] = this[i + start]\n }\n } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {\n // ascending copy from start\n for (i = 0; i < len; ++i) {\n target[i + targetStart] = this[i + start]\n }\n } else {\n Uint8Array.prototype.set.call(\n target,\n this.subarray(start, start + len),\n targetStart\n )\n }\n\n return len\n}\n\n// Usage:\n// buffer.fill(number[, offset[, end]])\n// buffer.fill(buffer[, offset[, end]])\n// buffer.fill(string[, offset[, end]][, encoding])\nBuffer.prototype.fill = function fill (val, start, end, encoding) {\n // Handle string cases:\n if (typeof val === 'string') {\n if (typeof start === 'string') {\n encoding = start\n start = 0\n end = this.length\n } else if (typeof end === 'string') {\n encoding = end\n end = this.length\n }\n if (val.length === 1) {\n var code = val.charCodeAt(0)\n if (code < 256) {\n val = code\n }\n }\n if (encoding !== undefined && typeof encoding !== 'string') {\n throw new TypeError('encoding must be a string')\n }\n if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {\n throw new TypeError('Unknown encoding: ' + encoding)\n }\n } else if (typeof val === 'number') {\n val = val & 255\n }\n\n // Invalid ranges are not set to a default, so can range check early.\n if (start < 0 || this.length < start || this.length < end) {\n throw new RangeError('Out of range index')\n }\n\n if (end <= start) {\n return this\n }\n\n start = start >>> 0\n end = end === undefined ? this.length : end >>> 0\n\n if (!val) val = 0\n\n var i\n if (typeof val === 'number') {\n for (i = start; i < end; ++i) {\n this[i] = val\n }\n } else {\n var bytes = Buffer.isBuffer(val)\n ? val\n : utf8ToBytes(new Buffer(val, encoding).toString())\n var len = bytes.length\n for (i = 0; i < end - start; ++i) {\n this[i + start] = bytes[i % len]\n }\n }\n\n return this\n}\n\n// HELPER FUNCTIONS\n// ================\n\nvar INVALID_BASE64_RE = /[^+\\/0-9A-Za-z-_]/g\n\nfunction base64clean (str) {\n // Node strips out invalid characters like \\n and \\t from the string, base64-js does not\n str = stringtrim(str).replace(INVALID_BASE64_RE, '')\n // Node converts strings with length < 2 to ''\n if (str.length < 2) return ''\n // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not\n while (str.length % 4 !== 0) {\n str = str + '='\n }\n return str\n}\n\nfunction stringtrim (str) {\n if (str.trim) return str.trim()\n return str.replace(/^\\s+|\\s+$/g, '')\n}\n\nfunction toHex (n) {\n if (n < 16) return '0' + n.toString(16)\n return n.toString(16)\n}\n\nfunction utf8ToBytes (string, units) {\n units = units || Infinity\n var codePoint\n var length = string.length\n var leadSurrogate = null\n var bytes = []\n\n for (var i = 0; i < length; ++i) {\n codePoint = string.charCodeAt(i)\n\n // is surrogate component\n if (codePoint > 0xD7FF && codePoint < 0xE000) {\n // last char was a lead\n if (!leadSurrogate) {\n // no lead yet\n if (codePoint > 0xDBFF) {\n // unexpected trail\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n continue\n } else if (i + 1 === length) {\n // unpaired lead\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n continue\n }\n\n // valid lead\n leadSurrogate = codePoint\n\n continue\n }\n\n // 2 leads in a row\n if (codePoint < 0xDC00) {\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n leadSurrogate = codePoint\n continue\n }\n\n // valid surrogate pair\n codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000\n } else if (leadSurrogate) {\n // valid bmp char, but last char was a lead\n if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)\n }\n\n leadSurrogate = null\n\n // encode utf8\n if (codePoint < 0x80) {\n if ((units -= 1) < 0) break\n bytes.push(codePoint)\n } else if (codePoint < 0x800) {\n if ((units -= 2) < 0) break\n bytes.push(\n codePoint >> 0x6 | 0xC0,\n codePoint & 0x3F | 0x80\n )\n } else if (codePoint < 0x10000) {\n if ((units -= 3) < 0) break\n bytes.push(\n codePoint >> 0xC | 0xE0,\n codePoint >> 0x6 & 0x3F | 0x80,\n codePoint & 0x3F | 0x80\n )\n } else if (codePoint < 0x110000) {\n if ((units -= 4) < 0) break\n bytes.push(\n codePoint >> 0x12 | 0xF0,\n codePoint >> 0xC & 0x3F | 0x80,\n codePoint >> 0x6 & 0x3F | 0x80,\n codePoint & 0x3F | 0x80\n )\n } else {\n throw new Error('Invalid code point')\n }\n }\n\n return bytes\n}\n\nfunction asciiToBytes (str) {\n var byteArray = []\n for (var i = 0; i < str.length; ++i) {\n // Node's code seems to be doing this and not & 0x7F..\n byteArray.push(str.charCodeAt(i) & 0xFF)\n }\n return byteArray\n}\n\nfunction utf16leToBytes (str, units) {\n var c, hi, lo\n var byteArray = []\n for (var i = 0; i < str.length; ++i) {\n if ((units -= 2) < 0) break\n\n c = str.charCodeAt(i)\n hi = c >> 8\n lo = c % 256\n byteArray.push(lo)\n byteArray.push(hi)\n }\n\n return byteArray\n}\n\nfunction base64ToBytes (str) {\n return base64.toByteArray(base64clean(str))\n}\n\nfunction blitBuffer (src, dst, offset, length) {\n for (var i = 0; i < length; ++i) {\n if ((i + offset >= dst.length) || (i >= src.length)) break\n dst[i + offset] = src[i]\n }\n return i\n}\n\nfunction isnan (val) {\n return val !== val // eslint-disable-line no-self-compare\n}\n","'use strict'\n\nexports.byteLength = byteLength\nexports.toByteArray = toByteArray\nexports.fromByteArray = fromByteArray\n\nvar lookup = []\nvar revLookup = []\nvar Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array\n\nvar code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\nfor (var i = 0, len = code.length; i < len; ++i) {\n lookup[i] = code[i]\n revLookup[code.charCodeAt(i)] = i\n}\n\n// Support decoding URL-safe base64 strings, as Node.js does.\n// See: https://en.wikipedia.org/wiki/Base64#URL_applications\nrevLookup['-'.charCodeAt(0)] = 62\nrevLookup['_'.charCodeAt(0)] = 63\n\nfunction getLens (b64) {\n var len = b64.length\n\n if (len % 4 > 0) {\n throw new Error('Invalid string. Length must be a multiple of 4')\n }\n\n // Trim off extra bytes after placeholder bytes are found\n // See: https://github.com/beatgammit/base64-js/issues/42\n var validLen = b64.indexOf('=')\n if (validLen === -1) validLen = len\n\n var placeHoldersLen = validLen === len\n ? 0\n : 4 - (validLen % 4)\n\n return [validLen, placeHoldersLen]\n}\n\n// base64 is 4/3 + up to two characters of the original data\nfunction byteLength (b64) {\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction _byteLength (b64, validLen, placeHoldersLen) {\n return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen\n}\n\nfunction toByteArray (b64) {\n var tmp\n var lens = getLens(b64)\n var validLen = lens[0]\n var placeHoldersLen = lens[1]\n\n var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))\n\n var curByte = 0\n\n // if there are placeholders, only get up to the last complete 4 chars\n var len = placeHoldersLen > 0\n ? validLen - 4\n : validLen\n\n var i\n for (i = 0; i < len; i += 4) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 18) |\n (revLookup[b64.charCodeAt(i + 1)] << 12) |\n (revLookup[b64.charCodeAt(i + 2)] << 6) |\n revLookup[b64.charCodeAt(i + 3)]\n arr[curByte++] = (tmp >> 16) & 0xFF\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 2) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 2) |\n (revLookup[b64.charCodeAt(i + 1)] >> 4)\n arr[curByte++] = tmp & 0xFF\n }\n\n if (placeHoldersLen === 1) {\n tmp =\n (revLookup[b64.charCodeAt(i)] << 10) |\n (revLookup[b64.charCodeAt(i + 1)] << 4) |\n (revLookup[b64.charCodeAt(i + 2)] >> 2)\n arr[curByte++] = (tmp >> 8) & 0xFF\n arr[curByte++] = tmp & 0xFF\n }\n\n return arr\n}\n\nfunction tripletToBase64 (num) {\n return lookup[num >> 18 & 0x3F] +\n lookup[num >> 12 & 0x3F] +\n lookup[num >> 6 & 0x3F] +\n lookup[num & 0x3F]\n}\n\nfunction encodeChunk (uint8, start, end) {\n var tmp\n var output = []\n for (var i = start; i < end; i += 3) {\n tmp =\n ((uint8[i] << 16) & 0xFF0000) +\n ((uint8[i + 1] << 8) & 0xFF00) +\n (uint8[i + 2] & 0xFF)\n output.push(tripletToBase64(tmp))\n }\n return output.join('')\n}\n\nfunction fromByteArray (uint8) {\n var tmp\n var len = uint8.length\n var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes\n var parts = []\n var maxChunkLength = 16383 // must be multiple of 3\n\n // go through the array every three bytes, we'll deal with trailing stuff later\n for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {\n parts.push(encodeChunk(\n uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)\n ))\n }\n\n // pad the end with zeros, but make sure to not forget the extra bytes\n if (extraBytes === 1) {\n tmp = uint8[len - 1]\n parts.push(\n lookup[tmp >> 2] +\n lookup[(tmp << 4) & 0x3F] +\n '=='\n )\n } else if (extraBytes === 2) {\n tmp = (uint8[len - 2] << 8) + uint8[len - 1]\n parts.push(\n lookup[tmp >> 10] +\n lookup[(tmp >> 4) & 0x3F] +\n lookup[(tmp << 2) & 0x3F] +\n '='\n )\n }\n\n return parts.join('')\n}\n","exports.read = function (buffer, offset, isLE, mLen, nBytes) {\n var e, m\n var eLen = (nBytes * 8) - mLen - 1\n var eMax = (1 << eLen) - 1\n var eBias = eMax >> 1\n var nBits = -7\n var i = isLE ? (nBytes - 1) : 0\n var d = isLE ? -1 : 1\n var s = buffer[offset + i]\n\n i += d\n\n e = s & ((1 << (-nBits)) - 1)\n s >>= (-nBits)\n nBits += eLen\n for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {}\n\n m = e & ((1 << (-nBits)) - 1)\n e >>= (-nBits)\n nBits += mLen\n for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {}\n\n if (e === 0) {\n e = 1 - eBias\n } else if (e === eMax) {\n return m ? NaN : ((s ? -1 : 1) * Infinity)\n } else {\n m = m + Math.pow(2, mLen)\n e = e - eBias\n }\n return (s ? -1 : 1) * m * Math.pow(2, e - mLen)\n}\n\nexports.write = function (buffer, value, offset, isLE, mLen, nBytes) {\n var e, m, c\n var eLen = (nBytes * 8) - mLen - 1\n var eMax = (1 << eLen) - 1\n var eBias = eMax >> 1\n var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)\n var i = isLE ? 0 : (nBytes - 1)\n var d = isLE ? 1 : -1\n var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0\n\n value = Math.abs(value)\n\n if (isNaN(value) || value === Infinity) {\n m = isNaN(value) ? 1 : 0\n e = eMax\n } else {\n e = Math.floor(Math.log(value) / Math.LN2)\n if (value * (c = Math.pow(2, -e)) < 1) {\n e--\n c *= 2\n }\n if (e + eBias >= 1) {\n value += rt / c\n } else {\n value += rt * Math.pow(2, 1 - eBias)\n }\n if (value * c >= 2) {\n e++\n c /= 2\n }\n\n if (e + eBias >= eMax) {\n m = 0\n e = eMax\n } else if (e + eBias >= 1) {\n m = ((value * c) - 1) * Math.pow(2, mLen)\n e = e + eBias\n } else {\n m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)\n e = 0\n }\n }\n\n for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}\n\n e = (e << mLen) | m\n eLen += mLen\n for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}\n\n buffer[offset + i - d] |= s * 128\n}\n","var toString = {}.toString;\n\nmodule.exports = Array.isArray || function (arr) {\n return toString.call(arr) == '[object Array]';\n};\n","'use strict';\n\nvar AxiosError = require('./AxiosError');\n\n/**\n * Resolve or reject a Promise based on response status.\n *\n * @param {Function} resolve A function that resolves the promise.\n * @param {Function} reject A function that rejects the promise.\n * @param {object} response The response.\n */\nmodule.exports = function settle(resolve, reject, response) {\n var validateStatus = response.config.validateStatus;\n if (!response.status || !validateStatus || validateStatus(response.status)) {\n resolve(response);\n } else {\n reject(new AxiosError(\n 'Request failed with status code ' + response.status,\n [AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4],\n response.config,\n response.request,\n response\n ));\n }\n};\n","'use strict';\n\nvar utils = require('./../utils');\n\nmodule.exports = (\n utils.isStandardBrowserEnv() ?\n\n // Standard browser envs support document.cookie\n (function standardBrowserEnv() {\n return {\n write: function write(name, value, expires, path, domain, secure) {\n var cookie = [];\n cookie.push(name + '=' + encodeURIComponent(value));\n\n if (utils.isNumber(expires)) {\n cookie.push('expires=' + new Date(expires).toGMTString());\n }\n\n if (utils.isString(path)) {\n cookie.push('path=' + path);\n }\n\n if (utils.isString(domain)) {\n cookie.push('domain=' + domain);\n }\n\n if (secure === true) {\n cookie.push('secure');\n }\n\n document.cookie = cookie.join('; ');\n },\n\n read: function read(name) {\n var match = document.cookie.match(new RegExp('(^|;\\\\s*)(' + name + ')=([^;]*)'));\n return (match ? decodeURIComponent(match[3]) : null);\n },\n\n remove: function remove(name) {\n this.write(name, '', Date.now() - 86400000);\n }\n };\n })() :\n\n // Non standard browser env (web workers, react-native) lack needed support.\n (function nonStandardBrowserEnv() {\n return {\n write: function write() {},\n read: function read() { return null; },\n remove: function remove() {}\n };\n })()\n);\n","'use strict';\n\n/**\n * Determines whether the specified URL is absolute\n *\n * @param {string} url The URL to test\n * @returns {boolean} True if the specified URL is absolute, otherwise false\n */\nmodule.exports = function isAbsoluteURL(url) {\n // A URL is considered absolute if it begins with \"://\" or \"//\" (protocol-relative URL).\n // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed\n // by any combination of letters, digits, plus, period, or hyphen.\n return /^([a-z][a-z\\d+\\-.]*:)?\\/\\//i.test(url);\n};\n","'use strict';\n\n/**\n * Creates a new URL by combining the specified URLs\n *\n * @param {string} baseURL The base URL\n * @param {string} relativeURL The relative URL\n * @returns {string} The combined URL\n */\nmodule.exports = function combineURLs(baseURL, relativeURL) {\n return relativeURL\n ? baseURL.replace(/\\/+$/, '') + '/' + relativeURL.replace(/^\\/+/, '')\n : baseURL;\n};\n","'use strict';\n\nvar utils = require('./../utils');\n\n// Headers whose duplicates are ignored by node\n// c.f. https://nodejs.org/api/http.html#http_message_headers\nvar ignoreDuplicateOf = [\n 'age', 'authorization', 'content-length', 'content-type', 'etag',\n 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since',\n 'last-modified', 'location', 'max-forwards', 'proxy-authorization',\n 'referer', 'retry-after', 'user-agent'\n];\n\n/**\n * Parse headers into an object\n *\n * ```\n * Date: Wed, 27 Aug 2014 08:58:49 GMT\n * Content-Type: application/json\n * Connection: keep-alive\n * Transfer-Encoding: chunked\n * ```\n *\n * @param {String} headers Headers needing to be parsed\n * @returns {Object} Headers parsed into an object\n */\nmodule.exports = function parseHeaders(headers) {\n var parsed = {};\n var key;\n var val;\n var i;\n\n if (!headers) { return parsed; }\n\n utils.forEach(headers.split('\\n'), function parser(line) {\n i = line.indexOf(':');\n key = utils.trim(line.substr(0, i)).toLowerCase();\n val = utils.trim(line.substr(i + 1));\n\n if (key) {\n if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) {\n return;\n }\n if (key === 'set-cookie') {\n parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]);\n } else {\n parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;\n }\n }\n });\n\n return parsed;\n};\n","'use strict';\n\nvar utils = require('./../utils');\n\nmodule.exports = (\n utils.isStandardBrowserEnv() ?\n\n // Standard browser envs have full support of the APIs needed to test\n // whether the request URL is of the same origin as current location.\n (function standardBrowserEnv() {\n var msie = /(msie|trident)/i.test(navigator.userAgent);\n var urlParsingNode = document.createElement('a');\n var originURL;\n\n /**\n * Parse a URL to discover it's components\n *\n * @param {String} url The URL to be parsed\n * @returns {Object}\n */\n function resolveURL(url) {\n var href = url;\n\n if (msie) {\n // IE needs attribute set twice to normalize properties\n urlParsingNode.setAttribute('href', href);\n href = urlParsingNode.href;\n }\n\n urlParsingNode.setAttribute('href', href);\n\n // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils\n return {\n href: urlParsingNode.href,\n protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',\n host: urlParsingNode.host,\n search: urlParsingNode.search ? urlParsingNode.search.replace(/^\\?/, '') : '',\n hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',\n hostname: urlParsingNode.hostname,\n port: urlParsingNode.port,\n pathname: (urlParsingNode.pathname.charAt(0) === '/') ?\n urlParsingNode.pathname :\n '/' + urlParsingNode.pathname\n };\n }\n\n originURL = resolveURL(window.location.href);\n\n /**\n * Determine if a URL shares the same origin as the current location\n *\n * @param {String} requestURL The URL to test\n * @returns {boolean} True if URL shares the same origin, otherwise false\n */\n return function isURLSameOrigin(requestURL) {\n var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL;\n return (parsed.protocol === originURL.protocol &&\n parsed.host === originURL.host);\n };\n })() :\n\n // Non standard browser envs (web workers, react-native) lack needed support.\n (function nonStandardBrowserEnv() {\n return function isURLSameOrigin() {\n return true;\n };\n })()\n);\n","'use strict';\n\nmodule.exports = function parseProtocol(url) {\n var match = /^([-+\\w]{1,25})(:?\\/\\/|:)/.exec(url);\n return match && match[1] || '';\n};\n","// eslint-disable-next-line strict\nmodule.exports = null;\n","'use strict';\n\nvar VERSION = require('../env/data').version;\nvar AxiosError = require('../core/AxiosError');\n\nvar validators = {};\n\n// eslint-disable-next-line func-names\n['object', 'boolean', 'number', 'function', 'string', 'symbol'].forEach(function(type, i) {\n validators[type] = function validator(thing) {\n return typeof thing === type || 'a' + (i < 1 ? 'n ' : ' ') + type;\n };\n});\n\nvar deprecatedWarnings = {};\n\n/**\n * Transitional option validator\n * @param {function|boolean?} validator - set to false if the transitional option has been removed\n * @param {string?} version - deprecated version / removed since version\n * @param {string?} message - some message with additional info\n * @returns {function}\n */\nvalidators.transitional = function transitional(validator, version, message) {\n function formatMessage(opt, desc) {\n return '[Axios v' + VERSION + '] Transitional option \\'' + opt + '\\'' + desc + (message ? '. ' + message : '');\n }\n\n // eslint-disable-next-line func-names\n return function(value, opt, opts) {\n if (validator === false) {\n throw new AxiosError(\n formatMessage(opt, ' has been removed' + (version ? ' in ' + version : '')),\n AxiosError.ERR_DEPRECATED\n );\n }\n\n if (version && !deprecatedWarnings[opt]) {\n deprecatedWarnings[opt] = true;\n // eslint-disable-next-line no-console\n console.warn(\n formatMessage(\n opt,\n ' has been deprecated since v' + version + ' and will be removed in the near future'\n )\n );\n }\n\n return validator ? validator(value, opt, opts) : true;\n };\n};\n\n/**\n * Assert object's properties type\n * @param {object} options\n * @param {object} schema\n * @param {boolean?} allowUnknown\n */\n\nfunction assertOptions(options, schema, allowUnknown) {\n if (typeof options !== 'object') {\n throw new AxiosError('options must be an object', AxiosError.ERR_BAD_OPTION_VALUE);\n }\n var keys = Object.keys(options);\n var i = keys.length;\n while (i-- > 0) {\n var opt = keys[i];\n var validator = schema[opt];\n if (validator) {\n var value = options[opt];\n var result = value === undefined || validator(value, opt, options);\n if (result !== true) {\n throw new AxiosError('option ' + opt + ' must be ' + result, AxiosError.ERR_BAD_OPTION_VALUE);\n }\n continue;\n }\n if (allowUnknown !== true) {\n throw new AxiosError('Unknown option ' + opt, AxiosError.ERR_BAD_OPTION);\n }\n }\n}\n\nmodule.exports = {\n assertOptions: assertOptions,\n validators: validators\n};\n","'use strict';\n\nvar CanceledError = require('./CanceledError');\n\n/**\n * A `CancelToken` is an object that can be used to request cancellation of an operation.\n *\n * @class\n * @param {Function} executor The executor function.\n */\nfunction CancelToken(executor) {\n if (typeof executor !== 'function') {\n throw new TypeError('executor must be a function.');\n }\n\n var resolvePromise;\n\n this.promise = new Promise(function promiseExecutor(resolve) {\n resolvePromise = resolve;\n });\n\n var token = this;\n\n // eslint-disable-next-line func-names\n this.promise.then(function(cancel) {\n if (!token._listeners) return;\n\n var i;\n var l = token._listeners.length;\n\n for (i = 0; i < l; i++) {\n token._listeners[i](cancel);\n }\n token._listeners = null;\n });\n\n // eslint-disable-next-line func-names\n this.promise.then = function(onfulfilled) {\n var _resolve;\n // eslint-disable-next-line func-names\n var promise = new Promise(function(resolve) {\n token.subscribe(resolve);\n _resolve = resolve;\n }).then(onfulfilled);\n\n promise.cancel = function reject() {\n token.unsubscribe(_resolve);\n };\n\n return promise;\n };\n\n executor(function cancel(message) {\n if (token.reason) {\n // Cancellation has already been requested\n return;\n }\n\n token.reason = new CanceledError(message);\n resolvePromise(token.reason);\n });\n}\n\n/**\n * Throws a `CanceledError` if cancellation has been requested.\n */\nCancelToken.prototype.throwIfRequested = function throwIfRequested() {\n if (this.reason) {\n throw this.reason;\n }\n};\n\n/**\n * Subscribe to the cancel signal\n */\n\nCancelToken.prototype.subscribe = function subscribe(listener) {\n if (this.reason) {\n listener(this.reason);\n return;\n }\n\n if (this._listeners) {\n this._listeners.push(listener);\n } else {\n this._listeners = [listener];\n }\n};\n\n/**\n * Unsubscribe from the cancel signal\n */\n\nCancelToken.prototype.unsubscribe = function unsubscribe(listener) {\n if (!this._listeners) {\n return;\n }\n var index = this._listeners.indexOf(listener);\n if (index !== -1) {\n this._listeners.splice(index, 1);\n }\n};\n\n/**\n * Returns an object that contains a new `CancelToken` and a function that, when called,\n * cancels the `CancelToken`.\n */\nCancelToken.source = function source() {\n var cancel;\n var token = new CancelToken(function executor(c) {\n cancel = c;\n });\n return {\n token: token,\n cancel: cancel\n };\n};\n\nmodule.exports = CancelToken;\n","'use strict';\n\n/**\n * Syntactic sugar for invoking a function and expanding an array for arguments.\n *\n * Common use case would be to use `Function.prototype.apply`.\n *\n * ```js\n * function f(x, y, z) {}\n * var args = [1, 2, 3];\n * f.apply(null, args);\n * ```\n *\n * With `spread` this example can be re-written.\n *\n * ```js\n * spread(function(x, y, z) {})([1, 2, 3]);\n * ```\n *\n * @param {Function} callback\n * @returns {Function}\n */\nmodule.exports = function spread(callback) {\n return function wrap(arr) {\n return callback.apply(null, arr);\n };\n};\n","'use strict';\n\nvar utils = require('./../utils');\n\n/**\n * Determines whether the payload is an error thrown by Axios\n *\n * @param {*} payload The value to test\n * @returns {boolean} True if the payload is an error thrown by Axios, otherwise false\n */\nmodule.exports = function isAxiosError(payload) {\n return utils.isObject(payload) && (payload.isAxiosError === true);\n};\n","'use strict';\n\nvar keysShim;\nif (!Object.keys) {\n\t// modified from https://github.com/es-shims/es5-shim\n\tvar has = Object.prototype.hasOwnProperty;\n\tvar toStr = Object.prototype.toString;\n\tvar isArgs = require('./isArguments'); // eslint-disable-line global-require\n\tvar isEnumerable = Object.prototype.propertyIsEnumerable;\n\tvar hasDontEnumBug = !isEnumerable.call({ toString: null }, 'toString');\n\tvar hasProtoEnumBug = isEnumerable.call(function () {}, 'prototype');\n\tvar dontEnums = [\n\t\t'toString',\n\t\t'toLocaleString',\n\t\t'valueOf',\n\t\t'hasOwnProperty',\n\t\t'isPrototypeOf',\n\t\t'propertyIsEnumerable',\n\t\t'constructor'\n\t];\n\tvar equalsConstructorPrototype = function (o) {\n\t\tvar ctor = o.constructor;\n\t\treturn ctor && ctor.prototype === o;\n\t};\n\tvar excludedKeys = {\n\t\t$applicationCache: true,\n\t\t$console: true,\n\t\t$external: true,\n\t\t$frame: true,\n\t\t$frameElement: true,\n\t\t$frames: true,\n\t\t$innerHeight: true,\n\t\t$innerWidth: true,\n\t\t$onmozfullscreenchange: true,\n\t\t$onmozfullscreenerror: true,\n\t\t$outerHeight: true,\n\t\t$outerWidth: true,\n\t\t$pageXOffset: true,\n\t\t$pageYOffset: true,\n\t\t$parent: true,\n\t\t$scrollLeft: true,\n\t\t$scrollTop: true,\n\t\t$scrollX: true,\n\t\t$scrollY: true,\n\t\t$self: true,\n\t\t$webkitIndexedDB: true,\n\t\t$webkitStorageInfo: true,\n\t\t$window: true\n\t};\n\tvar hasAutomationEqualityBug = (function () {\n\t\t/* global window */\n\t\tif (typeof window === 'undefined') { return false; }\n\t\tfor (var k in window) {\n\t\t\ttry {\n\t\t\t\tif (!excludedKeys['$' + k] && has.call(window, k) && window[k] !== null && typeof window[k] === 'object') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tequalsConstructorPrototype(window[k]);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}());\n\tvar equalsConstructorPrototypeIfNotBuggy = function (o) {\n\t\t/* global window */\n\t\tif (typeof window === 'undefined' || !hasAutomationEqualityBug) {\n\t\t\treturn equalsConstructorPrototype(o);\n\t\t}\n\t\ttry {\n\t\t\treturn equalsConstructorPrototype(o);\n\t\t} catch (e) {\n\t\t\treturn false;\n\t\t}\n\t};\n\n\tkeysShim = function keys(object) {\n\t\tvar isObject = object !== null && typeof object === 'object';\n\t\tvar isFunction = toStr.call(object) === '[object Function]';\n\t\tvar isArguments = isArgs(object);\n\t\tvar isString = isObject && toStr.call(object) === '[object String]';\n\t\tvar theKeys = [];\n\n\t\tif (!isObject && !isFunction && !isArguments) {\n\t\t\tthrow new TypeError('Object.keys called on a non-object');\n\t\t}\n\n\t\tvar skipProto = hasProtoEnumBug && isFunction;\n\t\tif (isString && object.length > 0 && !has.call(object, 0)) {\n\t\t\tfor (var i = 0; i < object.length; ++i) {\n\t\t\t\ttheKeys.push(String(i));\n\t\t\t}\n\t\t}\n\n\t\tif (isArguments && object.length > 0) {\n\t\t\tfor (var j = 0; j < object.length; ++j) {\n\t\t\t\ttheKeys.push(String(j));\n\t\t\t}\n\t\t} else {\n\t\t\tfor (var name in object) {\n\t\t\t\tif (!(skipProto && name === 'prototype') && has.call(object, name)) {\n\t\t\t\t\ttheKeys.push(String(name));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (hasDontEnumBug) {\n\t\t\tvar skipConstructor = equalsConstructorPrototypeIfNotBuggy(object);\n\n\t\t\tfor (var k = 0; k < dontEnums.length; ++k) {\n\t\t\t\tif (!(skipConstructor && dontEnums[k] === 'constructor') && has.call(object, dontEnums[k])) {\n\t\t\t\t\ttheKeys.push(dontEnums[k]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn theKeys;\n\t};\n}\nmodule.exports = keysShim;\n","'use strict';\n\nvar hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';\nvar toStr = Object.prototype.toString;\n\nvar isStandardArguments = function isArguments(value) {\n\tif (hasToStringTag && value && typeof value === 'object' && Symbol.toStringTag in value) {\n\t\treturn false;\n\t}\n\treturn toStr.call(value) === '[object Arguments]';\n};\n\nvar isLegacyArguments = function isArguments(value) {\n\tif (isStandardArguments(value)) {\n\t\treturn true;\n\t}\n\treturn value !== null &&\n\t\ttypeof value === 'object' &&\n\t\ttypeof value.length === 'number' &&\n\t\tvalue.length >= 0 &&\n\t\ttoStr.call(value) !== '[object Array]' &&\n\t\ttoStr.call(value.callee) === '[object Function]';\n};\n\nvar supportsStandardArguments = (function () {\n\treturn isStandardArguments(arguments);\n}());\n\nisStandardArguments.isLegacyArguments = isLegacyArguments; // for tests\n\nmodule.exports = supportsStandardArguments ? isStandardArguments : isLegacyArguments;\n","\"use strict\";\n\n/* https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.is */\n\nvar NumberIsNaN = function (value) {\n\treturn value !== value;\n};\n\nmodule.exports = function is(a, b) {\n\tif (a === 0 && b === 0) {\n\t\treturn 1 / a === 1 / b;\n\t} else if (a === b) {\n\t\treturn true;\n\t} else if (NumberIsNaN(a) && NumberIsNaN(b)) {\n\t\treturn true;\n\t}\n\treturn false;\n};\n\n","'use strict';\n\nvar has = require('has');\nvar regexExec = RegExp.prototype.exec;\nvar gOPD = Object.getOwnPropertyDescriptor;\n\nvar tryRegexExecCall = function tryRegexExec(value) {\n\ttry {\n\t\tvar lastIndex = value.lastIndex;\n\t\tvalue.lastIndex = 0;\n\n\t\tregexExec.call(value);\n\t\treturn true;\n\t} catch (e) {\n\t\treturn false;\n\t} finally {\n\t\tvalue.lastIndex = lastIndex;\n\t}\n};\nvar toStr = Object.prototype.toString;\nvar regexClass = '[object RegExp]';\nvar hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';\n\nmodule.exports = function isRegex(value) {\n\tif (!value || typeof value !== 'object') {\n\t\treturn false;\n\t}\n\tif (!hasToStringTag) {\n\t\treturn toStr.call(value) === regexClass;\n\t}\n\n\tvar descriptor = gOPD(value, 'lastIndex');\n\tvar hasLastIndexDataProperty = descriptor && has(descriptor, 'value');\n\tif (!hasLastIndexDataProperty) {\n\t\treturn false;\n\t}\n\n\treturn tryRegexExecCall(value);\n};\n","'use strict';\n\n/* eslint no-invalid-this: 1 */\n\nvar ERROR_MESSAGE = 'Function.prototype.bind called on incompatible ';\nvar slice = Array.prototype.slice;\nvar toStr = Object.prototype.toString;\nvar funcType = '[object Function]';\n\nmodule.exports = function bind(that) {\n var target = this;\n if (typeof target !== 'function' || toStr.call(target) !== funcType) {\n throw new TypeError(ERROR_MESSAGE + target);\n }\n var args = slice.call(arguments, 1);\n\n var bound;\n var binder = function () {\n if (this instanceof bound) {\n var result = target.apply(\n this,\n args.concat(slice.call(arguments))\n );\n if (Object(result) === result) {\n return result;\n }\n return this;\n } else {\n return target.apply(\n that,\n args.concat(slice.call(arguments))\n );\n }\n };\n\n var boundLength = Math.max(0, target.length - args.length);\n var boundArgs = [];\n for (var i = 0; i < boundLength; i++) {\n boundArgs.push('$' + i);\n }\n\n bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder);\n\n if (target.prototype) {\n var Empty = function Empty() {};\n Empty.prototype = target.prototype;\n bound.prototype = new Empty();\n Empty.prototype = null;\n }\n\n return bound;\n};\n","'use strict';\n\nvar define = require('define-properties');\n\nvar implementation = require('./implementation');\nvar getPolyfill = require('./polyfill');\nvar shim = require('./shim');\n\nvar flagsBound = Function.call.bind(implementation);\n\ndefine(flagsBound, {\n\tgetPolyfill: getPolyfill,\n\timplementation: implementation,\n\tshim: shim\n});\n\nmodule.exports = flagsBound;\n","'use strict';\n\nvar supportsDescriptors = require('define-properties').supportsDescriptors;\nvar getPolyfill = require('./polyfill');\nvar gOPD = Object.getOwnPropertyDescriptor;\nvar defineProperty = Object.defineProperty;\nvar TypeErr = TypeError;\nvar getProto = Object.getPrototypeOf;\nvar regex = /a/;\n\nmodule.exports = function shimFlags() {\n\tif (!supportsDescriptors || !getProto) {\n\t\tthrow new TypeErr('RegExp.prototype.flags requires a true ES5 environment that supports property descriptors');\n\t}\n\tvar polyfill = getPolyfill();\n\tvar proto = getProto(regex);\n\tvar descriptor = gOPD(proto, 'flags');\n\tif (!descriptor || descriptor.get !== polyfill) {\n\t\tdefineProperty(proto, 'flags', {\n\t\t\tconfigurable: true,\n\t\t\tenumerable: false,\n\t\t\tget: polyfill\n\t\t});\n\t}\n\treturn polyfill;\n};\n","'use strict';\n\nvar getDay = Date.prototype.getDay;\nvar tryDateObject = function tryDateObject(value) {\n\ttry {\n\t\tgetDay.call(value);\n\t\treturn true;\n\t} catch (e) {\n\t\treturn false;\n\t}\n};\n\nvar toStr = Object.prototype.toString;\nvar dateClass = '[object Date]';\nvar hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';\n\nmodule.exports = function isDateObject(value) {\n\tif (typeof value !== 'object' || value === null) { return false; }\n\treturn hasToStringTag ? tryDateObject(value) : toStr.call(value) === dateClass;\n};\n","'use strict';\n\nvar getSideChannel = require('side-channel');\nvar utils = require('./utils');\nvar formats = require('./formats');\nvar has = Object.prototype.hasOwnProperty;\n\nvar arrayPrefixGenerators = {\n brackets: function brackets(prefix) {\n return prefix + '[]';\n },\n comma: 'comma',\n indices: function indices(prefix, key) {\n return prefix + '[' + key + ']';\n },\n repeat: function repeat(prefix) {\n return prefix;\n }\n};\n\nvar isArray = Array.isArray;\nvar split = String.prototype.split;\nvar push = Array.prototype.push;\nvar pushToArray = function (arr, valueOrArray) {\n push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);\n};\n\nvar toISO = Date.prototype.toISOString;\n\nvar defaultFormat = formats['default'];\nvar defaults = {\n addQueryPrefix: false,\n allowDots: false,\n charset: 'utf-8',\n charsetSentinel: false,\n delimiter: '&',\n encode: true,\n encoder: utils.encode,\n encodeValuesOnly: false,\n format: defaultFormat,\n formatter: formats.formatters[defaultFormat],\n // deprecated\n indices: false,\n serializeDate: function serializeDate(date) {\n return toISO.call(date);\n },\n skipNulls: false,\n strictNullHandling: false\n};\n\nvar isNonNullishPrimitive = function isNonNullishPrimitive(v) {\n return typeof v === 'string'\n || typeof v === 'number'\n || typeof v === 'boolean'\n || typeof v === 'symbol'\n || typeof v === 'bigint';\n};\n\nvar sentinel = {};\n\nvar stringify = function stringify(\n object,\n prefix,\n generateArrayPrefix,\n commaRoundTrip,\n strictNullHandling,\n skipNulls,\n encoder,\n filter,\n sort,\n allowDots,\n serializeDate,\n format,\n formatter,\n encodeValuesOnly,\n charset,\n sideChannel\n) {\n var obj = object;\n\n var tmpSc = sideChannel;\n var step = 0;\n var findFlag = false;\n while ((tmpSc = tmpSc.get(sentinel)) !== void undefined && !findFlag) {\n // Where object last appeared in the ref tree\n var pos = tmpSc.get(object);\n step += 1;\n if (typeof pos !== 'undefined') {\n if (pos === step) {\n throw new RangeError('Cyclic object value');\n } else {\n findFlag = true; // Break while\n }\n }\n if (typeof tmpSc.get(sentinel) === 'undefined') {\n step = 0;\n }\n }\n\n if (typeof filter === 'function') {\n obj = filter(prefix, obj);\n } else if (obj instanceof Date) {\n obj = serializeDate(obj);\n } else if (generateArrayPrefix === 'comma' && isArray(obj)) {\n obj = utils.maybeMap(obj, function (value) {\n if (value instanceof Date) {\n return serializeDate(value);\n }\n return value;\n });\n }\n\n if (obj === null) {\n if (strictNullHandling) {\n return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset, 'key', format) : prefix;\n }\n\n obj = '';\n }\n\n if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {\n if (encoder) {\n var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key', format);\n if (generateArrayPrefix === 'comma' && encodeValuesOnly) {\n var valuesArray = split.call(String(obj), ',');\n var valuesJoined = '';\n for (var i = 0; i < valuesArray.length; ++i) {\n valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format));\n }\n return [formatter(keyValue) + (commaRoundTrip && isArray(obj) && valuesArray.length === 1 ? '[]' : '') + '=' + valuesJoined];\n }\n return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))];\n }\n return [formatter(prefix) + '=' + formatter(String(obj))];\n }\n\n var values = [];\n\n if (typeof obj === 'undefined') {\n return values;\n }\n\n var objKeys;\n if (generateArrayPrefix === 'comma' && isArray(obj)) {\n // we need to join elements in\n objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }];\n } else if (isArray(filter)) {\n objKeys = filter;\n } else {\n var keys = Object.keys(obj);\n objKeys = sort ? keys.sort(sort) : keys;\n }\n\n var adjustedPrefix = commaRoundTrip && isArray(obj) && obj.length === 1 ? prefix + '[]' : prefix;\n\n for (var j = 0; j < objKeys.length; ++j) {\n var key = objKeys[j];\n var value = typeof key === 'object' && typeof key.value !== 'undefined' ? key.value : obj[key];\n\n if (skipNulls && value === null) {\n continue;\n }\n\n var keyPrefix = isArray(obj)\n ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(adjustedPrefix, key) : adjustedPrefix\n : adjustedPrefix + (allowDots ? '.' + key : '[' + key + ']');\n\n sideChannel.set(object, step);\n var valueSideChannel = getSideChannel();\n valueSideChannel.set(sentinel, sideChannel);\n pushToArray(values, stringify(\n value,\n keyPrefix,\n generateArrayPrefix,\n commaRoundTrip,\n strictNullHandling,\n skipNulls,\n encoder,\n filter,\n sort,\n allowDots,\n serializeDate,\n format,\n formatter,\n encodeValuesOnly,\n charset,\n valueSideChannel\n ));\n }\n\n return values;\n};\n\nvar normalizeStringifyOptions = function normalizeStringifyOptions(opts) {\n if (!opts) {\n return defaults;\n }\n\n if (opts.encoder !== null && typeof opts.encoder !== 'undefined' && typeof opts.encoder !== 'function') {\n throw new TypeError('Encoder has to be a function.');\n }\n\n var charset = opts.charset || defaults.charset;\n if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {\n throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');\n }\n\n var format = formats['default'];\n if (typeof opts.format !== 'undefined') {\n if (!has.call(formats.formatters, opts.format)) {\n throw new TypeError('Unknown format option provided.');\n }\n format = opts.format;\n }\n var formatter = formats.formatters[format];\n\n var filter = defaults.filter;\n if (typeof opts.filter === 'function' || isArray(opts.filter)) {\n filter = opts.filter;\n }\n\n return {\n addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix,\n allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots,\n charset: charset,\n charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,\n delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter,\n encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode,\n encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder,\n encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly,\n filter: filter,\n format: format,\n formatter: formatter,\n serializeDate: typeof opts.serializeDate === 'function' ? opts.serializeDate : defaults.serializeDate,\n skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls,\n sort: typeof opts.sort === 'function' ? opts.sort : null,\n strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling\n };\n};\n\nmodule.exports = function (object, opts) {\n var obj = object;\n var options = normalizeStringifyOptions(opts);\n\n var objKeys;\n var filter;\n\n if (typeof options.filter === 'function') {\n filter = options.filter;\n obj = filter('', obj);\n } else if (isArray(options.filter)) {\n filter = options.filter;\n objKeys = filter;\n }\n\n var keys = [];\n\n if (typeof obj !== 'object' || obj === null) {\n return '';\n }\n\n var arrayFormat;\n if (opts && opts.arrayFormat in arrayPrefixGenerators) {\n arrayFormat = opts.arrayFormat;\n } else if (opts && 'indices' in opts) {\n arrayFormat = opts.indices ? 'indices' : 'repeat';\n } else {\n arrayFormat = 'indices';\n }\n\n var generateArrayPrefix = arrayPrefixGenerators[arrayFormat];\n if (opts && 'commaRoundTrip' in opts && typeof opts.commaRoundTrip !== 'boolean') {\n throw new TypeError('`commaRoundTrip` must be a boolean, or absent');\n }\n var commaRoundTrip = generateArrayPrefix === 'comma' && opts && opts.commaRoundTrip;\n\n if (!objKeys) {\n objKeys = Object.keys(obj);\n }\n\n if (options.sort) {\n objKeys.sort(options.sort);\n }\n\n var sideChannel = getSideChannel();\n for (var i = 0; i < objKeys.length; ++i) {\n var key = objKeys[i];\n\n if (options.skipNulls && obj[key] === null) {\n continue;\n }\n pushToArray(keys, stringify(\n obj[key],\n key,\n generateArrayPrefix,\n commaRoundTrip,\n options.strictNullHandling,\n options.skipNulls,\n options.encode ? options.encoder : null,\n options.filter,\n options.sort,\n options.allowDots,\n options.serializeDate,\n options.format,\n options.formatter,\n options.encodeValuesOnly,\n options.charset,\n sideChannel\n ));\n }\n\n var joined = keys.join(options.delimiter);\n var prefix = options.addQueryPrefix === true ? '?' : '';\n\n if (options.charsetSentinel) {\n if (options.charset === 'iso-8859-1') {\n // encodeURIComponent('✓'), the \"numeric entity\" representation of a checkmark\n prefix += 'utf8=%26%2310003%3B&';\n } else {\n // encodeURIComponent('✓')\n prefix += 'utf8=%E2%9C%93&';\n }\n }\n\n return joined.length > 0 ? prefix + joined : '';\n};\n","'use strict';\n\nvar GetIntrinsic = require('get-intrinsic');\nvar callBound = require('call-bind/callBound');\nvar inspect = require('object-inspect');\n\nvar $TypeError = GetIntrinsic('%TypeError%');\nvar $WeakMap = GetIntrinsic('%WeakMap%', true);\nvar $Map = GetIntrinsic('%Map%', true);\n\nvar $weakMapGet = callBound('WeakMap.prototype.get', true);\nvar $weakMapSet = callBound('WeakMap.prototype.set', true);\nvar $weakMapHas = callBound('WeakMap.prototype.has', true);\nvar $mapGet = callBound('Map.prototype.get', true);\nvar $mapSet = callBound('Map.prototype.set', true);\nvar $mapHas = callBound('Map.prototype.has', true);\n\n/*\n * This function traverses the list returning the node corresponding to the\n * given key.\n *\n * That node is also moved to the head of the list, so that if it's accessed\n * again we don't need to traverse the whole list. By doing so, all the recently\n * used nodes can be accessed relatively quickly.\n */\nvar listGetNode = function (list, key) { // eslint-disable-line consistent-return\n\tfor (var prev = list, curr; (curr = prev.next) !== null; prev = curr) {\n\t\tif (curr.key === key) {\n\t\t\tprev.next = curr.next;\n\t\t\tcurr.next = list.next;\n\t\t\tlist.next = curr; // eslint-disable-line no-param-reassign\n\t\t\treturn curr;\n\t\t}\n\t}\n};\n\nvar listGet = function (objects, key) {\n\tvar node = listGetNode(objects, key);\n\treturn node && node.value;\n};\nvar listSet = function (objects, key, value) {\n\tvar node = listGetNode(objects, key);\n\tif (node) {\n\t\tnode.value = value;\n\t} else {\n\t\t// Prepend the new node to the beginning of the list\n\t\tobjects.next = { // eslint-disable-line no-param-reassign\n\t\t\tkey: key,\n\t\t\tnext: objects.next,\n\t\t\tvalue: value\n\t\t};\n\t}\n};\nvar listHas = function (objects, key) {\n\treturn !!listGetNode(objects, key);\n};\n\nmodule.exports = function getSideChannel() {\n\tvar $wm;\n\tvar $m;\n\tvar $o;\n\tvar channel = {\n\t\tassert: function (key) {\n\t\t\tif (!channel.has(key)) {\n\t\t\t\tthrow new $TypeError('Side channel does not contain ' + inspect(key));\n\t\t\t}\n\t\t},\n\t\tget: function (key) { // eslint-disable-line consistent-return\n\t\t\tif ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) {\n\t\t\t\tif ($wm) {\n\t\t\t\t\treturn $weakMapGet($wm, key);\n\t\t\t\t}\n\t\t\t} else if ($Map) {\n\t\t\t\tif ($m) {\n\t\t\t\t\treturn $mapGet($m, key);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif ($o) { // eslint-disable-line no-lonely-if\n\t\t\t\t\treturn listGet($o, key);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\thas: function (key) {\n\t\t\tif ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) {\n\t\t\t\tif ($wm) {\n\t\t\t\t\treturn $weakMapHas($wm, key);\n\t\t\t\t}\n\t\t\t} else if ($Map) {\n\t\t\t\tif ($m) {\n\t\t\t\t\treturn $mapHas($m, key);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif ($o) { // eslint-disable-line no-lonely-if\n\t\t\t\t\treturn listHas($o, key);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t\tset: function (key, value) {\n\t\t\tif ($WeakMap && key && (typeof key === 'object' || typeof key === 'function')) {\n\t\t\t\tif (!$wm) {\n\t\t\t\t\t$wm = new $WeakMap();\n\t\t\t\t}\n\t\t\t\t$weakMapSet($wm, key, value);\n\t\t\t} else if ($Map) {\n\t\t\t\tif (!$m) {\n\t\t\t\t\t$m = new $Map();\n\t\t\t\t}\n\t\t\t\t$mapSet($m, key, value);\n\t\t\t} else {\n\t\t\t\tif (!$o) {\n\t\t\t\t\t/*\n\t\t\t\t\t * Initialize the linked list as an empty node, so that we don't have\n\t\t\t\t\t * to special-case handling of the first node: we can always refer to\n\t\t\t\t\t * it as (previous node).next, instead of something like (list).head\n\t\t\t\t\t */\n\t\t\t\t\t$o = { key: {}, next: null };\n\t\t\t\t}\n\t\t\t\tlistSet($o, key, value);\n\t\t\t}\n\t\t}\n\t};\n\treturn channel;\n};\n","'use strict';\n\nvar origSymbol = typeof Symbol !== 'undefined' && Symbol;\nvar hasSymbolSham = require('./shams');\n\nmodule.exports = function hasNativeSymbols() {\n\tif (typeof origSymbol !== 'function') { return false; }\n\tif (typeof Symbol !== 'function') { return false; }\n\tif (typeof origSymbol('foo') !== 'symbol') { return false; }\n\tif (typeof Symbol('bar') !== 'symbol') { return false; }\n\n\treturn hasSymbolSham();\n};\n","'use strict';\n\n/* eslint complexity: [2, 18], max-statements: [2, 33] */\nmodule.exports = function hasSymbols() {\n\tif (typeof Symbol !== 'function' || typeof Object.getOwnPropertySymbols !== 'function') { return false; }\n\tif (typeof Symbol.iterator === 'symbol') { return true; }\n\n\tvar obj = {};\n\tvar sym = Symbol('test');\n\tvar symObj = Object(sym);\n\tif (typeof sym === 'string') { return false; }\n\n\tif (Object.prototype.toString.call(sym) !== '[object Symbol]') { return false; }\n\tif (Object.prototype.toString.call(symObj) !== '[object Symbol]') { return false; }\n\n\t// temp disabled per https://github.com/ljharb/object.assign/issues/17\n\t// if (sym instanceof Symbol) { return false; }\n\t// temp disabled per https://github.com/WebReflection/get-own-property-symbols/issues/4\n\t// if (!(symObj instanceof Symbol)) { return false; }\n\n\t// if (typeof Symbol.prototype.toString !== 'function') { return false; }\n\t// if (String(sym) !== Symbol.prototype.toString.call(sym)) { return false; }\n\n\tvar symVal = 42;\n\tobj[sym] = symVal;\n\tfor (sym in obj) { return false; } // eslint-disable-line no-restricted-syntax, no-unreachable-loop\n\tif (typeof Object.keys === 'function' && Object.keys(obj).length !== 0) { return false; }\n\n\tif (typeof Object.getOwnPropertyNames === 'function' && Object.getOwnPropertyNames(obj).length !== 0) { return false; }\n\n\tvar syms = Object.getOwnPropertySymbols(obj);\n\tif (syms.length !== 1 || syms[0] !== sym) { return false; }\n\n\tif (!Object.prototype.propertyIsEnumerable.call(obj, sym)) { return false; }\n\n\tif (typeof Object.getOwnPropertyDescriptor === 'function') {\n\t\tvar descriptor = Object.getOwnPropertyDescriptor(obj, sym);\n\t\tif (descriptor.value !== symVal || descriptor.enumerable !== true) { return false; }\n\t}\n\n\treturn true;\n};\n","'use strict';\n\nvar GetIntrinsic = require('get-intrinsic');\n\nvar callBind = require('./');\n\nvar $indexOf = callBind(GetIntrinsic('String.prototype.indexOf'));\n\nmodule.exports = function callBoundIntrinsic(name, allowMissing) {\n\tvar intrinsic = GetIntrinsic(name, !!allowMissing);\n\tif (typeof intrinsic === 'function' && $indexOf(name, '.prototype.') > -1) {\n\t\treturn callBind(intrinsic);\n\t}\n\treturn intrinsic;\n};\n","'use strict';\n\nvar bind = require('function-bind');\nvar GetIntrinsic = require('get-intrinsic');\n\nvar $apply = GetIntrinsic('%Function.prototype.apply%');\nvar $call = GetIntrinsic('%Function.prototype.call%');\nvar $reflectApply = GetIntrinsic('%Reflect.apply%', true) || bind.call($call, $apply);\n\nvar $gOPD = GetIntrinsic('%Object.getOwnPropertyDescriptor%', true);\nvar $defineProperty = GetIntrinsic('%Object.defineProperty%', true);\nvar $max = GetIntrinsic('%Math.max%');\n\nif ($defineProperty) {\n\ttry {\n\t\t$defineProperty({}, 'a', { value: 1 });\n\t} catch (e) {\n\t\t// IE 8 has a broken defineProperty\n\t\t$defineProperty = null;\n\t}\n}\n\nmodule.exports = function callBind(originalFunction) {\n\tvar func = $reflectApply(bind, $call, arguments);\n\tif ($gOPD && $defineProperty) {\n\t\tvar desc = $gOPD(func, 'length');\n\t\tif (desc.configurable) {\n\t\t\t// original length, plus the receiver, minus any additional arguments (after the receiver)\n\t\t\t$defineProperty(\n\t\t\t\tfunc,\n\t\t\t\t'length',\n\t\t\t\t{ value: 1 + $max(0, originalFunction.length - (arguments.length - 1)) }\n\t\t\t);\n\t\t}\n\t}\n\treturn func;\n};\n\nvar applyBind = function applyBind() {\n\treturn $reflectApply(bind, $apply, arguments);\n};\n\nif ($defineProperty) {\n\t$defineProperty(module.exports, 'apply', { value: applyBind });\n} else {\n\tmodule.exports.apply = applyBind;\n}\n","var hasMap = typeof Map === 'function' && Map.prototype;\nvar mapSizeDescriptor = Object.getOwnPropertyDescriptor && hasMap ? Object.getOwnPropertyDescriptor(Map.prototype, 'size') : null;\nvar mapSize = hasMap && mapSizeDescriptor && typeof mapSizeDescriptor.get === 'function' ? mapSizeDescriptor.get : null;\nvar mapForEach = hasMap && Map.prototype.forEach;\nvar hasSet = typeof Set === 'function' && Set.prototype;\nvar setSizeDescriptor = Object.getOwnPropertyDescriptor && hasSet ? Object.getOwnPropertyDescriptor(Set.prototype, 'size') : null;\nvar setSize = hasSet && setSizeDescriptor && typeof setSizeDescriptor.get === 'function' ? setSizeDescriptor.get : null;\nvar setForEach = hasSet && Set.prototype.forEach;\nvar hasWeakMap = typeof WeakMap === 'function' && WeakMap.prototype;\nvar weakMapHas = hasWeakMap ? WeakMap.prototype.has : null;\nvar hasWeakSet = typeof WeakSet === 'function' && WeakSet.prototype;\nvar weakSetHas = hasWeakSet ? WeakSet.prototype.has : null;\nvar hasWeakRef = typeof WeakRef === 'function' && WeakRef.prototype;\nvar weakRefDeref = hasWeakRef ? WeakRef.prototype.deref : null;\nvar booleanValueOf = Boolean.prototype.valueOf;\nvar objectToString = Object.prototype.toString;\nvar functionToString = Function.prototype.toString;\nvar $match = String.prototype.match;\nvar $slice = String.prototype.slice;\nvar $replace = String.prototype.replace;\nvar $toUpperCase = String.prototype.toUpperCase;\nvar $toLowerCase = String.prototype.toLowerCase;\nvar $test = RegExp.prototype.test;\nvar $concat = Array.prototype.concat;\nvar $join = Array.prototype.join;\nvar $arrSlice = Array.prototype.slice;\nvar $floor = Math.floor;\nvar bigIntValueOf = typeof BigInt === 'function' ? BigInt.prototype.valueOf : null;\nvar gOPS = Object.getOwnPropertySymbols;\nvar symToString = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? Symbol.prototype.toString : null;\nvar hasShammedSymbols = typeof Symbol === 'function' && typeof Symbol.iterator === 'object';\n// ie, `has-tostringtag/shams\nvar toStringTag = typeof Symbol === 'function' && Symbol.toStringTag && (typeof Symbol.toStringTag === hasShammedSymbols ? 'object' : 'symbol')\n ? Symbol.toStringTag\n : null;\nvar isEnumerable = Object.prototype.propertyIsEnumerable;\n\nvar gPO = (typeof Reflect === 'function' ? Reflect.getPrototypeOf : Object.getPrototypeOf) || (\n [].__proto__ === Array.prototype // eslint-disable-line no-proto\n ? function (O) {\n return O.__proto__; // eslint-disable-line no-proto\n }\n : null\n);\n\nfunction addNumericSeparator(num, str) {\n if (\n num === Infinity\n || num === -Infinity\n || num !== num\n || (num && num > -1000 && num < 1000)\n || $test.call(/e/, str)\n ) {\n return str;\n }\n var sepRegex = /[0-9](?=(?:[0-9]{3})+(?![0-9]))/g;\n if (typeof num === 'number') {\n var int = num < 0 ? -$floor(-num) : $floor(num); // trunc(num)\n if (int !== num) {\n var intStr = String(int);\n var dec = $slice.call(str, intStr.length + 1);\n return $replace.call(intStr, sepRegex, '$&_') + '.' + $replace.call($replace.call(dec, /([0-9]{3})/g, '$&_'), /_$/, '');\n }\n }\n return $replace.call(str, sepRegex, '$&_');\n}\n\nvar utilInspect = require('./util.inspect');\nvar inspectCustom = utilInspect.custom;\nvar inspectSymbol = isSymbol(inspectCustom) ? inspectCustom : null;\n\nmodule.exports = function inspect_(obj, options, depth, seen) {\n var opts = options || {};\n\n if (has(opts, 'quoteStyle') && (opts.quoteStyle !== 'single' && opts.quoteStyle !== 'double')) {\n throw new TypeError('option \"quoteStyle\" must be \"single\" or \"double\"');\n }\n if (\n has(opts, 'maxStringLength') && (typeof opts.maxStringLength === 'number'\n ? opts.maxStringLength < 0 && opts.maxStringLength !== Infinity\n : opts.maxStringLength !== null\n )\n ) {\n throw new TypeError('option \"maxStringLength\", if provided, must be a positive integer, Infinity, or `null`');\n }\n var customInspect = has(opts, 'customInspect') ? opts.customInspect : true;\n if (typeof customInspect !== 'boolean' && customInspect !== 'symbol') {\n throw new TypeError('option \"customInspect\", if provided, must be `true`, `false`, or `\\'symbol\\'`');\n }\n\n if (\n has(opts, 'indent')\n && opts.indent !== null\n && opts.indent !== '\\t'\n && !(parseInt(opts.indent, 10) === opts.indent && opts.indent > 0)\n ) {\n throw new TypeError('option \"indent\" must be \"\\\\t\", an integer > 0, or `null`');\n }\n if (has(opts, 'numericSeparator') && typeof opts.numericSeparator !== 'boolean') {\n throw new TypeError('option \"numericSeparator\", if provided, must be `true` or `false`');\n }\n var numericSeparator = opts.numericSeparator;\n\n if (typeof obj === 'undefined') {\n return 'undefined';\n }\n if (obj === null) {\n return 'null';\n }\n if (typeof obj === 'boolean') {\n return obj ? 'true' : 'false';\n }\n\n if (typeof obj === 'string') {\n return inspectString(obj, opts);\n }\n if (typeof obj === 'number') {\n if (obj === 0) {\n return Infinity / obj > 0 ? '0' : '-0';\n }\n var str = String(obj);\n return numericSeparator ? addNumericSeparator(obj, str) : str;\n }\n if (typeof obj === 'bigint') {\n var bigIntStr = String(obj) + 'n';\n return numericSeparator ? addNumericSeparator(obj, bigIntStr) : bigIntStr;\n }\n\n var maxDepth = typeof opts.depth === 'undefined' ? 5 : opts.depth;\n if (typeof depth === 'undefined') { depth = 0; }\n if (depth >= maxDepth && maxDepth > 0 && typeof obj === 'object') {\n return isArray(obj) ? '[Array]' : '[Object]';\n }\n\n var indent = getIndent(opts, depth);\n\n if (typeof seen === 'undefined') {\n seen = [];\n } else if (indexOf(seen, obj) >= 0) {\n return '[Circular]';\n }\n\n function inspect(value, from, noIndent) {\n if (from) {\n seen = $arrSlice.call(seen);\n seen.push(from);\n }\n if (noIndent) {\n var newOpts = {\n depth: opts.depth\n };\n if (has(opts, 'quoteStyle')) {\n newOpts.quoteStyle = opts.quoteStyle;\n }\n return inspect_(value, newOpts, depth + 1, seen);\n }\n return inspect_(value, opts, depth + 1, seen);\n }\n\n if (typeof obj === 'function' && !isRegExp(obj)) { // in older engines, regexes are callable\n var name = nameOf(obj);\n var keys = arrObjKeys(obj, inspect);\n return '[Function' + (name ? ': ' + name : ' (anonymous)') + ']' + (keys.length > 0 ? ' { ' + $join.call(keys, ', ') + ' }' : '');\n }\n if (isSymbol(obj)) {\n var symString = hasShammedSymbols ? $replace.call(String(obj), /^(Symbol\\(.*\\))_[^)]*$/, '$1') : symToString.call(obj);\n return typeof obj === 'object' && !hasShammedSymbols ? markBoxed(symString) : symString;\n }\n if (isElement(obj)) {\n var s = '<' + $toLowerCase.call(String(obj.nodeName));\n var attrs = obj.attributes || [];\n for (var i = 0; i < attrs.length; i++) {\n s += ' ' + attrs[i].name + '=' + wrapQuotes(quote(attrs[i].value), 'double', opts);\n }\n s += '>';\n if (obj.childNodes && obj.childNodes.length) { s += '...'; }\n s += '';\n return s;\n }\n if (isArray(obj)) {\n if (obj.length === 0) { return '[]'; }\n var xs = arrObjKeys(obj, inspect);\n if (indent && !singleLineValues(xs)) {\n return '[' + indentedJoin(xs, indent) + ']';\n }\n return '[ ' + $join.call(xs, ', ') + ' ]';\n }\n if (isError(obj)) {\n var parts = arrObjKeys(obj, inspect);\n if (!('cause' in Error.prototype) && 'cause' in obj && !isEnumerable.call(obj, 'cause')) {\n return '{ [' + String(obj) + '] ' + $join.call($concat.call('[cause]: ' + inspect(obj.cause), parts), ', ') + ' }';\n }\n if (parts.length === 0) { return '[' + String(obj) + ']'; }\n return '{ [' + String(obj) + '] ' + $join.call(parts, ', ') + ' }';\n }\n if (typeof obj === 'object' && customInspect) {\n if (inspectSymbol && typeof obj[inspectSymbol] === 'function' && utilInspect) {\n return utilInspect(obj, { depth: maxDepth - depth });\n } else if (customInspect !== 'symbol' && typeof obj.inspect === 'function') {\n return obj.inspect();\n }\n }\n if (isMap(obj)) {\n var mapParts = [];\n mapForEach.call(obj, function (value, key) {\n mapParts.push(inspect(key, obj, true) + ' => ' + inspect(value, obj));\n });\n return collectionOf('Map', mapSize.call(obj), mapParts, indent);\n }\n if (isSet(obj)) {\n var setParts = [];\n setForEach.call(obj, function (value) {\n setParts.push(inspect(value, obj));\n });\n return collectionOf('Set', setSize.call(obj), setParts, indent);\n }\n if (isWeakMap(obj)) {\n return weakCollectionOf('WeakMap');\n }\n if (isWeakSet(obj)) {\n return weakCollectionOf('WeakSet');\n }\n if (isWeakRef(obj)) {\n return weakCollectionOf('WeakRef');\n }\n if (isNumber(obj)) {\n return markBoxed(inspect(Number(obj)));\n }\n if (isBigInt(obj)) {\n return markBoxed(inspect(bigIntValueOf.call(obj)));\n }\n if (isBoolean(obj)) {\n return markBoxed(booleanValueOf.call(obj));\n }\n if (isString(obj)) {\n return markBoxed(inspect(String(obj)));\n }\n if (!isDate(obj) && !isRegExp(obj)) {\n var ys = arrObjKeys(obj, inspect);\n var isPlainObject = gPO ? gPO(obj) === Object.prototype : obj instanceof Object || obj.constructor === Object;\n var protoTag = obj instanceof Object ? '' : 'null prototype';\n var stringTag = !isPlainObject && toStringTag && Object(obj) === obj && toStringTag in obj ? $slice.call(toStr(obj), 8, -1) : protoTag ? 'Object' : '';\n var constructorTag = isPlainObject || typeof obj.constructor !== 'function' ? '' : obj.constructor.name ? obj.constructor.name + ' ' : '';\n var tag = constructorTag + (stringTag || protoTag ? '[' + $join.call($concat.call([], stringTag || [], protoTag || []), ': ') + '] ' : '');\n if (ys.length === 0) { return tag + '{}'; }\n if (indent) {\n return tag + '{' + indentedJoin(ys, indent) + '}';\n }\n return tag + '{ ' + $join.call(ys, ', ') + ' }';\n }\n return String(obj);\n};\n\nfunction wrapQuotes(s, defaultStyle, opts) {\n var quoteChar = (opts.quoteStyle || defaultStyle) === 'double' ? '\"' : \"'\";\n return quoteChar + s + quoteChar;\n}\n\nfunction quote(s) {\n return $replace.call(String(s), /\"/g, '"');\n}\n\nfunction isArray(obj) { return toStr(obj) === '[object Array]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }\nfunction isDate(obj) { return toStr(obj) === '[object Date]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }\nfunction isRegExp(obj) { return toStr(obj) === '[object RegExp]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }\nfunction isError(obj) { return toStr(obj) === '[object Error]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }\nfunction isString(obj) { return toStr(obj) === '[object String]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }\nfunction isNumber(obj) { return toStr(obj) === '[object Number]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }\nfunction isBoolean(obj) { return toStr(obj) === '[object Boolean]' && (!toStringTag || !(typeof obj === 'object' && toStringTag in obj)); }\n\n// Symbol and BigInt do have Symbol.toStringTag by spec, so that can't be used to eliminate false positives\nfunction isSymbol(obj) {\n if (hasShammedSymbols) {\n return obj && typeof obj === 'object' && obj instanceof Symbol;\n }\n if (typeof obj === 'symbol') {\n return true;\n }\n if (!obj || typeof obj !== 'object' || !symToString) {\n return false;\n }\n try {\n symToString.call(obj);\n return true;\n } catch (e) {}\n return false;\n}\n\nfunction isBigInt(obj) {\n if (!obj || typeof obj !== 'object' || !bigIntValueOf) {\n return false;\n }\n try {\n bigIntValueOf.call(obj);\n return true;\n } catch (e) {}\n return false;\n}\n\nvar hasOwn = Object.prototype.hasOwnProperty || function (key) { return key in this; };\nfunction has(obj, key) {\n return hasOwn.call(obj, key);\n}\n\nfunction toStr(obj) {\n return objectToString.call(obj);\n}\n\nfunction nameOf(f) {\n if (f.name) { return f.name; }\n var m = $match.call(functionToString.call(f), /^function\\s*([\\w$]+)/);\n if (m) { return m[1]; }\n return null;\n}\n\nfunction indexOf(xs, x) {\n if (xs.indexOf) { return xs.indexOf(x); }\n for (var i = 0, l = xs.length; i < l; i++) {\n if (xs[i] === x) { return i; }\n }\n return -1;\n}\n\nfunction isMap(x) {\n if (!mapSize || !x || typeof x !== 'object') {\n return false;\n }\n try {\n mapSize.call(x);\n try {\n setSize.call(x);\n } catch (s) {\n return true;\n }\n return x instanceof Map; // core-js workaround, pre-v2.5.0\n } catch (e) {}\n return false;\n}\n\nfunction isWeakMap(x) {\n if (!weakMapHas || !x || typeof x !== 'object') {\n return false;\n }\n try {\n weakMapHas.call(x, weakMapHas);\n try {\n weakSetHas.call(x, weakSetHas);\n } catch (s) {\n return true;\n }\n return x instanceof WeakMap; // core-js workaround, pre-v2.5.0\n } catch (e) {}\n return false;\n}\n\nfunction isWeakRef(x) {\n if (!weakRefDeref || !x || typeof x !== 'object') {\n return false;\n }\n try {\n weakRefDeref.call(x);\n return true;\n } catch (e) {}\n return false;\n}\n\nfunction isSet(x) {\n if (!setSize || !x || typeof x !== 'object') {\n return false;\n }\n try {\n setSize.call(x);\n try {\n mapSize.call(x);\n } catch (m) {\n return true;\n }\n return x instanceof Set; // core-js workaround, pre-v2.5.0\n } catch (e) {}\n return false;\n}\n\nfunction isWeakSet(x) {\n if (!weakSetHas || !x || typeof x !== 'object') {\n return false;\n }\n try {\n weakSetHas.call(x, weakSetHas);\n try {\n weakMapHas.call(x, weakMapHas);\n } catch (s) {\n return true;\n }\n return x instanceof WeakSet; // core-js workaround, pre-v2.5.0\n } catch (e) {}\n return false;\n}\n\nfunction isElement(x) {\n if (!x || typeof x !== 'object') { return false; }\n if (typeof HTMLElement !== 'undefined' && x instanceof HTMLElement) {\n return true;\n }\n return typeof x.nodeName === 'string' && typeof x.getAttribute === 'function';\n}\n\nfunction inspectString(str, opts) {\n if (str.length > opts.maxStringLength) {\n var remaining = str.length - opts.maxStringLength;\n var trailer = '... ' + remaining + ' more character' + (remaining > 1 ? 's' : '');\n return inspectString($slice.call(str, 0, opts.maxStringLength), opts) + trailer;\n }\n // eslint-disable-next-line no-control-regex\n var s = $replace.call($replace.call(str, /(['\\\\])/g, '\\\\$1'), /[\\x00-\\x1f]/g, lowbyte);\n return wrapQuotes(s, 'single', opts);\n}\n\nfunction lowbyte(c) {\n var n = c.charCodeAt(0);\n var x = {\n 8: 'b',\n 9: 't',\n 10: 'n',\n 12: 'f',\n 13: 'r'\n }[n];\n if (x) { return '\\\\' + x; }\n return '\\\\x' + (n < 0x10 ? '0' : '') + $toUpperCase.call(n.toString(16));\n}\n\nfunction markBoxed(str) {\n return 'Object(' + str + ')';\n}\n\nfunction weakCollectionOf(type) {\n return type + ' { ? }';\n}\n\nfunction collectionOf(type, size, entries, indent) {\n var joinedEntries = indent ? indentedJoin(entries, indent) : $join.call(entries, ', ');\n return type + ' (' + size + ') {' + joinedEntries + '}';\n}\n\nfunction singleLineValues(xs) {\n for (var i = 0; i < xs.length; i++) {\n if (indexOf(xs[i], '\\n') >= 0) {\n return false;\n }\n }\n return true;\n}\n\nfunction getIndent(opts, depth) {\n var baseIndent;\n if (opts.indent === '\\t') {\n baseIndent = '\\t';\n } else if (typeof opts.indent === 'number' && opts.indent > 0) {\n baseIndent = $join.call(Array(opts.indent + 1), ' ');\n } else {\n return null;\n }\n return {\n base: baseIndent,\n prev: $join.call(Array(depth + 1), baseIndent)\n };\n}\n\nfunction indentedJoin(xs, indent) {\n if (xs.length === 0) { return ''; }\n var lineJoiner = '\\n' + indent.prev + indent.base;\n return lineJoiner + $join.call(xs, ',' + lineJoiner) + '\\n' + indent.prev;\n}\n\nfunction arrObjKeys(obj, inspect) {\n var isArr = isArray(obj);\n var xs = [];\n if (isArr) {\n xs.length = obj.length;\n for (var i = 0; i < obj.length; i++) {\n xs[i] = has(obj, i) ? inspect(obj[i], obj) : '';\n }\n }\n var syms = typeof gOPS === 'function' ? gOPS(obj) : [];\n var symMap;\n if (hasShammedSymbols) {\n symMap = {};\n for (var k = 0; k < syms.length; k++) {\n symMap['$' + syms[k]] = syms[k];\n }\n }\n\n for (var key in obj) { // eslint-disable-line no-restricted-syntax\n if (!has(obj, key)) { continue; } // eslint-disable-line no-restricted-syntax, no-continue\n if (isArr && String(Number(key)) === key && key < obj.length) { continue; } // eslint-disable-line no-restricted-syntax, no-continue\n if (hasShammedSymbols && symMap['$' + key] instanceof Symbol) {\n // this is to prevent shammed Symbols, which are stored as strings, from being included in the string key section\n continue; // eslint-disable-line no-restricted-syntax, no-continue\n } else if ($test.call(/[^\\w$]/, key)) {\n xs.push(inspect(key, obj) + ': ' + inspect(obj[key], obj));\n } else {\n xs.push(key + ': ' + inspect(obj[key], obj));\n }\n }\n if (typeof gOPS === 'function') {\n for (var j = 0; j < syms.length; j++) {\n if (isEnumerable.call(obj, syms[j])) {\n xs.push('[' + inspect(syms[j]) + ']: ' + inspect(obj[syms[j]], obj));\n }\n }\n }\n return xs;\n}\n","'use strict';\n\nvar utils = require('./utils');\n\nvar has = Object.prototype.hasOwnProperty;\nvar isArray = Array.isArray;\n\nvar defaults = {\n allowDots: false,\n allowPrototypes: false,\n allowSparse: false,\n arrayLimit: 20,\n charset: 'utf-8',\n charsetSentinel: false,\n comma: false,\n decoder: utils.decode,\n delimiter: '&',\n depth: 5,\n ignoreQueryPrefix: false,\n interpretNumericEntities: false,\n parameterLimit: 1000,\n parseArrays: true,\n plainObjects: false,\n strictNullHandling: false\n};\n\nvar interpretNumericEntities = function (str) {\n return str.replace(/&#(\\d+);/g, function ($0, numberStr) {\n return String.fromCharCode(parseInt(numberStr, 10));\n });\n};\n\nvar parseArrayValue = function (val, options) {\n if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) {\n return val.split(',');\n }\n\n return val;\n};\n\n// This is what browsers will submit when the ✓ character occurs in an\n// application/x-www-form-urlencoded body and the encoding of the page containing\n// the form is iso-8859-1, or when the submitted form has an accept-charset\n// attribute of iso-8859-1. Presumably also with other charsets that do not contain\n// the ✓ character, such as us-ascii.\nvar isoSentinel = 'utf8=%26%2310003%3B'; // encodeURIComponent('✓')\n\n// These are the percent-encoded utf-8 octets representing a checkmark, indicating that the request actually is utf-8 encoded.\nvar charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')\n\nvar parseValues = function parseQueryStringValues(str, options) {\n var obj = {};\n var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\\?/, '') : str;\n var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;\n var parts = cleanStr.split(options.delimiter, limit);\n var skipIndex = -1; // Keep track of where the utf8 sentinel was found\n var i;\n\n var charset = options.charset;\n if (options.charsetSentinel) {\n for (i = 0; i < parts.length; ++i) {\n if (parts[i].indexOf('utf8=') === 0) {\n if (parts[i] === charsetSentinel) {\n charset = 'utf-8';\n } else if (parts[i] === isoSentinel) {\n charset = 'iso-8859-1';\n }\n skipIndex = i;\n i = parts.length; // The eslint settings do not allow break;\n }\n }\n }\n\n for (i = 0; i < parts.length; ++i) {\n if (i === skipIndex) {\n continue;\n }\n var part = parts[i];\n\n var bracketEqualsPos = part.indexOf(']=');\n var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;\n\n var key, val;\n if (pos === -1) {\n key = options.decoder(part, defaults.decoder, charset, 'key');\n val = options.strictNullHandling ? null : '';\n } else {\n key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key');\n val = utils.maybeMap(\n parseArrayValue(part.slice(pos + 1), options),\n function (encodedVal) {\n return options.decoder(encodedVal, defaults.decoder, charset, 'value');\n }\n );\n }\n\n if (val && options.interpretNumericEntities && charset === 'iso-8859-1') {\n val = interpretNumericEntities(val);\n }\n\n if (part.indexOf('[]=') > -1) {\n val = isArray(val) ? [val] : val;\n }\n\n if (has.call(obj, key)) {\n obj[key] = utils.combine(obj[key], val);\n } else {\n obj[key] = val;\n }\n }\n\n return obj;\n};\n\nvar parseObject = function (chain, val, options, valuesParsed) {\n var leaf = valuesParsed ? val : parseArrayValue(val, options);\n\n for (var i = chain.length - 1; i >= 0; --i) {\n var obj;\n var root = chain[i];\n\n if (root === '[]' && options.parseArrays) {\n obj = [].concat(leaf);\n } else {\n obj = options.plainObjects ? Object.create(null) : {};\n var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;\n var index = parseInt(cleanRoot, 10);\n if (!options.parseArrays && cleanRoot === '') {\n obj = { 0: leaf };\n } else if (\n !isNaN(index)\n && root !== cleanRoot\n && String(index) === cleanRoot\n && index >= 0\n && (options.parseArrays && index <= options.arrayLimit)\n ) {\n obj = [];\n obj[index] = leaf;\n } else if (cleanRoot !== '__proto__') {\n obj[cleanRoot] = leaf;\n }\n }\n\n leaf = obj;\n }\n\n return leaf;\n};\n\nvar parseKeys = function parseQueryStringKeys(givenKey, val, options, valuesParsed) {\n if (!givenKey) {\n return;\n }\n\n // Transform dot notation to bracket notation\n var key = options.allowDots ? givenKey.replace(/\\.([^.[]+)/g, '[$1]') : givenKey;\n\n // The regex chunks\n\n var brackets = /(\\[[^[\\]]*])/;\n var child = /(\\[[^[\\]]*])/g;\n\n // Get the parent\n\n var segment = options.depth > 0 && brackets.exec(key);\n var parent = segment ? key.slice(0, segment.index) : key;\n\n // Stash the parent if it exists\n\n var keys = [];\n if (parent) {\n // If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties\n if (!options.plainObjects && has.call(Object.prototype, parent)) {\n if (!options.allowPrototypes) {\n return;\n }\n }\n\n keys.push(parent);\n }\n\n // Loop through children appending to the array until we hit depth\n\n var i = 0;\n while (options.depth > 0 && (segment = child.exec(key)) !== null && i < options.depth) {\n i += 1;\n if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) {\n if (!options.allowPrototypes) {\n return;\n }\n }\n keys.push(segment[1]);\n }\n\n // If there's a remainder, just add whatever is left\n\n if (segment) {\n keys.push('[' + key.slice(segment.index) + ']');\n }\n\n return parseObject(keys, val, options, valuesParsed);\n};\n\nvar normalizeParseOptions = function normalizeParseOptions(opts) {\n if (!opts) {\n return defaults;\n }\n\n if (opts.decoder !== null && opts.decoder !== undefined && typeof opts.decoder !== 'function') {\n throw new TypeError('Decoder has to be a function.');\n }\n\n if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {\n throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');\n }\n var charset = typeof opts.charset === 'undefined' ? defaults.charset : opts.charset;\n\n return {\n allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots,\n allowPrototypes: typeof opts.allowPrototypes === 'boolean' ? opts.allowPrototypes : defaults.allowPrototypes,\n allowSparse: typeof opts.allowSparse === 'boolean' ? opts.allowSparse : defaults.allowSparse,\n arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit,\n charset: charset,\n charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,\n comma: typeof opts.comma === 'boolean' ? opts.comma : defaults.comma,\n decoder: typeof opts.decoder === 'function' ? opts.decoder : defaults.decoder,\n delimiter: typeof opts.delimiter === 'string' || utils.isRegExp(opts.delimiter) ? opts.delimiter : defaults.delimiter,\n // eslint-disable-next-line no-implicit-coercion, no-extra-parens\n depth: (typeof opts.depth === 'number' || opts.depth === false) ? +opts.depth : defaults.depth,\n ignoreQueryPrefix: opts.ignoreQueryPrefix === true,\n interpretNumericEntities: typeof opts.interpretNumericEntities === 'boolean' ? opts.interpretNumericEntities : defaults.interpretNumericEntities,\n parameterLimit: typeof opts.parameterLimit === 'number' ? opts.parameterLimit : defaults.parameterLimit,\n parseArrays: opts.parseArrays !== false,\n plainObjects: typeof opts.plainObjects === 'boolean' ? opts.plainObjects : defaults.plainObjects,\n strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling\n };\n};\n\nmodule.exports = function (str, opts) {\n var options = normalizeParseOptions(opts);\n\n if (str === '' || str === null || typeof str === 'undefined') {\n return options.plainObjects ? Object.create(null) : {};\n }\n\n var tempObj = typeof str === 'string' ? parseValues(str, options) : str;\n var obj = options.plainObjects ? Object.create(null) : {};\n\n // Iterate over the keys and setup the new object\n\n var keys = Object.keys(tempObj);\n for (var i = 0; i < keys.length; ++i) {\n var key = keys[i];\n var newObj = parseKeys(key, tempObj[key], options, typeof str === 'string');\n obj = utils.merge(obj, newObj, options);\n }\n\n if (options.allowSparse === true) {\n return obj;\n }\n\n return utils.compact(obj);\n};\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.isNavigator = exports.isBrowser = exports.off = exports.on = exports.noop = void 0;\nvar noop = function () { };\nexports.noop = noop;\nfunction on(obj) {\n var args = [];\n for (var _i = 1; _i < arguments.length; _i++) {\n args[_i - 1] = arguments[_i];\n }\n if (obj && obj.addEventListener) {\n obj.addEventListener.apply(obj, args);\n }\n}\nexports.on = on;\nfunction off(obj) {\n var args = [];\n for (var _i = 1; _i < arguments.length; _i++) {\n args[_i - 1] = arguments[_i];\n }\n if (obj && obj.removeEventListener) {\n obj.removeEventListener.apply(obj, args);\n }\n}\nexports.off = off;\nexports.isBrowser = typeof window !== 'undefined';\nexports.isNavigator = typeof navigator !== 'undefined';\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-is.production.min.js');\n} else {\n module.exports = require('./cjs/react-is.development.js');\n}\n","/** @license React v16.13.1\n * react-is.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';var b=\"function\"===typeof Symbol&&Symbol.for,c=b?Symbol.for(\"react.element\"):60103,d=b?Symbol.for(\"react.portal\"):60106,e=b?Symbol.for(\"react.fragment\"):60107,f=b?Symbol.for(\"react.strict_mode\"):60108,g=b?Symbol.for(\"react.profiler\"):60114,h=b?Symbol.for(\"react.provider\"):60109,k=b?Symbol.for(\"react.context\"):60110,l=b?Symbol.for(\"react.async_mode\"):60111,m=b?Symbol.for(\"react.concurrent_mode\"):60111,n=b?Symbol.for(\"react.forward_ref\"):60112,p=b?Symbol.for(\"react.suspense\"):60113,q=b?\nSymbol.for(\"react.suspense_list\"):60120,r=b?Symbol.for(\"react.memo\"):60115,t=b?Symbol.for(\"react.lazy\"):60116,v=b?Symbol.for(\"react.block\"):60121,w=b?Symbol.for(\"react.fundamental\"):60117,x=b?Symbol.for(\"react.responder\"):60118,y=b?Symbol.for(\"react.scope\"):60119;\nfunction z(a){if(\"object\"===typeof a&&null!==a){var u=a.$$typeof;switch(u){case c:switch(a=a.type,a){case l:case m:case e:case g:case f:case p:return a;default:switch(a=a&&a.$$typeof,a){case k:case n:case t:case r:case h:return a;default:return u}}case d:return u}}}function A(a){return z(a)===m}exports.AsyncMode=l;exports.ConcurrentMode=m;exports.ContextConsumer=k;exports.ContextProvider=h;exports.Element=c;exports.ForwardRef=n;exports.Fragment=e;exports.Lazy=t;exports.Memo=r;exports.Portal=d;\nexports.Profiler=g;exports.StrictMode=f;exports.Suspense=p;exports.isAsyncMode=function(a){return A(a)||z(a)===l};exports.isConcurrentMode=A;exports.isContextConsumer=function(a){return z(a)===k};exports.isContextProvider=function(a){return z(a)===h};exports.isElement=function(a){return\"object\"===typeof a&&null!==a&&a.$$typeof===c};exports.isForwardRef=function(a){return z(a)===n};exports.isFragment=function(a){return z(a)===e};exports.isLazy=function(a){return z(a)===t};\nexports.isMemo=function(a){return z(a)===r};exports.isPortal=function(a){return z(a)===d};exports.isProfiler=function(a){return z(a)===g};exports.isStrictMode=function(a){return z(a)===f};exports.isSuspense=function(a){return z(a)===p};\nexports.isValidElementType=function(a){return\"string\"===typeof a||\"function\"===typeof a||a===e||a===m||a===g||a===f||a===p||a===q||\"object\"===typeof a&&null!==a&&(a.$$typeof===t||a.$$typeof===r||a.$$typeof===h||a.$$typeof===k||a.$$typeof===n||a.$$typeof===w||a.$$typeof===x||a.$$typeof===y||a.$$typeof===v)};exports.typeOf=z;\n","/** @license React v17.0.2\n * react-is.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n'use strict';var b=60103,c=60106,d=60107,e=60108,f=60114,g=60109,h=60110,k=60112,l=60113,m=60120,n=60115,p=60116,q=60121,r=60122,u=60117,v=60129,w=60131;\nif(\"function\"===typeof Symbol&&Symbol.for){var x=Symbol.for;b=x(\"react.element\");c=x(\"react.portal\");d=x(\"react.fragment\");e=x(\"react.strict_mode\");f=x(\"react.profiler\");g=x(\"react.provider\");h=x(\"react.context\");k=x(\"react.forward_ref\");l=x(\"react.suspense\");m=x(\"react.suspense_list\");n=x(\"react.memo\");p=x(\"react.lazy\");q=x(\"react.block\");r=x(\"react.server.block\");u=x(\"react.fundamental\");v=x(\"react.debug_trace_mode\");w=x(\"react.legacy_hidden\")}\nfunction y(a){if(\"object\"===typeof a&&null!==a){var t=a.$$typeof;switch(t){case b:switch(a=a.type,a){case d:case f:case e:case l:case m:return a;default:switch(a=a&&a.$$typeof,a){case h:case k:case p:case n:case g:return a;default:return t}}case c:return t}}}var z=g,A=b,B=k,C=d,D=p,E=n,F=c,G=f,H=e,I=l;exports.ContextConsumer=h;exports.ContextProvider=z;exports.Element=A;exports.ForwardRef=B;exports.Fragment=C;exports.Lazy=D;exports.Memo=E;exports.Portal=F;exports.Profiler=G;exports.StrictMode=H;\nexports.Suspense=I;exports.isAsyncMode=function(){return!1};exports.isConcurrentMode=function(){return!1};exports.isContextConsumer=function(a){return y(a)===h};exports.isContextProvider=function(a){return y(a)===g};exports.isElement=function(a){return\"object\"===typeof a&&null!==a&&a.$$typeof===b};exports.isForwardRef=function(a){return y(a)===k};exports.isFragment=function(a){return y(a)===d};exports.isLazy=function(a){return y(a)===p};exports.isMemo=function(a){return y(a)===n};\nexports.isPortal=function(a){return y(a)===c};exports.isProfiler=function(a){return y(a)===f};exports.isStrictMode=function(a){return y(a)===e};exports.isSuspense=function(a){return y(a)===l};exports.isValidElementType=function(a){return\"string\"===typeof a||\"function\"===typeof a||a===d||a===f||a===v||a===e||a===l||a===m||a===w||\"object\"===typeof a&&null!==a&&(a.$$typeof===p||a.$$typeof===n||a.$$typeof===g||a.$$typeof===h||a.$$typeof===k||a.$$typeof===u||a.$$typeof===q||a[0]===r)?!0:!1};\nexports.typeOf=y;\n","var arrayWithHoles = require(\"./arrayWithHoles\");\n\nvar iterableToArrayLimit = require(\"./iterableToArrayLimit\");\n\nvar nonIterableRest = require(\"./nonIterableRest\");\n\nfunction _slicedToArray(arr, i) {\n return arrayWithHoles(arr) || iterableToArrayLimit(arr, i) || nonIterableRest();\n}\n\nmodule.exports = _slicedToArray;","function _arrayWithHoles(arr) {\n if (Array.isArray(arr)) return arr;\n}\n\nmodule.exports = _arrayWithHoles;","function _iterableToArrayLimit(arr, i) {\n if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === \"[object Arguments]\")) {\n return;\n }\n\n var _arr = [];\n var _n = true;\n var _d = false;\n var _e = undefined;\n\n try {\n for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {\n _arr.push(_s.value);\n\n if (i && _arr.length === i) break;\n }\n } catch (err) {\n _d = true;\n _e = err;\n } finally {\n try {\n if (!_n && _i[\"return\"] != null) _i[\"return\"]();\n } finally {\n if (_d) throw _e;\n }\n }\n\n return _arr;\n}\n\nmodule.exports = _iterableToArrayLimit;","function _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance\");\n}\n\nmodule.exports = _nonIterableRest;","var objectWithoutPropertiesLoose = require(\"./objectWithoutPropertiesLoose\");\n\nfunction _objectWithoutProperties(source, excluded) {\n if (source == null) return {};\n var target = objectWithoutPropertiesLoose(source, excluded);\n var key, i;\n\n if (Object.getOwnPropertySymbols) {\n var sourceSymbolKeys = Object.getOwnPropertySymbols(source);\n\n for (i = 0; i < sourceSymbolKeys.length; i++) {\n key = sourceSymbolKeys[i];\n if (excluded.indexOf(key) >= 0) continue;\n if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;\n target[key] = source[key];\n }\n }\n\n return target;\n}\n\nmodule.exports = _objectWithoutProperties;","function _objectWithoutPropertiesLoose(source, excluded) {\n if (source == null) return {};\n var target = {};\n var sourceKeys = Object.keys(source);\n var key, i;\n\n for (i = 0; i < sourceKeys.length; i++) {\n key = sourceKeys[i];\n if (excluded.indexOf(key) >= 0) continue;\n target[key] = source[key];\n }\n\n return target;\n}\n\nmodule.exports = _objectWithoutPropertiesLoose;","var arrayWithoutHoles = require(\"./arrayWithoutHoles\");\n\nvar iterableToArray = require(\"./iterableToArray\");\n\nvar nonIterableSpread = require(\"./nonIterableSpread\");\n\nfunction _toConsumableArray(arr) {\n return arrayWithoutHoles(arr) || iterableToArray(arr) || nonIterableSpread();\n}\n\nmodule.exports = _toConsumableArray;","function _arrayWithoutHoles(arr) {\n if (Array.isArray(arr)) {\n for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) {\n arr2[i] = arr[i];\n }\n\n return arr2;\n }\n}\n\nmodule.exports = _arrayWithoutHoles;","function _iterableToArray(iter) {\n if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === \"[object Arguments]\") return Array.from(iter);\n}\n\nmodule.exports = _iterableToArray;","function _nonIterableSpread() {\n throw new TypeError(\"Invalid attempt to spread non-iterable instance\");\n}\n\nmodule.exports = _nonIterableSpread;","function _defineProperty(obj, key, value) {\n if (key in obj) {\n Object.defineProperty(obj, key, {\n value: value,\n enumerable: true,\n configurable: true,\n writable: true\n });\n } else {\n obj[key] = value;\n }\n\n return obj;\n}\n\nmodule.exports = _defineProperty;","var arrayWithHoles = require(\"./arrayWithHoles.js\");\n\nvar iterableToArrayLimit = require(\"./iterableToArrayLimit.js\");\n\nvar unsupportedIterableToArray = require(\"./unsupportedIterableToArray.js\");\n\nvar nonIterableRest = require(\"./nonIterableRest.js\");\n\nfunction _slicedToArray(arr, i) {\n return arrayWithHoles(arr) || iterableToArrayLimit(arr, i) || unsupportedIterableToArray(arr, i) || nonIterableRest();\n}\n\nmodule.exports = _slicedToArray, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","function _arrayWithHoles(arr) {\n if (Array.isArray(arr)) return arr;\n}\n\nmodule.exports = _arrayWithHoles, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","function _iterableToArrayLimit(arr, i) {\n var _i = arr == null ? null : typeof Symbol !== \"undefined\" && arr[Symbol.iterator] || arr[\"@@iterator\"];\n\n if (_i == null) return;\n var _arr = [];\n var _n = true;\n var _d = false;\n\n var _s, _e;\n\n try {\n for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {\n _arr.push(_s.value);\n\n if (i && _arr.length === i) break;\n }\n } catch (err) {\n _d = true;\n _e = err;\n } finally {\n try {\n if (!_n && _i[\"return\"] != null) _i[\"return\"]();\n } finally {\n if (_d) throw _e;\n }\n }\n\n return _arr;\n}\n\nmodule.exports = _iterableToArrayLimit, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","function _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}\n\nmodule.exports = _nonIterableRest, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","var objectWithoutPropertiesLoose = require(\"./objectWithoutPropertiesLoose.js\");\n\nfunction _objectWithoutProperties(source, excluded) {\n if (source == null) return {};\n var target = objectWithoutPropertiesLoose(source, excluded);\n var key, i;\n\n if (Object.getOwnPropertySymbols) {\n var sourceSymbolKeys = Object.getOwnPropertySymbols(source);\n\n for (i = 0; i < sourceSymbolKeys.length; i++) {\n key = sourceSymbolKeys[i];\n if (excluded.indexOf(key) >= 0) continue;\n if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;\n target[key] = source[key];\n }\n }\n\n return target;\n}\n\nmodule.exports = _objectWithoutProperties, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","function _objectWithoutPropertiesLoose(source, excluded) {\n if (source == null) return {};\n var target = {};\n var sourceKeys = Object.keys(source);\n var key, i;\n\n for (i = 0; i < sourceKeys.length; i++) {\n key = sourceKeys[i];\n if (excluded.indexOf(key) >= 0) continue;\n target[key] = source[key];\n }\n\n return target;\n}\n\nmodule.exports = _objectWithoutPropertiesLoose, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","var arrayWithoutHoles = require(\"./arrayWithoutHoles.js\");\n\nvar iterableToArray = require(\"./iterableToArray.js\");\n\nvar unsupportedIterableToArray = require(\"./unsupportedIterableToArray.js\");\n\nvar nonIterableSpread = require(\"./nonIterableSpread.js\");\n\nfunction _toConsumableArray(arr) {\n return arrayWithoutHoles(arr) || iterableToArray(arr) || unsupportedIterableToArray(arr) || nonIterableSpread();\n}\n\nmodule.exports = _toConsumableArray, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","var arrayLikeToArray = require(\"./arrayLikeToArray.js\");\n\nfunction _arrayWithoutHoles(arr) {\n if (Array.isArray(arr)) return arrayLikeToArray(arr);\n}\n\nmodule.exports = _arrayWithoutHoles, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","function _iterableToArray(iter) {\n if (typeof Symbol !== \"undefined\" && iter[Symbol.iterator] != null || iter[\"@@iterator\"] != null) return Array.from(iter);\n}\n\nmodule.exports = _iterableToArray, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","function _nonIterableSpread() {\n throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}\n\nmodule.exports = _nonIterableSpread, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","function _taggedTemplateLiteral(strings, raw) {\n if (!raw) {\n raw = strings.slice(0);\n }\n\n return Object.freeze(Object.defineProperties(strings, {\n raw: {\n value: Object.freeze(raw)\n }\n }));\n}\n\nmodule.exports = _taggedTemplateLiteral, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","function _typeof(obj) {\n \"@babel/helpers - typeof\";\n\n return (module.exports = _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (obj) {\n return typeof obj;\n } : function (obj) {\n return obj && \"function\" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj;\n }, module.exports.__esModule = true, module.exports[\"default\"] = module.exports), _typeof(obj);\n}\n\nmodule.exports = _typeof, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","function _defineProperty(obj, key, value) {\n if (key in obj) {\n Object.defineProperty(obj, key, {\n value: value,\n enumerable: true,\n configurable: true,\n writable: true\n });\n } else {\n obj[key] = value;\n }\n\n return obj;\n}\n\nmodule.exports = _defineProperty, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","function _extends() {\n module.exports = _extends = Object.assign ? Object.assign.bind() : function (target) {\n for (var i = 1; i < arguments.length; i++) {\n var source = arguments[i];\n\n for (var key in source) {\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n target[key] = source[key];\n }\n }\n }\n\n return target;\n }, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;\n return _extends.apply(this, arguments);\n}\n\nmodule.exports = _extends, module.exports.__esModule = true, module.exports[\"default\"] = module.exports;","'use strict';\n\nvar destroy = require('./plugin/destroy');\nvar initialize = require('./plugin/initialize');\nvar update = require('./plugin/update');\n\nmodule.exports = {\n initialize: initialize,\n update: update,\n destroy: destroy\n};\n","'use strict';\n\nvar _ = require('../lib/helper');\nvar dom = require('../lib/dom');\nvar instances = require('./instances');\n\nmodule.exports = function (element) {\n var i = instances.get(element);\n\n if (!i) {\n return;\n }\n\n i.event.unbindAll();\n dom.remove(i.scrollbarX);\n dom.remove(i.scrollbarY);\n dom.remove(i.scrollbarXRail);\n dom.remove(i.scrollbarYRail);\n _.removePsClasses(element);\n\n instances.remove(element);\n};\n","'use strict';\n\nmodule.exports = {\n handlers: ['click-rail', 'drag-scrollbar', 'keyboard', 'wheel', 'touch'],\n maxScrollbarLength: null,\n minScrollbarLength: null,\n scrollXMarginOffset: 0,\n scrollYMarginOffset: 0,\n suppressScrollX: false,\n suppressScrollY: false,\n swipePropagation: true,\n useBothWheelAxes: false,\n wheelPropagation: false,\n wheelSpeed: 1,\n theme: 'default'\n};\n","'use strict';\n\nvar EventElement = function (element) {\n this.element = element;\n this.events = {};\n};\n\nEventElement.prototype.bind = function (eventName, handler) {\n if (typeof this.events[eventName] === 'undefined') {\n this.events[eventName] = [];\n }\n this.events[eventName].push(handler);\n this.element.addEventListener(eventName, handler, false);\n};\n\nEventElement.prototype.unbind = function (eventName, handler) {\n var isHandlerProvided = (typeof handler !== 'undefined');\n this.events[eventName] = this.events[eventName].filter(function (hdlr) {\n if (isHandlerProvided && hdlr !== handler) {\n return true;\n }\n this.element.removeEventListener(eventName, hdlr, false);\n return false;\n }, this);\n};\n\nEventElement.prototype.unbindAll = function () {\n for (var name in this.events) {\n this.unbind(name);\n }\n};\n\nvar EventManager = function () {\n this.eventElements = [];\n};\n\nEventManager.prototype.eventElement = function (element) {\n var ee = this.eventElements.filter(function (eventElement) {\n return eventElement.element === element;\n })[0];\n if (typeof ee === 'undefined') {\n ee = new EventElement(element);\n this.eventElements.push(ee);\n }\n return ee;\n};\n\nEventManager.prototype.bind = function (element, eventName, handler) {\n this.eventElement(element).bind(eventName, handler);\n};\n\nEventManager.prototype.unbind = function (element, eventName, handler) {\n this.eventElement(element).unbind(eventName, handler);\n};\n\nEventManager.prototype.unbindAll = function () {\n for (var i = 0; i < this.eventElements.length; i++) {\n this.eventElements[i].unbindAll();\n }\n};\n\nEventManager.prototype.once = function (element, eventName, handler) {\n var ee = this.eventElement(element);\n var onceHandler = function (e) {\n ee.unbind(eventName, onceHandler);\n handler(e);\n };\n ee.bind(eventName, onceHandler);\n};\n\nmodule.exports = EventManager;\n","'use strict';\n\nmodule.exports = (function () {\n function s4() {\n return Math.floor((1 + Math.random()) * 0x10000)\n .toString(16)\n .substring(1);\n }\n return function () {\n return s4() + s4() + '-' + s4() + '-' + s4() + '-' +\n s4() + '-' + s4() + s4() + s4();\n };\n})();\n","'use strict';\n\nvar _ = require('../lib/helper');\nvar cls = require('../lib/class');\nvar instances = require('./instances');\nvar updateGeometry = require('./update-geometry');\n\n// Handlers\nvar handlers = {\n 'click-rail': require('./handler/click-rail'),\n 'drag-scrollbar': require('./handler/drag-scrollbar'),\n 'keyboard': require('./handler/keyboard'),\n 'wheel': require('./handler/mouse-wheel'),\n 'touch': require('./handler/touch'),\n 'selection': require('./handler/selection')\n};\nvar nativeScrollHandler = require('./handler/native-scroll');\n\nmodule.exports = function (element, userSettings) {\n userSettings = typeof userSettings === 'object' ? userSettings : {};\n\n cls.add(element, 'ps-container');\n\n // Create a plugin instance.\n var i = instances.add(element);\n\n i.settings = _.extend(i.settings, userSettings);\n cls.add(element, 'ps-theme-' + i.settings.theme);\n\n i.settings.handlers.forEach(function (handlerName) {\n handlers[handlerName](element);\n });\n\n nativeScrollHandler(element);\n\n updateGeometry(element);\n};\n","'use strict';\n\nvar instances = require('../instances');\nvar updateGeometry = require('../update-geometry');\nvar updateScroll = require('../update-scroll');\n\nfunction bindClickRailHandler(element, i) {\n function pageOffset(el) {\n return el.getBoundingClientRect();\n }\n var stopPropagation = function (e) { e.stopPropagation(); };\n\n i.event.bind(i.scrollbarY, 'click', stopPropagation);\n i.event.bind(i.scrollbarYRail, 'click', function (e) {\n var positionTop = e.pageY - window.pageYOffset - pageOffset(i.scrollbarYRail).top;\n var direction = positionTop > i.scrollbarYTop ? 1 : -1;\n\n updateScroll(element, 'top', element.scrollTop + direction * i.containerHeight);\n updateGeometry(element);\n\n e.stopPropagation();\n });\n\n i.event.bind(i.scrollbarX, 'click', stopPropagation);\n i.event.bind(i.scrollbarXRail, 'click', function (e) {\n var positionLeft = e.pageX - window.pageXOffset - pageOffset(i.scrollbarXRail).left;\n var direction = positionLeft > i.scrollbarXLeft ? 1 : -1;\n\n updateScroll(element, 'left', element.scrollLeft + direction * i.containerWidth);\n updateGeometry(element);\n\n e.stopPropagation();\n });\n}\n\nmodule.exports = function (element) {\n var i = instances.get(element);\n bindClickRailHandler(element, i);\n};\n","'use strict';\n\nvar _ = require('../../lib/helper');\nvar dom = require('../../lib/dom');\nvar instances = require('../instances');\nvar updateGeometry = require('../update-geometry');\nvar updateScroll = require('../update-scroll');\n\nfunction bindMouseScrollXHandler(element, i) {\n var currentLeft = null;\n var currentPageX = null;\n\n function updateScrollLeft(deltaX) {\n var newLeft = currentLeft + (deltaX * i.railXRatio);\n var maxLeft = Math.max(0, i.scrollbarXRail.getBoundingClientRect().left) + (i.railXRatio * (i.railXWidth - i.scrollbarXWidth));\n\n if (newLeft < 0) {\n i.scrollbarXLeft = 0;\n } else if (newLeft > maxLeft) {\n i.scrollbarXLeft = maxLeft;\n } else {\n i.scrollbarXLeft = newLeft;\n }\n\n var scrollLeft = _.toInt(i.scrollbarXLeft * (i.contentWidth - i.containerWidth) / (i.containerWidth - (i.railXRatio * i.scrollbarXWidth))) - i.negativeScrollAdjustment;\n updateScroll(element, 'left', scrollLeft);\n }\n\n var mouseMoveHandler = function (e) {\n updateScrollLeft(e.pageX - currentPageX);\n updateGeometry(element);\n e.stopPropagation();\n e.preventDefault();\n };\n\n var mouseUpHandler = function () {\n _.stopScrolling(element, 'x');\n i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler);\n };\n\n i.event.bind(i.scrollbarX, 'mousedown', function (e) {\n currentPageX = e.pageX;\n currentLeft = _.toInt(dom.css(i.scrollbarX, 'left')) * i.railXRatio;\n _.startScrolling(element, 'x');\n\n i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler);\n i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler);\n\n e.stopPropagation();\n e.preventDefault();\n });\n}\n\nfunction bindMouseScrollYHandler(element, i) {\n var currentTop = null;\n var currentPageY = null;\n\n function updateScrollTop(deltaY) {\n var newTop = currentTop + (deltaY * i.railYRatio);\n var maxTop = Math.max(0, i.scrollbarYRail.getBoundingClientRect().top) + (i.railYRatio * (i.railYHeight - i.scrollbarYHeight));\n\n if (newTop < 0) {\n i.scrollbarYTop = 0;\n } else if (newTop > maxTop) {\n i.scrollbarYTop = maxTop;\n } else {\n i.scrollbarYTop = newTop;\n }\n\n var scrollTop = _.toInt(i.scrollbarYTop * (i.contentHeight - i.containerHeight) / (i.containerHeight - (i.railYRatio * i.scrollbarYHeight)));\n updateScroll(element, 'top', scrollTop);\n }\n\n var mouseMoveHandler = function (e) {\n updateScrollTop(e.pageY - currentPageY);\n updateGeometry(element);\n e.stopPropagation();\n e.preventDefault();\n };\n\n var mouseUpHandler = function () {\n _.stopScrolling(element, 'y');\n i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler);\n };\n\n i.event.bind(i.scrollbarY, 'mousedown', function (e) {\n currentPageY = e.pageY;\n currentTop = _.toInt(dom.css(i.scrollbarY, 'top')) * i.railYRatio;\n _.startScrolling(element, 'y');\n\n i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler);\n i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler);\n\n e.stopPropagation();\n e.preventDefault();\n });\n}\n\nmodule.exports = function (element) {\n var i = instances.get(element);\n bindMouseScrollXHandler(element, i);\n bindMouseScrollYHandler(element, i);\n};\n","'use strict';\n\nvar _ = require('../../lib/helper');\nvar dom = require('../../lib/dom');\nvar instances = require('../instances');\nvar updateGeometry = require('../update-geometry');\nvar updateScroll = require('../update-scroll');\n\nfunction bindKeyboardHandler(element, i) {\n var hovered = false;\n i.event.bind(element, 'mouseenter', function () {\n hovered = true;\n });\n i.event.bind(element, 'mouseleave', function () {\n hovered = false;\n });\n\n var shouldPrevent = false;\n function shouldPreventDefault(deltaX, deltaY) {\n var scrollTop = element.scrollTop;\n if (deltaX === 0) {\n if (!i.scrollbarYActive) {\n return false;\n }\n if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) {\n return !i.settings.wheelPropagation;\n }\n }\n\n var scrollLeft = element.scrollLeft;\n if (deltaY === 0) {\n if (!i.scrollbarXActive) {\n return false;\n }\n if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) {\n return !i.settings.wheelPropagation;\n }\n }\n return true;\n }\n\n i.event.bind(i.ownerDocument, 'keydown', function (e) {\n if ((e.isDefaultPrevented && e.isDefaultPrevented()) || e.defaultPrevented) {\n return;\n }\n\n var focused = dom.matches(i.scrollbarX, ':focus') ||\n dom.matches(i.scrollbarY, ':focus');\n\n if (!hovered && !focused) {\n return;\n }\n\n var activeElement = document.activeElement ? document.activeElement : i.ownerDocument.activeElement;\n if (activeElement) {\n if (activeElement.tagName === 'IFRAME') {\n activeElement = activeElement.contentDocument.activeElement;\n } else {\n // go deeper if element is a webcomponent\n while (activeElement.shadowRoot) {\n activeElement = activeElement.shadowRoot.activeElement;\n }\n }\n if (_.isEditable(activeElement)) {\n return;\n }\n }\n\n var deltaX = 0;\n var deltaY = 0;\n\n switch (e.which) {\n case 37: // left\n if (e.metaKey) {\n deltaX = -i.contentWidth;\n } else if (e.altKey) {\n deltaX = -i.containerWidth;\n } else {\n deltaX = -30;\n }\n break;\n case 38: // up\n if (e.metaKey) {\n deltaY = i.contentHeight;\n } else if (e.altKey) {\n deltaY = i.containerHeight;\n } else {\n deltaY = 30;\n }\n break;\n case 39: // right\n if (e.metaKey) {\n deltaX = i.contentWidth;\n } else if (e.altKey) {\n deltaX = i.containerWidth;\n } else {\n deltaX = 30;\n }\n break;\n case 40: // down\n if (e.metaKey) {\n deltaY = -i.contentHeight;\n } else if (e.altKey) {\n deltaY = -i.containerHeight;\n } else {\n deltaY = -30;\n }\n break;\n case 33: // page up\n deltaY = 90;\n break;\n case 32: // space bar\n if (e.shiftKey) {\n deltaY = 90;\n } else {\n deltaY = -90;\n }\n break;\n case 34: // page down\n deltaY = -90;\n break;\n case 35: // end\n if (e.ctrlKey) {\n deltaY = -i.contentHeight;\n } else {\n deltaY = -i.containerHeight;\n }\n break;\n case 36: // home\n if (e.ctrlKey) {\n deltaY = element.scrollTop;\n } else {\n deltaY = i.containerHeight;\n }\n break;\n default:\n return;\n }\n\n updateScroll(element, 'top', element.scrollTop - deltaY);\n updateScroll(element, 'left', element.scrollLeft + deltaX);\n updateGeometry(element);\n\n shouldPrevent = shouldPreventDefault(deltaX, deltaY);\n if (shouldPrevent) {\n e.preventDefault();\n }\n });\n}\n\nmodule.exports = function (element) {\n var i = instances.get(element);\n bindKeyboardHandler(element, i);\n};\n","'use strict';\n\nvar instances = require('../instances');\nvar updateGeometry = require('../update-geometry');\nvar updateScroll = require('../update-scroll');\n\nfunction bindMouseWheelHandler(element, i) {\n var shouldPrevent = false;\n\n function shouldPreventDefault(deltaX, deltaY) {\n var scrollTop = element.scrollTop;\n if (deltaX === 0) {\n if (!i.scrollbarYActive) {\n return false;\n }\n if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) {\n return !i.settings.wheelPropagation;\n }\n }\n\n var scrollLeft = element.scrollLeft;\n if (deltaY === 0) {\n if (!i.scrollbarXActive) {\n return false;\n }\n if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) {\n return !i.settings.wheelPropagation;\n }\n }\n return true;\n }\n\n function getDeltaFromEvent(e) {\n var deltaX = e.deltaX;\n var deltaY = -1 * e.deltaY;\n\n if (typeof deltaX === \"undefined\" || typeof deltaY === \"undefined\") {\n // OS X Safari\n deltaX = -1 * e.wheelDeltaX / 6;\n deltaY = e.wheelDeltaY / 6;\n }\n\n if (e.deltaMode && e.deltaMode === 1) {\n // Firefox in deltaMode 1: Line scrolling\n deltaX *= 10;\n deltaY *= 10;\n }\n\n if (deltaX !== deltaX && deltaY !== deltaY/* NaN checks */) {\n // IE in some mouse drivers\n deltaX = 0;\n deltaY = e.wheelDelta;\n }\n\n if (e.shiftKey) {\n // reverse axis with shift key\n return [-deltaY, -deltaX];\n }\n return [deltaX, deltaY];\n }\n\n function shouldBeConsumedByChild(deltaX, deltaY) {\n var child = element.querySelector('textarea:hover, select[multiple]:hover, .ps-child:hover');\n if (child) {\n if (!window.getComputedStyle(child).overflow.match(/(scroll|auto)/)) {\n // if not scrollable\n return false;\n }\n\n var maxScrollTop = child.scrollHeight - child.clientHeight;\n if (maxScrollTop > 0) {\n if (!(child.scrollTop === 0 && deltaY > 0) && !(child.scrollTop === maxScrollTop && deltaY < 0)) {\n return true;\n }\n }\n var maxScrollLeft = child.scrollLeft - child.clientWidth;\n if (maxScrollLeft > 0) {\n if (!(child.scrollLeft === 0 && deltaX < 0) && !(child.scrollLeft === maxScrollLeft && deltaX > 0)) {\n return true;\n }\n }\n }\n return false;\n }\n\n function mousewheelHandler(e) {\n var delta = getDeltaFromEvent(e);\n\n var deltaX = delta[0];\n var deltaY = delta[1];\n\n if (shouldBeConsumedByChild(deltaX, deltaY)) {\n return;\n }\n\n shouldPrevent = false;\n if (!i.settings.useBothWheelAxes) {\n // deltaX will only be used for horizontal scrolling and deltaY will\n // only be used for vertical scrolling - this is the default\n updateScroll(element, 'top', element.scrollTop - (deltaY * i.settings.wheelSpeed));\n updateScroll(element, 'left', element.scrollLeft + (deltaX * i.settings.wheelSpeed));\n } else if (i.scrollbarYActive && !i.scrollbarXActive) {\n // only vertical scrollbar is active and useBothWheelAxes option is\n // active, so let's scroll vertical bar using both mouse wheel axes\n if (deltaY) {\n updateScroll(element, 'top', element.scrollTop - (deltaY * i.settings.wheelSpeed));\n } else {\n updateScroll(element, 'top', element.scrollTop + (deltaX * i.settings.wheelSpeed));\n }\n shouldPrevent = true;\n } else if (i.scrollbarXActive && !i.scrollbarYActive) {\n // useBothWheelAxes and only horizontal bar is active, so use both\n // wheel axes for horizontal bar\n if (deltaX) {\n updateScroll(element, 'left', element.scrollLeft + (deltaX * i.settings.wheelSpeed));\n } else {\n updateScroll(element, 'left', element.scrollLeft - (deltaY * i.settings.wheelSpeed));\n }\n shouldPrevent = true;\n }\n\n updateGeometry(element);\n\n shouldPrevent = (shouldPrevent || shouldPreventDefault(deltaX, deltaY));\n if (shouldPrevent) {\n e.stopPropagation();\n e.preventDefault();\n }\n }\n\n if (typeof window.onwheel !== \"undefined\") {\n i.event.bind(element, 'wheel', mousewheelHandler);\n } else if (typeof window.onmousewheel !== \"undefined\") {\n i.event.bind(element, 'mousewheel', mousewheelHandler);\n }\n}\n\nmodule.exports = function (element) {\n var i = instances.get(element);\n bindMouseWheelHandler(element, i);\n};\n","'use strict';\n\nvar _ = require('../../lib/helper');\nvar instances = require('../instances');\nvar updateGeometry = require('../update-geometry');\nvar updateScroll = require('../update-scroll');\n\nfunction bindTouchHandler(element, i, supportsTouch, supportsIePointer) {\n function shouldPreventDefault(deltaX, deltaY) {\n var scrollTop = element.scrollTop;\n var scrollLeft = element.scrollLeft;\n var magnitudeX = Math.abs(deltaX);\n var magnitudeY = Math.abs(deltaY);\n\n if (magnitudeY > magnitudeX) {\n // user is perhaps trying to swipe up/down the page\n\n if (((deltaY < 0) && (scrollTop === i.contentHeight - i.containerHeight)) ||\n ((deltaY > 0) && (scrollTop === 0))) {\n return !i.settings.swipePropagation;\n }\n } else if (magnitudeX > magnitudeY) {\n // user is perhaps trying to swipe left/right across the page\n\n if (((deltaX < 0) && (scrollLeft === i.contentWidth - i.containerWidth)) ||\n ((deltaX > 0) && (scrollLeft === 0))) {\n return !i.settings.swipePropagation;\n }\n }\n\n return true;\n }\n\n function applyTouchMove(differenceX, differenceY) {\n updateScroll(element, 'top', element.scrollTop - differenceY);\n updateScroll(element, 'left', element.scrollLeft - differenceX);\n\n updateGeometry(element);\n }\n\n var startOffset = {};\n var startTime = 0;\n var speed = {};\n var easingLoop = null;\n var inGlobalTouch = false;\n var inLocalTouch = false;\n\n function globalTouchStart() {\n inGlobalTouch = true;\n }\n function globalTouchEnd() {\n inGlobalTouch = false;\n }\n\n function getTouch(e) {\n if (e.targetTouches) {\n return e.targetTouches[0];\n } else {\n // Maybe IE pointer\n return e;\n }\n }\n function shouldHandle(e) {\n if (e.targetTouches && e.targetTouches.length === 1) {\n return true;\n }\n if (e.pointerType && e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {\n return true;\n }\n return false;\n }\n function touchStart(e) {\n if (shouldHandle(e)) {\n inLocalTouch = true;\n\n var touch = getTouch(e);\n\n startOffset.pageX = touch.pageX;\n startOffset.pageY = touch.pageY;\n\n startTime = (new Date()).getTime();\n\n if (easingLoop !== null) {\n clearInterval(easingLoop);\n }\n\n e.stopPropagation();\n }\n }\n function touchMove(e) {\n if (!inLocalTouch && i.settings.swipePropagation) {\n touchStart(e);\n }\n if (!inGlobalTouch && inLocalTouch && shouldHandle(e)) {\n var touch = getTouch(e);\n\n var currentOffset = {pageX: touch.pageX, pageY: touch.pageY};\n\n var differenceX = currentOffset.pageX - startOffset.pageX;\n var differenceY = currentOffset.pageY - startOffset.pageY;\n\n applyTouchMove(differenceX, differenceY);\n startOffset = currentOffset;\n\n var currentTime = (new Date()).getTime();\n\n var timeGap = currentTime - startTime;\n if (timeGap > 0) {\n speed.x = differenceX / timeGap;\n speed.y = differenceY / timeGap;\n startTime = currentTime;\n }\n\n if (shouldPreventDefault(differenceX, differenceY)) {\n e.stopPropagation();\n e.preventDefault();\n }\n }\n }\n function touchEnd() {\n if (!inGlobalTouch && inLocalTouch) {\n inLocalTouch = false;\n\n clearInterval(easingLoop);\n easingLoop = setInterval(function () {\n if (!instances.get(element)) {\n clearInterval(easingLoop);\n return;\n }\n\n if (!speed.x && !speed.y) {\n clearInterval(easingLoop);\n return;\n }\n\n if (Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) {\n clearInterval(easingLoop);\n return;\n }\n\n applyTouchMove(speed.x * 30, speed.y * 30);\n\n speed.x *= 0.8;\n speed.y *= 0.8;\n }, 10);\n }\n }\n\n if (supportsTouch) {\n i.event.bind(window, 'touchstart', globalTouchStart);\n i.event.bind(window, 'touchend', globalTouchEnd);\n i.event.bind(element, 'touchstart', touchStart);\n i.event.bind(element, 'touchmove', touchMove);\n i.event.bind(element, 'touchend', touchEnd);\n }\n\n if (supportsIePointer) {\n if (window.PointerEvent) {\n i.event.bind(window, 'pointerdown', globalTouchStart);\n i.event.bind(window, 'pointerup', globalTouchEnd);\n i.event.bind(element, 'pointerdown', touchStart);\n i.event.bind(element, 'pointermove', touchMove);\n i.event.bind(element, 'pointerup', touchEnd);\n } else if (window.MSPointerEvent) {\n i.event.bind(window, 'MSPointerDown', globalTouchStart);\n i.event.bind(window, 'MSPointerUp', globalTouchEnd);\n i.event.bind(element, 'MSPointerDown', touchStart);\n i.event.bind(element, 'MSPointerMove', touchMove);\n i.event.bind(element, 'MSPointerUp', touchEnd);\n }\n }\n}\n\nmodule.exports = function (element) {\n if (!_.env.supportsTouch && !_.env.supportsIePointer) {\n return;\n }\n\n var i = instances.get(element);\n bindTouchHandler(element, i, _.env.supportsTouch, _.env.supportsIePointer);\n};\n","'use strict';\n\nvar _ = require('../../lib/helper');\nvar instances = require('../instances');\nvar updateGeometry = require('../update-geometry');\nvar updateScroll = require('../update-scroll');\n\nfunction bindSelectionHandler(element, i) {\n function getRangeNode() {\n var selection = window.getSelection ? window.getSelection() :\n document.getSelection ? document.getSelection() : '';\n if (selection.toString().length === 0) {\n return null;\n } else {\n return selection.getRangeAt(0).commonAncestorContainer;\n }\n }\n\n var scrollingLoop = null;\n var scrollDiff = {top: 0, left: 0};\n function startScrolling() {\n if (!scrollingLoop) {\n scrollingLoop = setInterval(function () {\n if (!instances.get(element)) {\n clearInterval(scrollingLoop);\n return;\n }\n\n updateScroll(element, 'top', element.scrollTop + scrollDiff.top);\n updateScroll(element, 'left', element.scrollLeft + scrollDiff.left);\n updateGeometry(element);\n }, 50); // every .1 sec\n }\n }\n function stopScrolling() {\n if (scrollingLoop) {\n clearInterval(scrollingLoop);\n scrollingLoop = null;\n }\n _.stopScrolling(element);\n }\n\n var isSelected = false;\n i.event.bind(i.ownerDocument, 'selectionchange', function () {\n if (element.contains(getRangeNode())) {\n isSelected = true;\n } else {\n isSelected = false;\n stopScrolling();\n }\n });\n i.event.bind(window, 'mouseup', function () {\n if (isSelected) {\n isSelected = false;\n stopScrolling();\n }\n });\n i.event.bind(window, 'keyup', function () {\n if (isSelected) {\n isSelected = false;\n stopScrolling();\n }\n });\n\n i.event.bind(window, 'mousemove', function (e) {\n if (isSelected) {\n var mousePosition = {x: e.pageX, y: e.pageY};\n var containerGeometry = {\n left: element.offsetLeft,\n right: element.offsetLeft + element.offsetWidth,\n top: element.offsetTop,\n bottom: element.offsetTop + element.offsetHeight\n };\n\n if (mousePosition.x < containerGeometry.left + 3) {\n scrollDiff.left = -5;\n _.startScrolling(element, 'x');\n } else if (mousePosition.x > containerGeometry.right - 3) {\n scrollDiff.left = 5;\n _.startScrolling(element, 'x');\n } else {\n scrollDiff.left = 0;\n }\n\n if (mousePosition.y < containerGeometry.top + 3) {\n if (containerGeometry.top + 3 - mousePosition.y < 5) {\n scrollDiff.top = -5;\n } else {\n scrollDiff.top = -20;\n }\n _.startScrolling(element, 'y');\n } else if (mousePosition.y > containerGeometry.bottom - 3) {\n if (mousePosition.y - containerGeometry.bottom + 3 < 5) {\n scrollDiff.top = 5;\n } else {\n scrollDiff.top = 20;\n }\n _.startScrolling(element, 'y');\n } else {\n scrollDiff.top = 0;\n }\n\n if (scrollDiff.top === 0 && scrollDiff.left === 0) {\n stopScrolling();\n } else {\n startScrolling();\n }\n }\n });\n}\n\nmodule.exports = function (element) {\n var i = instances.get(element);\n bindSelectionHandler(element, i);\n};\n","'use strict';\n\nvar instances = require('../instances');\nvar updateGeometry = require('../update-geometry');\n\nfunction bindNativeScrollHandler(element, i) {\n i.event.bind(element, 'scroll', function () {\n updateGeometry(element);\n });\n}\n\nmodule.exports = function (element) {\n var i = instances.get(element);\n bindNativeScrollHandler(element, i);\n};\n","'use strict';\n\nvar _ = require('../lib/helper');\nvar dom = require('../lib/dom');\nvar instances = require('./instances');\nvar updateGeometry = require('./update-geometry');\nvar updateScroll = require('./update-scroll');\n\nmodule.exports = function (element) {\n var i = instances.get(element);\n\n if (!i) {\n return;\n }\n\n // Recalcuate negative scrollLeft adjustment\n i.negativeScrollAdjustment = i.isNegativeScroll ? element.scrollWidth - element.clientWidth : 0;\n\n // Recalculate rail margins\n dom.css(i.scrollbarXRail, 'display', 'block');\n dom.css(i.scrollbarYRail, 'display', 'block');\n i.railXMarginWidth = _.toInt(dom.css(i.scrollbarXRail, 'marginLeft')) + _.toInt(dom.css(i.scrollbarXRail, 'marginRight'));\n i.railYMarginHeight = _.toInt(dom.css(i.scrollbarYRail, 'marginTop')) + _.toInt(dom.css(i.scrollbarYRail, 'marginBottom'));\n\n // Hide scrollbars not to affect scrollWidth and scrollHeight\n dom.css(i.scrollbarXRail, 'display', 'none');\n dom.css(i.scrollbarYRail, 'display', 'none');\n\n updateGeometry(element);\n\n // Update top/left scroll to trigger events\n updateScroll(element, 'top', element.scrollTop);\n updateScroll(element, 'left', element.scrollLeft);\n\n dom.css(i.scrollbarXRail, 'display', '');\n dom.css(i.scrollbarYRail, 'display', '');\n};\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar _1 = require(\"./\");\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\nvar should_polyfill_1 = require(\"./should-polyfill\");\nvar to_locale_string_1 = require(\"./src/to_locale_string\");\nif (should_polyfill_1.shouldPolyfill()) {\n ecma402_abstract_1.defineProperty(Intl, 'DateTimeFormat', { value: _1.DateTimeFormat });\n ecma402_abstract_1.defineProperty(Date.prototype, 'toLocaleString', {\n value: function toLocaleString(locales, options) {\n return to_locale_string_1.toLocaleString(this, locales, options);\n },\n });\n ecma402_abstract_1.defineProperty(Date.prototype, 'toLocaleDateString', {\n value: function toLocaleDateString(locales, options) {\n return to_locale_string_1.toLocaleDateString(this, locales, options);\n },\n });\n ecma402_abstract_1.defineProperty(Date.prototype, 'toLocaleTimeString', {\n value: function toLocaleTimeString(locales, options) {\n return to_locale_string_1.toLocaleTimeString(this, locales, options);\n },\n });\n}\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar tslib_1 = require(\"tslib\");\ntslib_1.__exportStar(require(\"./src/core\"), exports);\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.LookupMatcher = void 0;\nvar utils_1 = require(\"./utils\");\nvar BestAvailableLocale_1 = require(\"./BestAvailableLocale\");\n/**\n * https://tc39.es/ecma402/#sec-lookupmatcher\n * @param availableLocales\n * @param requestedLocales\n * @param getDefaultLocale\n */\nfunction LookupMatcher(availableLocales, requestedLocales, getDefaultLocale) {\n var result = { locale: '' };\n for (var _i = 0, requestedLocales_1 = requestedLocales; _i < requestedLocales_1.length; _i++) {\n var locale = requestedLocales_1[_i];\n var noExtensionLocale = locale.replace(utils_1.UNICODE_EXTENSION_SEQUENCE_REGEX, '');\n var availableLocale = BestAvailableLocale_1.BestAvailableLocale(availableLocales, noExtensionLocale);\n if (availableLocale) {\n result.locale = availableLocale;\n if (locale !== noExtensionLocale) {\n result.extension = locale.slice(noExtensionLocale.length + 1, locale.length);\n }\n return result;\n }\n }\n result.locale = getDefaultLocale();\n return result;\n}\nexports.LookupMatcher = LookupMatcher;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.BestFitMatcher = void 0;\nvar BestAvailableLocale_1 = require(\"./BestAvailableLocale\");\nvar utils_1 = require(\"./utils\");\n/**\n * https://tc39.es/ecma402/#sec-bestfitmatcher\n * @param availableLocales\n * @param requestedLocales\n * @param getDefaultLocale\n */\nfunction BestFitMatcher(availableLocales, requestedLocales, getDefaultLocale) {\n var minimizedAvailableLocaleMap = Array.from(availableLocales).reduce(function (all, l) {\n all[l] = l;\n return all;\n }, {});\n var minimizedAvailableLocales = new Set();\n availableLocales.forEach(function (locale) {\n var minimizedLocale = new Intl.Locale(locale)\n .minimize()\n .toString();\n minimizedAvailableLocaleMap[minimizedLocale] = locale;\n minimizedAvailableLocales.add(minimizedLocale);\n });\n var foundLocale;\n for (var _i = 0, requestedLocales_1 = requestedLocales; _i < requestedLocales_1.length; _i++) {\n var l = requestedLocales_1[_i];\n if (foundLocale) {\n break;\n }\n var noExtensionLocale = l.replace(utils_1.UNICODE_EXTENSION_SEQUENCE_REGEX, '');\n if (availableLocales.has(noExtensionLocale)) {\n foundLocale = noExtensionLocale;\n break;\n }\n if (minimizedAvailableLocales.has(noExtensionLocale)) {\n foundLocale = noExtensionLocale;\n break;\n }\n var locale = new Intl.Locale(noExtensionLocale);\n var maximizedRequestedLocale = locale.maximize().toString();\n var minimizedRequestedLocale = locale.minimize().toString();\n // Check minimized locale\n if (minimizedAvailableLocales.has(minimizedRequestedLocale)) {\n foundLocale = minimizedRequestedLocale;\n break;\n }\n // Lookup algo on maximized locale\n foundLocale = BestAvailableLocale_1.BestAvailableLocale(minimizedAvailableLocales, maximizedRequestedLocale);\n }\n return {\n locale: (foundLocale && minimizedAvailableLocaleMap[foundLocale]) ||\n getDefaultLocale(),\n };\n}\nexports.BestFitMatcher = BestFitMatcher;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.UnicodeExtensionValue = void 0;\nvar utils_1 = require(\"./utils\");\n/**\n * https://tc39.es/ecma402/#sec-unicodeextensionvalue\n * @param extension\n * @param key\n */\nfunction UnicodeExtensionValue(extension, key) {\n utils_1.invariant(key.length === 2, 'key must have 2 elements');\n var size = extension.length;\n var searchValue = \"-\" + key + \"-\";\n var pos = extension.indexOf(searchValue);\n if (pos !== -1) {\n var start = pos + 4;\n var end = start;\n var k = start;\n var done = false;\n while (!done) {\n var e = extension.indexOf('-', k);\n var len = void 0;\n if (e === -1) {\n len = size - k;\n }\n else {\n len = e - k;\n }\n if (len === 2) {\n done = true;\n }\n else if (e === -1) {\n end = size;\n done = true;\n }\n else {\n end = e;\n k = e + 1;\n }\n }\n return extension.slice(start, end);\n }\n searchValue = \"-\" + key;\n pos = extension.indexOf(searchValue);\n if (pos !== -1 && pos + 3 === size) {\n return '';\n }\n return undefined;\n}\nexports.UnicodeExtensionValue = UnicodeExtensionValue;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.LookupSupportedLocales = void 0;\nvar utils_1 = require(\"./utils\");\nvar BestAvailableLocale_1 = require(\"./BestAvailableLocale\");\n/**\n * https://tc39.es/ecma402/#sec-lookupsupportedlocales\n * @param availableLocales\n * @param requestedLocales\n */\nfunction LookupSupportedLocales(availableLocales, requestedLocales) {\n var subset = [];\n for (var _i = 0, requestedLocales_1 = requestedLocales; _i < requestedLocales_1.length; _i++) {\n var locale = requestedLocales_1[_i];\n var noExtensionLocale = locale.replace(utils_1.UNICODE_EXTENSION_SEQUENCE_REGEX, '');\n var availableLocale = BestAvailableLocale_1.BestAvailableLocale(availableLocales, noExtensionLocale);\n if (availableLocale) {\n subset.push(availableLocale);\n }\n }\n return subset;\n}\nexports.LookupSupportedLocales = LookupSupportedLocales;\n","\"use strict\";\n// Type-only circular import\n// eslint-disable-next-line import/no-cycle\nObject.defineProperty(exports, \"__esModule\", { value: true });\nvar internalSlotMap = new WeakMap();\nfunction getInternalSlots(x) {\n var internalSlots = internalSlotMap.get(x);\n if (!internalSlots) {\n internalSlots = Object.create(null);\n internalSlotMap.set(x, internalSlots);\n }\n return internalSlots;\n}\nexports.default = getInternalSlots;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\n// @generated\n// prettier-ignore\nexports.default = {\n \"Africa/Asmera\": \"Africa/Nairobi\",\n \"Africa/Timbuktu\": \"Africa/Abidjan\",\n \"America/Argentina/ComodRivadavia\": \"America/Argentina/Catamarca\",\n \"America/Atka\": \"America/Adak\",\n \"America/Buenos_Aires\": \"America/Argentina/Buenos_Aires\",\n \"America/Catamarca\": \"America/Argentina/Catamarca\",\n \"America/Coral_Harbour\": \"America/Atikokan\",\n \"America/Cordoba\": \"America/Argentina/Cordoba\",\n \"America/Ensenada\": \"America/Tijuana\",\n \"America/Fort_Wayne\": \"America/Indiana/Indianapolis\",\n \"America/Godthab\": \"America/Nuuk\",\n \"America/Indianapolis\": \"America/Indiana/Indianapolis\",\n \"America/Jujuy\": \"America/Argentina/Jujuy\",\n \"America/Knox_IN\": \"America/Indiana/Knox\",\n \"America/Louisville\": \"America/Kentucky/Louisville\",\n \"America/Mendoza\": \"America/Argentina/Mendoza\",\n \"America/Montreal\": \"America/Toronto\",\n \"America/Porto_Acre\": \"America/Rio_Branco\",\n \"America/Rosario\": \"America/Argentina/Cordoba\",\n \"America/Santa_Isabel\": \"America/Tijuana\",\n \"America/Shiprock\": \"America/Denver\",\n \"America/Virgin\": \"America/Port_of_Spain\",\n \"Antarctica/South_Pole\": \"Pacific/Auckland\",\n \"Asia/Ashkhabad\": \"Asia/Ashgabat\",\n \"Asia/Calcutta\": \"Asia/Kolkata\",\n \"Asia/Chongqing\": \"Asia/Shanghai\",\n \"Asia/Chungking\": \"Asia/Shanghai\",\n \"Asia/Dacca\": \"Asia/Dhaka\",\n \"Asia/Harbin\": \"Asia/Shanghai\",\n \"Asia/Kashgar\": \"Asia/Urumqi\",\n \"Asia/Katmandu\": \"Asia/Kathmandu\",\n \"Asia/Macao\": \"Asia/Macau\",\n \"Asia/Rangoon\": \"Asia/Yangon\",\n \"Asia/Saigon\": \"Asia/Ho_Chi_Minh\",\n \"Asia/Tel_Aviv\": \"Asia/Jerusalem\",\n \"Asia/Thimbu\": \"Asia/Thimphu\",\n \"Asia/Ujung_Pandang\": \"Asia/Makassar\",\n \"Asia/Ulan_Bator\": \"Asia/Ulaanbaatar\",\n \"Atlantic/Faeroe\": \"Atlantic/Faroe\",\n \"Atlantic/Jan_Mayen\": \"Europe/Oslo\",\n \"Australia/ACT\": \"Australia/Sydney\",\n \"Australia/Canberra\": \"Australia/Sydney\",\n \"Australia/Currie\": \"Australia/Hobart\",\n \"Australia/LHI\": \"Australia/Lord_Howe\",\n \"Australia/NSW\": \"Australia/Sydney\",\n \"Australia/North\": \"Australia/Darwin\",\n \"Australia/Queensland\": \"Australia/Brisbane\",\n \"Australia/South\": \"Australia/Adelaide\",\n \"Australia/Tasmania\": \"Australia/Hobart\",\n \"Australia/Victoria\": \"Australia/Melbourne\",\n \"Australia/West\": \"Australia/Perth\",\n \"Australia/Yancowinna\": \"Australia/Broken_Hill\",\n \"Brazil/Acre\": \"America/Rio_Branco\",\n \"Brazil/DeNoronha\": \"America/Noronha\",\n \"Brazil/East\": \"America/Sao_Paulo\",\n \"Brazil/West\": \"America/Manaus\",\n \"Canada/Atlantic\": \"America/Halifax\",\n \"Canada/Central\": \"America/Winnipeg\",\n \"Canada/Eastern\": \"America/Toronto\",\n \"Canada/Mountain\": \"America/Edmonton\",\n \"Canada/Newfoundland\": \"America/St_Johns\",\n \"Canada/Pacific\": \"America/Vancouver\",\n \"Canada/Saskatchewan\": \"America/Regina\",\n \"Canada/Yukon\": \"America/Whitehorse\",\n \"Chile/Continental\": \"America/Santiago\",\n \"Chile/EasterIsland\": \"Pacific/Easter\",\n \"Cuba\": \"America/Havana\",\n \"Egypt\": \"Africa/Cairo\",\n \"Eire\": \"Europe/Dublin\",\n \"Etc/UCT\": \"Etc/UTC\",\n \"Europe/Belfast\": \"Europe/London\",\n \"Europe/Tiraspol\": \"Europe/Chisinau\",\n \"GB\": \"Europe/London\",\n \"GB-Eire\": \"Europe/London\",\n \"GMT+0\": \"Etc/GMT\",\n \"GMT-0\": \"Etc/GMT\",\n \"GMT0\": \"Etc/GMT\",\n \"Greenwich\": \"Etc/GMT\",\n \"Hongkong\": \"Asia/Hong_Kong\",\n \"Iceland\": \"Atlantic/Reykjavik\",\n \"Iran\": \"Asia/Tehran\",\n \"Israel\": \"Asia/Jerusalem\",\n \"Jamaica\": \"America/Jamaica\",\n \"Japan\": \"Asia/Tokyo\",\n \"Kwajalein\": \"Pacific/Kwajalein\",\n \"Libya\": \"Africa/Tripoli\",\n \"Mexico/BajaNorte\": \"America/Tijuana\",\n \"Mexico/BajaSur\": \"America/Mazatlan\",\n \"Mexico/General\": \"America/Mexico_City\",\n \"NZ\": \"Pacific/Auckland\",\n \"NZ-CHAT\": \"Pacific/Chatham\",\n \"Navajo\": \"America/Denver\",\n \"PRC\": \"Asia/Shanghai\",\n \"Pacific/Johnston\": \"Pacific/Honolulu\",\n \"Pacific/Ponape\": \"Pacific/Pohnpei\",\n \"Pacific/Samoa\": \"Pacific/Pago_Pago\",\n \"Pacific/Truk\": \"Pacific/Chuuk\",\n \"Pacific/Yap\": \"Pacific/Chuuk\",\n \"Poland\": \"Europe/Warsaw\",\n \"Portugal\": \"Europe/Lisbon\",\n \"ROC\": \"Asia/Taipei\",\n \"ROK\": \"Asia/Seoul\",\n \"Singapore\": \"Asia/Singapore\",\n \"Turkey\": \"Europe/Istanbul\",\n \"UCT\": \"Etc/UTC\",\n \"US/Alaska\": \"America/Anchorage\",\n \"US/Aleutian\": \"America/Adak\",\n \"US/Arizona\": \"America/Phoenix\",\n \"US/Central\": \"America/Chicago\",\n \"US/East-Indiana\": \"America/Indiana/Indianapolis\",\n \"US/Eastern\": \"America/New_York\",\n \"US/Hawaii\": \"Pacific/Honolulu\",\n \"US/Indiana-Starke\": \"America/Indiana/Knox\",\n \"US/Michigan\": \"America/Detroit\",\n \"US/Mountain\": \"America/Denver\",\n \"US/Pacific\": \"America/Los_Angeles\",\n \"US/Samoa\": \"Pacific/Pago_Pago\",\n \"UTC\": \"Etc/UTC\",\n \"Universal\": \"Etc/UTC\",\n \"W-SU\": \"Europe/Moscow\",\n \"Zulu\": \"Etc/UTC\"\n};\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.unpack = exports.pack = void 0;\nvar tslib_1 = require(\"tslib\");\nfunction pack(data) {\n var zoneNames = Object.keys(data.zones);\n zoneNames.sort(); // so output is stable\n return {\n zones: zoneNames.map(function (zone) {\n return tslib_1.__spreadArray([\n zone\n ], data.zones[zone].map(function (_a) {\n var ts = _a[0], others = _a.slice(1);\n return tslib_1.__spreadArray([ts === '' ? '' : ts.toString(36)], others).join(',');\n })).join('|');\n }),\n abbrvs: data.abbrvs.join('|'),\n offsets: data.offsets.map(function (o) { return o.toString(36); }).join('|'),\n };\n}\nexports.pack = pack;\nfunction unpack(data) {\n var abbrvs = data.abbrvs.split('|');\n var offsets = data.offsets.split('|').map(function (n) { return parseInt(n, 36); });\n var packedZones = data.zones;\n var zones = {};\n for (var _i = 0, packedZones_1 = packedZones; _i < packedZones_1.length; _i++) {\n var d = packedZones_1[_i];\n var _a = d.split('|'), zone = _a[0], zoneData = _a.slice(1);\n zones[zone] = zoneData\n .map(function (z) { return z.split(','); })\n .map(function (_a) {\n var ts = _a[0], abbrvIndex = _a[1], offsetIndex = _a[2], dst = _a[3];\n return [\n ts === '' ? -Infinity : parseInt(ts, 36),\n abbrvs[+abbrvIndex],\n offsets[+offsetIndex],\n dst === '1',\n ];\n });\n }\n return zones;\n}\nexports.unpack = unpack;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.FormatDateTime = void 0;\nvar PartitionDateTimePattern_1 = require(\"./PartitionDateTimePattern\");\n/**\n * https://tc39.es/ecma402/#sec-formatdatetime\n * @param dtf DateTimeFormat\n * @param x\n */\nfunction FormatDateTime(dtf, x, implDetails) {\n var parts = PartitionDateTimePattern_1.PartitionDateTimePattern(dtf, x, implDetails);\n var result = '';\n for (var _i = 0, parts_1 = parts; _i < parts_1.length; _i++) {\n var part = parts_1[_i];\n result += part.value;\n }\n return result;\n}\nexports.FormatDateTime = FormatDateTime;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.InitializeDateTimeFormat = void 0;\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\nvar BasicFormatMatcher_1 = require(\"./BasicFormatMatcher\");\nvar BestFitFormatMatcher_1 = require(\"./BestFitFormatMatcher\");\nvar utils_1 = require(\"./utils\");\nvar DateTimeStyleFormat_1 = require(\"./DateTimeStyleFormat\");\nvar ToDateTimeOptions_1 = require(\"./ToDateTimeOptions\");\nvar intl_localematcher_1 = require(\"@formatjs/intl-localematcher\");\nfunction isTimeRelated(opt) {\n for (var _i = 0, _a = ['hour', 'minute', 'second']; _i < _a.length; _i++) {\n var prop = _a[_i];\n var value = opt[prop];\n if (value !== undefined) {\n return true;\n }\n }\n return false;\n}\nfunction resolveHourCycle(hc, hcDefault, hour12) {\n if (hc == null) {\n hc = hcDefault;\n }\n if (hour12 !== undefined) {\n if (hour12) {\n if (hcDefault === 'h11' || hcDefault === 'h23') {\n hc = 'h11';\n }\n else {\n hc = 'h12';\n }\n }\n else {\n ecma402_abstract_1.invariant(!hour12, 'hour12 must not be set');\n if (hcDefault === 'h11' || hcDefault === 'h23') {\n hc = 'h23';\n }\n else {\n hc = 'h24';\n }\n }\n }\n return hc;\n}\nvar TYPE_REGEX = /^[a-z0-9]{3,8}$/i;\n/**\n * https://tc39.es/ecma402/#sec-initializedatetimeformat\n * @param dtf DateTimeFormat\n * @param locales locales\n * @param opts options\n */\nfunction InitializeDateTimeFormat(dtf, locales, opts, _a) {\n var getInternalSlots = _a.getInternalSlots, availableLocales = _a.availableLocales, localeData = _a.localeData, getDefaultLocale = _a.getDefaultLocale, getDefaultTimeZone = _a.getDefaultTimeZone, relevantExtensionKeys = _a.relevantExtensionKeys, tzData = _a.tzData, uppercaseLinks = _a.uppercaseLinks;\n // @ts-ignore\n var requestedLocales = ecma402_abstract_1.CanonicalizeLocaleList(locales);\n var options = ToDateTimeOptions_1.ToDateTimeOptions(opts, 'any', 'date');\n var opt = Object.create(null);\n var matcher = ecma402_abstract_1.GetOption(options, 'localeMatcher', 'string', ['lookup', 'best fit'], 'best fit');\n opt.localeMatcher = matcher;\n var calendar = ecma402_abstract_1.GetOption(options, 'calendar', 'string', undefined, undefined);\n if (calendar !== undefined && !TYPE_REGEX.test(calendar)) {\n throw new RangeError('Malformed calendar');\n }\n var internalSlots = getInternalSlots(dtf);\n opt.ca = calendar;\n var numberingSystem = ecma402_abstract_1.GetOption(options, 'numberingSystem', 'string', undefined, undefined);\n if (numberingSystem !== undefined && !TYPE_REGEX.test(numberingSystem)) {\n throw new RangeError('Malformed numbering system');\n }\n opt.nu = numberingSystem;\n var hour12 = ecma402_abstract_1.GetOption(options, 'hour12', 'boolean', undefined, undefined);\n var hourCycle = ecma402_abstract_1.GetOption(options, 'hourCycle', 'string', ['h11', 'h12', 'h23', 'h24'], undefined);\n if (hour12 !== undefined) {\n // @ts-ignore\n hourCycle = null;\n }\n opt.hc = hourCycle;\n var r = intl_localematcher_1.ResolveLocale(availableLocales, requestedLocales, opt, relevantExtensionKeys, localeData, getDefaultLocale);\n internalSlots.locale = r.locale;\n calendar = r.ca;\n internalSlots.calendar = calendar;\n internalSlots.hourCycle = r.hc;\n internalSlots.numberingSystem = r.nu;\n var dataLocale = r.dataLocale;\n internalSlots.dataLocale = dataLocale;\n var timeZone = options.timeZone;\n if (timeZone !== undefined) {\n timeZone = String(timeZone);\n if (!ecma402_abstract_1.IsValidTimeZoneName(timeZone, { tzData: tzData, uppercaseLinks: uppercaseLinks })) {\n throw new RangeError('Invalid timeZoneName');\n }\n timeZone = ecma402_abstract_1.CanonicalizeTimeZoneName(timeZone, { tzData: tzData, uppercaseLinks: uppercaseLinks });\n }\n else {\n timeZone = getDefaultTimeZone();\n }\n internalSlots.timeZone = timeZone;\n opt = Object.create(null);\n opt.weekday = ecma402_abstract_1.GetOption(options, 'weekday', 'string', ['narrow', 'short', 'long'], undefined);\n opt.era = ecma402_abstract_1.GetOption(options, 'era', 'string', ['narrow', 'short', 'long'], undefined);\n opt.year = ecma402_abstract_1.GetOption(options, 'year', 'string', ['2-digit', 'numeric'], undefined);\n opt.month = ecma402_abstract_1.GetOption(options, 'month', 'string', ['2-digit', 'numeric', 'narrow', 'short', 'long'], undefined);\n opt.day = ecma402_abstract_1.GetOption(options, 'day', 'string', ['2-digit', 'numeric'], undefined);\n opt.hour = ecma402_abstract_1.GetOption(options, 'hour', 'string', ['2-digit', 'numeric'], undefined);\n opt.minute = ecma402_abstract_1.GetOption(options, 'minute', 'string', ['2-digit', 'numeric'], undefined);\n opt.second = ecma402_abstract_1.GetOption(options, 'second', 'string', ['2-digit', 'numeric'], undefined);\n opt.timeZoneName = ecma402_abstract_1.GetOption(options, 'timeZoneName', 'string', ['short', 'long'], undefined);\n opt.fractionalSecondDigits = ecma402_abstract_1.GetNumberOption(options, 'fractionalSecondDigits', 1, 3, \n // @ts-expect-error\n undefined);\n var dataLocaleData = localeData[dataLocale];\n ecma402_abstract_1.invariant(!!dataLocaleData, \"Missing locale data for \" + dataLocale);\n var formats = dataLocaleData.formats[calendar];\n // UNSPECCED: IMPLEMENTATION DETAILS\n if (!formats) {\n throw new RangeError(\"Calendar \\\"\" + calendar + \"\\\" is not supported. Try setting \\\"calendar\\\" to 1 of the following: \" + Object.keys(dataLocaleData.formats).join(', '));\n }\n var formatMatcher = ecma402_abstract_1.GetOption(options, 'formatMatcher', 'string', ['basic', 'best fit'], 'best fit');\n var dateStyle = ecma402_abstract_1.GetOption(options, 'dateStyle', 'string', ['full', 'long', 'medium', 'short'], undefined);\n internalSlots.dateStyle = dateStyle;\n var timeStyle = ecma402_abstract_1.GetOption(options, 'timeStyle', 'string', ['full', 'long', 'medium', 'short'], undefined);\n internalSlots.timeStyle = timeStyle;\n var bestFormat;\n if (dateStyle === undefined && timeStyle === undefined) {\n if (formatMatcher === 'basic') {\n bestFormat = BasicFormatMatcher_1.BasicFormatMatcher(opt, formats);\n }\n else {\n // IMPL DETAILS START\n if (isTimeRelated(opt)) {\n var hc = resolveHourCycle(internalSlots.hourCycle, dataLocaleData.hourCycle, hour12);\n opt.hour12 = hc === 'h11' || hc === 'h12';\n }\n // IMPL DETAILS END\n bestFormat = BestFitFormatMatcher_1.BestFitFormatMatcher(opt, formats);\n }\n }\n else {\n for (var _i = 0, DATE_TIME_PROPS_1 = utils_1.DATE_TIME_PROPS; _i < DATE_TIME_PROPS_1.length; _i++) {\n var prop = DATE_TIME_PROPS_1[_i];\n var p = opt[prop];\n if (p !== undefined) {\n throw new TypeError(\"Intl.DateTimeFormat can't set option \" + prop + \" when \" + (dateStyle ? 'dateStyle' : 'timeStyle') + \" is used\");\n }\n }\n bestFormat = DateTimeStyleFormat_1.DateTimeStyleFormat(dateStyle, timeStyle, dataLocaleData);\n }\n // IMPL DETAIL START\n // For debugging\n internalSlots.format = bestFormat;\n // IMPL DETAIL END\n for (var prop in opt) {\n var p = bestFormat[prop];\n if (p !== undefined) {\n internalSlots[prop] = p;\n }\n }\n var pattern;\n var rangePatterns;\n if (internalSlots.hour !== undefined) {\n var hc = resolveHourCycle(internalSlots.hourCycle, dataLocaleData.hourCycle, hour12);\n internalSlots.hourCycle = hc;\n if (hc === 'h11' || hc === 'h12') {\n pattern = bestFormat.pattern12;\n rangePatterns = bestFormat.rangePatterns12;\n }\n else {\n pattern = bestFormat.pattern;\n rangePatterns = bestFormat.rangePatterns;\n }\n }\n else {\n // @ts-ignore\n internalSlots.hourCycle = undefined;\n pattern = bestFormat.pattern;\n rangePatterns = bestFormat.rangePatterns;\n }\n internalSlots.pattern = pattern;\n internalSlots.rangePatterns = rangePatterns;\n return dtf;\n}\nexports.InitializeDateTimeFormat = InitializeDateTimeFormat;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.BasicFormatMatcher = void 0;\nvar tslib_1 = require(\"tslib\");\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\nvar utils_1 = require(\"./utils\");\n/**\n * https://tc39.es/ecma402/#sec-basicformatmatcher\n * @param options\n * @param formats\n */\nfunction BasicFormatMatcher(options, formats) {\n var bestScore = -Infinity;\n var bestFormat = formats[0];\n ecma402_abstract_1.invariant(Array.isArray(formats), 'formats should be a list of things');\n for (var _i = 0, formats_1 = formats; _i < formats_1.length; _i++) {\n var format = formats_1[_i];\n var score = 0;\n for (var _a = 0, DATE_TIME_PROPS_1 = utils_1.DATE_TIME_PROPS; _a < DATE_TIME_PROPS_1.length; _a++) {\n var prop = DATE_TIME_PROPS_1[_a];\n var optionsProp = options[prop];\n var formatProp = format[prop];\n if (optionsProp === undefined && formatProp !== undefined) {\n score -= utils_1.additionPenalty;\n }\n else if (optionsProp !== undefined && formatProp === undefined) {\n score -= utils_1.removalPenalty;\n }\n else if (optionsProp !== formatProp) {\n var values = void 0;\n if (prop === 'fractionalSecondDigits') {\n values = [1, 2, 3];\n }\n else {\n values = ['2-digit', 'numeric', 'narrow', 'short', 'long'];\n }\n var optionsPropIndex = values.indexOf(optionsProp);\n var formatPropIndex = values.indexOf(formatProp);\n var delta = Math.max(-2, Math.min(formatPropIndex - optionsPropIndex, 2));\n if (delta === 2) {\n score -= utils_1.longMorePenalty;\n }\n else if (delta === 1) {\n score -= utils_1.shortMorePenalty;\n }\n else if (delta === -1) {\n score -= utils_1.shortLessPenalty;\n }\n else if (delta === -2) {\n score -= utils_1.longLessPenalty;\n }\n }\n }\n if (score > bestScore) {\n bestScore = score;\n bestFormat = format;\n }\n }\n return tslib_1.__assign({}, bestFormat);\n}\nexports.BasicFormatMatcher = BasicFormatMatcher;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.BestFitFormatMatcher = exports.bestFitFormatMatcherScore = void 0;\nvar tslib_1 = require(\"tslib\");\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\nvar utils_1 = require(\"./utils\");\nvar skeleton_1 = require(\"./skeleton\");\nfunction isNumericType(t) {\n return t === 'numeric' || t === '2-digit';\n}\n/**\n * Credit: https://github.com/andyearnshaw/Intl.js/blob/0958dc1ad8153f1056653ea22b8208f0df289a4e/src/12.datetimeformat.js#L611\n * with some modifications\n * @param options\n * @param format\n */\nfunction bestFitFormatMatcherScore(options, format) {\n var score = 0;\n if (options.hour12 && !format.hour12) {\n score -= utils_1.removalPenalty;\n }\n else if (!options.hour12 && format.hour12) {\n score -= utils_1.additionPenalty;\n }\n for (var _i = 0, DATE_TIME_PROPS_1 = utils_1.DATE_TIME_PROPS; _i < DATE_TIME_PROPS_1.length; _i++) {\n var prop = DATE_TIME_PROPS_1[_i];\n var optionsProp = options[prop];\n var formatProp = format[prop];\n if (optionsProp === undefined && formatProp !== undefined) {\n score -= utils_1.additionPenalty;\n }\n else if (optionsProp !== undefined && formatProp === undefined) {\n score -= utils_1.removalPenalty;\n }\n else if (optionsProp !== formatProp) {\n // extra penalty for numeric vs non-numeric\n if (isNumericType(optionsProp) !==\n isNumericType(formatProp)) {\n score -= utils_1.differentNumericTypePenalty;\n }\n else {\n var values = ['2-digit', 'numeric', 'narrow', 'short', 'long'];\n var optionsPropIndex = values.indexOf(optionsProp);\n var formatPropIndex = values.indexOf(formatProp);\n var delta = Math.max(-2, Math.min(formatPropIndex - optionsPropIndex, 2));\n if (delta === 2) {\n score -= utils_1.longMorePenalty;\n }\n else if (delta === 1) {\n score -= utils_1.shortMorePenalty;\n }\n else if (delta === -1) {\n score -= utils_1.shortLessPenalty;\n }\n else if (delta === -2) {\n score -= utils_1.longLessPenalty;\n }\n }\n }\n }\n return score;\n}\nexports.bestFitFormatMatcherScore = bestFitFormatMatcherScore;\n/**\n * https://tc39.es/ecma402/#sec-bestfitformatmatcher\n * Just alias to basic for now\n * @param options\n * @param formats\n * @param implDetails Implementation details\n */\nfunction BestFitFormatMatcher(options, formats) {\n var bestScore = -Infinity;\n var bestFormat = formats[0];\n ecma402_abstract_1.invariant(Array.isArray(formats), 'formats should be a list of things');\n for (var _i = 0, formats_1 = formats; _i < formats_1.length; _i++) {\n var format = formats_1[_i];\n var score = bestFitFormatMatcherScore(options, format);\n if (score > bestScore) {\n bestScore = score;\n bestFormat = format;\n }\n }\n var skeletonFormat = tslib_1.__assign({}, bestFormat);\n var patternFormat = { rawPattern: bestFormat.rawPattern };\n skeleton_1.processDateTimePattern(bestFormat.rawPattern, patternFormat);\n // Kinda following https://github.com/unicode-org/icu/blob/dd50e38f459d84e9bf1b0c618be8483d318458ad/icu4j/main/classes/core/src/com/ibm/icu/text/DateTimePatternGenerator.java\n // Method adjustFieldTypes\n for (var prop in skeletonFormat) {\n var skeletonValue = skeletonFormat[prop];\n var patternValue = patternFormat[prop];\n var requestedValue = options[prop];\n // Don't mess with minute/second or we can get in the situation of\n // 7:0:0 which is weird\n if (prop === 'minute' || prop === 'second') {\n continue;\n }\n // Nothing to do here\n if (!requestedValue) {\n continue;\n }\n // https://unicode.org/reports/tr35/tr35-dates.html#Matching_Skeletons\n // Looks like we should not convert numeric to alphabetic but the other way\n // around is ok\n if (isNumericType(patternValue) &&\n !isNumericType(requestedValue)) {\n continue;\n }\n if (skeletonValue === requestedValue) {\n continue;\n }\n patternFormat[prop] = requestedValue;\n }\n // Copy those over\n patternFormat.pattern = skeletonFormat.pattern;\n patternFormat.pattern12 = skeletonFormat.pattern12;\n patternFormat.skeleton = skeletonFormat.skeleton;\n patternFormat.rangePatterns = skeletonFormat.rangePatterns;\n patternFormat.rangePatterns12 = skeletonFormat.rangePatterns12;\n return patternFormat;\n}\nexports.BestFitFormatMatcher = BestFitFormatMatcher;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.DateTimeStyleFormat = void 0;\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\nfunction DateTimeStyleFormat(dateStyle, timeStyle, dataLocaleData) {\n var dateFormat, timeFormat;\n if (timeStyle !== undefined) {\n ecma402_abstract_1.invariant(timeStyle === 'full' ||\n timeStyle === 'long' ||\n timeStyle === 'medium' ||\n timeStyle === 'short', 'invalid timeStyle');\n timeFormat = dataLocaleData.timeFormat[timeStyle];\n }\n if (dateStyle !== undefined) {\n ecma402_abstract_1.invariant(dateStyle === 'full' ||\n dateStyle === 'long' ||\n dateStyle === 'medium' ||\n dateStyle === 'short', 'invalid dateStyle');\n dateFormat = dataLocaleData.dateFormat[dateStyle];\n }\n if (dateStyle !== undefined && timeStyle !== undefined) {\n var format = {};\n for (var field in dateFormat) {\n if (field !== 'pattern') {\n // @ts-ignore\n format[field] = dateFormat[field];\n }\n }\n for (var field in timeFormat) {\n if (field !== 'pattern' && field !== 'pattern12') {\n // @ts-ignore\n format[field] = timeFormat[field];\n }\n }\n var connector = dataLocaleData.dateTimeFormat[dateStyle];\n var pattern = connector\n .replace('{0}', timeFormat.pattern)\n .replace('{1}', dateFormat.pattern);\n format.pattern = pattern;\n if ('pattern12' in timeFormat) {\n var pattern12 = connector\n .replace('{0}', timeFormat.pattern12)\n .replace('{1}', dateFormat.pattern);\n format.pattern12 = pattern12;\n }\n return format;\n }\n if (timeStyle !== undefined) {\n return timeFormat;\n }\n ecma402_abstract_1.invariant(dateStyle !== undefined, 'dateStyle should not be undefined');\n return dateFormat;\n}\nexports.DateTimeStyleFormat = DateTimeStyleFormat;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.FormatDateTimeToParts = void 0;\nvar PartitionDateTimePattern_1 = require(\"./PartitionDateTimePattern\");\nvar ecma402_abstract_1 = require(\"@formatjs/ecma402-abstract\");\n/**\n * https://tc39.es/ecma402/#sec-formatdatetimetoparts\n *\n * @param dtf\n * @param x\n * @param implDetails\n */\nfunction FormatDateTimeToParts(dtf, x, implDetails) {\n var parts = PartitionDateTimePattern_1.PartitionDateTimePattern(dtf, x, implDetails);\n var result = ecma402_abstract_1.ArrayCreate(0);\n for (var _i = 0, parts_1 = parts; _i < parts_1.length; _i++) {\n var part = parts_1[_i];\n result.push({\n type: part.type,\n value: part.value,\n });\n }\n return result;\n}\nexports.FormatDateTimeToParts = FormatDateTimeToParts;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.FormatDateTimeRangeToParts = void 0;\nvar PartitionDateTimeRangePattern_1 = require(\"./PartitionDateTimeRangePattern\");\nfunction FormatDateTimeRangeToParts(dtf, x, y, implDetails) {\n var parts = PartitionDateTimeRangePattern_1.PartitionDateTimeRangePattern(dtf, x, y, implDetails);\n var result = new Array(0);\n for (var _i = 0, parts_1 = parts; _i < parts_1.length; _i++) {\n var part = parts_1[_i];\n result.push({\n type: part.type,\n value: part.value,\n source: part.source,\n });\n }\n return result;\n}\nexports.FormatDateTimeRangeToParts = FormatDateTimeRangeToParts;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.FormatDateTimeRange = void 0;\nvar PartitionDateTimeRangePattern_1 = require(\"./PartitionDateTimeRangePattern\");\nfunction FormatDateTimeRange(dtf, x, y, implDetails) {\n var parts = PartitionDateTimeRangePattern_1.PartitionDateTimeRangePattern(dtf, x, y, implDetails);\n var result = '';\n for (var _i = 0, parts_1 = parts; _i < parts_1.length; _i++) {\n var part = parts_1[_i];\n result += part.value;\n }\n return result;\n}\nexports.FormatDateTimeRange = FormatDateTimeRange;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.shouldPolyfill = void 0;\nfunction supportsDateStyle() {\n try {\n return !!new Intl.DateTimeFormat(undefined, {\n dateStyle: 'short',\n }).resolvedOptions().dateStyle;\n }\n catch (e) {\n return false;\n }\n}\n/**\n * https://bugs.chromium.org/p/chromium/issues/detail?id=865351\n */\nfunction hasChromeLt71Bug() {\n try {\n return (new Intl.DateTimeFormat('en', {\n hourCycle: 'h11',\n hour: 'numeric',\n }).formatToParts(0)[2].type !== 'dayPeriod');\n }\n catch (e) {\n return false;\n }\n}\n/**\n * Node 14's version of Intl.DateTimeFormat does not throw\n * when dateStyle/timeStyle is used with other options.\n * This was fixed in newer V8 versions\n */\nfunction hasUnthrownDateTimeStyleBug() {\n try {\n return !!new Intl.DateTimeFormat('en', {\n dateStyle: 'short',\n hour: 'numeric',\n }).format(new Date(0));\n }\n catch (e) {\n return false;\n }\n}\nfunction supportedLocalesOf(locale) {\n if (!locale) {\n return true;\n }\n var locales = Array.isArray(locale) ? locale : [locale];\n return (Intl.DateTimeFormat.supportedLocalesOf(locales).length === locales.length);\n}\nfunction shouldPolyfill(locale) {\n return (!('DateTimeFormat' in Intl) ||\n !('formatToParts' in Intl.DateTimeFormat.prototype) ||\n !('formatRange' in Intl.DateTimeFormat.prototype) ||\n hasChromeLt71Bug() ||\n hasUnthrownDateTimeStyleBug() ||\n !supportsDateStyle() ||\n !supportedLocalesOf(locale));\n}\nexports.shouldPolyfill = shouldPolyfill;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.toLocaleTimeString = exports.toLocaleDateString = exports.toLocaleString = void 0;\n// eslint-disable-next-line import/no-cycle\nvar core_1 = require(\"./core\");\nvar ToDateTimeOptions_1 = require(\"./abstract/ToDateTimeOptions\");\n/**\n * Number.prototype.toLocaleString ponyfill\n * https://tc39.es/ecma402/#sup-number.prototype.tolocalestring\n */\nfunction toLocaleString(x, locales, options) {\n var dtf = new core_1.DateTimeFormat(locales, options);\n return dtf.format(x);\n}\nexports.toLocaleString = toLocaleString;\nfunction toLocaleDateString(x, locales, options) {\n var dtf = new core_1.DateTimeFormat(locales, ToDateTimeOptions_1.ToDateTimeOptions(options, 'date', 'date'));\n return dtf.format(x);\n}\nexports.toLocaleDateString = toLocaleDateString;\nfunction toLocaleTimeString(x, locales, options) {\n var dtf = new core_1.DateTimeFormat(locales, ToDateTimeOptions_1.ToDateTimeOptions(options, 'time', 'time'));\n return dtf.format(x);\n}\nexports.toLocaleTimeString = toLocaleTimeString;\n","/* @generated */\t\n\n // prettier-ignore\n if (Intl.DateTimeFormat && typeof Intl.DateTimeFormat.__addLocaleData === 'function') {\n Intl.DateTimeFormat.__addLocaleData({\"data\":{\"am\":\"AM\",\"pm\":\"PM\",\"weekday\":{\"narrow\":[\"S\",\"M\",\"T\",\"W\",\"T\",\"F\",\"S\"],\"short\":[\"Sun\",\"Mon\",\"Tue\",\"Wed\",\"Thu\",\"Fri\",\"Sat\"],\"long\":[\"Sunday\",\"Monday\",\"Tuesday\",\"Wednesday\",\"Thursday\",\"Friday\",\"Saturday\"]},\"era\":{\"narrow\":{\"BC\":\"B\",\"AD\":\"A\"},\"short\":{\"BC\":\"BC\",\"AD\":\"AD\"},\"long\":{\"BC\":\"Before Christ\",\"AD\":\"Anno Domini\"}},\"month\":{\"narrow\":[\"J\",\"F\",\"M\",\"A\",\"M\",\"J\",\"J\",\"A\",\"S\",\"O\",\"N\",\"D\"],\"short\":[\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"],\"long\":[\"January\",\"February\",\"March\",\"April\",\"May\",\"June\",\"July\",\"August\",\"September\",\"October\",\"November\",\"December\"]},\"timeZoneName\":{\"America/Rio_Branco\":{\"long\":[\"Acre Standard Time\",\"Acre Summer Time\"]},\"Asia/Kabul\":{\"long\":[\"Afghanistan Time\",\"Afghanistan Time\"]},\"Africa/Maputo\":{\"long\":[\"Central Africa Time\",\"Central Africa Time\"]},\"Africa/Bujumbura\":{\"long\":[\"Central Africa Time\",\"Central Africa Time\"]},\"Africa/Gaborone\":{\"long\":[\"Central Africa Time\",\"Central Africa Time\"]},\"Africa/Lubumbashi\":{\"long\":[\"Central Africa Time\",\"Central Africa Time\"]},\"Africa/Blantyre\":{\"long\":[\"Central Africa Time\",\"Central Africa Time\"]},\"Africa/Kigali\":{\"long\":[\"Central Africa Time\",\"Central Africa Time\"]},\"Africa/Lusaka\":{\"long\":[\"Central Africa Time\",\"Central Africa Time\"]},\"Africa/Harare\":{\"long\":[\"Central Africa Time\",\"Central Africa Time\"]},\"Africa/Nairobi\":{\"long\":[\"East Africa Time\",\"East Africa Time\"]},\"Africa/Djibouti\":{\"long\":[\"East Africa Time\",\"East Africa Time\"]},\"Africa/Asmera\":{\"long\":[\"East Africa Time\",\"East Africa Time\"]},\"Africa/Addis_Ababa\":{\"long\":[\"East Africa Time\",\"East Africa Time\"]},\"Indian/Comoro\":{\"long\":[\"East Africa Time\",\"East Africa Time\"]},\"Indian/Antananarivo\":{\"long\":[\"East Africa Time\",\"East Africa Time\"]},\"Africa/Mogadishu\":{\"long\":[\"East Africa Time\",\"East Africa Time\"]},\"Africa/Dar_es_Salaam\":{\"long\":[\"East Africa Time\",\"East Africa Time\"]},\"Africa/Kampala\":{\"long\":[\"East Africa Time\",\"East Africa Time\"]},\"Indian/Mayotte\":{\"long\":[\"East Africa Time\",\"East Africa Time\"]},\"Africa/Johannesburg\":{\"long\":[\"South Africa Standard Time\",\"South Africa Standard Time\"]},\"Africa/Maseru\":{\"long\":[\"South Africa Standard Time\",\"South Africa Standard Time\"]},\"Africa/Mbabane\":{\"long\":[\"South Africa Standard Time\",\"South Africa Standard Time\"]},\"Africa/Lagos\":{\"long\":[\"West Africa Standard Time\",\"West Africa Summer Time\"]},\"Africa/Luanda\":{\"long\":[\"West Africa Standard Time\",\"West Africa Summer Time\"]},\"Africa/Porto-Novo\":{\"long\":[\"West Africa Standard Time\",\"West Africa Summer Time\"]},\"Africa/Kinshasa\":{\"long\":[\"West Africa Standard Time\",\"West Africa Summer Time\"]},\"Africa/Bangui\":{\"long\":[\"West Africa Standard Time\",\"West Africa Summer Time\"]},\"Africa/Brazzaville\":{\"long\":[\"West Africa Standard Time\",\"West Africa Summer Time\"]},\"Africa/Douala\":{\"long\":[\"West Africa Standard Time\",\"West Africa Summer Time\"]},\"Africa/Libreville\":{\"long\":[\"West Africa Standard Time\",\"West Africa Summer Time\"]},\"Africa/Malabo\":{\"long\":[\"West Africa Standard Time\",\"West Africa Summer Time\"]},\"Africa/Niamey\":{\"long\":[\"West Africa Standard Time\",\"West Africa Summer Time\"]},\"Africa/Ndjamena\":{\"long\":[\"West Africa Standard Time\",\"West Africa Summer Time\"]},\"Asia/Aqtobe\":{\"long\":[\"West Kazakhstan Time\",\"West Kazakhstan Time\"]},\"America/Juneau\":{\"long\":[\"Alaska Standard Time\",\"Alaska Daylight Time\"],\"short\":[\"AKST\",\"AKDT\"]},\"Asia/Almaty\":{\"long\":[\"East Kazakhstan Time\",\"East Kazakhstan Time\"]},\"America/Manaus\":{\"long\":[\"Amazon Standard Time\",\"Amazon Summer Time\"]},\"America/Chicago\":{\"long\":[\"Central Standard Time\",\"Central Daylight Time\"],\"short\":[\"CST\",\"CDT\"]},\"America/Belize\":{\"long\":[\"Central Standard Time\",\"Central Daylight Time\"],\"short\":[\"CST\",\"CDT\"]},\"America/Winnipeg\":{\"long\":[\"Central Standard Time\",\"Central Daylight Time\"],\"short\":[\"CST\",\"CDT\"]},\"America/Costa_Rica\":{\"long\":[\"Central Standard Time\",\"Central Daylight Time\"],\"short\":[\"CST\",\"CDT\"]},\"America/Guatemala\":{\"long\":[\"Central Standard Time\",\"Central Daylight Time\"],\"short\":[\"CST\",\"CDT\"]},\"America/Tegucigalpa\":{\"long\":[\"Central Standard Time\",\"Central Daylight Time\"],\"short\":[\"CST\",\"CDT\"]},\"America/Mexico_City\":{\"long\":[\"Central Standard Time\",\"Central Daylight Time\"],\"short\":[\"CST\",\"CDT\"]},\"America/El_Salvador\":{\"long\":[\"Central Standard Time\",\"Central Daylight Time\"],\"short\":[\"CST\",\"CDT\"]},\"America/New_York\":{\"long\":[\"Eastern Standard Time\",\"Eastern Daylight Time\"],\"short\":[\"EST\",\"EDT\"]},\"America/Nassau\":{\"long\":[\"Eastern Standard Time\",\"Eastern Daylight Time\"],\"short\":[\"EST\",\"EDT\"]},\"America/Toronto\":{\"long\":[\"Eastern Standard Time\",\"Eastern Daylight Time\"],\"short\":[\"EST\",\"EDT\"]},\"America/Port-au-Prince\":{\"long\":[\"Eastern Standard Time\",\"Eastern Daylight Time\"],\"short\":[\"EST\",\"EDT\"]},\"America/Jamaica\":{\"long\":[\"Eastern Standard Time\",\"Eastern Daylight Time\"],\"short\":[\"EST\",\"EDT\"]},\"America/Cayman\":{\"long\":[\"Eastern Standard Time\",\"Eastern Daylight Time\"],\"short\":[\"EST\",\"EDT\"]},\"America/Panama\":{\"long\":[\"Eastern Standard Time\",\"Eastern Daylight Time\"],\"short\":[\"EST\",\"EDT\"]},\"America/Denver\":{\"long\":[\"Mountain Standard Time\",\"Mountain Daylight Time\"],\"short\":[\"MST\",\"MDT\"]},\"America/Edmonton\":{\"long\":[\"Mountain Standard Time\",\"Mountain Daylight Time\"],\"short\":[\"MST\",\"MDT\"]},\"America/Hermosillo\":{\"long\":[\"Mountain Standard Time\",\"Mountain Daylight Time\"],\"short\":[\"MST\",\"MDT\"]},\"America/Los_Angeles\":{\"long\":[\"Pacific Standard Time\",\"Pacific Daylight Time\"],\"short\":[\"PST\",\"PDT\"]},\"America/Vancouver\":{\"long\":[\"Pacific Standard Time\",\"Pacific Daylight Time\"],\"short\":[\"PST\",\"PDT\"]},\"America/Tijuana\":{\"long\":[\"Pacific Standard Time\",\"Pacific Daylight Time\"],\"short\":[\"PST\",\"PDT\"]},\"Asia/Anadyr\":{\"long\":[\"Anadyr Standard Time\",\"Anadyr Summer Time\"]},\"Pacific/Apia\":{\"long\":[\"Apia Standard Time\",\"Apia Daylight Time\"]},\"Asia/Riyadh\":{\"long\":[\"Arabian Standard Time\",\"Arabian Daylight Time\"]},\"Asia/Bahrain\":{\"long\":[\"Arabian Standard Time\",\"Arabian Daylight Time\"]},\"Asia/Baghdad\":{\"long\":[\"Arabian Standard Time\",\"Arabian Daylight Time\"]},\"Asia/Kuwait\":{\"long\":[\"Arabian Standard Time\",\"Arabian Daylight Time\"]},\"Asia/Qatar\":{\"long\":[\"Arabian Standard Time\",\"Arabian Daylight Time\"]},\"Asia/Aden\":{\"long\":[\"Arabian Standard Time\",\"Arabian Daylight Time\"]},\"America/Buenos_Aires\":{\"long\":[\"Argentina Standard Time\",\"Argentina Summer Time\"]},\"America/Argentina/San_Luis\":{\"long\":[\"Western Argentina Standard Time\",\"Western Argentina Summer Time\"]},\"Asia/Ashgabat\":{\"long\":[\"Turkmenistan Standard Time\",\"Turkmenistan Summer Time\"]},\"America/Halifax\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Antigua\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Anguilla\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Aruba\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Barbados\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"Atlantic/Bermuda\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Kralendijk\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Curacao\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Dominica\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Grenada\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Thule\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Guadeloupe\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/St_Kitts\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/St_Lucia\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Marigot\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Martinique\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Montserrat\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Puerto_Rico\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Lower_Princes\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Port_of_Spain\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/St_Vincent\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/Tortola\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"America/St_Thomas\":{\"long\":[\"Atlantic Standard Time\",\"Atlantic Daylight Time\"],\"short\":[\"AST\",\"ADT\"]},\"Australia/Adelaide\":{\"long\":[\"Australian Central Standard Time\",\"Australian Central Daylight Time\"]},\"Australia/Eucla\":{\"long\":[\"Australian Central Western Standard Time\",\"Australian Central Western Daylight Time\"]},\"Australia/Sydney\":{\"long\":[\"Australian Eastern Standard Time\",\"Australian Eastern Daylight Time\"]},\"Australia/Perth\":{\"long\":[\"Australian Western Standard Time\",\"Australian Western Daylight Time\"]},\"Atlantic/Azores\":{\"long\":[\"Azores Standard Time\",\"Azores Summer Time\"]},\"Asia/Thimphu\":{\"long\":[\"Bhutan Time\",\"Bhutan Time\"]},\"America/La_Paz\":{\"long\":[\"Bolivia Time\",\"Bolivia Time\"]},\"Asia/Kuching\":{\"long\":[\"Malaysia Time\",\"Malaysia Time\"]},\"America/Sao_Paulo\":{\"long\":[\"Brasilia Standard Time\",\"Brasilia Summer Time\"]},\"Europe/London\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"Asia/Brunei\":{\"long\":[\"Brunei Darussalam Time\",\"Brunei Darussalam Time\"]},\"Atlantic/Cape_Verde\":{\"long\":[\"Cape Verde Standard Time\",\"Cape Verde Summer Time\"]},\"Antarctica/Casey\":{\"long\":[\"Casey Time\",\"Casey Time\"]},\"Pacific/Saipan\":{\"long\":[\"North Mariana Islands Time\",\"North Mariana Islands Time\"]},\"Pacific/Guam\":{\"long\":[\"Guam Standard Time\",\"Guam Standard Time\"]},\"Pacific/Chatham\":{\"long\":[\"Chatham Standard Time\",\"Chatham Daylight Time\"]},\"America/Santiago\":{\"long\":[\"Chile Standard Time\",\"Chile Summer Time\"]},\"Asia/Shanghai\":{\"long\":[\"China Standard Time\",\"China Daylight Time\"]},\"Asia/Choibalsan\":{\"long\":[\"Choibalsan Standard Time\",\"Choibalsan Summer Time\"]},\"Indian/Christmas\":{\"long\":[\"Christmas Island Time\",\"Christmas Island Time\"]},\"Indian/Cocos\":{\"long\":[\"Cocos Islands Time\",\"Cocos Islands Time\"]},\"America/Bogota\":{\"long\":[\"Colombia Standard Time\",\"Colombia Summer Time\"]},\"Pacific/Rarotonga\":{\"long\":[\"Cook Islands Standard Time\",\"Cook Islands Half Summer Time\"]},\"America/Havana\":{\"long\":[\"Cuba Standard Time\",\"Cuba Daylight Time\"]},\"Antarctica/Davis\":{\"long\":[\"Davis Time\",\"Davis Time\"]},\"Antarctica/DumontDUrville\":{\"long\":[\"Dumont-d’Urville Time\",\"Dumont-d’Urville Time\"]},\"Asia/Dushanbe\":{\"long\":[\"Tajikistan Time\",\"Tajikistan Time\"]},\"America/Paramaribo\":{\"long\":[\"Suriname Time\",\"Suriname Time\"]},\"Asia/Dili\":{\"long\":[\"East Timor Time\",\"East Timor Time\"]},\"Pacific/Easter\":{\"long\":[\"Easter Island Standard Time\",\"Easter Island Summer Time\"]},\"America/Guayaquil\":{\"long\":[\"Ecuador Time\",\"Ecuador Time\"]},\"Europe/Paris\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Andorra\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Tirane\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Vienna\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Sarajevo\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Brussels\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Zurich\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Prague\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Berlin\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Copenhagen\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Madrid\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Gibraltar\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Zagreb\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Budapest\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Rome\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Vaduz\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Luxembourg\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Monaco\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Podgorica\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Skopje\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Malta\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Amsterdam\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Oslo\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Warsaw\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Belgrade\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Stockholm\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Ljubljana\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Arctic/Longyearbyen\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Bratislava\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/San_Marino\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Africa/Tunis\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Vatican\":{\"long\":[\"Central European Standard Time\",\"Central European Summer Time\"]},\"Europe/Bucharest\":{\"long\":[\"Eastern European Standard Time\",\"Eastern European Summer Time\"]},\"Europe/Mariehamn\":{\"long\":[\"Eastern European Standard Time\",\"Eastern European Summer Time\"]},\"Europe/Sofia\":{\"long\":[\"Eastern European Standard Time\",\"Eastern European Summer Time\"]},\"Asia/Nicosia\":{\"long\":[\"Eastern European Standard Time\",\"Eastern European Summer Time\"]},\"Africa/Cairo\":{\"long\":[\"Eastern European Standard Time\",\"Eastern European Summer Time\"]},\"Europe/Helsinki\":{\"long\":[\"Eastern European Standard Time\",\"Eastern European Summer Time\"]},\"Europe/Athens\":{\"long\":[\"Eastern European Standard Time\",\"Eastern European Summer Time\"]},\"Asia/Amman\":{\"long\":[\"Eastern European Standard Time\",\"Eastern European Summer Time\"]},\"Asia/Beirut\":{\"long\":[\"Eastern European Standard Time\",\"Eastern European Summer Time\"]},\"Asia/Damascus\":{\"long\":[\"Eastern European Standard Time\",\"Eastern European Summer Time\"]},\"Europe/Minsk\":{\"long\":[\"Further-eastern European Time\",\"Further-eastern European Time\"]},\"Europe/Kaliningrad\":{\"long\":[\"Further-eastern European Time\",\"Further-eastern European Time\"]},\"Atlantic/Canary\":{\"long\":[\"Western European Standard Time\",\"Western European Summer Time\"]},\"Atlantic/Faeroe\":{\"long\":[\"Western European Standard Time\",\"Western European Summer Time\"]},\"Atlantic/Stanley\":{\"long\":[\"Falkland Islands Standard Time\",\"Falkland Islands Summer Time\"]},\"Pacific/Fiji\":{\"long\":[\"Fiji Standard Time\",\"Fiji Summer Time\"]},\"America/Cayenne\":{\"long\":[\"French Guiana Time\",\"French Guiana Time\"]},\"Indian/Kerguelen\":{\"long\":[\"French Southern & Antarctic Time\",\"French Southern & Antarctic Time\"]},\"Asia/Bishkek\":{\"long\":[\"Kyrgyzstan Time\",\"Kyrgyzstan Time\"]},\"Pacific/Galapagos\":{\"long\":[\"Galapagos Time\",\"Galapagos Time\"]},\"Pacific/Gambier\":{\"long\":[\"Gambier Time\",\"Gambier Time\"]},\"Pacific/Tarawa\":{\"long\":[\"Gilbert Islands Time\",\"Gilbert Islands Time\"]},\"Atlantic/Reykjavik\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"Africa/Ouagadougou\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"Africa/Abidjan\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"Africa/Accra\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"Africa/Banjul\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"Africa/Conakry\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"Africa/Bamako\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"Africa/Nouakchott\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"Atlantic/St_Helena\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"Africa/Freetown\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"Africa/Dakar\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"Africa/Lome\":{\"long\":[\"Greenwich Mean Time\",\"Greenwich Mean Time\"],\"short\":[\"GMT\",\"GMT\"]},\"America/Scoresbysund\":{\"long\":[\"East Greenland Standard Time\",\"East Greenland Summer Time\"]},\"America/Godthab\":{\"long\":[\"West Greenland Standard Time\",\"West Greenland Summer Time\"]},\"Asia/Dubai\":{\"long\":[\"Gulf Standard Time\",\"Gulf Standard Time\"]},\"Asia/Muscat\":{\"long\":[\"Gulf Standard Time\",\"Gulf Standard Time\"]},\"America/Guyana\":{\"long\":[\"Guyana Time\",\"Guyana Time\"]},\"Pacific/Honolulu\":{\"long\":[\"Hawaii-Aleutian Standard Time\",\"Hawaii-Aleutian Daylight Time\"],\"short\":[\"HAST\",\"HADT\"]},\"Asia/Hong_Kong\":{\"long\":[\"Hong Kong Standard Time\",\"Hong Kong Summer Time\"]},\"Asia/Hovd\":{\"long\":[\"Hovd Standard Time\",\"Hovd Summer Time\"]},\"Asia/Calcutta\":{\"long\":[\"India Standard Time\",\"India Standard Time\"]},\"Asia/Colombo\":{\"long\":[\"Lanka Time\",\"Lanka Time\"]},\"Indian/Chagos\":{\"long\":[\"Indian Ocean Time\",\"Indian Ocean Time\"]},\"Asia/Bangkok\":{\"long\":[\"Indochina Time\",\"Indochina Time\"]},\"Asia/Phnom_Penh\":{\"long\":[\"Indochina Time\",\"Indochina Time\"]},\"Asia/Vientiane\":{\"long\":[\"Indochina Time\",\"Indochina Time\"]},\"Asia/Makassar\":{\"long\":[\"Central Indonesia Time\",\"Central Indonesia Time\"]},\"Asia/Jayapura\":{\"long\":[\"Eastern Indonesia Time\",\"Eastern Indonesia Time\"]},\"Asia/Jakarta\":{\"long\":[\"Western Indonesia Time\",\"Western Indonesia Time\"]},\"Asia/Tehran\":{\"long\":[\"Iran Standard Time\",\"Iran Daylight Time\"]},\"Asia/Irkutsk\":{\"long\":[\"Irkutsk Standard Time\",\"Irkutsk Summer Time\"]},\"Asia/Jerusalem\":{\"long\":[\"Israel Standard Time\",\"Israel Daylight Time\"]},\"Asia/Tokyo\":{\"long\":[\"Japan Standard Time\",\"Japan Daylight Time\"]},\"Asia/Kamchatka\":{\"long\":[\"Petropavlovsk-Kamchatski Standard Time\",\"Petropavlovsk-Kamchatski Summer Time\"]},\"Asia/Karachi\":{\"long\":[\"Pakistan Standard Time\",\"Pakistan Summer Time\"]},\"Asia/Qyzylorda\":{\"long\":[\"Qyzylorda Standard Time\",\"Qyzylorda Summer Time\"]},\"Asia/Seoul\":{\"long\":[\"Korean Standard Time\",\"Korean Daylight Time\"]},\"Pacific/Kosrae\":{\"long\":[\"Kosrae Time\",\"Kosrae Time\"]},\"Asia/Krasnoyarsk\":{\"long\":[\"Krasnoyarsk Standard Time\",\"Krasnoyarsk Summer Time\"]},\"Europe/Samara\":{\"long\":[\"Samara Standard Time\",\"Samara Summer Time\"]},\"Pacific/Kiritimati\":{\"long\":[\"Line Islands Time\",\"Line Islands Time\"]},\"Australia/Lord_Howe\":{\"long\":[\"Lord Howe Standard Time\",\"Lord Howe Daylight Time\"]},\"Asia/Macau\":{\"long\":[\"Macao Standard Time\",\"Macao Summer Time\"]},\"Antarctica/Macquarie\":{\"long\":[\"Macquarie Island Time\",\"Macquarie Island Time\"]},\"Asia/Magadan\":{\"long\":[\"Magadan Standard Time\",\"Magadan Summer Time\"]},\"Indian/Maldives\":{\"long\":[\"Maldives Time\",\"Maldives Time\"]},\"Pacific/Marquesas\":{\"long\":[\"Marquesas Time\",\"Marquesas Time\"]},\"Pacific/Majuro\":{\"long\":[\"Marshall Islands Time\",\"Marshall Islands Time\"]},\"Indian/Mauritius\":{\"long\":[\"Mauritius Standard Time\",\"Mauritius Summer Time\"]},\"Antarctica/Mawson\":{\"long\":[\"Mawson Time\",\"Mawson Time\"]},\"America/Santa_Isabel\":{\"long\":[\"Northwest Mexico Standard Time\",\"Northwest Mexico Daylight Time\"]},\"America/Mazatlan\":{\"long\":[\"Mexican Pacific Standard Time\",\"Mexican Pacific Daylight Time\"]},\"Asia/Ulaanbaatar\":{\"long\":[\"Ulaanbaatar Standard Time\",\"Ulaanbaatar Summer Time\"]},\"Europe/Moscow\":{\"long\":[\"Moscow Standard Time\",\"Moscow Summer Time\"]},\"Asia/Rangoon\":{\"long\":[\"Myanmar Time\",\"Myanmar Time\"]},\"Pacific/Nauru\":{\"long\":[\"Nauru Time\",\"Nauru Time\"]},\"Asia/Katmandu\":{\"long\":[\"Nepal Time\",\"Nepal Time\"]},\"Pacific/Noumea\":{\"long\":[\"New Caledonia Standard Time\",\"New Caledonia Summer Time\"]},\"Pacific/Auckland\":{\"long\":[\"New Zealand Standard Time\",\"New Zealand Daylight Time\"]},\"Antarctica/McMurdo\":{\"long\":[\"New Zealand Standard Time\",\"New Zealand Daylight Time\"]},\"America/St_Johns\":{\"long\":[\"Newfoundland Standard Time\",\"Newfoundland Daylight Time\"]},\"Pacific/Niue\":{\"long\":[\"Niue Time\",\"Niue Time\"]},\"Pacific/Norfolk\":{\"long\":[\"Norfolk Island Standard Time\",\"Norfolk Island Daylight Time\"]},\"America/Noronha\":{\"long\":[\"Fernando de Noronha Standard Time\",\"Fernando de Noronha Summer Time\"]},\"Asia/Novosibirsk\":{\"long\":[\"Novosibirsk Standard Time\",\"Novosibirsk Summer Time\"]},\"Asia/Omsk\":{\"long\":[\"Omsk Standard Time\",\"Omsk Summer Time\"]},\"Pacific/Palau\":{\"long\":[\"Palau Time\",\"Palau Time\"]},\"Pacific/Port_Moresby\":{\"long\":[\"Papua New Guinea Time\",\"Papua New Guinea Time\"]},\"America/Asuncion\":{\"long\":[\"Paraguay Standard Time\",\"Paraguay Summer Time\"]},\"America/Lima\":{\"long\":[\"Peru Standard Time\",\"Peru Summer Time\"]},\"Asia/Manila\":{\"long\":[\"Philippine Standard Time\",\"Philippine Summer Time\"]},\"Pacific/Enderbury\":{\"long\":[\"Phoenix Islands Time\",\"Phoenix Islands Time\"]},\"America/Miquelon\":{\"long\":[\"St. Pierre & Miquelon Standard Time\",\"St. Pierre & Miquelon Daylight Time\"]},\"Pacific/Pitcairn\":{\"long\":[\"Pitcairn Time\",\"Pitcairn Time\"]},\"Pacific/Ponape\":{\"long\":[\"Ponape Time\",\"Ponape Time\"]},\"Asia/Pyongyang\":{\"long\":[\"Pyongyang Time\",\"Pyongyang Time\"]},\"Indian/Reunion\":{\"long\":[\"Réunion Time\",\"Réunion Time\"]},\"Antarctica/Rothera\":{\"long\":[\"Rothera Time\",\"Rothera Time\"]},\"Asia/Sakhalin\":{\"long\":[\"Sakhalin Standard Time\",\"Sakhalin Summer Time\"]},\"Pacific/Pago_Pago\":{\"long\":[\"Samoa Standard Time\",\"Samoa Daylight Time\"]},\"Indian/Mahe\":{\"long\":[\"Seychelles Time\",\"Seychelles Time\"]},\"Asia/Singapore\":{\"long\":[\"Singapore Standard Time\",\"Singapore Standard Time\"]},\"Pacific/Guadalcanal\":{\"long\":[\"Solomon Islands Time\",\"Solomon Islands Time\"]},\"Atlantic/South_Georgia\":{\"long\":[\"South Georgia Time\",\"South Georgia Time\"]},\"Asia/Yekaterinburg\":{\"long\":[\"Yekaterinburg Standard Time\",\"Yekaterinburg Summer Time\"]},\"Antarctica/Syowa\":{\"long\":[\"Syowa Time\",\"Syowa Time\"]},\"Pacific/Tahiti\":{\"long\":[\"Tahiti Time\",\"Tahiti Time\"]},\"Asia/Taipei\":{\"long\":[\"Taipei Standard Time\",\"Taipei Daylight Time\"]},\"Asia/Tashkent\":{\"long\":[\"Uzbekistan Standard Time\",\"Uzbekistan Summer Time\"]},\"Pacific/Fakaofo\":{\"long\":[\"Tokelau Time\",\"Tokelau Time\"]},\"Pacific/Tongatapu\":{\"long\":[\"Tonga Standard Time\",\"Tonga Summer Time\"]},\"Pacific/Truk\":{\"long\":[\"Chuuk Time\",\"Chuuk Time\"]},\"Pacific/Funafuti\":{\"long\":[\"Tuvalu Time\",\"Tuvalu Time\"]},\"America/Montevideo\":{\"long\":[\"Uruguay Standard Time\",\"Uruguay Summer Time\"]},\"Pacific/Efate\":{\"long\":[\"Vanuatu Standard Time\",\"Vanuatu Summer Time\"]},\"America/Caracas\":{\"long\":[\"Venezuela Time\",\"Venezuela Time\"]},\"Asia/Vladivostok\":{\"long\":[\"Vladivostok Standard Time\",\"Vladivostok Summer Time\"]},\"Europe/Volgograd\":{\"long\":[\"Volgograd Standard Time\",\"Volgograd Summer Time\"]},\"Antarctica/Vostok\":{\"long\":[\"Vostok Time\",\"Vostok Time\"]},\"Pacific/Wake\":{\"long\":[\"Wake Island Time\",\"Wake Island Time\"]},\"Pacific/Wallis\":{\"long\":[\"Wallis & Futuna Time\",\"Wallis & Futuna Time\"]},\"Asia/Yakutsk\":{\"long\":[\"Yakutsk Standard Time\",\"Yakutsk Summer Time\"]},\"UTC\":{\"long\":[\"Coordinated Universal Time\",\"Coordinated Universal Time\"],\"short\":[\"UTC\",\"UTC\"]}},\"gmtFormat\":\"GMT{0}\",\"hourFormat\":\"+HH:mm;-HH:mm\",\"dateFormat\":{\"full\":\"EEEE, MMMM d, y\",\"long\":\"MMMM d, y\",\"medium\":\"MMM d, y\",\"short\":\"M/d/yy\"},\"timeFormat\":{\"full\":\"h:mm:ss a zzzz\",\"long\":\"h:mm:ss a z\",\"medium\":\"h:mm:ss a\",\"short\":\"h:mm a\"},\"dateTimeFormat\":{\"full\":\"{1} 'at' {0}\",\"long\":\"{1} 'at' {0}\",\"medium\":\"{1}, {0}\",\"short\":\"{1}, {0}\"},\"formats\":{\"gregory\":{\"Bh\":\"h B\",\"Bhm\":\"h:mm B\",\"Bhms\":\"h:mm:ss B\",\"d\":\"d\",\"E\":\"ccc\",\"EBhm\":\"E h:mm B\",\"EBhms\":\"E h:mm:ss B\",\"Ed\":\"d E\",\"Ehm\":\"E h:mm a\",\"EHm\":\"E HH:mm\",\"Ehms\":\"E h:mm:ss a\",\"EHms\":\"E HH:mm:ss\",\"Gy\":\"y G\",\"GyMMM\":\"MMM y G\",\"GyMMMd\":\"MMM d, y G\",\"GyMMMEd\":\"E, MMM d, y G\",\"h\":\"h a\",\"H\":\"HH\",\"hm\":\"h:mm a\",\"Hm\":\"HH:mm\",\"hms\":\"h:mm:ss a\",\"Hms\":\"HH:mm:ss\",\"hmsv\":\"h:mm:ss a v\",\"Hmsv\":\"HH:mm:ss v\",\"hmv\":\"h:mm a v\",\"Hmv\":\"HH:mm v\",\"M\":\"L\",\"Md\":\"M/d\",\"MEd\":\"E, M/d\",\"MMM\":\"LLL\",\"MMMd\":\"MMM d\",\"MMMEd\":\"E, MMM d\",\"MMMMd\":\"MMMM d\",\"ms\":\"mm:ss\",\"y\":\"y\",\"yM\":\"M/y\",\"yMd\":\"M/d/y\",\"yMEd\":\"E, M/d/y\",\"yMMM\":\"MMM y\",\"yMMMd\":\"MMM d, y\",\"yMMMEd\":\"E, MMM d, y\",\"yMMMM\":\"MMMM y\",\"EEEE, MMMM d, y\":\"EEEE, MMMM d, y\",\"MMMM d, y\":\"MMMM d, y\",\"MMM d, y\":\"MMM d, y\",\"M/d/yy\":\"M/d/yy\",\"h:mm:ss a zzzz\":\"h:mm:ss a zzzz\",\"h:mm:ss a z\":\"h:mm:ss a z\",\"h:mm:ss a\":\"h:mm:ss a\",\"h:mm a\":\"h:mm a\",\"EEEE, MMMM d, y 'at' h:mm:ss a zzzz\":\"EEEE, MMMM d, y 'at' h:mm:ss a zzzz\",\"MMMM d, y 'at' h:mm:ss a zzzz\":\"MMMM d, y 'at' h:mm:ss a zzzz\",\"MMM d, y, h:mm:ss a zzzz\":\"MMM d, y, h:mm:ss a zzzz\",\"M/d/yy, h:mm:ss a zzzz\":\"M/d/yy, h:mm:ss a zzzz\",\"d, h:mm:ss a zzzz\":\"d, h:mm:ss a zzzz\",\"E, h:mm:ss a zzzz\":\"ccc, h:mm:ss a zzzz\",\"Ed, h:mm:ss a zzzz\":\"d E, h:mm:ss a zzzz\",\"Gy, h:mm:ss a zzzz\":\"y G, h:mm:ss a zzzz\",\"GyMMM, h:mm:ss a zzzz\":\"MMM y G, h:mm:ss a zzzz\",\"GyMMMd, h:mm:ss a zzzz\":\"MMM d, y G, h:mm:ss a zzzz\",\"GyMMMEd, h:mm:ss a zzzz\":\"E, MMM d, y G, h:mm:ss a zzzz\",\"M, h:mm:ss a zzzz\":\"L, h:mm:ss a zzzz\",\"Md, h:mm:ss a zzzz\":\"M/d, h:mm:ss a zzzz\",\"MEd, h:mm:ss a zzzz\":\"E, M/d, h:mm:ss a zzzz\",\"MMM, h:mm:ss a zzzz\":\"LLL, h:mm:ss a zzzz\",\"MMMd, h:mm:ss a zzzz\":\"MMM d, h:mm:ss a zzzz\",\"MMMEd, h:mm:ss a zzzz\":\"E, MMM d, h:mm:ss a zzzz\",\"MMMMd 'at' h:mm:ss a zzzz\":\"MMMM d 'at' h:mm:ss a zzzz\",\"y, h:mm:ss a zzzz\":\"y, h:mm:ss a zzzz\",\"yM, h:mm:ss a zzzz\":\"M/y, h:mm:ss a zzzz\",\"yMd, h:mm:ss a zzzz\":\"M/d/y, h:mm:ss a zzzz\",\"yMEd, h:mm:ss a zzzz\":\"E, M/d/y, h:mm:ss a zzzz\",\"yMMM, h:mm:ss a zzzz\":\"MMM y, h:mm:ss a zzzz\",\"yMMMd, h:mm:ss a zzzz\":\"MMM d, y, h:mm:ss a zzzz\",\"yMMMEd, h:mm:ss a zzzz\":\"E, MMM d, y, h:mm:ss a zzzz\",\"yMMMM 'at' h:mm:ss a zzzz\":\"MMMM y 'at' h:mm:ss a zzzz\",\"EEEE, MMMM d, y 'at' h:mm:ss a z\":\"EEEE, MMMM d, y 'at' h:mm:ss a z\",\"MMMM d, y 'at' h:mm:ss a z\":\"MMMM d, y 'at' h:mm:ss a z\",\"MMM d, y, h:mm:ss a z\":\"MMM d, y, h:mm:ss a z\",\"M/d/yy, h:mm:ss a z\":\"M/d/yy, h:mm:ss a z\",\"d, h:mm:ss a z\":\"d, h:mm:ss a z\",\"E, h:mm:ss a z\":\"ccc, h:mm:ss a z\",\"Ed, h:mm:ss a z\":\"d E, h:mm:ss a z\",\"Gy, h:mm:ss a z\":\"y G, h:mm:ss a z\",\"GyMMM, h:mm:ss a z\":\"MMM y G, h:mm:ss a z\",\"GyMMMd, h:mm:ss a z\":\"MMM d, y G, h:mm:ss a z\",\"GyMMMEd, h:mm:ss a z\":\"E, MMM d, y G, h:mm:ss a z\",\"M, h:mm:ss a z\":\"L, h:mm:ss a z\",\"Md, h:mm:ss a z\":\"M/d, h:mm:ss a z\",\"MEd, h:mm:ss a z\":\"E, M/d, h:mm:ss a z\",\"MMM, h:mm:ss a z\":\"LLL, h:mm:ss a z\",\"MMMd, h:mm:ss a z\":\"MMM d, h:mm:ss a z\",\"MMMEd, h:mm:ss a z\":\"E, MMM d, h:mm:ss a z\",\"MMMMd 'at' h:mm:ss a z\":\"MMMM d 'at' h:mm:ss a z\",\"y, h:mm:ss a z\":\"y, h:mm:ss a z\",\"yM, h:mm:ss a z\":\"M/y, h:mm:ss a z\",\"yMd, h:mm:ss a z\":\"M/d/y, h:mm:ss a z\",\"yMEd, h:mm:ss a z\":\"E, M/d/y, h:mm:ss a z\",\"yMMM, h:mm:ss a z\":\"MMM y, h:mm:ss a z\",\"yMMMd, h:mm:ss a z\":\"MMM d, y, h:mm:ss a z\",\"yMMMEd, h:mm:ss a z\":\"E, MMM d, y, h:mm:ss a z\",\"yMMMM 'at' h:mm:ss a z\":\"MMMM y 'at' h:mm:ss a z\",\"EEEE, MMMM d, y 'at' h:mm:ss a\":\"EEEE, MMMM d, y 'at' h:mm:ss a\",\"MMMM d, y 'at' h:mm:ss a\":\"MMMM d, y 'at' h:mm:ss a\",\"MMM d, y, h:mm:ss a\":\"MMM d, y, h:mm:ss a\",\"M/d/yy, h:mm:ss a\":\"M/d/yy, h:mm:ss a\",\"d, h:mm:ss a\":\"d, h:mm:ss a\",\"E, h:mm:ss a\":\"ccc, h:mm:ss a\",\"Ed, h:mm:ss a\":\"d E, h:mm:ss a\",\"Gy, h:mm:ss a\":\"y G, h:mm:ss a\",\"GyMMM, h:mm:ss a\":\"MMM y G, h:mm:ss a\",\"GyMMMd, h:mm:ss a\":\"MMM d, y G, h:mm:ss a\",\"GyMMMEd, h:mm:ss a\":\"E, MMM d, y G, h:mm:ss a\",\"M, h:mm:ss a\":\"L, h:mm:ss a\",\"Md, h:mm:ss a\":\"M/d, h:mm:ss a\",\"MEd, h:mm:ss a\":\"E, M/d, h:mm:ss a\",\"MMM, h:mm:ss a\":\"LLL, h:mm:ss a\",\"MMMd, h:mm:ss a\":\"MMM d, h:mm:ss a\",\"MMMEd, h:mm:ss a\":\"E, MMM d, h:mm:ss a\",\"MMMMd 'at' h:mm:ss a\":\"MMMM d 'at' h:mm:ss a\",\"y, h:mm:ss a\":\"y, h:mm:ss a\",\"yM, h:mm:ss a\":\"M/y, h:mm:ss a\",\"yMd, h:mm:ss a\":\"M/d/y, h:mm:ss a\",\"yMEd, h:mm:ss a\":\"E, M/d/y, h:mm:ss a\",\"yMMM, h:mm:ss a\":\"MMM y, h:mm:ss a\",\"yMMMd, h:mm:ss a\":\"MMM d, y, h:mm:ss a\",\"yMMMEd, h:mm:ss a\":\"E, MMM d, y, h:mm:ss a\",\"yMMMM 'at' h:mm:ss a\":\"MMMM y 'at' h:mm:ss a\",\"EEEE, MMMM d, y 'at' h:mm a\":\"EEEE, MMMM d, y 'at' h:mm a\",\"MMMM d, y 'at' h:mm a\":\"MMMM d, y 'at' h:mm a\",\"MMM d, y, h:mm a\":\"MMM d, y, h:mm a\",\"M/d/yy, h:mm a\":\"M/d/yy, h:mm a\",\"d, h:mm a\":\"d, h:mm a\",\"E, h:mm a\":\"ccc, h:mm a\",\"Ed, h:mm a\":\"d E, h:mm a\",\"Gy, h:mm a\":\"y G, h:mm a\",\"GyMMM, h:mm a\":\"MMM y G, h:mm a\",\"GyMMMd, h:mm a\":\"MMM d, y G, h:mm a\",\"GyMMMEd, h:mm a\":\"E, MMM d, y G, h:mm a\",\"M, h:mm a\":\"L, h:mm a\",\"Md, h:mm a\":\"M/d, h:mm a\",\"MEd, h:mm a\":\"E, M/d, h:mm a\",\"MMM, h:mm a\":\"LLL, h:mm a\",\"MMMd, h:mm a\":\"MMM d, h:mm a\",\"MMMEd, h:mm a\":\"E, MMM d, h:mm a\",\"MMMMd 'at' h:mm a\":\"MMMM d 'at' h:mm a\",\"y, h:mm a\":\"y, h:mm a\",\"yM, h:mm a\":\"M/y, h:mm a\",\"yMd, h:mm a\":\"M/d/y, h:mm a\",\"yMEd, h:mm a\":\"E, M/d/y, h:mm a\",\"yMMM, h:mm a\":\"MMM y, h:mm a\",\"yMMMd, h:mm a\":\"MMM d, y, h:mm a\",\"yMMMEd, h:mm a\":\"E, MMM d, y, h:mm a\",\"yMMMM 'at' h:mm a\":\"MMMM y 'at' h:mm a\",\"EEEE, MMMM d, y 'at' Bh\":\"EEEE, MMMM d, y 'at' h B\",\"MMMM d, y 'at' Bh\":\"MMMM d, y 'at' h B\",\"MMM d, y, Bh\":\"MMM d, y, h B\",\"M/d/yy, Bh\":\"M/d/yy, h B\",\"d, Bh\":\"d, h B\",\"E, Bh\":\"ccc, h B\",\"Ed, Bh\":\"d E, h B\",\"Gy, Bh\":\"y G, h B\",\"GyMMM, Bh\":\"MMM y G, h B\",\"GyMMMd, Bh\":\"MMM d, y G, h B\",\"GyMMMEd, Bh\":\"E, MMM d, y G, h B\",\"M, Bh\":\"L, h B\",\"Md, Bh\":\"M/d, h B\",\"MEd, Bh\":\"E, M/d, h B\",\"MMM, Bh\":\"LLL, h B\",\"MMMd, Bh\":\"MMM d, h B\",\"MMMEd, Bh\":\"E, MMM d, h B\",\"MMMMd 'at' Bh\":\"MMMM d 'at' h B\",\"y, Bh\":\"y, h B\",\"yM, Bh\":\"M/y, h B\",\"yMd, Bh\":\"M/d/y, h B\",\"yMEd, Bh\":\"E, M/d/y, h B\",\"yMMM, Bh\":\"MMM y, h B\",\"yMMMd, Bh\":\"MMM d, y, h B\",\"yMMMEd, Bh\":\"E, MMM d, y, h B\",\"yMMMM 'at' Bh\":\"MMMM y 'at' h B\",\"EEEE, MMMM d, y 'at' Bhm\":\"EEEE, MMMM d, y 'at' h:mm B\",\"MMMM d, y 'at' Bhm\":\"MMMM d, y 'at' h:mm B\",\"MMM d, y, Bhm\":\"MMM d, y, h:mm B\",\"M/d/yy, Bhm\":\"M/d/yy, h:mm B\",\"d, Bhm\":\"d, h:mm B\",\"E, Bhm\":\"ccc, h:mm B\",\"Ed, Bhm\":\"d E, h:mm B\",\"Gy, Bhm\":\"y G, h:mm B\",\"GyMMM, Bhm\":\"MMM y G, h:mm B\",\"GyMMMd, Bhm\":\"MMM d, y G, h:mm B\",\"GyMMMEd, Bhm\":\"E, MMM d, y G, h:mm B\",\"M, Bhm\":\"L, h:mm B\",\"Md, Bhm\":\"M/d, h:mm B\",\"MEd, Bhm\":\"E, M/d, h:mm B\",\"MMM, Bhm\":\"LLL, h:mm B\",\"MMMd, Bhm\":\"MMM d, h:mm B\",\"MMMEd, Bhm\":\"E, MMM d, h:mm B\",\"MMMMd 'at' Bhm\":\"MMMM d 'at' h:mm B\",\"y, Bhm\":\"y, h:mm B\",\"yM, Bhm\":\"M/y, h:mm B\",\"yMd, Bhm\":\"M/d/y, h:mm B\",\"yMEd, Bhm\":\"E, M/d/y, h:mm B\",\"yMMM, Bhm\":\"MMM y, h:mm B\",\"yMMMd, Bhm\":\"MMM d, y, h:mm B\",\"yMMMEd, Bhm\":\"E, MMM d, y, h:mm B\",\"yMMMM 'at' Bhm\":\"MMMM y 'at' h:mm B\",\"EEEE, MMMM d, y 'at' Bhms\":\"EEEE, MMMM d, y 'at' h:mm:ss B\",\"MMMM d, y 'at' Bhms\":\"MMMM d, y 'at' h:mm:ss B\",\"MMM d, y, Bhms\":\"MMM d, y, h:mm:ss B\",\"M/d/yy, Bhms\":\"M/d/yy, h:mm:ss B\",\"d, Bhms\":\"d, h:mm:ss B\",\"E, Bhms\":\"ccc, h:mm:ss B\",\"Ed, Bhms\":\"d E, h:mm:ss B\",\"Gy, Bhms\":\"y G, h:mm:ss B\",\"GyMMM, Bhms\":\"MMM y G, h:mm:ss B\",\"GyMMMd, Bhms\":\"MMM d, y G, h:mm:ss B\",\"GyMMMEd, Bhms\":\"E, MMM d, y G, h:mm:ss B\",\"M, Bhms\":\"L, h:mm:ss B\",\"Md, Bhms\":\"M/d, h:mm:ss B\",\"MEd, Bhms\":\"E, M/d, h:mm:ss B\",\"MMM, Bhms\":\"LLL, h:mm:ss B\",\"MMMd, Bhms\":\"MMM d, h:mm:ss B\",\"MMMEd, Bhms\":\"E, MMM d, h:mm:ss B\",\"MMMMd 'at' Bhms\":\"MMMM d 'at' h:mm:ss B\",\"y, Bhms\":\"y, h:mm:ss B\",\"yM, Bhms\":\"M/y, h:mm:ss B\",\"yMd, Bhms\":\"M/d/y, h:mm:ss B\",\"yMEd, Bhms\":\"E, M/d/y, h:mm:ss B\",\"yMMM, Bhms\":\"MMM y, h:mm:ss B\",\"yMMMd, Bhms\":\"MMM d, y, h:mm:ss B\",\"yMMMEd, Bhms\":\"E, MMM d, y, h:mm:ss B\",\"yMMMM 'at' Bhms\":\"MMMM y 'at' h:mm:ss B\",\"EEEE, MMMM d, y 'at' h\":\"EEEE, MMMM d, y 'at' h a\",\"MMMM d, y 'at' h\":\"MMMM d, y 'at' h a\",\"MMM d, y, h\":\"MMM d, y, h a\",\"M/d/yy, h\":\"M/d/yy, h a\",\"d, h\":\"d, h a\",\"E, h\":\"ccc, h a\",\"Ed, h\":\"d E, h a\",\"Gy, h\":\"y G, h a\",\"GyMMM, h\":\"MMM y G, h a\",\"GyMMMd, h\":\"MMM d, y G, h a\",\"GyMMMEd, h\":\"E, MMM d, y G, h a\",\"M, h\":\"L, h a\",\"Md, h\":\"M/d, h a\",\"MEd, h\":\"E, M/d, h a\",\"MMM, h\":\"LLL, h a\",\"MMMd, h\":\"MMM d, h a\",\"MMMEd, h\":\"E, MMM d, h a\",\"MMMMd 'at' h\":\"MMMM d 'at' h a\",\"y, h\":\"y, h a\",\"yM, h\":\"M/y, h a\",\"yMd, h\":\"M/d/y, h a\",\"yMEd, h\":\"E, M/d/y, h a\",\"yMMM, h\":\"MMM y, h a\",\"yMMMd, h\":\"MMM d, y, h a\",\"yMMMEd, h\":\"E, MMM d, y, h a\",\"yMMMM 'at' h\":\"MMMM y 'at' h a\",\"EEEE, MMMM d, y 'at' H\":\"EEEE, MMMM d, y 'at' HH\",\"MMMM d, y 'at' H\":\"MMMM d, y 'at' HH\",\"MMM d, y, H\":\"MMM d, y, HH\",\"M/d/yy, H\":\"M/d/yy, HH\",\"d, H\":\"d, HH\",\"E, H\":\"ccc, HH\",\"Ed, H\":\"d E, HH\",\"Gy, H\":\"y G, HH\",\"GyMMM, H\":\"MMM y G, HH\",\"GyMMMd, H\":\"MMM d, y G, HH\",\"GyMMMEd, H\":\"E, MMM d, y G, HH\",\"M, H\":\"L, HH\",\"Md, H\":\"M/d, HH\",\"MEd, H\":\"E, M/d, HH\",\"MMM, H\":\"LLL, HH\",\"MMMd, H\":\"MMM d, HH\",\"MMMEd, H\":\"E, MMM d, HH\",\"MMMMd 'at' H\":\"MMMM d 'at' HH\",\"y, H\":\"y, HH\",\"yM, H\":\"M/y, HH\",\"yMd, H\":\"M/d/y, HH\",\"yMEd, H\":\"E, M/d/y, HH\",\"yMMM, H\":\"MMM y, HH\",\"yMMMd, H\":\"MMM d, y, HH\",\"yMMMEd, H\":\"E, MMM d, y, HH\",\"yMMMM 'at' H\":\"MMMM y 'at' HH\",\"EEEE, MMMM d, y 'at' hm\":\"EEEE, MMMM d, y 'at' h:mm a\",\"MMMM d, y 'at' hm\":\"MMMM d, y 'at' h:mm a\",\"MMM d, y, hm\":\"MMM d, y, h:mm a\",\"M/d/yy, hm\":\"M/d/yy, h:mm a\",\"d, hm\":\"d, h:mm a\",\"E, hm\":\"ccc, h:mm a\",\"Ed, hm\":\"d E, h:mm a\",\"Gy, hm\":\"y G, h:mm a\",\"GyMMM, hm\":\"MMM y G, h:mm a\",\"GyMMMd, hm\":\"MMM d, y G, h:mm a\",\"GyMMMEd, hm\":\"E, MMM d, y G, h:mm a\",\"M, hm\":\"L, h:mm a\",\"Md, hm\":\"M/d, h:mm a\",\"MEd, hm\":\"E, M/d, h:mm a\",\"MMM, hm\":\"LLL, h:mm a\",\"MMMd, hm\":\"MMM d, h:mm a\",\"MMMEd, hm\":\"E, MMM d, h:mm a\",\"MMMMd 'at' hm\":\"MMMM d 'at' h:mm a\",\"y, hm\":\"y, h:mm a\",\"yM, hm\":\"M/y, h:mm a\",\"yMd, hm\":\"M/d/y, h:mm a\",\"yMEd, hm\":\"E, M/d/y, h:mm a\",\"yMMM, hm\":\"MMM y, h:mm a\",\"yMMMd, hm\":\"MMM d, y, h:mm a\",\"yMMMEd, hm\":\"E, MMM d, y, h:mm a\",\"yMMMM 'at' hm\":\"MMMM y 'at' h:mm a\",\"EEEE, MMMM d, y 'at' Hm\":\"EEEE, MMMM d, y 'at' HH:mm\",\"MMMM d, y 'at' Hm\":\"MMMM d, y 'at' HH:mm\",\"MMM d, y, Hm\":\"MMM d, y, HH:mm\",\"M/d/yy, Hm\":\"M/d/yy, HH:mm\",\"d, Hm\":\"d, HH:mm\",\"E, Hm\":\"ccc, HH:mm\",\"Ed, Hm\":\"d E, HH:mm\",\"Gy, Hm\":\"y G, HH:mm\",\"GyMMM, Hm\":\"MMM y G, HH:mm\",\"GyMMMd, Hm\":\"MMM d, y G, HH:mm\",\"GyMMMEd, Hm\":\"E, MMM d, y G, HH:mm\",\"M, Hm\":\"L, HH:mm\",\"Md, Hm\":\"M/d, HH:mm\",\"MEd, Hm\":\"E, M/d, HH:mm\",\"MMM, Hm\":\"LLL, HH:mm\",\"MMMd, Hm\":\"MMM d, HH:mm\",\"MMMEd, Hm\":\"E, MMM d, HH:mm\",\"MMMMd 'at' Hm\":\"MMMM d 'at' HH:mm\",\"y, Hm\":\"y, HH:mm\",\"yM, Hm\":\"M/y, HH:mm\",\"yMd, Hm\":\"M/d/y, HH:mm\",\"yMEd, Hm\":\"E, M/d/y, HH:mm\",\"yMMM, Hm\":\"MMM y, HH:mm\",\"yMMMd, Hm\":\"MMM d, y, HH:mm\",\"yMMMEd, Hm\":\"E, MMM d, y, HH:mm\",\"yMMMM 'at' Hm\":\"MMMM y 'at' HH:mm\",\"EEEE, MMMM d, y 'at' hms\":\"EEEE, MMMM d, y 'at' h:mm:ss a\",\"MMMM d, y 'at' hms\":\"MMMM d, y 'at' h:mm:ss a\",\"MMM d, y, hms\":\"MMM d, y, h:mm:ss a\",\"M/d/yy, hms\":\"M/d/yy, h:mm:ss a\",\"d, hms\":\"d, h:mm:ss a\",\"E, hms\":\"ccc, h:mm:ss a\",\"Ed, hms\":\"d E, h:mm:ss a\",\"Gy, hms\":\"y G, h:mm:ss a\",\"GyMMM, hms\":\"MMM y G, h:mm:ss a\",\"GyMMMd, hms\":\"MMM d, y G, h:mm:ss a\",\"GyMMMEd, hms\":\"E, MMM d, y G, h:mm:ss a\",\"M, hms\":\"L, h:mm:ss a\",\"Md, hms\":\"M/d, h:mm:ss a\",\"MEd, hms\":\"E, M/d, h:mm:ss a\",\"MMM, hms\":\"LLL, h:mm:ss a\",\"MMMd, hms\":\"MMM d, h:mm:ss a\",\"MMMEd, hms\":\"E, MMM d, h:mm:ss a\",\"MMMMd 'at' hms\":\"MMMM d 'at' h:mm:ss a\",\"y, hms\":\"y, h:mm:ss a\",\"yM, hms\":\"M/y, h:mm:ss a\",\"yMd, hms\":\"M/d/y, h:mm:ss a\",\"yMEd, hms\":\"E, M/d/y, h:mm:ss a\",\"yMMM, hms\":\"MMM y, h:mm:ss a\",\"yMMMd, hms\":\"MMM d, y, h:mm:ss a\",\"yMMMEd, hms\":\"E, MMM d, y, h:mm:ss a\",\"yMMMM 'at' hms\":\"MMMM y 'at' h:mm:ss a\",\"EEEE, MMMM d, y 'at' Hms\":\"EEEE, MMMM d, y 'at' HH:mm:ss\",\"MMMM d, y 'at' Hms\":\"MMMM d, y 'at' HH:mm:ss\",\"MMM d, y, Hms\":\"MMM d, y, HH:mm:ss\",\"M/d/yy, Hms\":\"M/d/yy, HH:mm:ss\",\"d, Hms\":\"d, HH:mm:ss\",\"E, Hms\":\"ccc, HH:mm:ss\",\"Ed, Hms\":\"d E, HH:mm:ss\",\"Gy, Hms\":\"y G, HH:mm:ss\",\"GyMMM, Hms\":\"MMM y G, HH:mm:ss\",\"GyMMMd, Hms\":\"MMM d, y G, HH:mm:ss\",\"GyMMMEd, Hms\":\"E, MMM d, y G, HH:mm:ss\",\"M, Hms\":\"L, HH:mm:ss\",\"Md, Hms\":\"M/d, HH:mm:ss\",\"MEd, Hms\":\"E, M/d, HH:mm:ss\",\"MMM, Hms\":\"LLL, HH:mm:ss\",\"MMMd, Hms\":\"MMM d, HH:mm:ss\",\"MMMEd, Hms\":\"E, MMM d, HH:mm:ss\",\"MMMMd 'at' Hms\":\"MMMM d 'at' HH:mm:ss\",\"y, Hms\":\"y, HH:mm:ss\",\"yM, Hms\":\"M/y, HH:mm:ss\",\"yMd, Hms\":\"M/d/y, HH:mm:ss\",\"yMEd, Hms\":\"E, M/d/y, HH:mm:ss\",\"yMMM, Hms\":\"MMM y, HH:mm:ss\",\"yMMMd, Hms\":\"MMM d, y, HH:mm:ss\",\"yMMMEd, Hms\":\"E, MMM d, y, HH:mm:ss\",\"yMMMM 'at' Hms\":\"MMMM y 'at' HH:mm:ss\",\"EEEE, MMMM d, y 'at' hmsv\":\"EEEE, MMMM d, y 'at' h:mm:ss a v\",\"MMMM d, y 'at' hmsv\":\"MMMM d, y 'at' h:mm:ss a v\",\"MMM d, y, hmsv\":\"MMM d, y, h:mm:ss a v\",\"M/d/yy, hmsv\":\"M/d/yy, h:mm:ss a v\",\"d, hmsv\":\"d, h:mm:ss a v\",\"E, hmsv\":\"ccc, h:mm:ss a v\",\"Ed, hmsv\":\"d E, h:mm:ss a v\",\"Gy, hmsv\":\"y G, h:mm:ss a v\",\"GyMMM, hmsv\":\"MMM y G, h:mm:ss a v\",\"GyMMMd, hmsv\":\"MMM d, y G, h:mm:ss a v\",\"GyMMMEd, hmsv\":\"E, MMM d, y G, h:mm:ss a v\",\"M, hmsv\":\"L, h:mm:ss a v\",\"Md, hmsv\":\"M/d, h:mm:ss a v\",\"MEd, hmsv\":\"E, M/d, h:mm:ss a v\",\"MMM, hmsv\":\"LLL, h:mm:ss a v\",\"MMMd, hmsv\":\"MMM d, h:mm:ss a v\",\"MMMEd, hmsv\":\"E, MMM d, h:mm:ss a v\",\"MMMMd 'at' hmsv\":\"MMMM d 'at' h:mm:ss a v\",\"y, hmsv\":\"y, h:mm:ss a v\",\"yM, hmsv\":\"M/y, h:mm:ss a v\",\"yMd, hmsv\":\"M/d/y, h:mm:ss a v\",\"yMEd, hmsv\":\"E, M/d/y, h:mm:ss a v\",\"yMMM, hmsv\":\"MMM y, h:mm:ss a v\",\"yMMMd, hmsv\":\"MMM d, y, h:mm:ss a v\",\"yMMMEd, hmsv\":\"E, MMM d, y, h:mm:ss a v\",\"yMMMM 'at' hmsv\":\"MMMM y 'at' h:mm:ss a v\",\"EEEE, MMMM d, y 'at' Hmsv\":\"EEEE, MMMM d, y 'at' HH:mm:ss v\",\"MMMM d, y 'at' Hmsv\":\"MMMM d, y 'at' HH:mm:ss v\",\"MMM d, y, Hmsv\":\"MMM d, y, HH:mm:ss v\",\"M/d/yy, Hmsv\":\"M/d/yy, HH:mm:ss v\",\"d, Hmsv\":\"d, HH:mm:ss v\",\"E, Hmsv\":\"ccc, HH:mm:ss v\",\"Ed, Hmsv\":\"d E, HH:mm:ss v\",\"Gy, Hmsv\":\"y G, HH:mm:ss v\",\"GyMMM, Hmsv\":\"MMM y G, HH:mm:ss v\",\"GyMMMd, Hmsv\":\"MMM d, y G, HH:mm:ss v\",\"GyMMMEd, Hmsv\":\"E, MMM d, y G, HH:mm:ss v\",\"M, Hmsv\":\"L, HH:mm:ss v\",\"Md, Hmsv\":\"M/d, HH:mm:ss v\",\"MEd, Hmsv\":\"E, M/d, HH:mm:ss v\",\"MMM, Hmsv\":\"LLL, HH:mm:ss v\",\"MMMd, Hmsv\":\"MMM d, HH:mm:ss v\",\"MMMEd, Hmsv\":\"E, MMM d, HH:mm:ss v\",\"MMMMd 'at' Hmsv\":\"MMMM d 'at' HH:mm:ss v\",\"y, Hmsv\":\"y, HH:mm:ss v\",\"yM, Hmsv\":\"M/y, HH:mm:ss v\",\"yMd, Hmsv\":\"M/d/y, HH:mm:ss v\",\"yMEd, Hmsv\":\"E, M/d/y, HH:mm:ss v\",\"yMMM, Hmsv\":\"MMM y, HH:mm:ss v\",\"yMMMd, Hmsv\":\"MMM d, y, HH:mm:ss v\",\"yMMMEd, Hmsv\":\"E, MMM d, y, HH:mm:ss v\",\"yMMMM 'at' Hmsv\":\"MMMM y 'at' HH:mm:ss v\",\"EEEE, MMMM d, y 'at' hmv\":\"EEEE, MMMM d, y 'at' h:mm a v\",\"MMMM d, y 'at' hmv\":\"MMMM d, y 'at' h:mm a v\",\"MMM d, y, hmv\":\"MMM d, y, h:mm a v\",\"M/d/yy, hmv\":\"M/d/yy, h:mm a v\",\"d, hmv\":\"d, h:mm a v\",\"E, hmv\":\"ccc, h:mm a v\",\"Ed, hmv\":\"d E, h:mm a v\",\"Gy, hmv\":\"y G, h:mm a v\",\"GyMMM, hmv\":\"MMM y G, h:mm a v\",\"GyMMMd, hmv\":\"MMM d, y G, h:mm a v\",\"GyMMMEd, hmv\":\"E, MMM d, y G, h:mm a v\",\"M, hmv\":\"L, h:mm a v\",\"Md, hmv\":\"M/d, h:mm a v\",\"MEd, hmv\":\"E, M/d, h:mm a v\",\"MMM, hmv\":\"LLL, h:mm a v\",\"MMMd, hmv\":\"MMM d, h:mm a v\",\"MMMEd, hmv\":\"E, MMM d, h:mm a v\",\"MMMMd 'at' hmv\":\"MMMM d 'at' h:mm a v\",\"y, hmv\":\"y, h:mm a v\",\"yM, hmv\":\"M/y, h:mm a v\",\"yMd, hmv\":\"M/d/y, h:mm a v\",\"yMEd, hmv\":\"E, M/d/y, h:mm a v\",\"yMMM, hmv\":\"MMM y, h:mm a v\",\"yMMMd, hmv\":\"MMM d, y, h:mm a v\",\"yMMMEd, hmv\":\"E, MMM d, y, h:mm a v\",\"yMMMM 'at' hmv\":\"MMMM y 'at' h:mm a v\",\"EEEE, MMMM d, y 'at' Hmv\":\"EEEE, MMMM d, y 'at' HH:mm v\",\"MMMM d, y 'at' Hmv\":\"MMMM d, y 'at' HH:mm v\",\"MMM d, y, Hmv\":\"MMM d, y, HH:mm v\",\"M/d/yy, Hmv\":\"M/d/yy, HH:mm v\",\"d, Hmv\":\"d, HH:mm v\",\"E, Hmv\":\"ccc, HH:mm v\",\"Ed, Hmv\":\"d E, HH:mm v\",\"Gy, Hmv\":\"y G, HH:mm v\",\"GyMMM, Hmv\":\"MMM y G, HH:mm v\",\"GyMMMd, Hmv\":\"MMM d, y G, HH:mm v\",\"GyMMMEd, Hmv\":\"E, MMM d, y G, HH:mm v\",\"M, Hmv\":\"L, HH:mm v\",\"Md, Hmv\":\"M/d, HH:mm v\",\"MEd, Hmv\":\"E, M/d, HH:mm v\",\"MMM, Hmv\":\"LLL, HH:mm v\",\"MMMd, Hmv\":\"MMM d, HH:mm v\",\"MMMEd, Hmv\":\"E, MMM d, HH:mm v\",\"MMMMd 'at' Hmv\":\"MMMM d 'at' HH:mm v\",\"y, Hmv\":\"y, HH:mm v\",\"yM, Hmv\":\"M/y, HH:mm v\",\"yMd, Hmv\":\"M/d/y, HH:mm v\",\"yMEd, Hmv\":\"E, M/d/y, HH:mm v\",\"yMMM, Hmv\":\"MMM y, HH:mm v\",\"yMMMd, Hmv\":\"MMM d, y, HH:mm v\",\"yMMMEd, Hmv\":\"E, MMM d, y, HH:mm v\",\"yMMMM 'at' Hmv\":\"MMMM y 'at' HH:mm v\",\"EEEE, MMMM d, y 'at' ms\":\"EEEE, MMMM d, y 'at' mm:ss\",\"MMMM d, y 'at' ms\":\"MMMM d, y 'at' mm:ss\",\"MMM d, y, ms\":\"MMM d, y, mm:ss\",\"M/d/yy, ms\":\"M/d/yy, mm:ss\",\"d, ms\":\"d, mm:ss\",\"E, ms\":\"ccc, mm:ss\",\"Ed, ms\":\"d E, mm:ss\",\"Gy, ms\":\"y G, mm:ss\",\"GyMMM, ms\":\"MMM y G, mm:ss\",\"GyMMMd, ms\":\"MMM d, y G, mm:ss\",\"GyMMMEd, ms\":\"E, MMM d, y G, mm:ss\",\"M, ms\":\"L, mm:ss\",\"Md, ms\":\"M/d, mm:ss\",\"MEd, ms\":\"E, M/d, mm:ss\",\"MMM, ms\":\"LLL, mm:ss\",\"MMMd, ms\":\"MMM d, mm:ss\",\"MMMEd, ms\":\"E, MMM d, mm:ss\",\"MMMMd 'at' ms\":\"MMMM d 'at' mm:ss\",\"y, ms\":\"y, mm:ss\",\"yM, ms\":\"M/y, mm:ss\",\"yMd, ms\":\"M/d/y, mm:ss\",\"yMEd, ms\":\"E, M/d/y, mm:ss\",\"yMMM, ms\":\"MMM y, mm:ss\",\"yMMMd, ms\":\"MMM d, y, mm:ss\",\"yMMMEd, ms\":\"E, MMM d, y, mm:ss\",\"yMMMM 'at' ms\":\"MMMM y 'at' mm:ss\"}},\"intervalFormats\":{\"intervalFormatFallback\":\"{0} – {1}\",\"Bh\":{\"B\":\"h B – h B\",\"h\":\"h – h B\"},\"Bhm\":{\"B\":\"h:mm B – h:mm B\",\"h\":\"h:mm – h:mm B\",\"m\":\"h:mm – h:mm B\"},\"d\":{\"d\":\"d – d\"},\"Gy\":{\"G\":\"y G – y G\",\"y\":\"y – y G\"},\"GyM\":{\"G\":\"M/y GGGGG – M/y GGGGG\",\"M\":\"M/y – M/y GGGGG\",\"y\":\"M/y – M/y GGGGG\"},\"GyMd\":{\"d\":\"M/d/y – M/d/y GGGGG\",\"G\":\"M/d/y GGGGG – M/d/y GGGGG\",\"M\":\"M/d/y – M/d/y GGGGG\",\"y\":\"M/d/y – M/d/y GGGGG\"},\"GyMEd\":{\"d\":\"E, M/d/y – E, M/d/y GGGGG\",\"G\":\"E, M/d/y GGGGG – E, M/d/y GGGGG\",\"M\":\"E, M/d/y – E, M/d/y GGGGG\",\"y\":\"E, M/d/y – E, M/d/y GGGGG\"},\"GyMMM\":{\"G\":\"MMM y G – MMM y G\",\"M\":\"MMM – MMM y G\",\"y\":\"MMM y – MMM y G\"},\"GyMMMd\":{\"d\":\"MMM d – d, y G\",\"G\":\"MMM d, y G – MMM d, y G\",\"M\":\"MMM d – MMM d, y G\",\"y\":\"MMM d, y – MMM d, y G\"},\"GyMMMEd\":{\"d\":\"E, MMM d – E, MMM d, y G\",\"G\":\"E, MMM d, y G – E, MMM d, y G\",\"M\":\"E, MMM d – E, MMM d, y G\",\"y\":\"E, MMM d, y – E, MMM d, y G\"},\"h\":{\"a\":\"h a – h a\",\"h\":\"h – h a\"},\"H\":{\"H\":\"HH – HH\"},\"hm\":{\"a\":\"h:mm a – h:mm a\",\"h\":\"h:mm – h:mm a\",\"m\":\"h:mm – h:mm a\"},\"Hm\":{\"H\":\"HH:mm – HH:mm\",\"m\":\"HH:mm – HH:mm\"},\"hmv\":{\"a\":\"h:mm a – h:mm a v\",\"h\":\"h:mm – h:mm a v\",\"m\":\"h:mm – h:mm a v\"},\"Hmv\":{\"H\":\"HH:mm – HH:mm v\",\"m\":\"HH:mm – HH:mm v\"},\"hv\":{\"a\":\"h a – h a v\",\"h\":\"h – h a v\"},\"Hv\":{\"H\":\"HH – HH v\"},\"M\":{\"M\":\"M – M\"},\"Md\":{\"d\":\"M/d – M/d\",\"M\":\"M/d – M/d\"},\"MEd\":{\"d\":\"E, M/d – E, M/d\",\"M\":\"E, M/d – E, M/d\"},\"MMM\":{\"M\":\"MMM – MMM\"},\"MMMd\":{\"d\":\"MMM d – d\",\"M\":\"MMM d – MMM d\"},\"MMMEd\":{\"d\":\"E, MMM d – E, MMM d\",\"M\":\"E, MMM d – E, MMM d\"},\"y\":{\"y\":\"y – y\"},\"yM\":{\"M\":\"M/y – M/y\",\"y\":\"M/y – M/y\"},\"yMd\":{\"d\":\"M/d/y – M/d/y\",\"M\":\"M/d/y – M/d/y\",\"y\":\"M/d/y – M/d/y\"},\"yMEd\":{\"d\":\"E, M/d/y – E, M/d/y\",\"M\":\"E, M/d/y – E, M/d/y\",\"y\":\"E, M/d/y – E, M/d/y\"},\"yMMM\":{\"M\":\"MMM – MMM y\",\"y\":\"MMM y – MMM y\"},\"yMMMd\":{\"d\":\"MMM d – d, y\",\"M\":\"MMM d – MMM d, y\",\"y\":\"MMM d, y – MMM d, y\"},\"yMMMEd\":{\"d\":\"E, MMM d – E, MMM d, y\",\"M\":\"E, MMM d – E, MMM d, y\",\"y\":\"E, MMM d, y – E, MMM d, y\"},\"yMMMM\":{\"M\":\"MMMM – MMMM y\",\"y\":\"MMMM y – MMMM y\"}},\"hourCycle\":\"h12\",\"nu\":[\"latn\"],\"ca\":[\"gregory\"],\"hc\":[\"h12\",\"\",\"h23\",\"\"]},\"locale\":\"en\"}\n)\n }","// @generated\n// prettier-ignore\nif ('DateTimeFormat' in Intl && Intl.DateTimeFormat.__addTZData) {\n Intl.DateTimeFormat.__addTZData({\"zones\":[\"Africa/Accra|,0,0,0|-s9p1ak,1,1,0|-q5eqo1,1,1,0|-q5eqo0,2,2,1|-q3g8pd,2,2,1|-q3g8pc,1,1,0|-pqwd41,1,1,0|-pqwd40,2,2,1|-pkmgpd,2,2,1|-pkmgpc,1,1,0|-p84fs1,1,1,0|-p84fs0,2,2,1|-p1ujdd,2,2,1|-p1ujdc,1,1,0|-opcig1,1,1,0|-opcig0,2,2,1|-oj2m1d,2,2,1|-oj2m1c,1,1,0|-o6kl41,1,1,0|-o6kl40,2,2,1|-o0aopd,2,2,1|-o0aopc,1,1,0|-nnqt41,1,1,0|-nnqt40,2,2,1|-nhgwpd,2,2,1|-nhgwpc,1,1,0|-n4yvs1,1,1,0|-n4yvs0,2,2,1|-myozdd,2,2,1|-myozdc,1,1,0|-mm6yg1,1,1,0|-mm6yg0,2,2,1|-mfx21d,2,2,1|-mfx21c,1,1,0|-m3f141,1,1,0|-m3f140,2,2,1|-lx54pd,2,2,1|-lx54pc,1,1,0|-lkl941,1,1,0|-lkl940,2,2,1|-lebcpd,2,2,1|-lebcpc,1,1,0|-l1tbs1,1,1,0|-l1tbs0,2,2,1|-kvjfdd,2,2,1|-kvjfdc,1,1,0|-kj1eg1,1,1,0|-kj1eg0,2,2,1|-kcri1d,2,2,1|-kcri1c,1,1,0|-k09h41,1,1,0|-k09h40,2,2,1|-jtzkpd,2,2,1|-jtzkpc,1,1,0|-jhfp41,1,1,0|-jhfp40,2,2,1|-jb5spd,2,2,1|-jb5spc,1,1,0|-iynrs1,1,1,0|-iynrs0,2,2,1|-isdvdd,2,2,1|-isdvdc,1,1,0|-ifvug1,1,1,0|-ifvug0,2,2,1|-i9ly1d,2,2,1|-i9ly1c,1,1,0|-hx3x41,1,1,0|-hx3x40,2,2,1|-hqu0pd,2,2,1|-hqu0pc,1,1,0|-hea541,1,1,0|-hea540,2,2,1|-h808pd,2,2,1|-h808pc,1,1,0|-gvi7s1,1,1,0|-gvi7s0,2,2,1|-gp8bdd,2,2,1|-gp8bdc,1,1,0|-gcqag1,1,1,0|-gcqag0,2,2,1|-g6ge1d,2,2,1|-g6ge1c,1,1,0|-ftyd41,1,1,0|-ftyd40,2,2,1|-fnogpd,2,2,1|-fnogpc,1,1,0|-fhgd41,1,1,0|-fhgd40,2,2,1|-f4uopd,2,2,1|-f4uopc,1,1,0|-eyofs1,1,1,0|-eyofs0,2,2,1|-em2rdd,2,2,1|-em2rdc,1,1,0|-ek4io1,1,1,0|-ek4io0,3,3,0|-cio421,3,3,0|-cio420,1,1,0|-a39mg1,1,1,0|-a39mg0,3,3,1|-9wzqi1,3,3,1|-9wzqi0,1,1,0|-9khp41,1,1,0|-9khp40,3,3,1|-9e7t61,3,3,1|-9e7t60,1,1,0|-91nx41,1,1,0|-91nx40,3,3,1|-8ve161,3,3,1|-8ve160,1,1,0|-8ivzs1,1,1,0|-8ivzs0,3,3,1|-8cm3u1,3,3,1|-8cm3u0,1,1,0|-8042g1,1,1,0|-8042g0,3,3,1|-7tu6i1,3,3,1|-7tu6i0,1,1,0|-7hc541,1,1,0|-7hc540,3,3,1|-7b2961,3,3,1|-7b2960,1,1,0\",\"Africa/Addis_Ababa|,0,4,0|-w6p5hg,4,5,0|-lnsey1,4,5,0|-lnsey0,5,6,0|-kvcdo1,5,6,0|-kvcdo0,4,5,0|-h80ka1,4,5,0|-h80ka0,6,7,0|-eb6ib1,6,7,0|-eb6ib0,5,6,0\",\"Africa/Algiers|,0,8,0|-154gb8c,7,9,0|-uozn3m,7,9,0|-uozn3l,8,1,0|-ry2lg1,8,1,0|-ry2lg0,9,10,1|-rsgqs1,9,10,1|-rsgqs0,8,1,0|-rjiis1,8,1,0|-rjiis0,9,10,1|-r9dpg1,9,10,1|-r9dpg0,8,1,0|-r1idg1,8,1,0|-r1idg0,9,10,1|-qqnms1,9,10,1|-qqnms0,8,1,0|-qj59g1,8,1,0|-qj59g0,9,10,1|-q7xk41,9,10,1|-q7xk40,8,1,0|-q15441,8,1,0|-q15440,9,10,1|-po6g41,9,10,1|-po6g40,8,1,0|-pgvhg1,8,1,0|-pgvhg0,9,10,1|-pbs5g1,9,10,1|-pbs5g0,8,1,0|-fte841,8,1,0|-fte840,9,10,1|-fpw801,9,10,1|-fpw800,8,1,0|-fkul41,8,1,0|-fkul40,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d62o01,11,11,1|-d62o00,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cofk41,11,11,1|-cofk40,10,10,0|-c4kqs1,10,10,0|-c4kqs0,8,1,0|-79mio1,8,1,0|-79mio0,10,10,0|-3i8is1,10,10,0|-3i8is0,8,1,0|oot7z,8,1,0|oot80,9,10,1|wlzvz,9,10,1|wlzw0,8,1,0|3tynzz,8,1,0|3tyo00,9,10,1|42lp7z,9,10,1|42lp80,10,10,0|4aiynz,10,10,0|4aiyo0,11,11,1|4jw2rz,11,11,1|4jw2s0,10,10,0|54et7z,10,10,0|54et80,8,1,0|5drxbz,8,1,0|5drxc0,9,10,1|5ni03z,9,10,1|5ni040,8,1,0|5wuynz,8,1,0|5wuyo0,10,10,0\",\"Africa/Asmara|,0,4,0|-w6p5hg,4,5,0|-lnsey1,4,5,0|-lnsey0,5,6,0|-kvcdo1,5,6,0|-kvcdo0,4,5,0|-h80ka1,4,5,0|-h80ka0,6,7,0|-eb6ib1,6,7,0|-eb6ib0,5,6,0\",\"Africa/Bamako|,0,12,0|-u9rgl4,1,1,0\",\"Africa/Bangui|,0,13,0|-xnxnan,1,1,0|-w3k001,1,1,0|-w3k000,0,13,0|-t85smo,0,13,0|-t85smn,3,3,0|-q9qc21,3,3,0|-q9qc20,12,10,0\",\"Africa/Banjul|,0,12,0|-u9rgl4,1,1,0\",\"Africa/Bissau|,0,14,0|-u9rek0,13,15,0|2lxk3z,13,15,0|2lxk40,1,1,0\",\"Africa/Blantyre|,0,16,0|-yvtfd8,14,11,0\",\"Africa/Brazzaville|,0,13,0|-xnxnan,1,1,0|-w3k001,1,1,0|-w3k000,0,13,0|-t85smo,0,13,0|-t85smn,3,3,0|-q9qc21,3,3,0|-q9qc20,12,10,0\",\"Africa/Bujumbura|,0,16,0|-yvtfd8,14,11,0\",\"Africa/Cairo|,0,17,0|-1054wgl,15,11,0|-fdls81,15,11,0|-fdls80,16,6,1|-f9lf01,16,6,1|-f9lf00,15,11,0|-ezidk1,15,11,0|-ezidk0,16,6,1|-erl9o1,16,6,1|-erl9o0,15,11,0|-ehgdk1,15,11,0|-ehgdk0,16,6,1|-e6pf01,16,6,1|-e6pf00,15,11,0|-dyog81,15,11,0|-dyog80,16,6,1|-dno8c1,16,6,1|-dno8c0,15,11,0|-dfuo81,15,11,0|-dfuo80,16,6,1|-d4ugc1,16,6,1|-d4ugc0,15,11,0|-cwayw1,15,11,0|-cwayw0,16,6,1|-cm2j01,16,6,1|-cm2j00,15,11,0|-6lluw1,15,11,0|-6lluw0,16,6,1|-6e79o1,16,6,1|-6e79o0,15,11,0|-63alk1,15,11,0|-63alk0,16,6,1|-5vfcc1,16,6,1|-5vfcc0,15,11,0|-5kilg1,15,11,0|-5kilg0,16,6,1|-5cp1c1,16,6,1|-5cp1c0,15,11,0|-51otg1,15,11,0|-51otg0,16,6,1|-4tv9c1,16,6,1|-4tv9c0,15,11,0|-4iww41,15,11,0|-4iww40,16,6,1|-4b3c01,16,6,1|-4b3c00,15,11,0|-404ys1,15,11,0|-404ys0,16,6,1|-3sbeo1,16,6,1|-3sbeo0,15,11,0|-3hd1g1,15,11,0|-3hd1g0,16,6,1|-39jhc1,16,6,1|-39jhc0,15,11,0|-2yj9g1,15,11,0|-2yj9g0,16,6,1|-2qppc1,16,6,1|-2qppc0,15,11,0|-2frc41,15,11,0|-2frc40,16,6,1|-27xs01,16,6,1|-27xs00,15,11,0|-1wzes1,15,11,0|-1wzes0,16,6,1|-1p4001,16,6,1|-1p4000,15,11,0|-1e7hg1,15,11,0|-1e7hg0,16,6,1|-16c2o1,16,6,1|-16c2o0,15,11,0|-vdpg1,15,11,0|-vdpg0,16,6,1|-niao1,16,6,1|-niao0,15,11,0|-cls41,15,11,0|-cls40,16,6,1|-4qdc1,16,6,1|-4qdc0,15,11,0|6657z,15,11,0|66580,16,6,1|e1jzz,16,6,1|e1k00,15,11,0|oy2jz,15,11,0|oy2k0,16,6,1|wthbz,16,6,1|wthc0,15,11,0|17rujz,15,11,0|17ruk0,16,6,1|1fn9bz,16,6,1|1fn9c0,15,11,0|1qjrvz,15,11,0|1qjrw0,16,6,1|1yf6nz,16,6,1|1yf6o0,15,11,0|29bp7z,15,11,0|29bp80,16,6,1|2h73zz,16,6,1|2h7400,15,11,0|2s3mjz,15,11,0|2s3mk0,16,6,1|2zz1bz,16,6,1|2zz1c0,15,11,0|3axejz,15,11,0|3axek0,16,6,1|3istbz,16,6,1|3istc0,15,11,0|3tpbvz,15,11,0|3tpbw0,16,6,1|41kqnz,16,6,1|41kqo0,15,11,0|4ch97z,15,11,0|4ch980,16,6,1|4kcnzz,16,6,1|4kco00,15,11,0|4v96jz,15,11,0|4v96k0,16,6,1|534lbz,16,6,1|534lc0,15,11,0|5e2yjz,15,11,0|5e2yk0,16,6,1|5lydbz,16,6,1|5lydc0,15,11,0|5wuvvz,15,11,0|5wuvw0,16,6,1|64qanz,16,6,1|64qao0,15,11,0|6k07vz,15,11,0|6k07w0,16,6,1|6ni7zz,16,6,1|6ni800,15,11,0|7242jz,15,11,0|7242k0,16,6,1|76a5bz,16,6,1|76a5c0,15,11,0|7h8ijz,15,11,0|7h8ik0,16,6,1|7p3xbz,16,6,1|7p3xc0,15,11,0|800fvz,15,11,0|800fw0,16,6,1|87vunz,16,6,1|87vuo0,15,11,0|8isd7z,15,11,0|8isd80,16,6,1|8qnrzz,16,6,1|8qns00,15,11,0|91kajz,15,11,0|91kak0,16,6,1|99fpbz,16,6,1|99fpc0,15,11,0|9ke2jz,15,11,0|9ke2k0,16,6,1|9s9hbz,16,6,1|9s9hc0,15,11,0|a3f97z,15,11,0|a3f980,16,6,1|ab1enz,16,6,1|ab1eo0,15,11,0|alxx7z,15,11,0|alxx80,16,6,1|attbzz,16,6,1|attc00,15,11,0|b4pujz,15,11,0|b4puk0,16,6,1|bcl9bz,16,6,1|bcl9c0,15,11,0|bnjmjz,15,11,0|bnjmk0,16,6,1|bvf1bz,16,6,1|bvf1c0,15,11,0|c6bjvz,15,11,0|c6bjw0,16,6,1|ce6ynz,16,6,1|ce6yo0,15,11,0|cp3h7z,15,11,0|cp3h80,16,6,1|cwyvzz,16,6,1|cwyw00,15,11,0|d7prrz,15,11,0|d7prs0,16,6,1|dfmvnz,16,6,1|dfmvo0,15,11,0|dqfufz,15,11,0|dqfug0,16,6,1|dycybz,16,6,1|dycyc0,15,11,0|e95x3z,15,11,0|e95x40,16,6,1|eh30zz,16,6,1|eh3100,15,11,0|ervzrz,15,11,0|ervzs0,16,6,1|ezt3nz,16,6,1|ezt3o0,15,11,0|faz13z,15,11,0|faz140,16,6,1|fiw4zz,16,6,1|fiw500,15,11,0|ftp3rz,15,11,0|ftp3s0,16,6,1|g1m7nz,16,6,1|g1m7o0,15,11,0|gcf6fz,15,11,0|gcf6g0,16,6,1|gkcabz,16,6,1|gkcac0,15,11,0|gv593z,15,11,0|gv5940,16,6,1|h32czz,16,6,1|h32d00,15,11,0|hdvbrz,15,11,0|hdvbs0,16,6,1|hlsfnz,16,6,1|hlsfo0,15,11,0|hwyd3z,15,11,0|hwyd40,16,6,1|i4vgzz,16,6,1|i4vh00,15,11,0|ifofrz,15,11,0|ifofs0,16,6,1|inljnz,16,6,1|inljo0,15,11,0|iyeifz,15,11,0|iyeig0,16,6,1|j5ynnz,16,6,1|j5yno0,15,11,0|jh4l3z,15,11,0|jh4l40,16,6,1|jnyszz,16,6,1|jnyt00,15,11,0|jzunrz,15,11,0|jzuns0,16,6,1|k6bwzz,16,6,1|k6bx00,15,11,0|kikqfz,15,11,0|kikqg0,16,6,1|kop0zz,16,6,1|kop100,15,11,0|l1nrrz,15,11,0|l1nrs0,16,6,1|l6yfnz,16,6,1|l6yfo0,15,11,0|l8i2fz,15,11,0|l8i2g0,16,6,1|l9kvnz,16,6,1|l9kvo0,15,11,0|n5myfz,15,11,0|n5myg0,16,6,1|n7snnz,16,6,1|n7sno0,15,11,0|n9ljrz,15,11,0|n9ljs0,16,6,1|nch6bz,16,6,1|nch6c0,15,11,0\",\"Africa/Casablanca|,0,18,0|-tblt9g,17,1,0|-fte5c1,17,1,0|-fte5c0,18,10,1|-fpwas1,18,10,1|-fpwas0,17,1,0|-fkuqo1,17,1,0|-fkuqo0,18,10,1|-cl6w41,18,10,1|-cl6w40,17,1,0|-a7hmo1,17,1,0|-a7hmo0,18,10,1|-a0ag41,18,10,1|-a0ag40,17,1,0|-1chdc1,17,1,0|-1chdc0,18,10,1|-16c5g1,18,10,1|-16c5g0,17,1,0|2c3rzz,17,1,0|2c3s00,18,10,1|2fnh7z,18,10,1|2fnh80,17,1,0|3axhbz,17,1,0|3axhc0,18,10,1|3fnrvz,18,10,1|3fnrw0,17,1,0|3tpenz,17,1,0|3tpeo0,18,10,1|41f3vz,18,10,1|41f3w0,17,1,0|4e2qnz,17,1,0|4e2qo0,18,10,1|4hd6jz,18,10,1|4hd6k0,17,1,0|7evenz,17,1,0|7eveo0,18,10,0|8cm57z,18,10,0|8cm580,17,1,0|k1rbzz,17,1,0|k1rc00,18,10,1|k6hmjz,18,10,1|k6hmk0,17,1,0|kkj9bz,17,1,0|kkj9c0,18,10,1|kop6jz,18,10,1|kop6k0,17,1,0|l1rmnz,17,1,0|l1rmo0,18,10,1|l6t17z,18,10,1|l6t180,17,1,0|lj1unz,17,1,0|lj1uo0,18,10,1|lp657z,18,10,1|lp6580,17,1,0|m37xjz,17,1,0|m37xk0,18,10,1|m7fs7z,18,10,1|m7fs80,17,1,0|m916vz,17,1,0|m916w0,18,10,1|mb547z,18,10,1|mb5480,17,1,0|mly07z,17,1,0|mly080,18,10,1|mpjmvz,18,10,1|mpjmw0,17,1,0|mraljz,17,1,0|mralk0,18,10,1|mvb1jz,18,10,1|mvb1k0,17,1,0|n3887z,17,1,0|n38880,18,10,1|n7uw7z,18,10,1|n7uw80,17,1,0|n9npjz,17,1,0|n9npk0,18,10,1|ne147z,18,10,1|ne1480,17,1,0|nlyavz,17,1,0|nlyaw0,18,10,1|npww7z,18,10,1|npww80,17,1,0|nrppjz,17,1,0|nrppk0,18,10,1|nwr6vz,18,10,1|nwr6w0,17,1,0|o4odjz,17,1,0|o4odk0,18,10,1|o8a07z,18,10,1|o8a080,17,1,0|oa2tjz,17,1,0|oa2tk0,18,10,1|ofu87z,18,10,1|ofu880,17,1,0|oneg7z,17,1,0|oneg80,18,10,1|oqa5jz,18,10,1|oqa5k0,17,1,0|osfxjz,17,1,0|osfxk0,18,10,1|oykavz,18,10,1|oykaw0,17,1,0|p64ivz,17,1,0|p64iw0,18,10,1|p8n9jz,18,10,1|p8n9k0,17,1,0|pag2vz,17,1,0|pag2w0,18,10,1|phadjz,18,10,1|phadk0,18,10,0|pr0djz,18,10,0|pr0dk0,17,1,1|pst6vz,17,1,1|pst6w0,18,10,0|q90ivz,18,10,0|q90iw0,17,1,1|qb6avz,17,1,1|qb6aw0,18,10,0|qrdmvz,18,10,0|qrdmw0,17,1,1|qt6g7z,17,1,1|qt6g80,18,10,0|r9ds7z,18,10,0|r9ds80,17,1,1|rbjk7z,17,1,1|rbjk80,18,10,0|rrqw7z,18,10,0|rrqw80,17,1,1|rtwo7z,17,1,1|rtwo80,18,10,0|sa407z,18,10,0|sa4080,17,1,1|sbwtjz,17,1,1|sbwtk0,18,10,0|ss45jz,18,10,0|ss45k0,17,1,1|su9xjz,17,1,1|su9xk0,18,10,0|tah9jz,18,10,0|tah9k0,17,1,1|tca2vz,17,1,1|tca2w0,18,10,0|tsudjz,18,10,0|tsudk0,17,1,1|tun6vz,17,1,1|tun6w0,18,10,0|uauivz,18,10,0|uauiw0,17,1,1|ud0avz,17,1,1|ud0aw0,18,10,0|ut7mvz,18,10,0|ut7mw0,17,1,1|uv0g7z,17,1,1|uv0g80,18,10,0|vb7s7z,18,10,0|vb7s80,17,1,1|vddk7z,17,1,1|vddk80,18,10,0|vtkw7z,18,10,0|vtkw80,17,1,1|vvqo7z,17,1,1|vvqo80,18,10,0|wby07z,18,10,0|wby080,17,1,1|wdqtjz,17,1,1|wdqtk0,18,10,0|wty5jz,18,10,0|wty5k0,17,1,1|ww3xjz,17,1,1|ww3xk0,18,10,0|xcb9jz,18,10,0|xcb9k0,17,1,1|xe42vz,17,1,1|xe42w0,18,10,0|xubevz,18,10,0|xubew0,17,1,1|xwh6vz,17,1,1|xwh6w0,18,10,0|ycoivz,18,10,0|ycoiw0,17,1,1|yeuavz,17,1,1|yeuaw0,18,10,0|yv1mvz,18,10,0|yv1mw0,17,1,1|ywug7z,17,1,1|ywug80,18,10,0|zd1s7z,18,10,0|zd1s80,17,1,1|zf7k7z,17,1,1|zf7k80,18,10,0\",\"Africa/Ceuta|,0,19,0|-100edc0,8,1,0|-qyiys1,8,1,0|-qyiys0,9,10,1|-qqluw1,9,10,1|-qqluw0,8,1,0|-nusqs1,8,1,0|-nusqs0,9,10,1|-nm0001,9,10,1|-nm0000,8,1,0|-mt71g1,8,1,0|-mt71g0,9,10,1|-mkjuo1,9,10,1|-mkjuo0,8,1,0|-matxg1,8,1,0|-matxg0,9,10,1|-m1ts01,9,10,1|-m1ts00,8,1,0|-lrqtc1,8,1,0|-lrqtc0,9,10,1|-liqqo1,9,10,1|-liqqo0,8,1,0|-1chdc1,8,1,0|-1chdc0,9,10,1|-16c5g1,9,10,1|-16c5g0,8,1,0|2c3rzz,8,1,0|2c3s00,9,10,1|2fnh7z,9,10,1|2fnh80,8,1,0|3axhbz,8,1,0|3axhc0,9,10,1|3fnrvz,9,10,1|3fnrw0,8,1,0|3tpenz,8,1,0|3tpeo0,9,10,1|41f3vz,9,10,1|41f3w0,8,1,0|4e2qnz,8,1,0|4e2qo0,9,10,1|4hd6jz,9,10,1|4hd6k0,8,1,0|7evenz,8,1,0|7eveo0,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Africa/Conakry|,0,12,0|-u9rgl4,1,1,0\",\"Africa/Dakar|,0,12,0|-u9rgl4,1,1,0\",\"Africa/Dar_es_Salaam|,0,4,0|-w6p5hg,4,5,0|-lnsey1,4,5,0|-lnsey0,5,6,0|-kvcdo1,5,6,0|-kvcdo0,4,5,0|-h80ka1,4,5,0|-h80ka0,6,7,0|-eb6ib1,6,7,0|-eb6ib0,5,6,0\",\"Africa/Djibouti|,0,4,0|-w6p5hg,4,5,0|-lnsey1,4,5,0|-lnsey0,5,6,0|-kvcdo1,5,6,0|-kvcdo0,4,5,0|-h80ka1,4,5,0|-h80ka0,6,7,0|-eb6ib1,6,7,0|-eb6ib0,5,6,0\",\"Africa/Douala|,0,13,0|-xnxnan,1,1,0|-w3k001,1,1,0|-w3k000,0,13,0|-t85smo,0,13,0|-t85smn,3,3,0|-q9qc21,3,3,0|-q9qc20,12,10,0\",\"Africa/El_Aaiun|,0,20,0|-isdxk0,13,15,0|3a22rz,13,15,0|3a22s0,17,1,0|3axhbz,17,1,0|3axhc0,18,10,1|3fnrvz,18,10,1|3fnrw0,17,1,0|3tpenz,17,1,0|3tpeo0,18,10,1|41f3vz,18,10,1|41f3w0,17,1,0|4e2qnz,17,1,0|4e2qo0,18,10,1|4hd6jz,18,10,1|4hd6k0,17,1,0|k1rbzz,17,1,0|k1rc00,18,10,1|k6hmjz,18,10,1|k6hmk0,17,1,0|kkj9bz,17,1,0|kkj9c0,18,10,1|kop6jz,18,10,1|kop6k0,17,1,0|l1rmnz,17,1,0|l1rmo0,18,10,1|l6t17z,18,10,1|l6t180,17,1,0|lj1unz,17,1,0|lj1uo0,18,10,1|lp657z,18,10,1|lp6580,17,1,0|m37xjz,17,1,0|m37xk0,18,10,1|m7fs7z,18,10,1|m7fs80,17,1,0|m916vz,17,1,0|m916w0,18,10,1|mb547z,18,10,1|mb5480,17,1,0|mly07z,17,1,0|mly080,18,10,1|mpjmvz,18,10,1|mpjmw0,17,1,0|mraljz,17,1,0|mralk0,18,10,1|mvb1jz,18,10,1|mvb1k0,17,1,0|n3887z,17,1,0|n38880,18,10,1|n7uw7z,18,10,1|n7uw80,17,1,0|n9npjz,17,1,0|n9npk0,18,10,1|ne147z,18,10,1|ne1480,17,1,0|nlyavz,17,1,0|nlyaw0,18,10,1|npww7z,18,10,1|npww80,17,1,0|nrppjz,17,1,0|nrppk0,18,10,1|nwr6vz,18,10,1|nwr6w0,17,1,0|o4odjz,17,1,0|o4odk0,18,10,1|o8a07z,18,10,1|o8a080,17,1,0|oa2tjz,17,1,0|oa2tk0,18,10,1|ofu87z,18,10,1|ofu880,17,1,0|oneg7z,17,1,0|oneg80,18,10,1|oqa5jz,18,10,1|oqa5k0,17,1,0|osfxjz,17,1,0|osfxk0,18,10,1|oykavz,18,10,1|oykaw0,17,1,0|p64ivz,17,1,0|p64iw0,18,10,1|p8n9jz,18,10,1|p8n9k0,17,1,0|pag2vz,17,1,0|pag2w0,18,10,1|phadjz,18,10,1|phadk0,18,10,0|pr0djz,18,10,0|pr0dk0,17,1,1|pst6vz,17,1,1|pst6w0,18,10,0|q90ivz,18,10,0|q90iw0,17,1,1|qb6avz,17,1,1|qb6aw0,18,10,0|qrdmvz,18,10,0|qrdmw0,17,1,1|qt6g7z,17,1,1|qt6g80,18,10,0|r9ds7z,18,10,0|r9ds80,17,1,1|rbjk7z,17,1,1|rbjk80,18,10,0|rrqw7z,18,10,0|rrqw80,17,1,1|rtwo7z,17,1,1|rtwo80,18,10,0|sa407z,18,10,0|sa4080,17,1,1|sbwtjz,17,1,1|sbwtk0,18,10,0|ss45jz,18,10,0|ss45k0,17,1,1|su9xjz,17,1,1|su9xk0,18,10,0|tah9jz,18,10,0|tah9k0,17,1,1|tca2vz,17,1,1|tca2w0,18,10,0|tsudjz,18,10,0|tsudk0,17,1,1|tun6vz,17,1,1|tun6w0,18,10,0|uauivz,18,10,0|uauiw0,17,1,1|ud0avz,17,1,1|ud0aw0,18,10,0|ut7mvz,18,10,0|ut7mw0,17,1,1|uv0g7z,17,1,1|uv0g80,18,10,0|vb7s7z,18,10,0|vb7s80,17,1,1|vddk7z,17,1,1|vddk80,18,10,0|vtkw7z,18,10,0|vtkw80,17,1,1|vvqo7z,17,1,1|vvqo80,18,10,0|wby07z,18,10,0|wby080,17,1,1|wdqtjz,17,1,1|wdqtk0,18,10,0|wty5jz,18,10,0|wty5k0,17,1,1|ww3xjz,17,1,1|ww3xk0,18,10,0|xcb9jz,18,10,0|xcb9k0,17,1,1|xe42vz,17,1,1|xe42w0,18,10,0|xubevz,18,10,0|xubew0,17,1,1|xwh6vz,17,1,1|xwh6w0,18,10,0|ycoivz,18,10,0|ycoiw0,17,1,1|yeuavz,17,1,1|yeuaw0,18,10,0|yv1mvz,18,10,0|yv1mw0,17,1,1|ywug7z,17,1,1|ywug80,18,10,0|zd1s7z,18,10,0|zd1s80,17,1,1|zf7k7z,17,1,1|zf7k80,18,10,0\",\"Africa/Freetown|,0,12,0|-u9rgl4,1,1,0\",\"Africa/Gaborone|,0,16,0|-yvtfd8,14,11,0\",\"Africa/Harare|,0,16,0|-yvtfd8,14,11,0\",\"Africa/Johannesburg|,0,21,0|-14nj6io,19,22,0|-yvtdi1,19,22,0|-yvtdi0,19,11,0|-e8lpc1,19,11,0|-e8lpc0,19,6,1|-dz8qs1,19,6,1|-dz8qs0,19,11,0|-dpvmo1,19,11,0|-dpvmo0,19,6,1|-dgio41,19,6,1|-dgio40,19,11,0\",\"Africa/Juba|,0,23,0|-kcrsis,14,11,0|662fz,14,11,0|662g0,20,6,1|er8zz,20,6,1|er900,14,11,0|ow53z,14,11,0|ow540,20,6,1|xj6bz,20,6,1|xj6c0,14,11,0|17px3z,14,11,0|17px40,20,6,1|1gcybz,20,6,1|1gcyc0,14,11,0|1qfzrz,14,11,0|1qfzs0,20,6,1|1z4vnz,20,6,1|1z4vo0,14,11,0|2962fz,14,11,0|2962g0,20,6,1|2hwszz,20,6,1|2hwt00,14,11,0|2rw53z,14,11,0|2rw540,20,6,1|30oqbz,20,6,1|30oqc0,14,11,0|3am7rz,14,11,0|3am7s0,20,6,1|3jiibz,20,6,1|3jiic0,14,11,0|3tcafz,14,11,0|3tcag0,20,6,1|42afnz,20,6,1|42afo0,14,11,0|4cfbrz,14,11,0|4cfbs0,20,6,1|4l2czz,20,6,1|4l2d00,14,11,0|4v5efz,14,11,0|4v5eg0,20,6,1|53uabz,20,6,1|53uac0,14,11,0|5dvh3z,14,11,0|5dvh40,20,6,1|5mo2bz,20,6,1|5mo2c0,14,11,0|5wljrz,14,11,0|5wljs0,20,6,1|65fznz,20,6,1|65fzo0,14,11,0|6fbmfz,14,11,0|6fbmg0,20,6,1|6o7wzz,20,6,1|6o7x00,14,11,0|6y1p3z,14,11,0|6y1p40,20,6,1|76zubz,20,6,1|76zuc0,14,11,0|7h4qfz,14,11,0|7h4qg0,20,6,1|7ptmbz,20,6,1|7ptmc0,14,11,0|7zut3z,14,11,0|7zut40,20,6,1|88ljnz,20,6,1|88ljo0,14,11,0|fodfrz,14,11,0|fodfs0,5,6,0|qntgzz,5,6,0|qnth00,14,11,0\",\"Africa/Kampala|,0,4,0|-w6p5hg,4,5,0|-lnsey1,4,5,0|-lnsey0,5,6,0|-kvcdo1,5,6,0|-kvcdo0,4,5,0|-h80ka1,4,5,0|-h80ka0,6,7,0|-eb6ib1,6,7,0|-eb6ib0,5,6,0\",\"Africa/Khartoum|,0,24,0|-kcrsow,14,11,0|662fz,14,11,0|662g0,20,6,1|er8zz,20,6,1|er900,14,11,0|ow53z,14,11,0|ow540,20,6,1|xj6bz,20,6,1|xj6c0,14,11,0|17px3z,14,11,0|17px40,20,6,1|1gcybz,20,6,1|1gcyc0,14,11,0|1qfzrz,14,11,0|1qfzs0,20,6,1|1z4vnz,20,6,1|1z4vo0,14,11,0|2962fz,14,11,0|2962g0,20,6,1|2hwszz,20,6,1|2hwt00,14,11,0|2rw53z,14,11,0|2rw540,20,6,1|30oqbz,20,6,1|30oqc0,14,11,0|3am7rz,14,11,0|3am7s0,20,6,1|3jiibz,20,6,1|3jiic0,14,11,0|3tcafz,14,11,0|3tcag0,20,6,1|42afnz,20,6,1|42afo0,14,11,0|4cfbrz,14,11,0|4cfbs0,20,6,1|4l2czz,20,6,1|4l2d00,14,11,0|4v5efz,14,11,0|4v5eg0,20,6,1|53uabz,20,6,1|53uac0,14,11,0|5dvh3z,14,11,0|5dvh40,20,6,1|5mo2bz,20,6,1|5mo2c0,14,11,0|5wljrz,14,11,0|5wljs0,20,6,1|65fznz,20,6,1|65fzo0,14,11,0|6fbmfz,14,11,0|6fbmg0,20,6,1|6o7wzz,20,6,1|6o7x00,14,11,0|6y1p3z,14,11,0|6y1p40,20,6,1|76zubz,20,6,1|76zuc0,14,11,0|7h4qfz,14,11,0|7h4qg0,20,6,1|7ptmbz,20,6,1|7ptmc0,14,11,0|7zut3z,14,11,0|7zut40,20,6,1|88ljnz,20,6,1|88ljo0,14,11,0|fodfrz,14,11,0|fodfs0,5,6,0|oypgzz,5,6,0|oyph00,14,11,0\",\"Africa/Kigali|,0,16,0|-yvtfd8,14,11,0\",\"Africa/Kinshasa|,0,13,0|-xnxnan,1,1,0|-w3k001,1,1,0|-w3k000,0,13,0|-t85smo,0,13,0|-t85smn,3,3,0|-q9qc21,3,3,0|-q9qc20,12,10,0\",\"Africa/Lagos|,0,13,0|-xnxnan,1,1,0|-w3k001,1,1,0|-w3k000,0,13,0|-t85smo,0,13,0|-t85smn,3,3,0|-q9qc21,3,3,0|-q9qc20,12,10,0\",\"Africa/Libreville|,0,13,0|-xnxnan,1,1,0|-w3k001,1,1,0|-w3k000,0,13,0|-t85smo,0,13,0|-t85smn,3,3,0|-q9qc21,3,3,0|-q9qc20,12,10,0\",\"Africa/Lome|,0,12,0|-u9rgl4,1,1,0\",\"Africa/Luanda|,0,13,0|-xnxnan,1,1,0|-w3k001,1,1,0|-w3k000,0,13,0|-t85smo,0,13,0|-t85smn,3,3,0|-q9qc21,3,3,0|-q9qc20,12,10,0\",\"Africa/Lubumbashi|,0,16,0|-yvtfd8,14,11,0\",\"Africa/Lusaka|,0,16,0|-yvtfd8,14,11,0\",\"Africa/Malabo|,0,13,0|-xnxnan,1,1,0|-w3k001,1,1,0|-w3k000,0,13,0|-t85smo,0,13,0|-t85smn,3,3,0|-q9qc21,3,3,0|-q9qc20,12,10,0\",\"Africa/Maputo|,0,16,0|-yvtfd8,14,11,0\",\"Africa/Maseru|,0,21,0|-14nj6io,19,22,0|-yvtdi1,19,22,0|-yvtdi0,19,11,0|-e8lpc1,19,11,0|-e8lpc0,19,6,1|-dz8qs1,19,6,1|-dz8qs0,19,11,0|-dpvmo1,19,11,0|-dpvmo0,19,6,1|-dgio41,19,6,1|-dgio40,19,11,0\",\"Africa/Mbabane|,0,21,0|-14nj6io,19,22,0|-yvtdi1,19,22,0|-yvtdi0,19,11,0|-e8lpc1,19,11,0|-e8lpc0,19,6,1|-dz8qs1,19,6,1|-dz8qs0,19,11,0|-dpvmo1,19,11,0|-dpvmo0,19,6,1|-dgio41,19,6,1|-dgio40,19,11,0\",\"Africa/Mogadishu|,0,4,0|-w6p5hg,4,5,0|-lnsey1,4,5,0|-lnsey0,5,6,0|-kvcdo1,5,6,0|-kvcdo0,4,5,0|-h80ka1,4,5,0|-h80ka0,6,7,0|-eb6ib1,6,7,0|-eb6ib0,5,6,0\",\"Africa/Monrovia|,0,25,0|-19xcbc4,21,25,0|-qj6zc5,21,25,0|-qj6zc4,21,26,0|11v0q5,21,26,0|11v0q6,1,1,0\",\"Africa/Nairobi|,0,4,0|-w6p5hg,4,5,0|-lnsey1,4,5,0|-lnsey0,5,6,0|-kvcdo1,5,6,0|-kvcdo0,4,5,0|-h80ka1,4,5,0|-h80ka0,6,7,0|-eb6ib1,6,7,0|-eb6ib0,5,6,0\",\"Africa/Ndjamena|,0,27,0|-u9rk4c,12,10,0|53sl7z,12,10,0|53sl80,22,11,1|5bavrz,22,11,1|5bavs0,12,10,0\",\"Africa/Niamey|,0,13,0|-xnxnan,1,1,0|-w3k001,1,1,0|-w3k000,0,13,0|-t85smo,0,13,0|-t85smn,3,3,0|-q9qc21,3,3,0|-q9qc20,12,10,0\",\"Africa/Nouakchott|,0,12,0|-u9rgl4,1,1,0\",\"Africa/Ouagadougou|,0,12,0|-u9rgl4,1,1,0\",\"Africa/Porto-Novo|,0,13,0|-xnxnan,1,1,0|-w3k001,1,1,0|-w3k000,0,13,0|-t85smo,0,13,0|-t85smn,3,3,0|-q9qc21,3,3,0|-q9qc20,12,10,0\",\"Africa/Sao_Tome|,0,28,0|-18vsjww,0,29,0|-u9rhc1,0,29,0|-u9rhc0,1,1,0|p1uqrz,1,1,0|p1uqs0,12,10,0|pkmo3z,12,10,0|pkmo40,1,1,0\",\"Africa/Tripoli|,0,30,0|-q3gfrw,10,10,0|-9ia581,10,10,0|-9ia580,11,11,1|-9e82w1,11,11,1|-9e82w0,10,10,0|-8gxp81,10,10,0|-8gxp80,11,11,1|-8cmdk1,11,11,1|-8cmdk0,10,10,0|-7fuo41,10,10,0|-7fuo40,11,11,1|-7b2iw1,11,11,1|-7b2iw0,10,10,0|-5qotg1,10,10,0|-5qotg0,15,11,0|69gifz,15,11,0|69gig0,10,10,0|6e397z,10,10,0|6e3980,11,11,1|6ni2fz,11,11,1|6ni2g0,10,10,0|6wv6jz,10,10,0|6wv6k0,11,11,1|769zrz,11,11,1|769zs0,10,10,0|7foyjz,10,10,0|7foyk0,11,11,1|7p3rrz,11,11,1|7p3rs0,10,10,0|7yq57z,10,10,0|7yq580,11,11,1|87vp3z,11,11,1|87vp40,10,10,0|8hed7z,10,10,0|8hed80,11,11,1|8qrbrz,11,11,1|8qrbs0,10,10,0|900qjz,10,10,0|900qk0,11,11,1|99fjrz,11,11,1|99fjs0,10,10,0|9iuijz,10,10,0|9iuik0,11,11,1|9s9brz,11,11,1|9s9bs0,10,10,0|a1mfvz,10,10,0|a1mfw0,11,11,1|ab193z,11,11,1|ab1940,10,10,0|am3h7z,10,10,0|am3h80,15,11,0|dyil3z,15,11,0|dyil40,10,10,0|e833vz,10,10,0|e833w0,11,11,1|ehhx3z,11,11,1|ehhx40,15,11,0|md8vzz,15,11,0|md8w00,10,10,0|mkeanz,10,10,0|mkeao0,11,11,1|mv76nz,11,11,1|mv76o0,15,11,0\",\"Africa/Tunis|,0,31,0|-1a9dr7w,7,9,0|-uozn3m,7,9,0|-uozn3l,10,10,0|-g12881,10,10,0|-g12880,11,11,1|-fpwdk1,11,11,1|-fpwdk0,10,10,0|-fkt1k1,10,10,0|-fkt1k0,11,11,1|-eqk5k1,11,11,1|-eqk5k0,10,10,0|-eimw41,10,10,0|-eimw40,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dxuo01,11,11,1|-dxuo00,10,10,0|-dxfrw1,10,10,0|-dxfrw0,11,11,1|-dp3uo1,11,11,1|-dp3uo0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d62tk1,11,11,1|-d62tk0,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cofmw1,11,11,1|-cofmw0,10,10,0|3tnh7z,10,10,0|3tnh80,11,11,1|417p7z,11,11,1|417p80,10,10,0|4ch97z,10,10,0|4ch980,11,11,1|4kcl7z,11,11,1|4kcl80,10,10,0|9lzh7z,10,10,0|9lzh80,11,11,1|9ryajz,11,11,1|9ryak0,10,10,0|a1bbvz,10,10,0|a1bbw0,11,11,1|aaod7z,11,11,1|aaod80,10,10,0|alxx7z,10,10,0|alxx80,11,11,1|atrejz,11,11,1|atrek0,10,10,0|ifs7vz,10,10,0|ifs7w0,11,11,1|inlrzz,11,11,1|inls00,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0\",\"Africa/Windhoek|,0,32,0|-14nj4i0,23,22,0|-yvtdi1,23,22,0|-yvtdi0,19,11,0|-e8lpc1,19,11,0|-e8lpc0,19,6,1|-dz8qs1,19,6,1|-dz8qs0,19,11,0|ajtx3z,19,11,0|ajtx40,14,11,0|cmzh3z,14,11,0|cmzh40,12,10,1|cvkyrz,12,10,1|cvkys0,14,11,0|d6drzz,14,11,0|d6ds00,12,10,1|deb1fz,12,10,1|deb1g0,14,11,0|dpgtbz,14,11,0|dpgtc0,12,10,1|dx143z,12,10,1|dx1440,14,11,0|e86vzz,14,11,0|e86w00,12,10,1|eg45fz,12,10,1|eg45g0,14,11,0|eqwynz,14,11,0|eqwyo0,12,10,1|eyu83z,12,10,1|eyu840,14,11,0|f9n1bz,14,11,0|f9n1c0,12,10,1|fhkarz,12,10,1|fhkas0,14,11,0|fsd3zz,14,11,0|fsd400,12,10,1|g0adfz,12,10,1|g0adg0,14,11,0|gb36nz,14,11,0|gb36o0,12,10,1|gj0g3z,12,10,1|gj0g40,14,11,0|gu67zz,14,11,0|gu6800,12,10,1|h1qirz,12,10,1|h1qis0,14,11,0|hcwanz,14,11,0|hcwao0,12,10,1|hktk3z,12,10,1|hktk40,14,11,0|hvmdbz,14,11,0|hvmdc0,12,10,1|i3jmrz,12,10,1|i3jms0,14,11,0|iecfzz,14,11,0|iecg00,12,10,1|im9pfz,12,10,1|im9pg0,14,11,0|ix2inz,14,11,0|ix2io0,12,10,1|j4zs3z,12,10,1|j4zs40,14,11,0|jfslbz,14,11,0|jfslc0,12,10,1|jnpurz,12,10,1|jnpus0,14,11,0|jyvmnz,14,11,0|jyvmo0,12,10,1|k6sw3z,12,10,1|k6sw40,14,11,0|khlpbz,14,11,0|khlpc0,12,10,1|kpiyrz,12,10,1|kpiys0,14,11,0|l0brzz,14,11,0|l0bs00,12,10,1|l891fz,12,10,1|l891g0,14,11,0|lj1unz,14,11,0|lj1uo0,12,10,1|lqz43z,12,10,1|lqz440,14,11,0|m1rxbz,14,11,0|m1rxc0,12,10,1|m9p6rz,12,10,1|m9p6s0,14,11,0|mkuynz,14,11,0|mkuyo0,12,10,1|msf9fz,12,10,1|msf9g0,14,11,0|n3l1bz,14,11,0|n3l1c0,12,10,1|nbiarz,12,10,1|nbias0,14,11,0|nmb3zz,14,11,0|nmb400,12,10,1|nu8dfz,12,10,1|nu8dg0,14,11,0|o516nz,14,11,0|o516o0,12,10,1|ocyg3z,12,10,1|ocyg40,14,11,0|onr9bz,14,11,0|onr9c0,12,10,1|ovoirz,12,10,1|ovois0,14,11,0\",\"America/Adak|,0,33,0|-1hc7qjz,0,34,0|-1078omb,0,34,0|-1078oma,24,35,0|-ek1nw1,24,35,0|-ek1nw0,25,36,1|-cq2tg1,25,36,1|-cq2tg0,26,36,1|-cnomo1,26,36,1|-cnomo0,24,35,0|-1fq441,24,35,0|-1fq440,27,35,0|-cs3w1,27,35,0|-cs3w0,28,36,1|-3f5c1,28,36,1|-3f5c0,27,35,0|5xyrz,27,35,0|5xys0,28,36,1|faxbz,28,36,1|faxc0,27,35,0|oo1fz,27,35,0|oo1g0,28,36,1|ydynz,28,36,1|ydyo0,27,35,0|17r2rz,27,35,0|17r2s0,28,36,1|1h41bz,28,36,1|1h41c0,27,35,0|1qh5fz,27,35,0|1qh5g0,28,36,1|1zu3zz,28,36,1|1zu400,27,35,0|23ftfz,27,35,0|23ftg0,28,36,1|2ik6nz,28,36,1|2ik6o0,27,35,0|2oomrz,27,35,0|2ooms0,28,36,1|31a9bz,28,36,1|31a9c0,27,35,0|3andfz,27,35,0|3andg0,28,36,1|3kdanz,28,36,1|3kdao0,27,35,0|3tdg3z,27,35,0|3tdg40,28,36,1|433dbz,28,36,1|433dc0,27,35,0|4cghfz,27,35,0|4cghg0,28,36,1|4ltfzz,28,36,1|4ltg00,27,35,0|4v6k3z,27,35,0|4v6k40,28,36,1|54jinz,28,36,1|54jio0,27,35,0|5dwmrz,27,35,0|5dwms0,28,36,1|5n9lbz,28,36,1|5n9lc0,27,35,0|5wmpfz,27,35,0|5wmpg0,28,36,1|65znzz,28,36,1|65zo00,27,35,0|6fcs3z,27,35,0|6fcs40,28,36,1|6p2pbz,28,36,1|6p2pc0,27,35,0|6y2urz,27,35,0|6y2us0,28,36,1|77srzz,28,36,1|77ss00,29,36,0|79e13z,29,36,0|79e140,30,36,0|7h5tbz,30,36,0|7h5tc0,31,37,1|7qirvz,31,37,1|7qirw0,30,36,0|7zvvzz,30,36,0|7zvw00,31,37,1|898ujz,31,37,1|898uk0,30,36,0|8ilynz,30,36,0|8ilyo0,31,37,1|8ryx7z,31,37,1|8ryx80,30,36,0|9095bz,30,36,0|9095c0,31,37,1|9aozvz,31,37,1|9aozw0,30,36,0|9iz7zz,30,36,0|9iz800,31,37,1|9ts17z,31,37,1|9ts180,30,36,0|a1panz,30,36,0|a1pao0,31,37,1|aci3vz,31,37,1|aci3w0,30,36,0|akfdbz,30,36,0|akfdc0,31,37,1|av86jz,31,37,1|av86k0,30,36,0|b3ienz,30,36,0|b3ieo0,31,37,1|bdy97z,31,37,1|bdy980,30,36,0|bm8hbz,30,36,0|bm8hc0,31,37,1|bwobvz,31,37,1|bwobw0,30,36,0|c4yjzz,30,36,0|c4yk00,31,37,1|cfrd7z,31,37,1|cfrd80,30,36,0|cnomnz,30,36,0|cnomo0,31,37,1|cyhfvz,31,37,1|cyhfw0,30,36,0|d6epbz,30,36,0|d6epc0,31,37,1|dh7ijz,31,37,1|dh7ik0,30,36,0|dphqnz,30,36,0|dphqo0,31,37,1|dzxl7z,31,37,1|dzxl80,30,36,0|e87tbz,30,36,0|e87tc0,31,37,1|einnvz,31,37,1|einnw0,30,36,0|eqxvzz,30,36,0|eqxw00,31,37,1|f1dqjz,31,37,1|f1dqk0,30,36,0|f9nynz,30,36,0|f9nyo0,31,37,1|fkgrvz,31,37,1|fkgrw0,30,36,0|fse1bz,30,36,0|fse1c0,31,37,1|g36ujz,31,37,1|g36uk0,30,36,0|gb43zz,30,36,0|gb4400,31,37,1|glwx7z,31,37,1|glwx80,30,36,0|gu75bz,30,36,0|gu75c0,31,37,1|h4mzvz,31,37,1|h4mzw0,30,36,0|hcx7zz,30,36,0|hcx800,31,37,1|hnd2jz,31,37,1|hnd2k0,30,36,0|hvnanz,30,36,0|hvnao0,31,37,1|i6g3vz,31,37,1|i6g3w0,30,36,0|ieddbz,30,36,0|ieddc0,31,37,1|ip66jz,31,37,1|ip66k0,30,36,0|ix3fzz,30,36,0|ix3g00,31,37,1|j7w97z,31,37,1|j7w980,30,36,0|jeqmnz,30,36,0|jeqmo0,31,37,1|jqzajz,31,37,1|jqzak0,30,36,0|jxgpbz,30,36,0|jxgpc0,31,37,1|k9pd7z,31,37,1|k9pd80,30,36,0|kg6rzz,30,36,0|kg6s00,31,37,1|ksffvz,31,37,1|ksffw0,30,36,0|kz9tbz,30,36,0|kz9tc0,31,37,1|lbih7z,31,37,1|lbih80,30,36,0|lhzvzz,30,36,0|lhzw00,31,37,1|lu8jvz,31,37,1|lu8jw0,30,36,0|m0pynz,30,36,0|m0pyo0,31,37,1|mcymjz,31,37,1|mcymk0,30,36,0|mjg1bz,30,36,0|mjg1c0,31,37,1|mvop7z,31,37,1|mvop80,30,36,0|n263zz,30,36,0|n26400,31,37,1|neervz,31,37,1|neerw0,30,36,0|nkw6nz,30,36,0|nkw6o0,31,37,1|nx4ujz,31,37,1|nx4uk0,30,36,0|o3z7zz,30,36,0|o3z800,31,37,1|og7vvz,31,37,1|og7vw0,30,36,0|ompanz,30,36,0|ompao0,31,37,1|oyxyjz,31,37,1|oyxyk0,30,36,0|p5fdbz,30,36,0|p5fdc0,31,37,1|pho17z,31,37,1|pho180,30,36,0|po5fzz,30,36,0|po5g00,31,37,1|q0e3vz,31,37,1|q0e3w0,30,36,0|q6vinz,30,36,0|q6vio0,31,37,1|qj46jz,31,37,1|qj46k0,30,36,0|qpyjzz,30,36,0|qpyk00,31,37,1|r277vz,31,37,1|r277w0,30,36,0|r8omnz,30,36,0|r8omo0,31,37,1|rkxajz,31,37,1|rkxak0,30,36,0|rrepbz,30,36,0|rrepc0,31,37,1|s3nd7z,31,37,1|s3nd80,30,36,0|sa4rzz,30,36,0|sa4s00,31,37,1|smdfvz,31,37,1|smdfw0,30,36,0|ssuunz,30,36,0|ssuuo0,31,37,1|t53ijz,31,37,1|t53ik0,30,36,0|tbkxbz,30,36,0|tbkxc0,31,37,1|tntl7z,31,37,1|tntl80,30,36,0|tunynz,30,36,0|tunyo0,31,37,1|u6wmjz,31,37,1|u6wmk0,30,36,0|ude1bz,30,36,0|ude1c0,31,37,1|upmp7z,31,37,1|upmp80,30,36,0|uw43zz,30,36,0|uw4400,31,37,1|v8crvz,31,37,1|v8crw0,30,36,0|veu6nz,30,36,0|veu6o0,31,37,1|vr2ujz,31,37,1|vr2uk0,30,36,0|vxk9bz,30,36,0|vxk9c0,31,37,1|w9sx7z,31,37,1|w9sx80,30,36,0|wgnanz,30,36,0|wgnao0,31,37,1|wsvyjz,31,37,1|wsvyk0,30,36,0|wzddbz,30,36,0|wzddc0,31,37,1|xbm17z,31,37,1|xbm180,30,36,0|xi3fzz,30,36,0|xi3g00,31,37,1|xuc3vz,31,37,1|xuc3w0,30,36,0|y0tinz,30,36,0|y0tio0,31,37,1|yd26jz,31,37,1|yd26k0,30,36,0|yjjlbz,30,36,0|yjjlc0,31,37,1|yvs97z,31,37,1|yvs980,30,36,0|z29nzz,30,36,0|z29o00,31,37,1|zeibvz,31,37,1|zeibw0,30,36,0\",\"America/Anchorage|,0,38,0|-1hc7qjz,0,39,0|-1078tkp,0,39,0|-1078tko,32,36,0|-ek1qo1,32,36,0|-ek1qo0,33,37,1|-cq2tg1,33,37,1|-cq2tg0,34,37,1|-cnopg1,34,37,1|-cnopg0,32,36,0|-1fq6w1,32,36,0|-1fq6w0,29,36,0|-cs6o1,29,36,0|-cs6o0,35,37,1|-3f841,35,37,1|-3f840,29,36,0|5xvzz,29,36,0|5xw00,35,37,1|faujz,35,37,1|fauk0,29,36,0|onynz,29,36,0|onyo0,35,37,1|ydvvz,35,37,1|ydvw0,29,36,0|17qzzz,29,36,0|17r000,35,37,1|1h3yjz,35,37,1|1h3yk0,29,36,0|1qh2nz,29,36,0|1qh2o0,35,37,1|1zu17z,35,37,1|1zu180,29,36,0|23fqnz,29,36,0|23fqo0,35,37,1|2ik3vz,35,37,1|2ik3w0,29,36,0|2oojzz,29,36,0|2ook00,35,37,1|31a6jz,35,37,1|31a6k0,29,36,0|3ananz,29,36,0|3anao0,35,37,1|3kd7vz,35,37,1|3kd7w0,29,36,0|3tddbz,29,36,0|3tddc0,35,37,1|433ajz,35,37,1|433ak0,29,36,0|4cgenz,29,36,0|4cgeo0,35,37,1|4ltd7z,35,37,1|4ltd80,29,36,0|4v6hbz,29,36,0|4v6hc0,35,37,1|54jfvz,35,37,1|54jfw0,29,36,0|5dwjzz,29,36,0|5dwk00,35,37,1|5n9ijz,35,37,1|5n9ik0,29,36,0|5wmmnz,29,36,0|5wmmo0,35,37,1|65zl7z,35,37,1|65zl80,29,36,0|6fcpbz,29,36,0|6fcpc0,35,37,1|6p2mjz,35,37,1|6p2mk0,29,36,0|6y2rzz,29,36,0|6y2s00,35,37,1|77sp7z,35,37,1|77sp80,36,37,0|79dybz,36,37,0|79dyc0,37,37,0|7h5qjz,37,37,0|7h5qk0,38,40,1|7qip3z,38,40,1|7qip40,37,37,0|7zvt7z,37,37,0|7zvt80,38,40,1|898rrz,38,40,1|898rs0,37,37,0|8ilvvz,37,37,0|8ilvw0,38,40,1|8ryufz,38,40,1|8ryug0,37,37,0|9092jz,37,37,0|9092k0,38,40,1|9aox3z,38,40,1|9aox40,37,37,0|9iz57z,37,37,0|9iz580,38,40,1|9tryfz,38,40,1|9tryg0,37,37,0|a1p7vz,37,37,0|a1p7w0,38,40,1|aci13z,38,40,1|aci140,37,37,0|akfajz,37,37,0|akfak0,38,40,1|av83rz,38,40,1|av83s0,37,37,0|b3ibvz,37,37,0|b3ibw0,38,40,1|bdy6fz,38,40,1|bdy6g0,37,37,0|bm8ejz,37,37,0|bm8ek0,38,40,1|bwo93z,38,40,1|bwo940,37,37,0|c4yh7z,37,37,0|c4yh80,38,40,1|cfrafz,38,40,1|cfrag0,37,37,0|cnojvz,37,37,0|cnojw0,38,40,1|cyhd3z,38,40,1|cyhd40,37,37,0|d6emjz,37,37,0|d6emk0,38,40,1|dh7frz,38,40,1|dh7fs0,37,37,0|dphnvz,37,37,0|dphnw0,38,40,1|dzxifz,38,40,1|dzxig0,37,37,0|e87qjz,37,37,0|e87qk0,38,40,1|einl3z,38,40,1|einl40,37,37,0|eqxt7z,37,37,0|eqxt80,38,40,1|f1dnrz,38,40,1|f1dns0,37,37,0|f9nvvz,37,37,0|f9nvw0,38,40,1|fkgp3z,38,40,1|fkgp40,37,37,0|fsdyjz,37,37,0|fsdyk0,38,40,1|g36rrz,38,40,1|g36rs0,37,37,0|gb417z,37,37,0|gb4180,38,40,1|glwufz,38,40,1|glwug0,37,37,0|gu72jz,37,37,0|gu72k0,38,40,1|h4mx3z,38,40,1|h4mx40,37,37,0|hcx57z,37,37,0|hcx580,38,40,1|hnczrz,38,40,1|hnczs0,37,37,0|hvn7vz,37,37,0|hvn7w0,38,40,1|i6g13z,38,40,1|i6g140,37,37,0|iedajz,37,37,0|iedak0,38,40,1|ip63rz,38,40,1|ip63s0,37,37,0|ix3d7z,37,37,0|ix3d80,38,40,1|j7w6fz,38,40,1|j7w6g0,37,37,0|jeqjvz,37,37,0|jeqjw0,38,40,1|jqz7rz,38,40,1|jqz7s0,37,37,0|jxgmjz,37,37,0|jxgmk0,38,40,1|k9pafz,38,40,1|k9pag0,37,37,0|kg6p7z,37,37,0|kg6p80,38,40,1|ksfd3z,38,40,1|ksfd40,37,37,0|kz9qjz,37,37,0|kz9qk0,38,40,1|lbiefz,38,40,1|lbieg0,37,37,0|lhzt7z,37,37,0|lhzt80,38,40,1|lu8h3z,38,40,1|lu8h40,37,37,0|m0pvvz,37,37,0|m0pvw0,38,40,1|mcyjrz,38,40,1|mcyjs0,37,37,0|mjfyjz,37,37,0|mjfyk0,38,40,1|mvomfz,38,40,1|mvomg0,37,37,0|n2617z,37,37,0|n26180,38,40,1|neep3z,38,40,1|neep40,37,37,0|nkw3vz,37,37,0|nkw3w0,38,40,1|nx4rrz,38,40,1|nx4rs0,37,37,0|o3z57z,37,37,0|o3z580,38,40,1|og7t3z,38,40,1|og7t40,37,37,0|omp7vz,37,37,0|omp7w0,38,40,1|oyxvrz,38,40,1|oyxvs0,37,37,0|p5fajz,37,37,0|p5fak0,38,40,1|phnyfz,38,40,1|phnyg0,37,37,0|po5d7z,37,37,0|po5d80,38,40,1|q0e13z,38,40,1|q0e140,37,37,0|q6vfvz,37,37,0|q6vfw0,38,40,1|qj43rz,38,40,1|qj43s0,37,37,0|qpyh7z,37,37,0|qpyh80,38,40,1|r2753z,38,40,1|r27540,37,37,0|r8ojvz,37,37,0|r8ojw0,38,40,1|rkx7rz,38,40,1|rkx7s0,37,37,0|rremjz,37,37,0|rremk0,38,40,1|s3nafz,38,40,1|s3nag0,37,37,0|sa4p7z,37,37,0|sa4p80,38,40,1|smdd3z,38,40,1|smdd40,37,37,0|ssurvz,37,37,0|ssurw0,38,40,1|t53frz,38,40,1|t53fs0,37,37,0|tbkujz,37,37,0|tbkuk0,38,40,1|tntifz,38,40,1|tntig0,37,37,0|tunvvz,37,37,0|tunvw0,38,40,1|u6wjrz,38,40,1|u6wjs0,37,37,0|uddyjz,37,37,0|uddyk0,38,40,1|upmmfz,38,40,1|upmmg0,37,37,0|uw417z,37,37,0|uw4180,38,40,1|v8cp3z,38,40,1|v8cp40,37,37,0|veu3vz,37,37,0|veu3w0,38,40,1|vr2rrz,38,40,1|vr2rs0,37,37,0|vxk6jz,37,37,0|vxk6k0,38,40,1|w9sufz,38,40,1|w9sug0,37,37,0|wgn7vz,37,37,0|wgn7w0,38,40,1|wsvvrz,38,40,1|wsvvs0,37,37,0|wzdajz,37,37,0|wzdak0,38,40,1|xblyfz,38,40,1|xblyg0,37,37,0|xi3d7z,37,37,0|xi3d80,38,40,1|xuc13z,38,40,1|xuc140,37,37,0|y0tfvz,37,37,0|y0tfw0,38,40,1|yd23rz,38,40,1|yd23s0,37,37,0|yjjijz,37,37,0|yjjik0,38,40,1|yvs6fz,38,40,1|yvs6g0,37,37,0|z29l7z,37,37,0|z29l80,38,40,1|zei93z,38,40,1|zei940,37,37,0\",\"America/Anguilla|,0,41,0|-u6m79w,32,42,0\",\"America/Antigua|,0,41,0|-u6m79w,32,42,0\",\"America/Araguaina|,0,43,0|-t85j2o,39,44,0|-jylag1,39,44,0|-jylag0,40,45,1|-jpb141,40,45,1|-jpb140,39,44,0|-jfsd01,39,44,0|-jfsd00,40,45,1|-j6j3s1,40,45,1|-j6j3s0,39,44,0|-ahcyc1,39,44,0|-ahcyc0,40,45,1|-aad3o1,40,45,1|-aad3o0,39,44,0|-9yl101,39,44,0|-9yl100,40,45,1|-9sd141,40,45,1|-9sd140,39,44,0|-9ft3o1,39,44,0|-9ft3o0,40,45,1|-99j941,40,45,1|-99j940,39,44,0|-8wzbo1,39,44,0|-8wzbo0,40,45,1|-8scqg1,40,45,1|-8scqg0,39,44,0|-35xmc1,39,44,0|-35xmc0,40,45,1|-31nzs1,40,45,1|-31nzs0,39,44,0|-2kdp01,39,44,0|-2kdp00,40,45,1|-2hcig1,40,45,1|-2hcig0,39,44,0|-24qqc1,39,44,0|-24qqc0,40,45,1|-204541,40,45,1|-204540,39,44,0|-1nid01,39,44,0|-1nid00,40,45,1|-1hc7s1,40,45,1|-1hc7s0,39,44,0|-14qfo1,39,44,0|-14qfo0,40,45,1|-yifs1,40,45,1|-yifs0,39,44,0|89jcbz,39,44,0|89jcc0,40,45,1|8gdk7z,40,45,1|8gdk80,39,44,0|8rwgbz,39,44,0|8rwgc0,40,45,1|8xns7z,40,45,1|8xns80,39,44,0|9aodnz,39,44,0|9aodo0,40,45,1|9g2qvz,40,45,1|9g2qw0,39,44,0|9t1hnz,39,44,0|9t1ho0,40,45,1|9yfuvz,40,45,1|9yfuw0,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|ahvuvz,40,45,1|ahvuw0,39,44,0|dggyzz,39,44,0|dggz00,40,45,1|dml9jz,40,45,1|dml9k0,39,44,0|dyu2zz,39,44,0|dyu300,40,45,1|e5oavz,40,45,1|e5oaw0,39,44,0|ehm0bz,39,44,0|ehm0c0,40,45,1|ep4avz,40,45,1|ep4aw0,39,44,0|f0n6zz,39,44,0|f0n700,40,45,1|f7hevz,40,45,1|f7hew0,39,44,0|fj0azz,39,44,0|fj0b00,40,45,1|fqkg7z,40,45,1|fqkg80,39,44,0|g23cbz,39,44,0|g23cc0,40,45,1|g8xk7z,40,45,1|g8xk80,39,44,0|gl6dnz,39,44,0|gl6do0,40,45,1|grnmvz,40,45,1|grnmw0,39,44,0|h4zcbz,39,44,0|h4zcc0,40,45,1|hadpjz,40,45,1|hadpk0,39,44,0|mc82zz,39,44,0|mc8300,40,45,1|micdjz,40,45,1|micdk0,39,44,0\",\"America/Argentina/Buenos_Aires|,0,46,0|-138aaic,41,47,0|-px7ys1,41,47,0|-px7ys0,42,42,0|-kecq81,42,42,0|-kecq80,39,44,1|-k84qc1,39,44,1|-k84qc0,42,42,0|-jxzu81,42,42,0|-jxzu80,39,44,1|-jqwd01,39,44,1|-jqwd00,42,42,0|-jeakw1,42,42,0|-jeakw0,39,44,1|-j84fo1,39,44,1|-j84fo0,42,42,0|-ivink1,42,42,0|-ivink0,39,44,1|-ipcic1,39,44,1|-ipcic0,42,42,0|-icqq81,42,42,0|-icqq80,39,44,1|-i6kl01,39,44,1|-i6kl00,42,42,0|-htysw1,42,42,0|-htysw0,39,44,1|-hnqt01,39,44,1|-hnqt00,42,42,0|-hb50w1,42,42,0|-hb50w0,39,44,1|-h4yvo1,39,44,1|-h4yvo0,42,42,0|-gsd3k1,42,42,0|-gsd3k0,39,44,1|-gm6yc1,39,44,1|-gm6yc0,42,42,0|-g9l681,42,42,0|-g9l680,39,44,1|-g3f101,39,44,1|-g3f100,42,42,0|-fqt8w1,42,42,0|-fqt8w0,39,44,1|-fkl901,39,44,1|-fkl900,42,42,0|-feb8w1,42,42,0|-feb8w0,39,44,1|-ewd101,39,44,1|-ewd100,42,42,0|-eq30w1,42,42,0|-eq30w0,39,44,1|-dse501,39,44,1|-dse500,42,42,0|-doj681,42,42,0|-doj680,39,44,1|-cfvuc1,39,44,1|-cfvuc0,42,42,0|-c4vgw1,42,42,0|-c4vgw0,39,44,1|-39hec1,39,44,1|-39hec0,42,42,0|-35mfk1,42,42,0|-35mfk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2pxm81,42,42,0|-2pxm80,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|9veobz,39,44,0|9veoc0,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|aiyqvz,40,45,1|aiyqw0,39,44,0|auulnz,39,44,0|auulo0,40,45,1|b1otjz,40,45,1|b1otk0,39,44,0|bdkobz,39,44,0|bdkoc0,40,45,1|bkew7z,40,45,1|bkew80,39,44,0|bwaqzz,39,44,0|bwar00,40,45,1|c3hxjz,40,45,1|c3hxk0,39,44,0|fj0azz,39,44,0|fj0b00,39,44,1|fqtsbz,39,44,1|fqtsc0,39,44,0|jtudnz,39,44,0|jtudo0,40,45,1|jxsw7z,40,45,1|jxsw80,39,44,0|k8ytnz,39,44,0|k8yto0,40,45,1|kgiyvz,40,45,1|kgiyw0,39,44,0\",\"America/Argentina/Catamarca|,0,48,0|-138a95g,41,47,0|-px7ys1,41,47,0|-px7ys0,42,42,0|-kecq81,42,42,0|-kecq80,39,44,1|-k84qc1,39,44,1|-k84qc0,42,42,0|-jxzu81,42,42,0|-jxzu80,39,44,1|-jqwd01,39,44,1|-jqwd00,42,42,0|-jeakw1,42,42,0|-jeakw0,39,44,1|-j84fo1,39,44,1|-j84fo0,42,42,0|-ivink1,42,42,0|-ivink0,39,44,1|-ipcic1,39,44,1|-ipcic0,42,42,0|-icqq81,42,42,0|-icqq80,39,44,1|-i6kl01,39,44,1|-i6kl00,42,42,0|-htysw1,42,42,0|-htysw0,39,44,1|-hnqt01,39,44,1|-hnqt00,42,42,0|-hb50w1,42,42,0|-hb50w0,39,44,1|-h4yvo1,39,44,1|-h4yvo0,42,42,0|-gsd3k1,42,42,0|-gsd3k0,39,44,1|-gm6yc1,39,44,1|-gm6yc0,42,42,0|-g9l681,42,42,0|-g9l680,39,44,1|-g3f101,39,44,1|-g3f100,42,42,0|-fqt8w1,42,42,0|-fqt8w0,39,44,1|-fkl901,39,44,1|-fkl900,42,42,0|-feb8w1,42,42,0|-feb8w0,39,44,1|-ewd101,39,44,1|-ewd100,42,42,0|-eq30w1,42,42,0|-eq30w0,39,44,1|-dse501,39,44,1|-dse500,42,42,0|-doj681,42,42,0|-doj680,39,44,1|-cfvuc1,39,44,1|-cfvuc0,42,42,0|-c4vgw1,42,42,0|-c4vgw0,39,44,1|-39hec1,39,44,1|-39hec0,42,42,0|-35mfk1,42,42,0|-35mfk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2pxm81,42,42,0|-2pxm80,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|9veobz,39,44,0|9veoc0,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|aiyqvz,40,45,1|aiyqw0,39,44,0|auulnz,39,44,0|auulo0,40,45,1|b1otjz,40,45,1|b1otk0,42,42,0|bdkr3z,42,42,0|bdkr40,40,45,1|bkew7z,40,45,1|bkew80,39,44,0|bwaqzz,39,44,0|bwar00,40,45,1|c3hxjz,40,45,1|c3hxk0,39,44,0|fj0azz,39,44,0|fj0b00,39,44,1|fqtsbz,39,44,1|fqtsc0,39,44,0|hym0bz,39,44,0|hym0c0,42,42,0|hzl9rz,42,42,0|hzl9s0,39,44,0|jtudnz,39,44,0|jtudo0,40,45,1|jxsw7z,40,45,1|jxsw80,39,44,0\",\"America/Argentina/Cordoba|,0,47,0|-138a9g0,41,47,0|-px7ys1,41,47,0|-px7ys0,42,42,0|-kecq81,42,42,0|-kecq80,39,44,1|-k84qc1,39,44,1|-k84qc0,42,42,0|-jxzu81,42,42,0|-jxzu80,39,44,1|-jqwd01,39,44,1|-jqwd00,42,42,0|-jeakw1,42,42,0|-jeakw0,39,44,1|-j84fo1,39,44,1|-j84fo0,42,42,0|-ivink1,42,42,0|-ivink0,39,44,1|-ipcic1,39,44,1|-ipcic0,42,42,0|-icqq81,42,42,0|-icqq80,39,44,1|-i6kl01,39,44,1|-i6kl00,42,42,0|-htysw1,42,42,0|-htysw0,39,44,1|-hnqt01,39,44,1|-hnqt00,42,42,0|-hb50w1,42,42,0|-hb50w0,39,44,1|-h4yvo1,39,44,1|-h4yvo0,42,42,0|-gsd3k1,42,42,0|-gsd3k0,39,44,1|-gm6yc1,39,44,1|-gm6yc0,42,42,0|-g9l681,42,42,0|-g9l680,39,44,1|-g3f101,39,44,1|-g3f100,42,42,0|-fqt8w1,42,42,0|-fqt8w0,39,44,1|-fkl901,39,44,1|-fkl900,42,42,0|-feb8w1,42,42,0|-feb8w0,39,44,1|-ewd101,39,44,1|-ewd100,42,42,0|-eq30w1,42,42,0|-eq30w0,39,44,1|-dse501,39,44,1|-dse500,42,42,0|-doj681,42,42,0|-doj680,39,44,1|-cfvuc1,39,44,1|-cfvuc0,42,42,0|-c4vgw1,42,42,0|-c4vgw0,39,44,1|-39hec1,39,44,1|-39hec0,42,42,0|-35mfk1,42,42,0|-35mfk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2pxm81,42,42,0|-2pxm80,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|9veobz,39,44,0|9veoc0,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|aiyqvz,40,45,1|aiyqw0,39,44,0|auulnz,39,44,0|auulo0,40,45,1|b1otjz,40,45,1|b1otk0,42,42,0|bdkr3z,42,42,0|bdkr40,40,45,1|bkew7z,40,45,1|bkew80,39,44,0|bwaqzz,39,44,0|bwar00,40,45,1|c3hxjz,40,45,1|c3hxk0,39,44,0|fj0azz,39,44,0|fj0b00,39,44,1|fqtsbz,39,44,1|fqtsc0,39,44,0|jtudnz,39,44,0|jtudo0,40,45,1|jxsw7z,40,45,1|jxsw80,39,44,0|k8ytnz,39,44,0|k8yto0,40,45,1|kgiyvz,40,45,1|kgiyw0,39,44,0\",\"America/Argentina/Jujuy|,0,49,0|-138a98o,41,47,0|-px7ys1,41,47,0|-px7ys0,42,42,0|-kecq81,42,42,0|-kecq80,39,44,1|-k84qc1,39,44,1|-k84qc0,42,42,0|-jxzu81,42,42,0|-jxzu80,39,44,1|-jqwd01,39,44,1|-jqwd00,42,42,0|-jeakw1,42,42,0|-jeakw0,39,44,1|-j84fo1,39,44,1|-j84fo0,42,42,0|-ivink1,42,42,0|-ivink0,39,44,1|-ipcic1,39,44,1|-ipcic0,42,42,0|-icqq81,42,42,0|-icqq80,39,44,1|-i6kl01,39,44,1|-i6kl00,42,42,0|-htysw1,42,42,0|-htysw0,39,44,1|-hnqt01,39,44,1|-hnqt00,42,42,0|-hb50w1,42,42,0|-hb50w0,39,44,1|-h4yvo1,39,44,1|-h4yvo0,42,42,0|-gsd3k1,42,42,0|-gsd3k0,39,44,1|-gm6yc1,39,44,1|-gm6yc0,42,42,0|-g9l681,42,42,0|-g9l680,39,44,1|-g3f101,39,44,1|-g3f100,42,42,0|-fqt8w1,42,42,0|-fqt8w0,39,44,1|-fkl901,39,44,1|-fkl900,42,42,0|-feb8w1,42,42,0|-feb8w0,39,44,1|-ewd101,39,44,1|-ewd100,42,42,0|-eq30w1,42,42,0|-eq30w0,39,44,1|-dse501,39,44,1|-dse500,42,42,0|-doj681,42,42,0|-doj680,39,44,1|-cfvuc1,39,44,1|-cfvuc0,42,42,0|-c4vgw1,42,42,0|-c4vgw0,39,44,1|-39hec1,39,44,1|-39hec0,42,42,0|-35mfk1,42,42,0|-35mfk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2pxm81,42,42,0|-2pxm80,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|9veobz,39,44,0|9veoc0,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|aiyqvz,40,45,1|aiyqw0,42,42,0|av7n3z,42,42,0|av7n40,39,44,1|b2etnz,39,44,1|b2eto0,42,42,0|bcutrz,42,42,0|bcuts0,40,45,1|bkew7z,40,45,1|bkew80,39,44,0|bwaqzz,39,44,0|bwar00,40,45,1|c3hxjz,40,45,1|c3hxk0,39,44,0|fj0azz,39,44,0|fj0b00,39,44,1|fqtsbz,39,44,1|fqtsc0,39,44,0|jtudnz,39,44,0|jtudo0,40,45,1|jxsw7z,40,45,1|jxsw80,39,44,0\",\"America/Argentina/La_Rioja|,0,50,0|-138a8yc,41,47,0|-px7ys1,41,47,0|-px7ys0,42,42,0|-kecq81,42,42,0|-kecq80,39,44,1|-k84qc1,39,44,1|-k84qc0,42,42,0|-jxzu81,42,42,0|-jxzu80,39,44,1|-jqwd01,39,44,1|-jqwd00,42,42,0|-jeakw1,42,42,0|-jeakw0,39,44,1|-j84fo1,39,44,1|-j84fo0,42,42,0|-ivink1,42,42,0|-ivink0,39,44,1|-ipcic1,39,44,1|-ipcic0,42,42,0|-icqq81,42,42,0|-icqq80,39,44,1|-i6kl01,39,44,1|-i6kl00,42,42,0|-htysw1,42,42,0|-htysw0,39,44,1|-hnqt01,39,44,1|-hnqt00,42,42,0|-hb50w1,42,42,0|-hb50w0,39,44,1|-h4yvo1,39,44,1|-h4yvo0,42,42,0|-gsd3k1,42,42,0|-gsd3k0,39,44,1|-gm6yc1,39,44,1|-gm6yc0,42,42,0|-g9l681,42,42,0|-g9l680,39,44,1|-g3f101,39,44,1|-g3f100,42,42,0|-fqt8w1,42,42,0|-fqt8w0,39,44,1|-fkl901,39,44,1|-fkl900,42,42,0|-feb8w1,42,42,0|-feb8w0,39,44,1|-ewd101,39,44,1|-ewd100,42,42,0|-eq30w1,42,42,0|-eq30w0,39,44,1|-dse501,39,44,1|-dse500,42,42,0|-doj681,42,42,0|-doj680,39,44,1|-cfvuc1,39,44,1|-cfvuc0,42,42,0|-c4vgw1,42,42,0|-c4vgw0,39,44,1|-39hec1,39,44,1|-39hec0,42,42,0|-35mfk1,42,42,0|-35mfk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2pxm81,42,42,0|-2pxm80,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|9veobz,39,44,0|9veoc0,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|aiyqvz,40,45,1|aiyqw0,39,44,0|auulnz,39,44,0|auulo0,40,45,1|b1l47z,40,45,1|b1l480,42,42,0|b51cfz,42,42,0|b51cg0,39,44,0|bdkobz,39,44,0|bdkoc0,40,45,1|bkew7z,40,45,1|bkew80,39,44,0|bwaqzz,39,44,0|bwar00,40,45,1|c3hxjz,40,45,1|c3hxk0,39,44,0|fj0azz,39,44,0|fj0b00,39,44,1|fqtsbz,39,44,1|fqtsc0,39,44,0|hym0bz,39,44,0|hym0c0,42,42,0|hzl9rz,42,42,0|hzl9s0,39,44,0|jtudnz,39,44,0|jtudo0,40,45,1|jxsw7z,40,45,1|jxsw80,39,44,0\",\"America/Argentina/Mendoza|,0,51,0|-138a8l8,41,47,0|-px7ys1,41,47,0|-px7ys0,42,42,0|-kecq81,42,42,0|-kecq80,39,44,1|-k84qc1,39,44,1|-k84qc0,42,42,0|-jxzu81,42,42,0|-jxzu80,39,44,1|-jqwd01,39,44,1|-jqwd00,42,42,0|-jeakw1,42,42,0|-jeakw0,39,44,1|-j84fo1,39,44,1|-j84fo0,42,42,0|-ivink1,42,42,0|-ivink0,39,44,1|-ipcic1,39,44,1|-ipcic0,42,42,0|-icqq81,42,42,0|-icqq80,39,44,1|-i6kl01,39,44,1|-i6kl00,42,42,0|-htysw1,42,42,0|-htysw0,39,44,1|-hnqt01,39,44,1|-hnqt00,42,42,0|-hb50w1,42,42,0|-hb50w0,39,44,1|-h4yvo1,39,44,1|-h4yvo0,42,42,0|-gsd3k1,42,42,0|-gsd3k0,39,44,1|-gm6yc1,39,44,1|-gm6yc0,42,42,0|-g9l681,42,42,0|-g9l680,39,44,1|-g3f101,39,44,1|-g3f100,42,42,0|-fqt8w1,42,42,0|-fqt8w0,39,44,1|-fkl901,39,44,1|-fkl900,42,42,0|-feb8w1,42,42,0|-feb8w0,39,44,1|-ewd101,39,44,1|-ewd100,42,42,0|-eq30w1,42,42,0|-eq30w0,39,44,1|-dse501,39,44,1|-dse500,42,42,0|-doj681,42,42,0|-doj680,39,44,1|-cfvuc1,39,44,1|-cfvuc0,42,42,0|-c4vgw1,42,42,0|-c4vgw0,39,44,1|-39hec1,39,44,1|-39hec0,42,42,0|-35mfk1,42,42,0|-35mfk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2pxm81,42,42,0|-2pxm80,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|9veobz,39,44,0|9veoc0,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|aiyqvz,40,45,1|aiyqw0,42,42,0|aujkfz,42,42,0|aujkg0,39,44,1|b1l6zz,39,44,1|b1l700,42,42,0|bdbhrz,42,42,0|bdbhs0,39,44,1|bkeyzz,39,44,1|bkez00,42,42,0|bwatrz,42,42,0|bwats0,40,45,1|c3hxjz,40,45,1|c3hxk0,39,44,0|fj0azz,39,44,0|fj0b00,39,44,1|fqtsbz,39,44,1|fqtsc0,39,44,0|hy5cbz,39,44,0|hy5cc0,42,42,0|i4mr3z,42,42,0|i4mr40,39,44,0|jtudnz,39,44,0|jtudo0,40,45,1|jxsw7z,40,45,1|jxsw80,39,44,0\",\"America/Argentina/Rio_Gallegos|,0,52,0|-138a8ik,41,47,0|-px7ys1,41,47,0|-px7ys0,42,42,0|-kecq81,42,42,0|-kecq80,39,44,1|-k84qc1,39,44,1|-k84qc0,42,42,0|-jxzu81,42,42,0|-jxzu80,39,44,1|-jqwd01,39,44,1|-jqwd00,42,42,0|-jeakw1,42,42,0|-jeakw0,39,44,1|-j84fo1,39,44,1|-j84fo0,42,42,0|-ivink1,42,42,0|-ivink0,39,44,1|-ipcic1,39,44,1|-ipcic0,42,42,0|-icqq81,42,42,0|-icqq80,39,44,1|-i6kl01,39,44,1|-i6kl00,42,42,0|-htysw1,42,42,0|-htysw0,39,44,1|-hnqt01,39,44,1|-hnqt00,42,42,0|-hb50w1,42,42,0|-hb50w0,39,44,1|-h4yvo1,39,44,1|-h4yvo0,42,42,0|-gsd3k1,42,42,0|-gsd3k0,39,44,1|-gm6yc1,39,44,1|-gm6yc0,42,42,0|-g9l681,42,42,0|-g9l680,39,44,1|-g3f101,39,44,1|-g3f100,42,42,0|-fqt8w1,42,42,0|-fqt8w0,39,44,1|-fkl901,39,44,1|-fkl900,42,42,0|-feb8w1,42,42,0|-feb8w0,39,44,1|-ewd101,39,44,1|-ewd100,42,42,0|-eq30w1,42,42,0|-eq30w0,39,44,1|-dse501,39,44,1|-dse500,42,42,0|-doj681,42,42,0|-doj680,39,44,1|-cfvuc1,39,44,1|-cfvuc0,42,42,0|-c4vgw1,42,42,0|-c4vgw0,39,44,1|-39hec1,39,44,1|-39hec0,42,42,0|-35mfk1,42,42,0|-35mfk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2pxm81,42,42,0|-2pxm80,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|9veobz,39,44,0|9veoc0,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|aiyqvz,40,45,1|aiyqw0,39,44,0|auulnz,39,44,0|auulo0,40,45,1|b1otjz,40,45,1|b1otk0,39,44,0|bdkobz,39,44,0|bdkoc0,40,45,1|bkew7z,40,45,1|bkew80,39,44,0|bwaqzz,39,44,0|bwar00,40,45,1|c3hxjz,40,45,1|c3hxk0,39,44,0|fj0azz,39,44,0|fj0b00,39,44,1|fqtsbz,39,44,1|fqtsc0,39,44,0|hym0bz,39,44,0|hym0c0,42,42,0|hzl9rz,42,42,0|hzl9s0,39,44,0|jtudnz,39,44,0|jtudo0,40,45,1|jxsw7z,40,45,1|jxsw80,39,44,0\",\"America/Argentina/Salta|,0,53,0|-138a97w,41,47,0|-px7ys1,41,47,0|-px7ys0,42,42,0|-kecq81,42,42,0|-kecq80,39,44,1|-k84qc1,39,44,1|-k84qc0,42,42,0|-jxzu81,42,42,0|-jxzu80,39,44,1|-jqwd01,39,44,1|-jqwd00,42,42,0|-jeakw1,42,42,0|-jeakw0,39,44,1|-j84fo1,39,44,1|-j84fo0,42,42,0|-ivink1,42,42,0|-ivink0,39,44,1|-ipcic1,39,44,1|-ipcic0,42,42,0|-icqq81,42,42,0|-icqq80,39,44,1|-i6kl01,39,44,1|-i6kl00,42,42,0|-htysw1,42,42,0|-htysw0,39,44,1|-hnqt01,39,44,1|-hnqt00,42,42,0|-hb50w1,42,42,0|-hb50w0,39,44,1|-h4yvo1,39,44,1|-h4yvo0,42,42,0|-gsd3k1,42,42,0|-gsd3k0,39,44,1|-gm6yc1,39,44,1|-gm6yc0,42,42,0|-g9l681,42,42,0|-g9l680,39,44,1|-g3f101,39,44,1|-g3f100,42,42,0|-fqt8w1,42,42,0|-fqt8w0,39,44,1|-fkl901,39,44,1|-fkl900,42,42,0|-feb8w1,42,42,0|-feb8w0,39,44,1|-ewd101,39,44,1|-ewd100,42,42,0|-eq30w1,42,42,0|-eq30w0,39,44,1|-dse501,39,44,1|-dse500,42,42,0|-doj681,42,42,0|-doj680,39,44,1|-cfvuc1,39,44,1|-cfvuc0,42,42,0|-c4vgw1,42,42,0|-c4vgw0,39,44,1|-39hec1,39,44,1|-39hec0,42,42,0|-35mfk1,42,42,0|-35mfk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2pxm81,42,42,0|-2pxm80,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|9veobz,39,44,0|9veoc0,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|aiyqvz,40,45,1|aiyqw0,39,44,0|auulnz,39,44,0|auulo0,40,45,1|b1otjz,40,45,1|b1otk0,42,42,0|bdkr3z,42,42,0|bdkr40,40,45,1|bkew7z,40,45,1|bkew80,39,44,0|bwaqzz,39,44,0|bwar00,40,45,1|c3hxjz,40,45,1|c3hxk0,39,44,0|fj0azz,39,44,0|fj0b00,39,44,1|fqtsbz,39,44,1|fqtsc0,39,44,0|jtudnz,39,44,0|jtudo0,40,45,1|jxsw7z,40,45,1|jxsw80,39,44,0\",\"America/Argentina/San_Juan|,0,54,0|-138a8n8,41,47,0|-px7ys1,41,47,0|-px7ys0,42,42,0|-kecq81,42,42,0|-kecq80,39,44,1|-k84qc1,39,44,1|-k84qc0,42,42,0|-jxzu81,42,42,0|-jxzu80,39,44,1|-jqwd01,39,44,1|-jqwd00,42,42,0|-jeakw1,42,42,0|-jeakw0,39,44,1|-j84fo1,39,44,1|-j84fo0,42,42,0|-ivink1,42,42,0|-ivink0,39,44,1|-ipcic1,39,44,1|-ipcic0,42,42,0|-icqq81,42,42,0|-icqq80,39,44,1|-i6kl01,39,44,1|-i6kl00,42,42,0|-htysw1,42,42,0|-htysw0,39,44,1|-hnqt01,39,44,1|-hnqt00,42,42,0|-hb50w1,42,42,0|-hb50w0,39,44,1|-h4yvo1,39,44,1|-h4yvo0,42,42,0|-gsd3k1,42,42,0|-gsd3k0,39,44,1|-gm6yc1,39,44,1|-gm6yc0,42,42,0|-g9l681,42,42,0|-g9l680,39,44,1|-g3f101,39,44,1|-g3f100,42,42,0|-fqt8w1,42,42,0|-fqt8w0,39,44,1|-fkl901,39,44,1|-fkl900,42,42,0|-feb8w1,42,42,0|-feb8w0,39,44,1|-ewd101,39,44,1|-ewd100,42,42,0|-eq30w1,42,42,0|-eq30w0,39,44,1|-dse501,39,44,1|-dse500,42,42,0|-doj681,42,42,0|-doj680,39,44,1|-cfvuc1,39,44,1|-cfvuc0,42,42,0|-c4vgw1,42,42,0|-c4vgw0,39,44,1|-39hec1,39,44,1|-39hec0,42,42,0|-35mfk1,42,42,0|-35mfk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2pxm81,42,42,0|-2pxm80,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|9veobz,39,44,0|9veoc0,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|aiyqvz,40,45,1|aiyqw0,39,44,0|auulnz,39,44,0|auulo0,40,45,1|b1l47z,40,45,1|b1l480,42,42,0|b51cfz,42,42,0|b51cg0,39,44,0|bdkobz,39,44,0|bdkoc0,40,45,1|bkew7z,40,45,1|bkew80,39,44,0|bwaqzz,39,44,0|bwar00,40,45,1|c3hxjz,40,45,1|c3hxk0,39,44,0|fj0azz,39,44,0|fj0b00,39,44,1|fqtsbz,39,44,1|fqtsc0,39,44,0|hyk5nz,39,44,0|hyk5o0,42,42,0|i1e33z,42,42,0|i1e340,39,44,0|jtudnz,39,44,0|jtudo0,40,45,1|jxsw7z,40,45,1|jxsw80,39,44,0\",\"America/Argentina/San_Luis|,0,55,0|-138a91o,41,47,0|-px7ys1,41,47,0|-px7ys0,42,42,0|-kecq81,42,42,0|-kecq80,39,44,1|-k84qc1,39,44,1|-k84qc0,42,42,0|-jxzu81,42,42,0|-jxzu80,39,44,1|-jqwd01,39,44,1|-jqwd00,42,42,0|-jeakw1,42,42,0|-jeakw0,39,44,1|-j84fo1,39,44,1|-j84fo0,42,42,0|-ivink1,42,42,0|-ivink0,39,44,1|-ipcic1,39,44,1|-ipcic0,42,42,0|-icqq81,42,42,0|-icqq80,39,44,1|-i6kl01,39,44,1|-i6kl00,42,42,0|-htysw1,42,42,0|-htysw0,39,44,1|-hnqt01,39,44,1|-hnqt00,42,42,0|-hb50w1,42,42,0|-hb50w0,39,44,1|-h4yvo1,39,44,1|-h4yvo0,42,42,0|-gsd3k1,42,42,0|-gsd3k0,39,44,1|-gm6yc1,39,44,1|-gm6yc0,42,42,0|-g9l681,42,42,0|-g9l680,39,44,1|-g3f101,39,44,1|-g3f100,42,42,0|-fqt8w1,42,42,0|-fqt8w0,39,44,1|-fkl901,39,44,1|-fkl900,42,42,0|-feb8w1,42,42,0|-feb8w0,39,44,1|-ewd101,39,44,1|-ewd100,42,42,0|-eq30w1,42,42,0|-eq30w0,39,44,1|-dse501,39,44,1|-dse500,42,42,0|-doj681,42,42,0|-doj680,39,44,1|-cfvuc1,39,44,1|-cfvuc0,42,42,0|-c4vgw1,42,42,0|-c4vgw0,39,44,1|-39hec1,39,44,1|-39hec0,42,42,0|-35mfk1,42,42,0|-35mfk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2pxm81,42,42,0|-2pxm80,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|9veobz,39,44,0|9veoc0,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|ajh9jz,40,45,1|ajh9k0,42,42,0|aujkfz,42,42,0|aujkg0,39,44,1|b1l6zz,39,44,1|b1l700,42,42,0|b6bn3z,42,42,0|b6bn40,39,44,0|fj0azz,39,44,0|fj0b00,39,44,1|fqtsbz,39,44,1|fqtsc0,39,44,0|hyk5nz,39,44,0|hyk5o0,42,42,0|i1e33z,42,42,0|i1e340,39,44,0|jtudnz,39,44,0|jtudo0,40,45,1|juz1jz,40,45,1|juz1k0,39,44,1|jxg0bz,39,44,1|jxg0c0,42,42,0|k8lxrz,42,42,0|k8lxs0,39,44,1|kg62zz,39,44,1|kg6300,42,42,0|krc0fz,42,42,0|krc0g0,39,44,0\",\"America/Argentina/Tucuman|,0,56,0|-138a998,41,47,0|-px7ys1,41,47,0|-px7ys0,42,42,0|-kecq81,42,42,0|-kecq80,39,44,1|-k84qc1,39,44,1|-k84qc0,42,42,0|-jxzu81,42,42,0|-jxzu80,39,44,1|-jqwd01,39,44,1|-jqwd00,42,42,0|-jeakw1,42,42,0|-jeakw0,39,44,1|-j84fo1,39,44,1|-j84fo0,42,42,0|-ivink1,42,42,0|-ivink0,39,44,1|-ipcic1,39,44,1|-ipcic0,42,42,0|-icqq81,42,42,0|-icqq80,39,44,1|-i6kl01,39,44,1|-i6kl00,42,42,0|-htysw1,42,42,0|-htysw0,39,44,1|-hnqt01,39,44,1|-hnqt00,42,42,0|-hb50w1,42,42,0|-hb50w0,39,44,1|-h4yvo1,39,44,1|-h4yvo0,42,42,0|-gsd3k1,42,42,0|-gsd3k0,39,44,1|-gm6yc1,39,44,1|-gm6yc0,42,42,0|-g9l681,42,42,0|-g9l680,39,44,1|-g3f101,39,44,1|-g3f100,42,42,0|-fqt8w1,42,42,0|-fqt8w0,39,44,1|-fkl901,39,44,1|-fkl900,42,42,0|-feb8w1,42,42,0|-feb8w0,39,44,1|-ewd101,39,44,1|-ewd100,42,42,0|-eq30w1,42,42,0|-eq30w0,39,44,1|-dse501,39,44,1|-dse500,42,42,0|-doj681,42,42,0|-doj680,39,44,1|-cfvuc1,39,44,1|-cfvuc0,42,42,0|-c4vgw1,42,42,0|-c4vgw0,39,44,1|-39hec1,39,44,1|-39hec0,42,42,0|-35mfk1,42,42,0|-35mfk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2pxm81,42,42,0|-2pxm80,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|9veobz,39,44,0|9veoc0,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|aiyqvz,40,45,1|aiyqw0,39,44,0|auulnz,39,44,0|auulo0,40,45,1|b1otjz,40,45,1|b1otk0,42,42,0|bdkr3z,42,42,0|bdkr40,40,45,1|bkew7z,40,45,1|bkew80,39,44,0|bwaqzz,39,44,0|bwar00,40,45,1|c3hxjz,40,45,1|c3hxk0,39,44,0|fj0azz,39,44,0|fj0b00,39,44,1|fqtsbz,39,44,1|fqtsc0,39,44,0|hym0bz,39,44,0|hym0c0,42,42,0|hz8b3z,42,42,0|hz8b40,39,44,0|jtudnz,39,44,0|jtudo0,40,45,1|jxsw7z,40,45,1|jxsw80,39,44,0|k8ytnz,39,44,0|k8yto0,40,45,1|kgiyvz,40,45,1|kgiyw0,39,44,0\",\"America/Argentina/Ushuaia|,0,57,0|-138a8oo,41,47,0|-px7ys1,41,47,0|-px7ys0,42,42,0|-kecq81,42,42,0|-kecq80,39,44,1|-k84qc1,39,44,1|-k84qc0,42,42,0|-jxzu81,42,42,0|-jxzu80,39,44,1|-jqwd01,39,44,1|-jqwd00,42,42,0|-jeakw1,42,42,0|-jeakw0,39,44,1|-j84fo1,39,44,1|-j84fo0,42,42,0|-ivink1,42,42,0|-ivink0,39,44,1|-ipcic1,39,44,1|-ipcic0,42,42,0|-icqq81,42,42,0|-icqq80,39,44,1|-i6kl01,39,44,1|-i6kl00,42,42,0|-htysw1,42,42,0|-htysw0,39,44,1|-hnqt01,39,44,1|-hnqt00,42,42,0|-hb50w1,42,42,0|-hb50w0,39,44,1|-h4yvo1,39,44,1|-h4yvo0,42,42,0|-gsd3k1,42,42,0|-gsd3k0,39,44,1|-gm6yc1,39,44,1|-gm6yc0,42,42,0|-g9l681,42,42,0|-g9l680,39,44,1|-g3f101,39,44,1|-g3f100,42,42,0|-fqt8w1,42,42,0|-fqt8w0,39,44,1|-fkl901,39,44,1|-fkl900,42,42,0|-feb8w1,42,42,0|-feb8w0,39,44,1|-ewd101,39,44,1|-ewd100,42,42,0|-eq30w1,42,42,0|-eq30w0,39,44,1|-dse501,39,44,1|-dse500,42,42,0|-doj681,42,42,0|-doj680,39,44,1|-cfvuc1,39,44,1|-cfvuc0,42,42,0|-c4vgw1,42,42,0|-c4vgw0,39,44,1|-39hec1,39,44,1|-39hec0,42,42,0|-35mfk1,42,42,0|-35mfk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2pxm81,42,42,0|-2pxm80,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|9veobz,39,44,0|9veoc0,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|aiyqvz,40,45,1|aiyqw0,39,44,0|auulnz,39,44,0|auulo0,40,45,1|b1otjz,40,45,1|b1otk0,39,44,0|bdkobz,39,44,0|bdkoc0,40,45,1|bkew7z,40,45,1|bkew80,39,44,0|bwaqzz,39,44,0|bwar00,40,45,1|c3hxjz,40,45,1|c3hxk0,39,44,0|fj0azz,39,44,0|fj0b00,39,44,1|fqtsbz,39,44,1|fqtsc0,39,44,0|hyiazz,39,44,0|hyib00,42,42,0|hzl9rz,42,42,0|hzl9s0,39,44,0|jtudnz,39,44,0|jtudo0,40,45,1|jxsw7z,40,45,1|jxsw80,39,44,0\",\"America/Aruba|,0,58,0|-u7lckd,43,59,0|-2lx4u1,43,59,0|-2lx4u0,32,42,0\",\"America/Asuncion|,0,60,0|-15r0ynk,44,60,0|-jy93zl,44,60,0|-jy93zk,42,42,0|1fnkfz,42,42,0|1fnkg0,39,44,0|27sgbz,39,44,0|27sgc0,42,42,0|2zzcfz,42,42,0|2zzcg0,39,44,1|37sqzz,39,44,1|37sr00,42,42,0|3it4fz,42,42,0|3it4g0,39,44,1|3qkobz,39,44,1|3qkoc0,42,42,0|41l1rz,42,42,0|41l1s0,39,44,1|49clnz,39,44,1|49clo0,42,42,0|4kcz3z,42,42,0|4kcz40,39,44,1|4tpxnz,39,44,1|4tpxo0,42,42,0|534wfz,42,42,0|534wg0,39,44,1|5cjpnz,39,44,1|5cjpo0,42,42,0|5lyofz,42,42,0|5lyog0,39,44,1|5vbmzz,39,44,1|5vbn00,42,42,0|64qlrz,42,42,0|64qls0,39,44,1|6e3kbz,39,44,1|6e3kc0,42,42,0|6nij3z,42,42,0|6nij40,39,44,1|6wvhnz,39,44,1|6wvho0,42,42,0|76agfz,42,42,0|76agg0,39,44,1|7fp9nz,39,44,1|7fp9o0,42,42,0|7p48fz,42,42,0|7p48g0,39,44,1|7yh6zz,39,44,1|7yh700,42,42,0|87w5rz,42,42,0|87w5s0,39,44,1|8h94bz,39,44,1|8h94c0,42,42,0|8qo33z,42,42,0|8qo340,39,44,1|9011nz,39,44,1|9011o0,42,42,0|99g0fz,42,42,0|99g0g0,39,44,1|9iutnz,39,44,1|9iuto0,42,42,0|9s9sfz,42,42,0|9s9sg0,39,44,1|a1mqzz,39,44,1|a1mr00,42,42,0|ac4lrz,42,42,0|ac4ls0,39,44,1|akeobz,39,44,1|akeoc0,42,42,0|attn3z,42,42,0|attn40,39,44,1|b36lnz,39,44,1|b36lo0,42,42,0|bcutrz,42,42,0|bcuts0,39,44,1|bkeyzz,39,44,1|bkez00,42,42,0|bvmr3z,42,42,0|bvmr40,39,44,1|c4qgbz,39,44,1|c4qgc0,42,42,0|ce79rz,42,42,0|ce79s0,39,44,1|clv4bz,39,44,1|clv4c0,42,42,0|cwz73z,42,42,0|cwz740,39,44,1|d4l6zz,39,44,1|d4l700,42,42,0|dfr4fz,42,42,0|dfr4g0,39,44,1|dnkizz,39,44,1|dnkj00,42,42,0|dyu5rz,42,42,0|dyu5s0,39,44,1|e61cbz,39,44,1|e61cc0,42,42,0|ehk8fz,42,42,0|ehk8g0,39,44,1|ep4dnz,39,44,1|ep4do0,42,42,0|f0ab3z,42,42,0|f0ab40,39,44,1|f87ezz,39,44,1|f87f00,42,42,0|fj0drz,42,42,0|fj0ds0,39,44,1|fqxhnz,39,44,1|fqxho0,42,42,0|g1qgfz,42,42,0|g1qgg0,39,44,1|g9nkbz,39,44,1|g9nkc0,42,42,0|gkthrz,42,42,0|gkths0,39,44,1|gu6gbz,39,44,1|gu6gc0,42,42,0|h1qr3z,42,42,0|h1qr40,39,44,1|hcwizz,39,44,1|hcwj00,42,42,0|hktsfz,42,42,0|hktsg0,39,44,1|hvmlnz,39,44,1|hvmlo0,42,42,0|i5pn3z,42,42,0|i5pn40,39,44,1|id9sbz,39,44,1|id9sc0,42,42,0|iofprz,42,42,0|iofps0,39,44,1|ivzuzz,39,44,1|ivzv00,42,42,0|j75sfz,42,42,0|j75sg0,39,44,1|jepxnz,39,44,1|jepxo0,42,42,0|jq8trz,42,42,0|jq8ts0,39,44,1|jxg0bz,39,44,1|jxg0c0,42,42,0|k8ywfz,42,42,0|k8ywg0,39,44,1|kg62zz,39,44,1|kg6300,42,42,0|kroz3z,42,42,0|kroz40,39,44,1|l0oyzz,39,44,1|l0oz00,42,42,0|l9p4fz,42,42,0|l9p4g0,39,44,1|ljf1nz,39,44,1|ljf1o0,42,42,0|lsf73z,42,42,0|lsf740,39,44,1|m254bz,39,44,1|m254c0,42,42,0|mbi8fz,42,42,0|mbi8g0,39,44,1|mk59nz,39,44,1|mk59o0,42,42,0|mu8b3z,42,42,0|mu8b40,39,44,1|n2vcbz,39,44,1|n2vcc0,42,42,0|ncydrz,42,42,0|ncyds0,39,44,1|nllezz,39,44,1|nllf00,42,42,0|nvogfz,42,42,0|nvogg0,39,44,1|o4ogbz,39,44,1|o4ogc0,42,42,0|oeej3z,42,42,0|oeej40,39,44,1|oneizz,39,44,1|onej00,42,42,0|ox4lrz,42,42,0|ox4ls0,39,44,1|p64lnz,39,44,1|p64lo0,42,42,0|pg7n3z,42,42,0|pg7n40,39,44,1|pouobz,39,44,1|pouoc0,42,42,0|pyxprz,42,42,0|pyxps0,39,44,1|q7kqzz,39,44,1|q7kr00,42,42,0|qhnsfz,42,42,0|qhnsg0,39,44,1|qqnsbz,39,44,1|qqnsc0,42,42,0|r0dv3z,42,42,0|r0dv40,39,44,1|r9duzz,39,44,1|r9dv00,42,42,0|rj3xrz,42,42,0|rj3xs0,39,44,1|rs3xnz,39,44,1|rs3xo0,42,42,0|s1u0fz,42,42,0|s1u0g0,39,44,1|sau0bz,39,44,1|sau0c0,42,42,0|skx1rz,42,42,0|skx1s0,39,44,1|stk2zz,39,44,1|stk300,42,42,0|t3n4fz,42,42,0|t3n4g0,39,44,1|tca5nz,39,44,1|tca5o0,42,42,0|tmd73z,42,42,0|tmd740,39,44,1|tvd6zz,39,44,1|tvd700,42,42,0|u539rz,42,42,0|u539s0,39,44,1|ue39nz,39,44,1|ue39o0,42,42,0|untcfz,42,42,0|untcg0,39,44,1|uwtcbz,39,44,1|uwtcc0,42,42,0|v6wdrz,42,42,0|v6wds0,39,44,1|vfjezz,39,44,1|vfjf00,42,42,0|vpmgfz,42,42,0|vpmgg0,39,44,1|vy9hnz,39,44,1|vy9ho0,42,42,0|w8cj3z,42,42,0|w8cj40,39,44,1|whcizz,39,44,1|whcj00,42,42,0|wr2lrz,42,42,0|wr2ls0,39,44,1|x02lnz,39,44,1|x02lo0,42,42,0|x9sofz,42,42,0|x9sog0,39,44,1|xisobz,39,44,1|xisoc0,42,42,0|xsir3z,42,42,0|xsir40,39,44,1|y1iqzz,39,44,1|y1ir00,42,42,0|yblsfz,42,42,0|yblsg0,39,44,1|yk8tnz,39,44,1|yk8to0,42,42,0|yubv3z,42,42,0|yubv40,39,44,1|z2ywbz,39,44,1|z2ywc0,42,42,0|zd1xrz,42,42,0|zd1xs0,39,44,1\",\"America/Atikokan|,0,61,0|-1353b18,45,62,0|-qzov41,45,62,0|-qzov40,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-f9ofc1,45,62,0|-f9ofc0,46,63,1|-ek21s1,46,63,1|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,49,63,0\",\"America/Bahia|,0,64,0|-t85kv8,39,44,0|-jylag1,39,44,0|-jylag0,40,45,1|-jpb141,40,45,1|-jpb140,39,44,0|-jfsd01,39,44,0|-jfsd00,40,45,1|-j6j3s1,40,45,1|-j6j3s0,39,44,0|-ahcyc1,39,44,0|-ahcyc0,40,45,1|-aad3o1,40,45,1|-aad3o0,39,44,0|-9yl101,39,44,0|-9yl100,40,45,1|-9sd141,40,45,1|-9sd140,39,44,0|-9ft3o1,39,44,0|-9ft3o0,40,45,1|-99j941,40,45,1|-99j940,39,44,0|-8wzbo1,39,44,0|-8wzbo0,40,45,1|-8scqg1,40,45,1|-8scqg0,39,44,0|-35xmc1,39,44,0|-35xmc0,40,45,1|-31nzs1,40,45,1|-31nzs0,39,44,0|-2kdp01,39,44,0|-2kdp00,40,45,1|-2hcig1,40,45,1|-2hcig0,39,44,0|-24qqc1,39,44,0|-24qqc0,40,45,1|-204541,40,45,1|-204540,39,44,0|-1nid01,39,44,0|-1nid00,40,45,1|-1hc7s1,40,45,1|-1hc7s0,39,44,0|-14qfo1,39,44,0|-14qfo0,40,45,1|-yifs1,40,45,1|-yifs0,39,44,0|89jcbz,39,44,0|89jcc0,40,45,1|8gdk7z,40,45,1|8gdk80,39,44,0|8rwgbz,39,44,0|8rwgc0,40,45,1|8xns7z,40,45,1|8xns80,39,44,0|9aodnz,39,44,0|9aodo0,40,45,1|9g2qvz,40,45,1|9g2qw0,39,44,0|9t1hnz,39,44,0|9t1ho0,40,45,1|9yfuvz,40,45,1|9yfuw0,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|ahvuvz,40,45,1|ahvuw0,39,44,0|auulnz,39,44,0|auulo0,40,45,1|b0yw7z,40,45,1|b0yw80,39,44,0|bdkobz,39,44,0|bdkoc0,40,45,1|bjc07z,40,45,1|bjc080,39,44,0|bwnpnz,39,44,0|bwnpo0,40,45,1|c1p47z,40,45,1|c1p480,39,44,0|cf0tnz,39,44,0|cf0to0,40,45,1|cli2vz,40,45,1|cli2w0,39,44,0|cxqwbz,39,44,0|cxqwc0,40,45,1|d485jz,40,45,1|d485k0,39,44,0|dggyzz,39,44,0|dggz00,40,45,1|dml9jz,40,45,1|dml9k0,39,44,0|dyu2zz,39,44,0|dyu300,40,45,1|e5oavz,40,45,1|e5oaw0,39,44,0|ehm0bz,39,44,0|ehm0c0,40,45,1|ep4avz,40,45,1|ep4aw0,39,44,0|f0n6zz,39,44,0|f0n700,40,45,1|f7hevz,40,45,1|f7hew0,39,44,0|fj0azz,39,44,0|fj0b00,40,45,1|fqkg7z,40,45,1|fqkg80,39,44,0|g23cbz,39,44,0|g23cc0,40,45,1|g8xk7z,40,45,1|g8xk80,39,44,0|gl6dnz,39,44,0|gl6do0,40,45,1|grnmvz,40,45,1|grnmw0,39,44,0|h4zcbz,39,44,0|h4zcc0,40,45,1|hadpjz,40,45,1|hadpk0,39,44,0|lt51nz,39,44,0|lt51o0,40,45,1|lzz9jz,40,45,1|lzz9k0,39,44,0\",\"America/Bahia_Banderas|,0,65,0|-p1u4k0,50,66,0|-m7mko1,50,66,0|-m7mko0,45,62,0|-kf67c1,45,62,0|-kf67c0,50,66,0|-k6j3c1,50,66,0|-k6j3c0,45,62,0|-jypm01,45,62,0|-jypm00,50,66,0|-jpan81,50,66,0|-jpan80,45,62,0|-eg9601,45,62,0|-eg9600,50,66,0|-axv381,50,66,0|-axv380,51,40,0|m7z,51,40,0|m80,50,66,0|dphibz,50,66,0|dphic0,52,62,1|dzxcvz,52,62,1|dzxcw0,50,66,0|e87kzz,50,66,0|e87l00,52,62,1|einfjz,52,62,1|einfk0,50,66,0|eqxnnz,50,66,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0|f9nqbz,50,66,0|f9nqc0,52,62,1|fkgjjz,52,62,1|fkgjk0,50,66,0|fsdszz,50,66,0|fsdt00,52,62,1|g36m7z,52,62,1|g36m80,50,66,0|gcwozz,50,66,0|gcwp00,52,62,1|gkgu7z,52,62,1|gkgu80,50,66,0|gu6wzz,50,66,0|gu6x00,52,62,1|h4mrjz,52,62,1|h4mrk0,50,66,0|hcwznz,50,66,0|hcwzo0,52,62,1|hncu7z,52,62,1|hncu80,50,66,0|hvn2bz,50,66,0|hvn2c0,52,62,1|i6fvjz,52,62,1|i6fvk0,50,66,0|ied4zz,50,66,0|ied500,52,62,1|ip5y7z,52,62,1|ip5y80,50,66,0|ix37nz,50,66,0|ix37o0,52,62,1|j7w0vz,52,62,1|j7w0w0,50,66,0|jftabz,50,66,0|jftac0,52,62,1|jqm3jz,52,62,1|jqm3k0,50,66,0|jywbnz,50,66,0|jywbo0,52,62,1|k9c67z,52,62,1|k9c680,50,66,0|khmebz,50,66,0|khmec0,52,62,1|ks28vz,52,62,1|ks28w0,50,66,0|l0cgzz,50,66,0|l0ch00,46,63,1|lb57fz,46,63,1|lb57g0,45,62,0|lj2gvz,45,62,0|lj2gw0,46,63,1|ltva3z,46,63,1|ltva40,45,62,0|m1sjjz,45,62,0|m1sjk0,46,63,1|mclcrz,46,63,1|mclcs0,45,62,0|mkvkvz,45,62,0|mkvkw0,46,63,1|mvbffz,46,63,1|mvbfg0,45,62,0|n3lnjz,45,62,0|n3lnk0,46,63,1|ne1i3z,46,63,1|ne1i40,45,62,0|nmbq7z,45,62,0|nmbq80,46,63,1|nwrkrz,46,63,1|nwrks0,45,62,0|o51svz,45,62,0|o51sw0,46,63,1|ofum3z,46,63,1|ofum40,45,62,0|onrvjz,45,62,0|onrvk0,46,63,1|oykorz,46,63,1|oykos0,45,62,0|p6hy7z,45,62,0|p6hy80,46,63,1|pharfz,46,63,1|pharg0,45,62,0|ppkzjz,45,62,0|ppkzk0,46,63,1|q00u3z,46,63,1|q00u40,45,62,0|q8b27z,45,62,0|q8b280,46,63,1|qiqwrz,46,63,1|qiqws0,45,62,0|qr14vz,45,62,0|qr14w0,46,63,1|r1ty3z,46,63,1|r1ty40,45,62,0|r9r7jz,45,62,0|r9r7k0,46,63,1|rkk0rz,46,63,1|rkk0s0,45,62,0|rsha7z,45,62,0|rsha80,46,63,1|s3a3fz,46,63,1|s3a3g0,45,62,0|sbkbjz,45,62,0|sbkbk0,46,63,1|sm063z,46,63,1|sm0640,45,62,0|suae7z,45,62,0|suae80,46,63,1|t4q8rz,46,63,1|t4q8s0,45,62,0|td0gvz,45,62,0|td0gw0,46,63,1|tngbfz,46,63,1|tngbg0,45,62,0|tvqjjz,45,62,0|tvqjk0,46,63,1|u6jcrz,46,63,1|u6jcs0,45,62,0|uegm7z,45,62,0|uegm80,46,63,1|up9ffz,46,63,1|up9fg0,45,62,0|ux6ovz,45,62,0|ux6ow0,46,63,1|v7zi3z,46,63,1|v7zi40,45,62,0|vg9q7z,45,62,0|vg9q80,46,63,1|vqpkrz,46,63,1|vqpks0,45,62,0|vyzsvz,45,62,0|vyzsw0,46,63,1|w9fnfz,46,63,1|w9fng0,45,62,0|whpvjz,45,62,0|whpvk0,46,63,1|wsiorz,46,63,1|wsios0,45,62,0|x0fy7z,45,62,0|x0fy80,46,63,1|xb8rfz,46,63,1|xb8rg0,45,62,0|xj60vz,45,62,0|xj60w0,46,63,1|xtyu3z,46,63,1|xtyu40,45,62,0|y1w3jz,45,62,0|y1w3k0,46,63,1|ycowrz,46,63,1|ycows0,45,62,0|ykz4vz,45,62,0|ykz4w0,46,63,1|yvezfz,46,63,1|yvezg0,45,62,0|z3p7jz,45,62,0|z3p7k0,46,63,1|ze523z,46,63,1|ze5240,45,62,0\",\"America/Barbados|,0,67,0|-o0aiaj,53,67,0|-jtzeak,53,67,0|-jtzeaj,32,42,0|3vvnbz,32,42,0|3vvnc0,54,44,1|41mz7z,54,44,1|41mz80,32,42,0|4bq0nz,32,42,0|4bq0o0,54,44,1|4kd1vz,54,44,1|4kd1w0,32,42,0|4ug3bz,32,42,0|4ug3c0,54,44,1|5334jz,54,44,1|5334k0,32,42,0|5dj4nz,32,42,0|5dj4o0,54,44,1|5lnn7z,54,44,1|5lnn80,32,42,0\",\"America/Belem|,0,68,0|-t85j0s,39,44,0|-jylag1,39,44,0|-jylag0,40,45,1|-jpb141,40,45,1|-jpb140,39,44,0|-jfsd01,39,44,0|-jfsd00,40,45,1|-j6j3s1,40,45,1|-j6j3s0,39,44,0|-ahcyc1,39,44,0|-ahcyc0,40,45,1|-aad3o1,40,45,1|-aad3o0,39,44,0|-9yl101,39,44,0|-9yl100,40,45,1|-9sd141,40,45,1|-9sd140,39,44,0|-9ft3o1,39,44,0|-9ft3o0,40,45,1|-99j941,40,45,1|-99j940,39,44,0|-8wzbo1,39,44,0|-8wzbo0,40,45,1|-8scqg1,40,45,1|-8scqg0,39,44,0|-35xmc1,39,44,0|-35xmc0,40,45,1|-31nzs1,40,45,1|-31nzs0,39,44,0|-2kdp01,39,44,0|-2kdp00,40,45,1|-2hcig1,40,45,1|-2hcig0,39,44,0|-24qqc1,39,44,0|-24qqc0,40,45,1|-204541,40,45,1|-204540,39,44,0|-1nid01,39,44,0|-1nid00,40,45,1|-1hc7s1,40,45,1|-1hc7s0,39,44,0|-14qfo1,39,44,0|-14qfo0,40,45,1|-yifs1,40,45,1|-yifs0,39,44,0|89jcbz,39,44,0|89jcc0,40,45,1|8gdk7z,40,45,1|8gdk80,39,44,0|8rwgbz,39,44,0|8rwgc0,40,45,1|8xns7z,40,45,1|8xns80,39,44,0|9aodnz,39,44,0|9aodo0,40,45,1|9g2qvz,40,45,1|9g2qw0,39,44,0\",\"America/Belize|,0,69,0|-u52ic0,45,62,0|-qqoy01,45,62,0|-qqoy00,55,70,1|-qk7ne1,55,70,1|-qk7ne0,45,62,0|-q7yvc1,45,62,0|-q7yvc0,55,70,1|-q14m21,55,70,1|-q14m20,45,62,0|-pp8so1,45,62,0|-pp8so0,55,70,1|-pieje1,55,70,1|-pieje0,45,62,0|-p6iq01,45,62,0|-p6iq00,55,70,1|-ozogq1,55,70,1|-ozogq0,45,62,0|-onfoo1,45,62,0|-onfoo0,55,70,1|-ogye21,55,70,1|-ogye20,45,62,0|-o4pm01,45,62,0|-o4pm00,55,70,1|-ny8be1,55,70,1|-ny8be0,45,62,0|-nlzjc1,45,62,0|-nlzjc0,55,70,1|-nf5a21,55,70,1|-nf5a20,45,62,0|-n39go1,45,62,0|-n39go0,55,70,1|-mwf7e1,55,70,1|-mwf7e0,45,62,0|-mkje01,45,62,0|-mkje00,55,70,1|-mdp4q1,55,70,1|-mdp4q0,45,62,0|-m1tbc1,45,62,0|-m1tbc0,55,70,1|-luz221,55,70,1|-luz220,45,62,0|-liqa01,45,62,0|-liqa00,55,70,1|-lc8ze1,55,70,1|-lc8ze0,45,62,0|-l007c1,45,62,0|-l007c0,55,70,1|-ktiwq1,55,70,1|-ktiwq0,45,62,0|-kha4o1,45,62,0|-kha4o0,55,70,1|-kafve1,55,70,1|-kafve0,45,62,0|-jyk201,45,62,0|-jyk200,55,70,1|-jrpsq1,55,70,1|-jrpsq0,45,62,0|-jftzc1,45,62,0|-jftzc0,55,70,1|-j8zq21,55,70,1|-j8zq20,45,62,0|-iwqy01,45,62,0|-iwqy00,55,70,1|-iq9ne1,55,70,1|-iq9ne0,45,62,0|-ie0vc1,45,62,0|-ie0vc0,55,70,1|-i7jkq1,55,70,1|-i7jkq0,45,62,0|-hvaso1,45,62,0|-hvaso0,55,70,1|-hoti21,55,70,1|-hoti20,45,62,0|-hckq01,45,62,0|-hckq00,55,70,1|-h5qgq1,55,70,1|-h5qgq0,45,62,0|-gtunc1,45,62,0|-gtunc0,55,70,1|-gn0e21,55,70,1|-gn0e20,45,62,0|-gb4ko1,45,62,0|-gb4ko0,55,70,1|-g4abe1,55,70,1|-g4abe0,45,62,0|-fs1jc1,45,62,0|-fs1jc0,55,70,1|-flk8q1,55,70,1|-flk8q0,45,62,0|-f9bgo1,45,62,0|-f9bgo0,55,70,1|-f2u621,55,70,1|-f2u620,45,62,0|-eqle01,45,62,0|-eqle00,55,70,1|-ejr4q1,55,70,1|-ejr4q0,45,62,0|-ecwso1,45,62,0|-ecwso0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cjqks1,48,63,1|-cjqks0,45,62,0|-blvzc1,45,62,0|-blvzc0,55,70,1|-bf1q21,55,70,1|-bf1q20,45,62,0|-b35wo1,45,62,0|-b35wo0,55,70,1|-awbne1,55,70,1|-awbne0,45,62,0|-akfu01,45,62,0|-akfu00,55,70,1|-adlkq1,55,70,1|-adlkq0,45,62,0|-a1cso1,45,62,0|-a1cso0,55,70,1|-9uvi21,55,70,1|-9uvi20,45,62,0|-9imq01,45,62,0|-9imq00,55,70,1|-9c5fe1,55,70,1|-9c5fe0,45,62,0|-8zwnc1,45,62,0|-8zwnc0,55,70,1|-8t2e21,55,70,1|-8t2e20,45,62,0|-8h6ko1,45,62,0|-8h6ko0,55,70,1|-8acbe1,55,70,1|-8acbe0,45,62,0|-7ygi01,45,62,0|-7ygi00,55,70,1|-7rm8q1,55,70,1|-7rm8q0,45,62,0|-7fqfc1,45,62,0|-7fqfc0,55,70,1|-78w621,55,70,1|-78w620,45,62,0|-6wne01,45,62,0|-6wne00,55,70,1|-6q63e1,55,70,1|-6q63e0,45,62,0|-6dxbc1,45,62,0|-6dxbc0,55,70,1|-67g0q1,55,70,1|-67g0q0,45,62,0|-5v78o1,45,62,0|-5v78o0,55,70,1|-5ocze1,55,70,1|-5ocze0,45,62,0|-5ch601,45,62,0|-5ch600,55,70,1|-55mwq1,55,70,1|-55mwq0,45,62,0|-4tr3c1,45,62,0|-4tr3c0,55,70,1|-4mwu21,55,70,1|-4mwu20,45,62,0|-4ao201,45,62,0|-4ao200,55,70,1|-446re1,55,70,1|-446re0,45,62,0|-3rxzc1,45,62,0|-3rxzc0,55,70,1|-3lgoq1,55,70,1|-3lgoq0,45,62,0|-397wo1,45,62,0|-397wo0,55,70,1|-32qm21,55,70,1|-32qm20,45,62,0|-2qhu01,45,62,0|-2qhu00,55,70,1|-2jnkq1,55,70,1|-2jnkq0,45,62,0|-27rrc1,45,62,0|-27rrc0,55,70,1|-20xi21,55,70,1|-20xi20,45,62,0|-1p1oo1,45,62,0|-1p1oo0,55,70,1|-1i7fe1,55,70,1|-1i7fe0,45,62,0|-15ync1,45,62,0|-15ync0,55,70,1|-zhcq1,55,70,1|-zhcq0,45,62,0|21s0nz,45,62,0|21s0o0,46,63,1|2565vz,46,63,1|2565w0,45,62,0|6rj4nz,45,62,0|6rj4o0,46,63,1|6uer7z,46,63,1|6uer80,45,62,0\",\"America/Blanc-Sablon|,0,71,0|-18vs838,32,42,0|-qzp0o1,32,42,0|-qzp0o0,54,44,1|-qpm4s1,54,44,1|-qpm4s0,32,42,0|-ek27c1,32,42,0|-ek27c0,33,44,1|-cq2tg1,33,44,1|-cq2tg0,34,44,1|-cnp641,34,44,1|-cnp640,32,42,0\",\"America/Boa_Vista|,0,72,0|-t85grk,42,42,0|-jyl7o1,42,42,0|-jyl7o0,39,44,1|-jpayc1,39,44,1|-jpayc0,42,42,0|-jfsa81,42,42,0|-jfsa80,39,44,1|-j6j101,39,44,1|-j6j100,42,42,0|-ahcvk1,42,42,0|-ahcvk0,39,44,1|-aad0w1,39,44,1|-aad0w0,42,42,0|-9yky81,42,42,0|-9yky80,39,44,1|-9scyc1,39,44,1|-9scyc0,42,42,0|-9ft0w1,42,42,0|-9ft0w0,39,44,1|-99j6c1,39,44,1|-99j6c0,42,42,0|-8wz8w1,42,42,0|-8wz8w0,39,44,1|-8scno1,39,44,1|-8scno0,42,42,0|-35xjk1,42,42,0|-35xjk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2kdm81,42,42,0|-2kdm80,39,44,1|-2hcfo1,39,44,1|-2hcfo0,42,42,0|-24qnk1,42,42,0|-24qnk0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1nia81,42,42,0|-1nia80,39,44,1|-1hc501,39,44,1|-1hc500,42,42,0|-14qcw1,42,42,0|-14qcw0,39,44,1|-yid01,39,44,1|-yid00,42,42,0|89jf3z,42,42,0|89jf40,39,44,1|8gdmzz,39,44,1|8gdn00,42,42,0|8rwj3z,42,42,0|8rwj40,39,44,1|8xnuzz,39,44,1|8xnv00,42,42,0|9aogfz,42,42,0|9aogg0,39,44,1|9g2tnz,39,44,1|9g2to0,42,42,0|fj0drz,42,42,0|fj0ds0,39,44,1|fqkizz,39,44,1|fqkj00,42,42,0|g23f3z,42,42,0|g23f40,39,44,1|g2gazz,39,44,1|g2gb00,42,42,0\",\"America/Bogota|,0,73,0|-18s2sy8,53,73,0|-srdoy9,53,73,0|-srdoy8,56,63,0|bnnsjz,56,63,0|bnnsk0,42,42,1|c4xxrz,42,42,1|c4xxs0,56,63,0\",\"America/Boise|,0,74,0|-18y0gg0,51,40,0|-r0emw1,51,40,0|-r0emw0,57,66,1|-qplto1,57,66,1|-qplto0,51,40,0|-qhok81,51,40,0|-qhok80,57,66,1|-q6vr01,57,66,1|-q6vr00,51,40,0|-oc9iw1,51,40,0|-oc9iw0,50,66,0|-ek1z01,50,66,0|-ek1z00,58,62,1|-cq2tg1,58,62,1|-cq2tg0,59,62,1|-cnoxs1,59,62,1|-cnoxs0,50,66,0|-1e8kc1,50,66,0|-1e8kc0,52,62,1|-14vls1,52,62,1|-14vls0,50,66,0|-viho1,50,66,0|-viho0,52,62,1|-m5j41,52,62,1|-m5j40,50,66,0|-csf01,50,66,0|-csf00,52,62,1|-3fgg1,52,62,1|-3fgg0,50,66,0|5xnnz,50,66,0|5xno0,52,62,1|fam7z,52,62,1|fam80,50,66,0|onqbz,50,66,0|onqc0,52,62,1|ydnjz,52,62,1|ydnk0,50,66,0|17qrnz,50,66,0|17qro0,52,62,1|1h3q7z,52,62,1|1h3q80,50,66,0|1qgubz,50,66,0|1qguc0,52,62,1|1ztsvz,52,62,1|1ztsw0,50,66,0|24vczz,50,66,0|24vd00,52,62,1|2ijvjz,52,62,1|2ijvk0,50,66,0|2oobnz,50,66,0|2oobo0,52,62,1|319y7z,52,62,1|319y80,50,66,0|3an2bz,50,66,0|3an2c0,52,62,1|3kczjz,52,62,1|3kczk0,50,66,0|3td4zz,50,66,0|3td500,52,62,1|43327z,52,62,1|433280,50,66,0|4cg6bz,50,66,0|4cg6c0,52,62,1|4lt4vz,52,62,1|4lt4w0,50,66,0|4v68zz,50,66,0|4v6900,52,62,1|54j7jz,52,62,1|54j7k0,50,66,0|5dwbnz,50,66,0|5dwbo0,52,62,1|5n9a7z,52,62,1|5n9a80,50,66,0|5wmebz,50,66,0|5wmec0,52,62,1|65zcvz,52,62,1|65zcw0,50,66,0|6fcgzz,50,66,0|6fch00,52,62,1|6p2e7z,52,62,1|6p2e80,50,66,0|6y2jnz,50,66,0|6y2jo0,52,62,1|77sgvz,52,62,1|77sgw0,50,66,0|7h5kzz,50,66,0|7h5l00,52,62,1|7qijjz,52,62,1|7qijk0,50,66,0|7zvnnz,50,66,0|7zvno0,52,62,1|898m7z,52,62,1|898m80,50,66,0|8ilqbz,50,66,0|8ilqc0,52,62,1|8ryovz,52,62,1|8ryow0,50,66,0|908wzz,50,66,0|908x00,52,62,1|9aorjz,52,62,1|9aork0,50,66,0|9iyznz,50,66,0|9iyzo0,52,62,1|9trsvz,52,62,1|9trsw0,50,66,0|a1p2bz,50,66,0|a1p2c0,52,62,1|achvjz,52,62,1|achvk0,50,66,0|akf4zz,50,66,0|akf500,52,62,1|av7y7z,52,62,1|av7y80,50,66,0|b3i6bz,50,66,0|b3i6c0,52,62,1|bdy0vz,52,62,1|bdy0w0,50,66,0|bm88zz,50,66,0|bm8900,52,62,1|bwo3jz,52,62,1|bwo3k0,50,66,0|c4ybnz,50,66,0|c4ybo0,52,62,1|cfr4vz,52,62,1|cfr4w0,50,66,0|cnoebz,50,66,0|cnoec0,52,62,1|cyh7jz,52,62,1|cyh7k0,50,66,0|d6egzz,50,66,0|d6eh00,52,62,1|dh7a7z,52,62,1|dh7a80,50,66,0|dphibz,50,66,0|dphic0,52,62,1|dzxcvz,52,62,1|dzxcw0,50,66,0|e87kzz,50,66,0|e87l00,52,62,1|einfjz,52,62,1|einfk0,50,66,0|eqxnnz,50,66,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0|f9nqbz,50,66,0|f9nqc0,52,62,1|fkgjjz,52,62,1|fkgjk0,50,66,0|fsdszz,50,66,0|fsdt00,52,62,1|g36m7z,52,62,1|g36m80,50,66,0|gb3vnz,50,66,0|gb3vo0,52,62,1|glwovz,52,62,1|glwow0,50,66,0|gu6wzz,50,66,0|gu6x00,52,62,1|h4mrjz,52,62,1|h4mrk0,50,66,0|hcwznz,50,66,0|hcwzo0,52,62,1|hncu7z,52,62,1|hncu80,50,66,0|hvn2bz,50,66,0|hvn2c0,52,62,1|i6fvjz,52,62,1|i6fvk0,50,66,0|ied4zz,50,66,0|ied500,52,62,1|ip5y7z,52,62,1|ip5y80,50,66,0|ix37nz,50,66,0|ix37o0,52,62,1|j7w0vz,52,62,1|j7w0w0,50,66,0|jeqebz,50,66,0|jeqec0,52,62,1|jqz27z,52,62,1|jqz280,50,66,0|jxggzz,50,66,0|jxgh00,52,62,1|k9p4vz,52,62,1|k9p4w0,50,66,0|kg6jnz,50,66,0|kg6jo0,52,62,1|ksf7jz,52,62,1|ksf7k0,50,66,0|kz9kzz,50,66,0|kz9l00,52,62,1|lbi8vz,52,62,1|lbi8w0,50,66,0|lhznnz,50,66,0|lhzno0,52,62,1|lu8bjz,52,62,1|lu8bk0,50,66,0|m0pqbz,50,66,0|m0pqc0,52,62,1|mcye7z,52,62,1|mcye80,50,66,0|mjfszz,50,66,0|mjft00,52,62,1|mvogvz,52,62,1|mvogw0,50,66,0|n25vnz,50,66,0|n25vo0,52,62,1|neejjz,52,62,1|neejk0,50,66,0|nkvybz,50,66,0|nkvyc0,52,62,1|nx4m7z,52,62,1|nx4m80,50,66,0|o3yznz,50,66,0|o3yzo0,52,62,1|og7njz,52,62,1|og7nk0,50,66,0|omp2bz,50,66,0|omp2c0,52,62,1|oyxq7z,52,62,1|oyxq80,50,66,0|p5f4zz,50,66,0|p5f500,52,62,1|phnsvz,52,62,1|phnsw0,50,66,0|po57nz,50,66,0|po57o0,52,62,1|q0dvjz,52,62,1|q0dvk0,50,66,0|q6vabz,50,66,0|q6vac0,52,62,1|qj3y7z,52,62,1|qj3y80,50,66,0|qpybnz,50,66,0|qpybo0,52,62,1|r26zjz,52,62,1|r26zk0,50,66,0|r8oebz,50,66,0|r8oec0,52,62,1|rkx27z,52,62,1|rkx280,50,66,0|rregzz,50,66,0|rreh00,52,62,1|s3n4vz,52,62,1|s3n4w0,50,66,0|sa4jnz,50,66,0|sa4jo0,52,62,1|smd7jz,52,62,1|smd7k0,50,66,0|ssumbz,50,66,0|ssumc0,52,62,1|t53a7z,52,62,1|t53a80,50,66,0|tbkozz,50,66,0|tbkp00,52,62,1|tntcvz,52,62,1|tntcw0,50,66,0|tunqbz,50,66,0|tunqc0,52,62,1|u6we7z,52,62,1|u6we80,50,66,0|uddszz,50,66,0|uddt00,52,62,1|upmgvz,52,62,1|upmgw0,50,66,0|uw3vnz,50,66,0|uw3vo0,52,62,1|v8cjjz,52,62,1|v8cjk0,50,66,0|vetybz,50,66,0|vetyc0,52,62,1|vr2m7z,52,62,1|vr2m80,50,66,0|vxk0zz,50,66,0|vxk100,52,62,1|w9sovz,52,62,1|w9sow0,50,66,0|wgn2bz,50,66,0|wgn2c0,52,62,1|wsvq7z,52,62,1|wsvq80,50,66,0|wzd4zz,50,66,0|wzd500,52,62,1|xblsvz,52,62,1|xblsw0,50,66,0|xi37nz,50,66,0|xi37o0,52,62,1|xubvjz,52,62,1|xubvk0,50,66,0|y0tabz,50,66,0|y0tac0,52,62,1|yd1y7z,52,62,1|yd1y80,50,66,0|yjjczz,50,66,0|yjjd00,52,62,1|yvs0vz,52,62,1|yvs0w0,50,66,0|z29fnz,50,66,0|z29fo0,52,62,1|zei3jz,52,62,1|zei3k0,50,66,0\",\"America/Cambridge_Bay|,60,1,0|-q3gdc0,50,66,0|-ek1z01,50,66,0|-ek1z00,58,62,1|-cq2tg1,58,62,1|-cq2tg0,59,62,1|-cnoxs1,59,62,1|-cnoxs0,50,66,0|-2g1tw1,50,66,0|-2g1tw0,61,63,1|-26btw1,61,63,1|-26btw0,50,66,0|5dwbnz,50,66,0|5dwbo0,52,62,1|5n9a7z,52,62,1|5n9a80,50,66,0|5wmebz,50,66,0|5wmec0,52,62,1|65zcvz,52,62,1|65zcw0,50,66,0|6fcgzz,50,66,0|6fch00,52,62,1|6p2e7z,52,62,1|6p2e80,50,66,0|6y2jnz,50,66,0|6y2jo0,52,62,1|77sgvz,52,62,1|77sgw0,50,66,0|7h5kzz,50,66,0|7h5l00,52,62,1|7qijjz,52,62,1|7qijk0,50,66,0|7zvnnz,50,66,0|7zvno0,52,62,1|898m7z,52,62,1|898m80,50,66,0|8ilqbz,50,66,0|8ilqc0,52,62,1|8ryovz,52,62,1|8ryow0,50,66,0|908wzz,50,66,0|908x00,52,62,1|9aorjz,52,62,1|9aork0,50,66,0|9iyznz,50,66,0|9iyzo0,52,62,1|9trsvz,52,62,1|9trsw0,50,66,0|a1p2bz,50,66,0|a1p2c0,52,62,1|achvjz,52,62,1|achvk0,50,66,0|akf4zz,50,66,0|akf500,52,62,1|av7y7z,52,62,1|av7y80,50,66,0|b3i6bz,50,66,0|b3i6c0,52,62,1|bdy0vz,52,62,1|bdy0w0,50,66,0|bm88zz,50,66,0|bm8900,52,62,1|bwo3jz,52,62,1|bwo3k0,50,66,0|c4ybnz,50,66,0|c4ybo0,52,62,1|cfr4vz,52,62,1|cfr4w0,50,66,0|cnoebz,50,66,0|cnoec0,52,62,1|cyh7jz,52,62,1|cyh7k0,50,66,0|d6egzz,50,66,0|d6eh00,52,62,1|dh7a7z,52,62,1|dh7a80,50,66,0|dphibz,50,66,0|dphic0,52,62,1|dzxcvz,52,62,1|dzxcw0,50,66,0|e87kzz,50,66,0|e87l00,52,62,1|einfjz,52,62,1|einfk0,50,66,0|eqxnnz,50,66,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0|f9nqbz,50,66,0|f9nqc0,52,62,1|fkgjjz,52,62,1|fkgjk0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,49,63,0|g3jcjz,49,63,0|g3jck0,45,62,0|gb3vnz,45,62,0|gb3vo0,52,62,1|glwovz,52,62,1|glwow0,50,66,0|gu6wzz,50,66,0|gu6x00,52,62,1|h4mrjz,52,62,1|h4mrk0,50,66,0|hcwznz,50,66,0|hcwzo0,52,62,1|hncu7z,52,62,1|hncu80,50,66,0|hvn2bz,50,66,0|hvn2c0,52,62,1|i6fvjz,52,62,1|i6fvk0,50,66,0|ied4zz,50,66,0|ied500,52,62,1|ip5y7z,52,62,1|ip5y80,50,66,0|ix37nz,50,66,0|ix37o0,52,62,1|j7w0vz,52,62,1|j7w0w0,50,66,0|jeqebz,50,66,0|jeqec0,52,62,1|jqz27z,52,62,1|jqz280,50,66,0|jxggzz,50,66,0|jxgh00,52,62,1|k9p4vz,52,62,1|k9p4w0,50,66,0|kg6jnz,50,66,0|kg6jo0,52,62,1|ksf7jz,52,62,1|ksf7k0,50,66,0|kz9kzz,50,66,0|kz9l00,52,62,1|lbi8vz,52,62,1|lbi8w0,50,66,0|lhznnz,50,66,0|lhzno0,52,62,1|lu8bjz,52,62,1|lu8bk0,50,66,0|m0pqbz,50,66,0|m0pqc0,52,62,1|mcye7z,52,62,1|mcye80,50,66,0|mjfszz,50,66,0|mjft00,52,62,1|mvogvz,52,62,1|mvogw0,50,66,0|n25vnz,50,66,0|n25vo0,52,62,1|neejjz,52,62,1|neejk0,50,66,0|nkvybz,50,66,0|nkvyc0,52,62,1|nx4m7z,52,62,1|nx4m80,50,66,0|o3yznz,50,66,0|o3yzo0,52,62,1|og7njz,52,62,1|og7nk0,50,66,0|omp2bz,50,66,0|omp2c0,52,62,1|oyxq7z,52,62,1|oyxq80,50,66,0|p5f4zz,50,66,0|p5f500,52,62,1|phnsvz,52,62,1|phnsw0,50,66,0|po57nz,50,66,0|po57o0,52,62,1|q0dvjz,52,62,1|q0dvk0,50,66,0|q6vabz,50,66,0|q6vac0,52,62,1|qj3y7z,52,62,1|qj3y80,50,66,0|qpybnz,50,66,0|qpybo0,52,62,1|r26zjz,52,62,1|r26zk0,50,66,0|r8oebz,50,66,0|r8oec0,52,62,1|rkx27z,52,62,1|rkx280,50,66,0|rregzz,50,66,0|rreh00,52,62,1|s3n4vz,52,62,1|s3n4w0,50,66,0|sa4jnz,50,66,0|sa4jo0,52,62,1|smd7jz,52,62,1|smd7k0,50,66,0|ssumbz,50,66,0|ssumc0,52,62,1|t53a7z,52,62,1|t53a80,50,66,0|tbkozz,50,66,0|tbkp00,52,62,1|tntcvz,52,62,1|tntcw0,50,66,0|tunqbz,50,66,0|tunqc0,52,62,1|u6we7z,52,62,1|u6we80,50,66,0|uddszz,50,66,0|uddt00,52,62,1|upmgvz,52,62,1|upmgw0,50,66,0|uw3vnz,50,66,0|uw3vo0,52,62,1|v8cjjz,52,62,1|v8cjk0,50,66,0|vetybz,50,66,0|vetyc0,52,62,1|vr2m7z,52,62,1|vr2m80,50,66,0|vxk0zz,50,66,0|vxk100,52,62,1|w9sovz,52,62,1|w9sow0,50,66,0|wgn2bz,50,66,0|wgn2c0,52,62,1|wsvq7z,52,62,1|wsvq80,50,66,0|wzd4zz,50,66,0|wzd500,52,62,1|xblsvz,52,62,1|xblsw0,50,66,0|xi37nz,50,66,0|xi37o0,52,62,1|xubvjz,52,62,1|xubvk0,50,66,0|y0tabz,50,66,0|y0tac0,52,62,1|yd1y7z,52,62,1|yd1y80,50,66,0|yjjczz,50,66,0|yjjd00,52,62,1|yvs0vz,52,62,1|yvs0w0,50,66,0|z29fnz,50,66,0|z29fo0,52,62,1|zei3jz,52,62,1|zei3k0,50,66,0\",\"America/Campo_Grande|,0,75,0|-t85hvw,42,42,0|-jyl7o1,42,42,0|-jyl7o0,39,44,1|-jpayc1,39,44,1|-jpayc0,42,42,0|-jfsa81,42,42,0|-jfsa80,39,44,1|-j6j101,39,44,1|-j6j100,42,42,0|-ahcvk1,42,42,0|-ahcvk0,39,44,1|-aad0w1,39,44,1|-aad0w0,42,42,0|-9yky81,42,42,0|-9yky80,39,44,1|-9scyc1,39,44,1|-9scyc0,42,42,0|-9ft0w1,42,42,0|-9ft0w0,39,44,1|-99j6c1,39,44,1|-99j6c0,42,42,0|-8wz8w1,42,42,0|-8wz8w0,39,44,1|-8scno1,39,44,1|-8scno0,42,42,0|-35xjk1,42,42,0|-35xjk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2kdm81,42,42,0|-2kdm80,39,44,1|-2hcfo1,39,44,1|-2hcfo0,42,42,0|-24qnk1,42,42,0|-24qnk0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1nia81,42,42,0|-1nia80,39,44,1|-1hc501,39,44,1|-1hc500,42,42,0|-14qcw1,42,42,0|-14qcw0,39,44,1|-yid01,39,44,1|-yid00,42,42,0|89jf3z,42,42,0|89jf40,39,44,1|8gdmzz,39,44,1|8gdn00,42,42,0|8rwj3z,42,42,0|8rwj40,39,44,1|8xnuzz,39,44,1|8xnv00,42,42,0|9aogfz,42,42,0|9aogg0,39,44,1|9g2tnz,39,44,1|9g2to0,42,42,0|9t1kfz,42,42,0|9t1kg0,39,44,1|9yfxnz,39,44,1|9yfxo0,42,42,0|abrn3z,42,42,0|abrn40,39,44,1|ahvxnz,39,44,1|ahvxo0,42,42,0|auuofz,42,42,0|auuog0,39,44,1|b0yyzz,39,44,1|b0yz00,42,42,0|bdkr3z,42,42,0|bdkr40,39,44,1|bjc2zz,39,44,1|bjc300,42,42,0|bwnsfz,42,42,0|bwnsg0,39,44,1|c1p6zz,39,44,1|c1p700,42,42,0|cf0wfz,42,42,0|cf0wg0,39,44,1|cli5nz,39,44,1|cli5o0,42,42,0|cxqz3z,42,42,0|cxqz40,39,44,1|d488bz,39,44,1|d488c0,42,42,0|dgh1rz,42,42,0|dgh1s0,39,44,1|dmlcbz,39,44,1|dmlcc0,42,42,0|dyu5rz,42,42,0|dyu5s0,39,44,1|e5odnz,39,44,1|e5odo0,42,42,0|ehm33z,42,42,0|ehm340,39,44,1|ep4dnz,39,44,1|ep4do0,42,42,0|f0n9rz,42,42,0|f0n9s0,39,44,1|f7hhnz,39,44,1|f7hho0,42,42,0|fj0drz,42,42,0|fj0ds0,39,44,1|fqkizz,39,44,1|fqkj00,42,42,0|g23f3z,42,42,0|g23f40,39,44,1|g8xmzz,39,44,1|g8xn00,42,42,0|gl6gfz,42,42,0|gl6gg0,39,44,1|grnpnz,39,44,1|grnpo0,42,42,0|h4zf3z,42,42,0|h4zf40,39,44,1|hadsbz,39,44,1|hadsc0,42,42,0|hmzkfz,42,42,0|hmzkg0,39,44,1|ht3uzz,39,44,1|ht3v00,42,42,0|i6j9rz,42,42,0|i6j9s0,39,44,1|ic6wbz,39,44,1|ic6wc0,42,42,0|iofprz,42,42,0|iofps0,39,44,1|iuwyzz,39,44,1|iuwz00,42,42,0|j88ofz,42,42,0|j88og0,39,44,1|je00bz,39,44,1|je00c0,42,42,0|jpvv3z,42,42,0|jpvv40,39,44,1|jwd4bz,39,44,1|jwd4c0,42,42,0|k8ywfz,42,42,0|k8ywg0,39,44,1|kf36zz,39,44,1|kf3700,42,42,0|kroz3z,42,42,0|kroz40,39,44,1|ky68bz,39,44,1|ky68c0,42,42,0|laf1rz,42,42,0|laf1s0,39,44,1|lgwazz,39,44,1|lgwb00,42,42,0|lt54fz,42,42,0|lt54g0,39,44,1|lzzcbz,39,44,1|lzzcc0,42,42,0|mc85rz,42,42,0|mc85s0,39,44,1|micgbz,39,44,1|micgc0,42,42,0|muy8fz,42,42,0|muy8g0,39,44,1|n12izz,39,44,1|n12j00,42,42,0|ndob3z,42,42,0|ndob40,39,44,1|nk5kbz,39,44,1|nk5kc0,42,42,0|nwedrz,42,42,0|nweds0,39,44,1|o2vmzz,39,44,1|o2vn00,42,42,0|of4gfz,42,42,0|of4gg0,39,44,1|ollpnz,39,44,1|ollpo0,42,42,0|oxuj3z,42,42,0|oxuj40,39,44,1|p4bsbz,39,44,1|p4bsc0,42,42,0|phnhrz,42,42,0|phnhs0,39,44,1|pn1uzz,39,44,1|pn1v00,42,42,0\",\"America/Cancun|,0,76,0|-p1u7c0,45,62,0|690gnz,45,62,0|690go0,49,63,0|dphcrz,49,63,0|dphcs0,62,42,1|dzx7bz,62,42,1|dzx7c0,49,63,0|e87ffz,49,63,0|e87fg0,62,42,1|ein9zz,62,42,1|eina00,49,63,0|eqxi3z,49,63,0|eqxi40,62,42,1|ex1snz,62,42,1|ex1so0,46,63,1|f1dffz,46,63,1|f1dfg0,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkggrz,46,63,1|fkggs0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,45,62,0|gcwm7z,45,62,0|gcwm80,46,63,1|gkgrfz,46,63,1|gkgrg0,45,62,0|gu6u7z,45,62,0|gu6u80,46,63,1|h4morz,46,63,1|h4mos0,45,62,0|hcwwvz,45,62,0|hcwww0,46,63,1|hncrfz,46,63,1|hncrg0,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fsrz,46,63,1|i6fss0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5vfz,46,63,1|ip5vg0,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jft7jz,45,62,0|jft7k0,46,63,1|jqm0rz,46,63,1|jqm0s0,45,62,0|jyw8vz,45,62,0|jyw8w0,46,63,1|k9c3fz,46,63,1|k9c3g0,45,62,0|khmbjz,45,62,0|khmbk0,46,63,1|ks263z,46,63,1|ks2640,45,62,0|l0ce7z,45,62,0|l0ce80,46,63,1|lb57fz,46,63,1|lb57g0,45,62,0|lj2gvz,45,62,0|lj2gw0,46,63,1|ltva3z,46,63,1|ltva40,45,62,0|m1sjjz,45,62,0|m1sjk0,46,63,1|mclcrz,46,63,1|mclcs0,45,62,0|mkvkvz,45,62,0|mkvkw0,46,63,1|mvbffz,46,63,1|mvbfg0,45,62,0|n3lnjz,45,62,0|n3lnk0,46,63,1|ne1i3z,46,63,1|ne1i40,45,62,0|nj327z,45,62,0|nj3280,49,63,0\",\"America/Caracas|,0,77,0|-15r0wxs,41,78,0|-u7lcxx,41,78,0|-u7lcxw,43,59,0|-2lx4u1,43,59,0|-2lx4u0,42,42,0|jsrsrz,42,42,0|jsrss0,43,59,0|o6hkrz,43,59,0|o6hks0,42,42,0\",\"America/Cayenne|,0,79,0|-uj7yb4,42,42,0|-16brk1,42,42,0|-16brk0,39,44,0\",\"America/Cayman|,0,80,0|-15r0uls,41,81,0|-w757vd,41,81,0|-w757vc,49,63,0\",\"America/Chicago|,0,82,0|-18y0m00,45,62,0|-r0esg1,45,62,0|-r0esg0,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-qhops1,45,62,0|-qhops0,46,63,1|-q6vwk1,46,63,1|-q6vwk0,45,62,0|-pv01s1,45,62,0|-pv01s0,46,63,1|-pnsv81,46,63,1|-pnsv80,45,62,0|-pg8kg1,45,62,0|-pg8kg0,46,63,1|-p52sk1,46,63,1|-p52sk0,45,62,0|-ovpog1,45,62,0|-ovpog0,46,63,1|-oo5j81,46,63,1|-oo5j80,45,62,0|-oczls1,45,62,0|-oczls0,46,63,1|-o52hw1,46,63,1|-o52hw0,45,62,0|-nu9j41,45,62,0|-nu9j40,46,63,1|-nmcf81,46,63,1|-nmcf80,45,62,0|-nbjgg1,45,62,0|-nbjgg0,46,63,1|-n3mck1,46,63,1|-n3mck0,45,62,0|-mstds1,45,62,0|-mstds0,46,63,1|-mkw9w1,46,63,1|-mkw9w0,45,62,0|-ma3b41,45,62,0|-ma3b40,46,63,1|-m26781,46,63,1|-m26780,45,62,0|-lr09s1,45,62,0|-lr09s0,46,63,1|-lj35w1,46,63,1|-lj35w0,45,62,0|-l8a741,45,62,0|-l8a740,46,63,1|-l0d381,46,63,1|-l0d380,45,62,0|-kpk4g1,45,62,0|-kpk4g0,46,63,1|-khn0k1,46,63,1|-khn0k0,45,62,0|-k6u1s1,45,62,0|-k6u1s0,46,63,1|-jywxw1,46,63,1|-jywxw0,45,62,0|-jo3z41,45,62,0|-jo3z40,46,63,1|-jg6v81,46,63,1|-jg6v80,45,62,0|-j50xs1,45,62,0|-j50xs0,46,63,1|-ixgsk1,46,63,1|-ixgsk0,45,62,0|-imav41,45,62,0|-imav40,46,63,1|-iedr81,46,63,1|-iedr80,45,62,0|-i3ksg1,45,62,0|-i3ksg0,46,63,1|-hvnok1,46,63,1|-hvnok0,45,62,0|-hnqf41,45,62,0|-hnqf40,49,63,0|-haev81,49,63,0|-haev80,45,62,0|-h24n41,45,62,0|-h24n40,46,63,1|-gu7j81,46,63,1|-gu7j80,45,62,0|-gjekg1,45,62,0|-gjekg0,46,63,1|-gbhgk1,46,63,1|-gbhgk0,45,62,0|-g0bj41,45,62,0|-g0bj40,46,63,1|-fsrdw1,46,63,1|-fsrdw0,45,62,0|-fhlgg1,45,62,0|-fhlgg0,46,63,1|-f9ock1,46,63,1|-f9ock0,45,62,0|-eyvds1,45,62,0|-eyvds0,46,63,1|-eqy9w1,46,63,1|-eqy9w0,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-ccw1s1,45,62,0|-ccw1s0,46,63,1|-c4yxw1,46,63,1|-c4yxw0,45,62,0|-bu5z41,45,62,0|-bu5z40,46,63,1|-bm8v81,46,63,1|-bm8v80,45,62,0|-bbfwg1,45,62,0|-bbfwg0,46,63,1|-b3isk1,46,63,1|-b3isk0,45,62,0|-aspts1,45,62,0|-aspts0,46,63,1|-akspw1,46,63,1|-akspw0,45,62,0|-a9msg1,45,62,0|-a9msg0,46,63,1|-a22n81,46,63,1|-a22n80,45,62,0|-9qwps1,45,62,0|-9qwps0,46,63,1|-9izlw1,46,63,1|-9izlw0,45,62,0|-986n41,45,62,0|-986n40,46,63,1|-909j81,46,63,1|-909j80,45,62,0|-8pgkg1,45,62,0|-8pgkg0,46,63,1|-8hjgk1,46,63,1|-8hjgk0,45,62,0|-86qhs1,45,62,0|-86qhs0,46,63,1|-7ytdw1,46,63,1|-7ytdw0,45,62,0|-7o0f41,45,62,0|-7o0f40,46,63,1|-7eahw1,46,63,1|-7eahw0,45,62,0|-74xds1,45,62,0|-74xds0,46,63,1|-6vkf81,46,63,1|-6vkf80,45,62,0|-6m7b41,45,62,0|-6m7b40,46,63,1|-6cuck1,46,63,1|-6cuck0,45,62,0|-63h8g1,45,62,0|-63h8g0,46,63,1|-5u49w1,46,63,1|-5u49w0,45,62,0|-5kr5s1,45,62,0|-5kr5s0,46,63,1|-5be781,46,63,1|-5be780,45,62,0|-521341,45,62,0|-521340,46,63,1|-4sb5w1,46,63,1|-4sb5w0,45,62,0|-4iy1s1,45,62,0|-4iy1s0,46,63,1|-49l381,46,63,1|-49l380,45,62,0|-407z41,45,62,0|-407z40,46,63,1|-3qv0k1,46,63,1|-3qv0k0,45,62,0|-3hhwg1,45,62,0|-3hhwg0,46,63,1|-384xw1,46,63,1|-384xw0,45,62,0|-2yrts1,45,62,0|-2yrts0,46,63,1|-2pev81,46,63,1|-2pev80,45,62,0|-2g1r41,45,62,0|-2g1r40,46,63,1|-26btw1,46,63,1|-26btw0,45,62,0|-1xbog1,45,62,0|-1xbog0,46,63,1|-1nlr81,46,63,1|-1nlr80,45,62,0|-1e8n41,45,62,0|-1e8n40,46,63,1|-14vok1,46,63,1|-14vok0,45,62,0|-vikg1,45,62,0|-vikg0,46,63,1|-m5lw1,46,63,1|-m5lw0,45,62,0|-cshs1,45,62,0|-cshs0,46,63,1|-3fj81,46,63,1|-3fj80,45,62,0|5xkvz,45,62,0|5xkw0,46,63,1|fajfz,46,63,1|fajg0,45,62,0|onnjz,45,62,0|onnk0,46,63,1|ydkrz,46,63,1|ydks0,45,62,0|17qovz,45,62,0|17qow0,46,63,1|1h3nfz,46,63,1|1h3ng0,45,62,0|1qgrjz,45,62,0|1qgrk0,46,63,1|1ztq3z,46,63,1|1ztq40,45,62,0|23ffjz,45,62,0|23ffk0,46,63,1|2ijsrz,46,63,1|2ijss0,45,62,0|2oo8vz,45,62,0|2oo8w0,46,63,1|319vfz,46,63,1|319vg0,45,62,0|3amzjz,45,62,0|3amzk0,46,63,1|3kcwrz,46,63,1|3kcws0,45,62,0|3td27z,45,62,0|3td280,46,63,1|432zfz,46,63,1|432zg0,45,62,0|4cg3jz,45,62,0|4cg3k0,46,63,1|4lt23z,46,63,1|4lt240,45,62,0|4v667z,45,62,0|4v6680,46,63,1|54j4rz,46,63,1|54j4s0,45,62,0|5dw8vz,45,62,0|5dw8w0,46,63,1|5n97fz,46,63,1|5n97g0,45,62,0|5wmbjz,45,62,0|5wmbk0,46,63,1|65za3z,46,63,1|65za40,45,62,0|6fce7z,45,62,0|6fce80,46,63,1|6p2bfz,46,63,1|6p2bg0,45,62,0|6y2gvz,45,62,0|6y2gw0,46,63,1|77se3z,46,63,1|77se40,45,62,0|7h5i7z,45,62,0|7h5i80,46,63,1|7qigrz,46,63,1|7qigs0,45,62,0|7zvkvz,45,62,0|7zvkw0,46,63,1|898jfz,46,63,1|898jg0,45,62,0|8ilnjz,45,62,0|8ilnk0,46,63,1|8rym3z,46,63,1|8rym40,45,62,0|908u7z,45,62,0|908u80,46,63,1|9aoorz,46,63,1|9aoos0,45,62,0|9iywvz,45,62,0|9iyww0,46,63,1|9trq3z,46,63,1|9trq40,45,62,0|a1ozjz,45,62,0|a1ozk0,46,63,1|achsrz,46,63,1|achss0,45,62,0|akf27z,45,62,0|akf280,46,63,1|av7vfz,46,63,1|av7vg0,45,62,0|b3i3jz,45,62,0|b3i3k0,46,63,1|bdxy3z,46,63,1|bdxy40,45,62,0|bm867z,45,62,0|bm8680,46,63,1|bwo0rz,46,63,1|bwo0s0,45,62,0|c4y8vz,45,62,0|c4y8w0,46,63,1|cfr23z,46,63,1|cfr240,45,62,0|cnobjz,45,62,0|cnobk0,46,63,1|cyh4rz,46,63,1|cyh4s0,45,62,0|d6ee7z,45,62,0|d6ee80,46,63,1|dh77fz,46,63,1|dh77g0,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxkvz,45,62,0|eqxkw0,46,63,1|f1dffz,46,63,1|f1dfg0,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkggrz,46,63,1|fkggs0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,45,62,0|gb3svz,45,62,0|gb3sw0,46,63,1|glwm3z,46,63,1|glwm40,45,62,0|gu6u7z,45,62,0|gu6u80,46,63,1|h4morz,46,63,1|h4mos0,45,62,0|hcwwvz,45,62,0|hcwww0,46,63,1|hncrfz,46,63,1|hncrg0,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fsrz,46,63,1|i6fss0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5vfz,46,63,1|ip5vg0,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jeqbjz,45,62,0|jeqbk0,46,63,1|jqyzfz,46,63,1|jqyzg0,45,62,0|jxge7z,45,62,0|jxge80,46,63,1|k9p23z,46,63,1|k9p240,45,62,0|kg6gvz,45,62,0|kg6gw0,46,63,1|ksf4rz,46,63,1|ksf4s0,45,62,0|kz9i7z,45,62,0|kz9i80,46,63,1|lbi63z,46,63,1|lbi640,45,62,0|lhzkvz,45,62,0|lhzkw0,46,63,1|lu88rz,46,63,1|lu88s0,45,62,0|m0pnjz,45,62,0|m0pnk0,46,63,1|mcybfz,46,63,1|mcybg0,45,62,0|mjfq7z,45,62,0|mjfq80,46,63,1|mvoe3z,46,63,1|mvoe40,45,62,0|n25svz,45,62,0|n25sw0,46,63,1|neegrz,46,63,1|neegs0,45,62,0|nkvvjz,45,62,0|nkvvk0,46,63,1|nx4jfz,46,63,1|nx4jg0,45,62,0|o3ywvz,45,62,0|o3yww0,46,63,1|og7krz,46,63,1|og7ks0,45,62,0|omozjz,45,62,0|omozk0,46,63,1|oyxnfz,46,63,1|oyxng0,45,62,0|p5f27z,45,62,0|p5f280,46,63,1|phnq3z,46,63,1|phnq40,45,62,0|po54vz,45,62,0|po54w0,46,63,1|q0dsrz,46,63,1|q0dss0,45,62,0|q6v7jz,45,62,0|q6v7k0,46,63,1|qj3vfz,46,63,1|qj3vg0,45,62,0|qpy8vz,45,62,0|qpy8w0,46,63,1|r26wrz,46,63,1|r26ws0,45,62,0|r8objz,45,62,0|r8obk0,46,63,1|rkwzfz,46,63,1|rkwzg0,45,62,0|rree7z,45,62,0|rree80,46,63,1|s3n23z,46,63,1|s3n240,45,62,0|sa4gvz,45,62,0|sa4gw0,46,63,1|smd4rz,46,63,1|smd4s0,45,62,0|ssujjz,45,62,0|ssujk0,46,63,1|t537fz,46,63,1|t537g0,45,62,0|tbkm7z,45,62,0|tbkm80,46,63,1|tnta3z,46,63,1|tnta40,45,62,0|tunnjz,45,62,0|tunnk0,46,63,1|u6wbfz,46,63,1|u6wbg0,45,62,0|uddq7z,45,62,0|uddq80,46,63,1|upme3z,46,63,1|upme40,45,62,0|uw3svz,45,62,0|uw3sw0,46,63,1|v8cgrz,46,63,1|v8cgs0,45,62,0|vetvjz,45,62,0|vetvk0,46,63,1|vr2jfz,46,63,1|vr2jg0,45,62,0|vxjy7z,45,62,0|vxjy80,46,63,1|w9sm3z,46,63,1|w9sm40,45,62,0|wgmzjz,45,62,0|wgmzk0,46,63,1|wsvnfz,46,63,1|wsvng0,45,62,0|wzd27z,45,62,0|wzd280,46,63,1|xblq3z,46,63,1|xblq40,45,62,0|xi34vz,45,62,0|xi34w0,46,63,1|xubsrz,46,63,1|xubss0,45,62,0|y0t7jz,45,62,0|y0t7k0,46,63,1|yd1vfz,46,63,1|yd1vg0,45,62,0|yjja7z,45,62,0|yjja80,46,63,1|yvry3z,46,63,1|yvry40,45,62,0|z29cvz,45,62,0|z29cw0,46,63,1|zei0rz,46,63,1|zei0s0,45,62,0\",\"America/Chihuahua|,0,83,0|-p1u4k0,50,66,0|-m7mko1,50,66,0|-m7mko0,45,62,0|-kf67c1,45,62,0|-kf67c0,50,66,0|-k6j3c1,50,66,0|-k6j3c0,45,62,0|-jypm01,45,62,0|-jypm00,50,66,0|-jpan81,50,66,0|-jpan80,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxnnz,45,62,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0|f9nqbz,50,66,0|f9nqc0,52,62,1|fkgjjz,52,62,1|fkgjk0,50,66,0|fsdszz,50,66,0|fsdt00,52,62,1|g36m7z,52,62,1|g36m80,50,66,0|gcwozz,50,66,0|gcwp00,52,62,1|gkgu7z,52,62,1|gkgu80,50,66,0|gu6wzz,50,66,0|gu6x00,52,62,1|h4mrjz,52,62,1|h4mrk0,50,66,0|hcwznz,50,66,0|hcwzo0,52,62,1|hncu7z,52,62,1|hncu80,50,66,0|hvn2bz,50,66,0|hvn2c0,52,62,1|i6fvjz,52,62,1|i6fvk0,50,66,0|ied4zz,50,66,0|ied500,52,62,1|ip5y7z,52,62,1|ip5y80,50,66,0|ix37nz,50,66,0|ix37o0,52,62,1|j7w0vz,52,62,1|j7w0w0,50,66,0|jftabz,50,66,0|jftac0,52,62,1|jqm3jz,52,62,1|jqm3k0,50,66,0|jywbnz,50,66,0|jywbo0,52,62,1|k9c67z,52,62,1|k9c680,50,66,0|khmebz,50,66,0|khmec0,52,62,1|ks28vz,52,62,1|ks28w0,50,66,0|l0cgzz,50,66,0|l0ch00,52,62,1|lb5a7z,52,62,1|lb5a80,50,66,0|lj2jnz,50,66,0|lj2jo0,52,62,1|ltvcvz,52,62,1|ltvcw0,50,66,0|m1smbz,50,66,0|m1smc0,52,62,1|mclfjz,52,62,1|mclfk0,50,66,0|mkvnnz,50,66,0|mkvno0,52,62,1|mvbi7z,52,62,1|mvbi80,50,66,0|n3lqbz,50,66,0|n3lqc0,52,62,1|ne1kvz,52,62,1|ne1kw0,50,66,0|nmbszz,50,66,0|nmbt00,52,62,1|nwrnjz,52,62,1|nwrnk0,50,66,0|o51vnz,50,66,0|o51vo0,52,62,1|ofuovz,52,62,1|ofuow0,50,66,0|onrybz,50,66,0|onryc0,52,62,1|oykrjz,52,62,1|oykrk0,50,66,0|p6i0zz,50,66,0|p6i100,52,62,1|phau7z,52,62,1|phau80,50,66,0|ppl2bz,50,66,0|ppl2c0,52,62,1|q00wvz,52,62,1|q00ww0,50,66,0|q8b4zz,50,66,0|q8b500,52,62,1|qiqzjz,52,62,1|qiqzk0,50,66,0|qr17nz,50,66,0|qr17o0,52,62,1|r1u0vz,52,62,1|r1u0w0,50,66,0|r9rabz,50,66,0|r9rac0,52,62,1|rkk3jz,52,62,1|rkk3k0,50,66,0|rshczz,50,66,0|rshd00,52,62,1|s3a67z,52,62,1|s3a680,50,66,0|sbkebz,50,66,0|sbkec0,52,62,1|sm08vz,52,62,1|sm08w0,50,66,0|suagzz,50,66,0|suah00,52,62,1|t4qbjz,52,62,1|t4qbk0,50,66,0|td0jnz,50,66,0|td0jo0,52,62,1|tnge7z,52,62,1|tnge80,50,66,0|tvqmbz,50,66,0|tvqmc0,52,62,1|u6jfjz,52,62,1|u6jfk0,50,66,0|uegozz,50,66,0|uegp00,52,62,1|up9i7z,52,62,1|up9i80,50,66,0|ux6rnz,50,66,0|ux6ro0,52,62,1|v7zkvz,52,62,1|v7zkw0,50,66,0|vg9szz,50,66,0|vg9t00,52,62,1|vqpnjz,52,62,1|vqpnk0,50,66,0|vyzvnz,50,66,0|vyzvo0,52,62,1|w9fq7z,52,62,1|w9fq80,50,66,0|whpybz,50,66,0|whpyc0,52,62,1|wsirjz,52,62,1|wsirk0,50,66,0|x0g0zz,50,66,0|x0g100,52,62,1|xb8u7z,52,62,1|xb8u80,50,66,0|xj63nz,50,66,0|xj63o0,52,62,1|xtywvz,52,62,1|xtyww0,50,66,0|y1w6bz,50,66,0|y1w6c0,52,62,1|ycozjz,52,62,1|ycozk0,50,66,0|ykz7nz,50,66,0|ykz7o0,52,62,1|yvf27z,52,62,1|yvf280,50,66,0|z3pabz,50,66,0|z3pac0,52,62,1|ze54vz,52,62,1|ze54w0,50,66,0\",\"America/Costa_Rica|,0,84,0|-15r0trn,63,84,0|-pjw8fo,63,84,0|-pjw8fn,45,62,0|4rxcnz,45,62,0|4rxco0,46,63,1|4wyr7z,46,63,1|4wyr80,45,62,0|5anfbz,45,62,0|5anfc0,46,63,1|5fotvz,46,63,1|5fotw0,45,62,0|azhhzz,45,62,0|azhi00,46,63,1|b7v9vz,46,63,1|b7v9w0,45,62,0|bi7knz,45,62,0|bi7ko0,46,63,1|bl51vz,46,63,1|bl51w0,45,62,0\",\"America/Creston|,0,85,0|-18vrx38,50,66,0|-rshz81,50,66,0|-rshz80,51,40,0|-qx64g1,51,40,0|-qx64g0,50,66,0\",\"America/Cuiaba|,0,86,0|-t85hm4,42,42,0|-jyl7o1,42,42,0|-jyl7o0,39,44,1|-jpayc1,39,44,1|-jpayc0,42,42,0|-jfsa81,42,42,0|-jfsa80,39,44,1|-j6j101,39,44,1|-j6j100,42,42,0|-ahcvk1,42,42,0|-ahcvk0,39,44,1|-aad0w1,39,44,1|-aad0w0,42,42,0|-9yky81,42,42,0|-9yky80,39,44,1|-9scyc1,39,44,1|-9scyc0,42,42,0|-9ft0w1,42,42,0|-9ft0w0,39,44,1|-99j6c1,39,44,1|-99j6c0,42,42,0|-8wz8w1,42,42,0|-8wz8w0,39,44,1|-8scno1,39,44,1|-8scno0,42,42,0|-35xjk1,42,42,0|-35xjk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2kdm81,42,42,0|-2kdm80,39,44,1|-2hcfo1,39,44,1|-2hcfo0,42,42,0|-24qnk1,42,42,0|-24qnk0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1nia81,42,42,0|-1nia80,39,44,1|-1hc501,39,44,1|-1hc500,42,42,0|-14qcw1,42,42,0|-14qcw0,39,44,1|-yid01,39,44,1|-yid00,42,42,0|89jf3z,42,42,0|89jf40,39,44,1|8gdmzz,39,44,1|8gdn00,42,42,0|8rwj3z,42,42,0|8rwj40,39,44,1|8xnuzz,39,44,1|8xnv00,42,42,0|9aogfz,42,42,0|9aogg0,39,44,1|9g2tnz,39,44,1|9g2to0,42,42,0|9t1kfz,42,42,0|9t1kg0,39,44,1|9yfxnz,39,44,1|9yfxo0,42,42,0|abrn3z,42,42,0|abrn40,39,44,1|ahvxnz,39,44,1|ahvxo0,42,42,0|auuofz,42,42,0|auuog0,39,44,1|b0yyzz,39,44,1|b0yz00,42,42,0|bdkr3z,42,42,0|bdkr40,39,44,1|bjc2zz,39,44,1|bjc300,42,42,0|bwnsfz,42,42,0|bwnsg0,39,44,1|c1p6zz,39,44,1|c1p700,42,42,0|cf0wfz,42,42,0|cf0wg0,39,44,1|cli5nz,39,44,1|cli5o0,42,42,0|cxqz3z,42,42,0|cxqz40,39,44,1|d488bz,39,44,1|d488c0,42,42,0|dgh1rz,42,42,0|dgh1s0,39,44,1|dmlcbz,39,44,1|dmlcc0,42,42,0|dyu5rz,42,42,0|dyu5s0,39,44,1|e5odnz,39,44,1|e5odo0,42,42,0|ehm33z,42,42,0|ehm340,39,44,1|ep4dnz,39,44,1|ep4do0,42,42,0|f0n9rz,42,42,0|f0n9s0,39,44,1|f7hhnz,39,44,1|f7hho0,42,42,0|fj0drz,42,42,0|fj0ds0,39,44,1|fqkizz,39,44,1|fqkj00,42,42,0|g23f3z,42,42,0|g23f40,39,44,1|g8xmzz,39,44,1|g8xn00,42,42,0|gl6gfz,42,42,0|gl6gg0,39,44,1|grnpnz,39,44,1|grnpo0,42,42,0|h4zf3z,42,42,0|h4zf40,39,44,1|hadsbz,39,44,1|hadsc0,42,42,0|i6j9rz,42,42,0|i6j9s0,39,44,1|ic6wbz,39,44,1|ic6wc0,42,42,0|iofprz,42,42,0|iofps0,39,44,1|iuwyzz,39,44,1|iuwz00,42,42,0|j88ofz,42,42,0|j88og0,39,44,1|je00bz,39,44,1|je00c0,42,42,0|jpvv3z,42,42,0|jpvv40,39,44,1|jwd4bz,39,44,1|jwd4c0,42,42,0|k8ywfz,42,42,0|k8ywg0,39,44,1|kf36zz,39,44,1|kf3700,42,42,0|kroz3z,42,42,0|kroz40,39,44,1|ky68bz,39,44,1|ky68c0,42,42,0|laf1rz,42,42,0|laf1s0,39,44,1|lgwazz,39,44,1|lgwb00,42,42,0|lt54fz,42,42,0|lt54g0,39,44,1|lzzcbz,39,44,1|lzzcc0,42,42,0|mc85rz,42,42,0|mc85s0,39,44,1|micgbz,39,44,1|micgc0,42,42,0|muy8fz,42,42,0|muy8g0,39,44,1|n12izz,39,44,1|n12j00,42,42,0|ndob3z,42,42,0|ndob40,39,44,1|nk5kbz,39,44,1|nk5kc0,42,42,0|nwedrz,42,42,0|nweds0,39,44,1|o2vmzz,39,44,1|o2vn00,42,42,0|of4gfz,42,42,0|of4gg0,39,44,1|ollpnz,39,44,1|ollpo0,42,42,0|oxuj3z,42,42,0|oxuj40,39,44,1|p4bsbz,39,44,1|p4bsc0,42,42,0|phnhrz,42,42,0|phnhs0,39,44,1|pn1uzz,39,44,1|pn1v00,42,42,0\",\"America/Curacao|,0,58,0|-u7lckd,43,59,0|-2lx4u1,43,59,0|-2lx4u0,32,42,0\",\"America/Danmarkshavn|,0,87,0|-rvusjk,39,44,0|5ct4jz,39,44,0|5ct4k0,40,45,1|5lsw3z,40,45,1|5lsw40,39,44,0|5v5xfz,39,44,0|5v5xg0,40,45,1|64iyrz,40,45,1|64iys0,39,44,0|6dw03z,39,44,0|6dw040,40,45,1|6n91fz,40,45,1|6n91g0,39,44,0|6wm2rz,39,44,0|6wm2s0,40,45,1|75z43z,40,45,1|75z440,39,44,0|7fc5fz,39,44,0|7fc5g0,40,45,1|7p25fz,40,45,1|7p25g0,39,44,0|7yf6rz,39,44,0|7yf6s0,40,45,1|87s83z,40,45,1|87s840,39,44,0|8h59fz,39,44,0|8h59g0,40,45,1|8qiarz,40,45,1|8qias0,39,44,0|8zvc3z,39,44,0|8zvc40,40,45,1|998dfz,40,45,1|998dg0,39,44,0|9ilerz,39,44,0|9iles0,40,45,1|9ryg3z,40,45,1|9ryg40,39,44,0|a1bhfz,39,44,0|a1bhg0,40,45,1|aaoirz,40,45,1|aaois0,39,44,0|ak1k3z,39,44,0|ak1k40,40,45,1|atrk3z,40,45,1|atrk40,39,44,0|b34lfz,39,44,0|b34lg0,40,45,1|bchmrz,40,45,1|bchms0,39,44,0|bluo3z,39,44,0|bluo40,40,45,1|bv7pfz,40,45,1|bv7pg0,39,44,0|c4kqrz,39,44,0|c4kqs0,40,45,1|cdxs3z,40,45,1|cdxs40,39,44,0|cnatfz,39,44,0|cnatg0,40,45,1|cwnurz,40,45,1|cwnus0,39,44,0|d60w3z,39,44,0|d60w40,40,45,1|dfdxfz,40,45,1|dfdxg0,39,44,0|dkhezz,39,44,0|dkhf00,1,1,0\",\"America/Dawson|,0,88,0|-1079suk,36,37,0|-qzoms1,36,37,0|-qzoms0,64,40,1|-qplqw1,64,40,1|-qplqw0,36,37,0|-qess41,36,37,0|-qess40,64,40,1|-q6kps1,64,40,1|-q6kps0,36,37,0|-ek1tg1,36,37,0|-ek1tg0,65,40,1|-cq2tg1,65,40,1|-cq2tg0,66,40,1|-cnos81,66,40,1|-cnos80,36,37,0|-2g1oc1,36,37,0|-2g1oc0,67,66,1|-26boc1,67,66,1|-26boc0,36,37,0|1ztvnz,36,37,0|1ztvo0,51,40,0|5dwefz,51,40,0|5dweg0,57,66,1|5n9czz,57,66,1|5n9d00,51,40,0|5wmh3z,51,40,0|5wmh40,57,66,1|65zfnz,57,66,1|65zfo0,51,40,0|6fcjrz,51,40,0|6fcjs0,57,66,1|6p2gzz,57,66,1|6p2h00,51,40,0|6y2mfz,51,40,0|6y2mg0,57,66,1|77sjnz,57,66,1|77sjo0,51,40,0|7h5nrz,51,40,0|7h5ns0,57,66,1|7qimbz,57,66,1|7qimc0,51,40,0|7zvqfz,51,40,0|7zvqg0,57,66,1|898ozz,57,66,1|898p00,51,40,0|8ilt3z,51,40,0|8ilt40,57,66,1|8ryrnz,57,66,1|8ryro0,51,40,0|908zrz,51,40,0|908zs0,57,66,1|9aoubz,57,66,1|9aouc0,51,40,0|9iz2fz,51,40,0|9iz2g0,57,66,1|9trvnz,57,66,1|9trvo0,51,40,0|a1p53z,51,40,0|a1p540,57,66,1|achybz,57,66,1|achyc0,51,40,0|akf7rz,51,40,0|akf7s0,57,66,1|av80zz,57,66,1|av8100,51,40,0|b3i93z,51,40,0|b3i940,57,66,1|bdy3nz,57,66,1|bdy3o0,51,40,0|bm8brz,51,40,0|bm8bs0,57,66,1|bwo6bz,57,66,1|bwo6c0,51,40,0|c4yefz,51,40,0|c4yeg0,57,66,1|cfr7nz,57,66,1|cfr7o0,51,40,0|cnoh3z,51,40,0|cnoh40,57,66,1|cyhabz,57,66,1|cyhac0,51,40,0|d6ejrz,51,40,0|d6ejs0,57,66,1|dh7czz,57,66,1|dh7d00,51,40,0|dphl3z,51,40,0|dphl40,57,66,1|dzxfnz,57,66,1|dzxfo0,51,40,0|e87nrz,51,40,0|e87ns0,57,66,1|einibz,57,66,1|einic0,51,40,0|eqxqfz,51,40,0|eqxqg0,57,66,1|f1dkzz,57,66,1|f1dl00,51,40,0|f9nt3z,51,40,0|f9nt40,57,66,1|fkgmbz,57,66,1|fkgmc0,51,40,0|fsdvrz,51,40,0|fsdvs0,57,66,1|g36ozz,57,66,1|g36p00,51,40,0|gb3yfz,51,40,0|gb3yg0,57,66,1|glwrnz,57,66,1|glwro0,51,40,0|gu6zrz,51,40,0|gu6zs0,57,66,1|h4mubz,57,66,1|h4muc0,51,40,0|hcx2fz,51,40,0|hcx2g0,57,66,1|hncwzz,57,66,1|hncx00,51,40,0|hvn53z,51,40,0|hvn540,57,66,1|i6fybz,57,66,1|i6fyc0,51,40,0|ied7rz,51,40,0|ied7s0,57,66,1|ip60zz,57,66,1|ip6100,51,40,0|ix3afz,51,40,0|ix3ag0,57,66,1|j7w3nz,57,66,1|j7w3o0,51,40,0|jeqh3z,51,40,0|jeqh40,57,66,1|jqz4zz,57,66,1|jqz500,51,40,0|jxgjrz,51,40,0|jxgjs0,57,66,1|k9p7nz,57,66,1|k9p7o0,51,40,0|kg6mfz,51,40,0|kg6mg0,57,66,1|ksfabz,57,66,1|ksfac0,51,40,0|kz9nrz,51,40,0|kz9ns0,57,66,1|lbibnz,57,66,1|lbibo0,51,40,0|lhzqfz,51,40,0|lhzqg0,57,66,1|lu8ebz,57,66,1|lu8ec0,51,40,0|m0pt3z,51,40,0|m0pt40,57,66,1|mcygzz,57,66,1|mcyh00,51,40,0|mjfvrz,51,40,0|mjfvs0,57,66,1|mvojnz,57,66,1|mvojo0,51,40,0|n25yfz,51,40,0|n25yg0,57,66,1|neembz,57,66,1|neemc0,51,40,0|nkw13z,51,40,0|nkw140,57,66,1|nx4ozz,57,66,1|nx4p00,51,40,0|o3z2fz,51,40,0|o3z2g0,57,66,1|og7qbz,57,66,1|og7qc0,51,40,0|omp53z,51,40,0|omp540,57,66,1|oyxszz,57,66,1|oyxt00,51,40,0|p5f7rz,51,40,0|p5f7s0,57,66,1|phnvnz,57,66,1|phnvo0,51,40,0|po5afz,51,40,0|po5ag0,57,66,1|q0dybz,57,66,1|q0dyc0,51,40,0|q6vd3z,51,40,0|q6vd40,57,66,1|qj3vfz,57,66,1|qj3vg0,50,66,0\",\"America/Dawson_Creek|,0,89,0|-18vrweg,51,40,0|-qzopk1,51,40,0|-qzopk0,57,66,1|-qplto1,57,66,1|-qplto0,51,40,0|-ek1w81,51,40,0|-ek1w80,68,66,1|-cq2tg1,68,66,1|-cq2tg0,69,66,1|-cnov01,69,66,1|-cnov00,51,40,0|-bu5tk1,51,40,0|-bu5tk0,57,66,1|-bm8po1,57,66,1|-bm8po0,51,40,0|-bbfqw1,51,40,0|-bbfqw0,57,66,1|-b3in01,57,66,1|-b3in00,51,40,0|-aspo81,51,40,0|-aspo80,57,66,1|-akskc1,57,66,1|-akskc0,51,40,0|-a9mmw1,51,40,0|-a9mmw0,57,66,1|-a22ho1,57,66,1|-a22ho0,51,40,0|-9qwk81,51,40,0|-9qwk80,57,66,1|-9izgc1,57,66,1|-9izgc0,51,40,0|-986hk1,51,40,0|-986hk0,57,66,1|-909do1,57,66,1|-909do0,51,40,0|-8pgew1,51,40,0|-8pgew0,57,66,1|-8hjb01,57,66,1|-8hjb00,51,40,0|-86qc81,51,40,0|-86qc80,57,66,1|-7yt8c1,57,66,1|-7yt8c0,51,40,0|-7o09k1,51,40,0|-7o09k0,57,66,1|-7g35o1,57,66,1|-7g35o0,51,40,0|-74x881,51,40,0|-74x880,57,66,1|-6x04c1,57,66,1|-6x04c0,51,40,0|-6m75k1,51,40,0|-6m75k0,57,66,1|-6ea1o1,57,66,1|-6ea1o0,51,40,0|-63h2w1,51,40,0|-63h2w0,57,66,1|-5vjz01,57,66,1|-5vjz00,51,40,0|-5kr081,51,40,0|-5kr080,57,66,1|-5ctwc1,57,66,1|-5ctwc0,51,40,0|-520xk1,51,40,0|-520xk0,57,66,1|-4u3to1,57,66,1|-4u3to0,51,40,0|-4ixw81,51,40,0|-4ixw80,57,66,1|-4bdr01,57,66,1|-4bdr00,51,40,0|-407tk1,51,40,0|-407tk0,57,66,1|-3quv01,57,66,1|-3quv00,51,40,0|-3hhqw1,51,40,0|-3hhqw0,57,66,1|-384sc1,57,66,1|-384sc0,51,40,0|-2yro81,51,40,0|-2yro80,57,66,1|-2pepo1,57,66,1|-2pepo0,51,40,0|-2g1lk1,51,40,0|-2g1lk0,57,66,1|-26boc1,57,66,1|-26boc0,51,40,0|-1xbiw1,51,40,0|-1xbiw0,57,66,1|-1nllo1,57,66,1|-1nllo0,51,40,0|-1e8hk1,51,40,0|-1e8hk0,57,66,1|-14vj01,57,66,1|-14vj00,51,40,0|-view1,51,40,0|-view0,57,66,1|-m5gc1,57,66,1|-m5gc0,51,40,0|-csc81,51,40,0|-csc80,57,66,1|-3fdo1,57,66,1|-3fdo0,51,40,0|5xqfz,51,40,0|5xqg0,57,66,1|faozz,57,66,1|fap00,51,40,0|ont3z,51,40,0|ont40,57,66,1|ydqbz,57,66,1|ydqc0,51,40,0|17qufz,51,40,0|17qug0,57,66,1|1e0ozz,57,66,1|1e0p00,50,66,0\",\"America/Denver|,0,90,0|-18y0j80,50,66,0|-r0epo1,50,66,0|-r0epo0,52,62,1|-qplwg1,52,62,1|-qplwg0,50,66,0|-qhon01,50,66,0|-qhon00,52,62,1|-q6vts1,52,62,1|-q6vts0,50,66,0|-pyykc1,50,66,0|-pyykc0,52,62,1|-pnssg1,52,62,1|-pnssg0,50,66,0|-pg8ho1,50,66,0|-pg8ho0,52,62,1|-pdcv41,52,62,1|-pdcv40,50,66,0|-ek1z01,50,66,0|-ek1z00,58,62,1|-cq2tg1,58,62,1|-cq2tg0,59,62,1|-cnoxs1,59,62,1|-cnoxs0,50,66,0|-2g1oc1,50,66,0|-2g1oc0,52,62,1|-26br41,52,62,1|-26br40,50,66,0|-1xblo1,50,66,0|-1xblo0,52,62,1|-1nlog1,52,62,1|-1nlog0,50,66,0|-1e8kc1,50,66,0|-1e8kc0,52,62,1|-14vls1,52,62,1|-14vls0,50,66,0|-viho1,50,66,0|-viho0,52,62,1|-m5j41,52,62,1|-m5j40,50,66,0|-csf01,50,66,0|-csf00,52,62,1|-3fgg1,52,62,1|-3fgg0,50,66,0|5xnnz,50,66,0|5xno0,52,62,1|fam7z,52,62,1|fam80,50,66,0|onqbz,50,66,0|onqc0,52,62,1|ydnjz,52,62,1|ydnk0,50,66,0|17qrnz,50,66,0|17qro0,52,62,1|1h3q7z,52,62,1|1h3q80,50,66,0|1qgubz,50,66,0|1qguc0,52,62,1|1ztsvz,52,62,1|1ztsw0,50,66,0|23fibz,50,66,0|23fic0,52,62,1|2ijvjz,52,62,1|2ijvk0,50,66,0|2oobnz,50,66,0|2oobo0,52,62,1|319y7z,52,62,1|319y80,50,66,0|3an2bz,50,66,0|3an2c0,52,62,1|3kczjz,52,62,1|3kczk0,50,66,0|3td4zz,50,66,0|3td500,52,62,1|43327z,52,62,1|433280,50,66,0|4cg6bz,50,66,0|4cg6c0,52,62,1|4lt4vz,52,62,1|4lt4w0,50,66,0|4v68zz,50,66,0|4v6900,52,62,1|54j7jz,52,62,1|54j7k0,50,66,0|5dwbnz,50,66,0|5dwbo0,52,62,1|5n9a7z,52,62,1|5n9a80,50,66,0|5wmebz,50,66,0|5wmec0,52,62,1|65zcvz,52,62,1|65zcw0,50,66,0|6fcgzz,50,66,0|6fch00,52,62,1|6p2e7z,52,62,1|6p2e80,50,66,0|6y2jnz,50,66,0|6y2jo0,52,62,1|77sgvz,52,62,1|77sgw0,50,66,0|7h5kzz,50,66,0|7h5l00,52,62,1|7qijjz,52,62,1|7qijk0,50,66,0|7zvnnz,50,66,0|7zvno0,52,62,1|898m7z,52,62,1|898m80,50,66,0|8ilqbz,50,66,0|8ilqc0,52,62,1|8ryovz,52,62,1|8ryow0,50,66,0|908wzz,50,66,0|908x00,52,62,1|9aorjz,52,62,1|9aork0,50,66,0|9iyznz,50,66,0|9iyzo0,52,62,1|9trsvz,52,62,1|9trsw0,50,66,0|a1p2bz,50,66,0|a1p2c0,52,62,1|achvjz,52,62,1|achvk0,50,66,0|akf4zz,50,66,0|akf500,52,62,1|av7y7z,52,62,1|av7y80,50,66,0|b3i6bz,50,66,0|b3i6c0,52,62,1|bdy0vz,52,62,1|bdy0w0,50,66,0|bm88zz,50,66,0|bm8900,52,62,1|bwo3jz,52,62,1|bwo3k0,50,66,0|c4ybnz,50,66,0|c4ybo0,52,62,1|cfr4vz,52,62,1|cfr4w0,50,66,0|cnoebz,50,66,0|cnoec0,52,62,1|cyh7jz,52,62,1|cyh7k0,50,66,0|d6egzz,50,66,0|d6eh00,52,62,1|dh7a7z,52,62,1|dh7a80,50,66,0|dphibz,50,66,0|dphic0,52,62,1|dzxcvz,52,62,1|dzxcw0,50,66,0|e87kzz,50,66,0|e87l00,52,62,1|einfjz,52,62,1|einfk0,50,66,0|eqxnnz,50,66,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0|f9nqbz,50,66,0|f9nqc0,52,62,1|fkgjjz,52,62,1|fkgjk0,50,66,0|fsdszz,50,66,0|fsdt00,52,62,1|g36m7z,52,62,1|g36m80,50,66,0|gb3vnz,50,66,0|gb3vo0,52,62,1|glwovz,52,62,1|glwow0,50,66,0|gu6wzz,50,66,0|gu6x00,52,62,1|h4mrjz,52,62,1|h4mrk0,50,66,0|hcwznz,50,66,0|hcwzo0,52,62,1|hncu7z,52,62,1|hncu80,50,66,0|hvn2bz,50,66,0|hvn2c0,52,62,1|i6fvjz,52,62,1|i6fvk0,50,66,0|ied4zz,50,66,0|ied500,52,62,1|ip5y7z,52,62,1|ip5y80,50,66,0|ix37nz,50,66,0|ix37o0,52,62,1|j7w0vz,52,62,1|j7w0w0,50,66,0|jeqebz,50,66,0|jeqec0,52,62,1|jqz27z,52,62,1|jqz280,50,66,0|jxggzz,50,66,0|jxgh00,52,62,1|k9p4vz,52,62,1|k9p4w0,50,66,0|kg6jnz,50,66,0|kg6jo0,52,62,1|ksf7jz,52,62,1|ksf7k0,50,66,0|kz9kzz,50,66,0|kz9l00,52,62,1|lbi8vz,52,62,1|lbi8w0,50,66,0|lhznnz,50,66,0|lhzno0,52,62,1|lu8bjz,52,62,1|lu8bk0,50,66,0|m0pqbz,50,66,0|m0pqc0,52,62,1|mcye7z,52,62,1|mcye80,50,66,0|mjfszz,50,66,0|mjft00,52,62,1|mvogvz,52,62,1|mvogw0,50,66,0|n25vnz,50,66,0|n25vo0,52,62,1|neejjz,52,62,1|neejk0,50,66,0|nkvybz,50,66,0|nkvyc0,52,62,1|nx4m7z,52,62,1|nx4m80,50,66,0|o3yznz,50,66,0|o3yzo0,52,62,1|og7njz,52,62,1|og7nk0,50,66,0|omp2bz,50,66,0|omp2c0,52,62,1|oyxq7z,52,62,1|oyxq80,50,66,0|p5f4zz,50,66,0|p5f500,52,62,1|phnsvz,52,62,1|phnsw0,50,66,0|po57nz,50,66,0|po57o0,52,62,1|q0dvjz,52,62,1|q0dvk0,50,66,0|q6vabz,50,66,0|q6vac0,52,62,1|qj3y7z,52,62,1|qj3y80,50,66,0|qpybnz,50,66,0|qpybo0,52,62,1|r26zjz,52,62,1|r26zk0,50,66,0|r8oebz,50,66,0|r8oec0,52,62,1|rkx27z,52,62,1|rkx280,50,66,0|rregzz,50,66,0|rreh00,52,62,1|s3n4vz,52,62,1|s3n4w0,50,66,0|sa4jnz,50,66,0|sa4jo0,52,62,1|smd7jz,52,62,1|smd7k0,50,66,0|ssumbz,50,66,0|ssumc0,52,62,1|t53a7z,52,62,1|t53a80,50,66,0|tbkozz,50,66,0|tbkp00,52,62,1|tntcvz,52,62,1|tntcw0,50,66,0|tunqbz,50,66,0|tunqc0,52,62,1|u6we7z,52,62,1|u6we80,50,66,0|uddszz,50,66,0|uddt00,52,62,1|upmgvz,52,62,1|upmgw0,50,66,0|uw3vnz,50,66,0|uw3vo0,52,62,1|v8cjjz,52,62,1|v8cjk0,50,66,0|vetybz,50,66,0|vetyc0,52,62,1|vr2m7z,52,62,1|vr2m80,50,66,0|vxk0zz,50,66,0|vxk100,52,62,1|w9sovz,52,62,1|w9sow0,50,66,0|wgn2bz,50,66,0|wgn2c0,52,62,1|wsvq7z,52,62,1|wsvq80,50,66,0|wzd4zz,50,66,0|wzd500,52,62,1|xblsvz,52,62,1|xblsw0,50,66,0|xi37nz,50,66,0|xi37o0,52,62,1|xubvjz,52,62,1|xubvk0,50,66,0|y0tabz,50,66,0|y0tac0,52,62,1|yd1y7z,52,62,1|yd1y80,50,66,0|yjjczz,50,66,0|yjjd00,52,62,1|yvs0vz,52,62,1|yvs0w0,50,66,0|z29fnz,50,66,0|z29fo0,52,62,1|zei3jz,52,62,1|zei3k0,50,66,0\",\"America/Detroit|,0,91,0|-xx8dyd,45,62,0|-sih341,45,62,0|-sih340,49,63,0|-ek24k1,49,63,0|-ek24k0,70,42,1|-cq2tg1,70,42,1|-cq2tg0,71,42,1|-cnp3c1,71,42,1|-cnp3c0,49,63,0|-bbfz81,49,63,0|-bbfz80,62,42,1|-b3ivc1,62,42,1|-b3ivc0,49,63,0|-1bxjed,49,63,0|-1bxjec,62,42,1|-14vrc1,62,42,1|-14vrc0,49,63,0|-vin81,49,63,0|-vin80,62,42,1|-m5oo1,62,42,1|-m5oo0,49,63,0|1qgorz,49,63,0|1qgos0,62,42,1|1ztnbz,62,42,1|1ztnc0,49,63,0|23fcrz,49,63,0|23fcs0,62,42,1|2ijpzz,62,42,1|2ijq00,49,63,0|2rwu3z,49,63,0|2rwu40,62,42,1|319snz,62,42,1|319so0,49,63,0|3amwrz,49,63,0|3amws0,62,42,1|3kctzz,62,42,1|3kcu00,49,63,0|3tczfz,49,63,0|3tczg0,62,42,1|432wnz,62,42,1|432wo0,49,63,0|4cg0rz,49,63,0|4cg0s0,62,42,1|4lszbz,62,42,1|4lszc0,49,63,0|4v63fz,49,63,0|4v63g0,62,42,1|54j1zz,62,42,1|54j200,49,63,0|5dw63z,49,63,0|5dw640,62,42,1|5n94nz,62,42,1|5n94o0,49,63,0|5wm8rz,49,63,0|5wm8s0,62,42,1|65z7bz,62,42,1|65z7c0,49,63,0|6fcbfz,49,63,0|6fcbg0,62,42,1|6p28nz,62,42,1|6p28o0,49,63,0|6y2e3z,49,63,0|6y2e40,62,42,1|77sbbz,62,42,1|77sbc0,49,63,0|7h5ffz,49,63,0|7h5fg0,62,42,1|7qidzz,62,42,1|7qie00,49,63,0|7zvi3z,49,63,0|7zvi40,62,42,1|898gnz,62,42,1|898go0,49,63,0|8ilkrz,49,63,0|8ilks0,62,42,1|8ryjbz,62,42,1|8ryjc0,49,63,0|908rfz,49,63,0|908rg0,62,42,1|9aolzz,62,42,1|9aom00,49,63,0|9iyu3z,49,63,0|9iyu40,62,42,1|9trnbz,62,42,1|9trnc0,49,63,0|a1owrz,49,63,0|a1ows0,62,42,1|achpzz,62,42,1|achq00,49,63,0|akezfz,49,63,0|akezg0,62,42,1|av7snz,62,42,1|av7so0,49,63,0|b3i0rz,49,63,0|b3i0s0,62,42,1|bdxvbz,62,42,1|bdxvc0,49,63,0|bm83fz,49,63,0|bm83g0,62,42,1|bwnxzz,62,42,1|bwny00,49,63,0|c4y63z,49,63,0|c4y640,62,42,1|cfqzbz,62,42,1|cfqzc0,49,63,0|cno8rz,49,63,0|cno8s0,62,42,1|cyh1zz,62,42,1|cyh200,49,63,0|d6ebfz,49,63,0|d6ebg0,62,42,1|dh74nz,62,42,1|dh74o0,49,63,0|dphcrz,49,63,0|dphcs0,62,42,1|dzx7bz,62,42,1|dzx7c0,49,63,0|e87ffz,49,63,0|e87fg0,62,42,1|ein9zz,62,42,1|eina00,49,63,0|eqxi3z,49,63,0|eqxi40,62,42,1|f1dcnz,62,42,1|f1dco0,49,63,0|f9nkrz,49,63,0|f9nks0,62,42,1|fkgdzz,62,42,1|fkge00,49,63,0|fsdnfz,49,63,0|fsdng0,62,42,1|g36gnz,62,42,1|g36go0,49,63,0|gb3q3z,49,63,0|gb3q40,62,42,1|glwjbz,62,42,1|glwjc0,49,63,0|gu6rfz,49,63,0|gu6rg0,62,42,1|h4mlzz,62,42,1|h4mm00,49,63,0|hcwu3z,49,63,0|hcwu40,62,42,1|hnconz,62,42,1|hncoo0,49,63,0|hvmwrz,49,63,0|hvmws0,62,42,1|i6fpzz,62,42,1|i6fq00,49,63,0|ieczfz,49,63,0|ieczg0,62,42,1|ip5snz,62,42,1|ip5so0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Dominica|,0,41,0|-u6m79w,32,42,0\",\"America/Edmonton|,0,92,0|-x1yazk,50,66,0|-qzosc1,50,66,0|-qzosc0,52,62,1|-qplwg1,52,62,1|-qplwg0,50,66,0|-qgypo1,50,66,0|-qgypo0,52,62,1|-qepb41,52,62,1|-qepb40,50,66,0|-pxipo1,50,66,0|-pxipo0,52,62,1|-pnssg1,52,62,1|-pnssg0,50,66,0|-pesn01,50,66,0|-pesn00,52,62,1|-p6vj41,52,62,1|-p6vj40,50,66,0|-ovplo1,50,66,0|-ovplo0,52,62,1|-oo5gg1,52,62,1|-oo5gg0,50,66,0|-oczj01,50,66,0|-oczj00,52,62,1|-o52f41,52,62,1|-o52f40,50,66,0|-ek1z01,50,66,0|-ek1z00,58,62,1|-cq2tg1,58,62,1|-cq2tg0,59,62,1|-cnoxs1,59,62,1|-cnoxs0,50,66,0|-bu5wc1,50,66,0|-bu5wc0,52,62,1|-bm8sg1,52,62,1|-bm8sg0,50,66,0|17qrnz,50,66,0|17qro0,52,62,1|1h3q7z,52,62,1|1h3q80,50,66,0|1qgubz,50,66,0|1qguc0,52,62,1|1ztsvz,52,62,1|1ztsw0,50,66,0|296wzz,50,66,0|296x00,52,62,1|2ijvjz,52,62,1|2ijvk0,50,66,0|2rwznz,50,66,0|2rwzo0,52,62,1|319y7z,52,62,1|319y80,50,66,0|3an2bz,50,66,0|3an2c0,52,62,1|3kczjz,52,62,1|3kczk0,50,66,0|3td4zz,50,66,0|3td500,52,62,1|43327z,52,62,1|433280,50,66,0|4cg6bz,50,66,0|4cg6c0,52,62,1|4lt4vz,52,62,1|4lt4w0,50,66,0|4v68zz,50,66,0|4v6900,52,62,1|54j7jz,52,62,1|54j7k0,50,66,0|5dwbnz,50,66,0|5dwbo0,52,62,1|5n9a7z,52,62,1|5n9a80,50,66,0|5wmebz,50,66,0|5wmec0,52,62,1|65zcvz,52,62,1|65zcw0,50,66,0|6fcgzz,50,66,0|6fch00,52,62,1|6p2e7z,52,62,1|6p2e80,50,66,0|6y2jnz,50,66,0|6y2jo0,52,62,1|77sgvz,52,62,1|77sgw0,50,66,0|7h5kzz,50,66,0|7h5l00,52,62,1|7qijjz,52,62,1|7qijk0,50,66,0|7zvnnz,50,66,0|7zvno0,52,62,1|898m7z,52,62,1|898m80,50,66,0|8ilqbz,50,66,0|8ilqc0,52,62,1|8ryovz,52,62,1|8ryow0,50,66,0|908wzz,50,66,0|908x00,52,62,1|9aorjz,52,62,1|9aork0,50,66,0|9iyznz,50,66,0|9iyzo0,52,62,1|9trsvz,52,62,1|9trsw0,50,66,0|a1p2bz,50,66,0|a1p2c0,52,62,1|achvjz,52,62,1|achvk0,50,66,0|akf4zz,50,66,0|akf500,52,62,1|av7y7z,52,62,1|av7y80,50,66,0|b3i6bz,50,66,0|b3i6c0,52,62,1|bdy0vz,52,62,1|bdy0w0,50,66,0|bm88zz,50,66,0|bm8900,52,62,1|bwo3jz,52,62,1|bwo3k0,50,66,0|c4ybnz,50,66,0|c4ybo0,52,62,1|cfr4vz,52,62,1|cfr4w0,50,66,0|cnoebz,50,66,0|cnoec0,52,62,1|cyh7jz,52,62,1|cyh7k0,50,66,0|d6egzz,50,66,0|d6eh00,52,62,1|dh7a7z,52,62,1|dh7a80,50,66,0|dphibz,50,66,0|dphic0,52,62,1|dzxcvz,52,62,1|dzxcw0,50,66,0|e87kzz,50,66,0|e87l00,52,62,1|einfjz,52,62,1|einfk0,50,66,0|eqxnnz,50,66,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0|f9nqbz,50,66,0|f9nqc0,52,62,1|fkgjjz,52,62,1|fkgjk0,50,66,0|fsdszz,50,66,0|fsdt00,52,62,1|g36m7z,52,62,1|g36m80,50,66,0|gb3vnz,50,66,0|gb3vo0,52,62,1|glwovz,52,62,1|glwow0,50,66,0|gu6wzz,50,66,0|gu6x00,52,62,1|h4mrjz,52,62,1|h4mrk0,50,66,0|hcwznz,50,66,0|hcwzo0,52,62,1|hncu7z,52,62,1|hncu80,50,66,0|hvn2bz,50,66,0|hvn2c0,52,62,1|i6fvjz,52,62,1|i6fvk0,50,66,0|ied4zz,50,66,0|ied500,52,62,1|ip5y7z,52,62,1|ip5y80,50,66,0|ix37nz,50,66,0|ix37o0,52,62,1|j7w0vz,52,62,1|j7w0w0,50,66,0|jeqebz,50,66,0|jeqec0,52,62,1|jqz27z,52,62,1|jqz280,50,66,0|jxggzz,50,66,0|jxgh00,52,62,1|k9p4vz,52,62,1|k9p4w0,50,66,0|kg6jnz,50,66,0|kg6jo0,52,62,1|ksf7jz,52,62,1|ksf7k0,50,66,0|kz9kzz,50,66,0|kz9l00,52,62,1|lbi8vz,52,62,1|lbi8w0,50,66,0|lhznnz,50,66,0|lhzno0,52,62,1|lu8bjz,52,62,1|lu8bk0,50,66,0|m0pqbz,50,66,0|m0pqc0,52,62,1|mcye7z,52,62,1|mcye80,50,66,0|mjfszz,50,66,0|mjft00,52,62,1|mvogvz,52,62,1|mvogw0,50,66,0|n25vnz,50,66,0|n25vo0,52,62,1|neejjz,52,62,1|neejk0,50,66,0|nkvybz,50,66,0|nkvyc0,52,62,1|nx4m7z,52,62,1|nx4m80,50,66,0|o3yznz,50,66,0|o3yzo0,52,62,1|og7njz,52,62,1|og7nk0,50,66,0|omp2bz,50,66,0|omp2c0,52,62,1|oyxq7z,52,62,1|oyxq80,50,66,0|p5f4zz,50,66,0|p5f500,52,62,1|phnsvz,52,62,1|phnsw0,50,66,0|po57nz,50,66,0|po57o0,52,62,1|q0dvjz,52,62,1|q0dvk0,50,66,0|q6vabz,50,66,0|q6vac0,52,62,1|qj3y7z,52,62,1|qj3y80,50,66,0|qpybnz,50,66,0|qpybo0,52,62,1|r26zjz,52,62,1|r26zk0,50,66,0|r8oebz,50,66,0|r8oec0,52,62,1|rkx27z,52,62,1|rkx280,50,66,0|rregzz,50,66,0|rreh00,52,62,1|s3n4vz,52,62,1|s3n4w0,50,66,0|sa4jnz,50,66,0|sa4jo0,52,62,1|smd7jz,52,62,1|smd7k0,50,66,0|ssumbz,50,66,0|ssumc0,52,62,1|t53a7z,52,62,1|t53a80,50,66,0|tbkozz,50,66,0|tbkp00,52,62,1|tntcvz,52,62,1|tntcw0,50,66,0|tunqbz,50,66,0|tunqc0,52,62,1|u6we7z,52,62,1|u6we80,50,66,0|uddszz,50,66,0|uddt00,52,62,1|upmgvz,52,62,1|upmgw0,50,66,0|uw3vnz,50,66,0|uw3vo0,52,62,1|v8cjjz,52,62,1|v8cjk0,50,66,0|vetybz,50,66,0|vetyc0,52,62,1|vr2m7z,52,62,1|vr2m80,50,66,0|vxk0zz,50,66,0|vxk100,52,62,1|w9sovz,52,62,1|w9sow0,50,66,0|wgn2bz,50,66,0|wgn2c0,52,62,1|wsvq7z,52,62,1|wsvq80,50,66,0|wzd4zz,50,66,0|wzd500,52,62,1|xblsvz,52,62,1|xblsw0,50,66,0|xi37nz,50,66,0|xi37o0,52,62,1|xubvjz,52,62,1|xubvk0,50,66,0|y0tabz,50,66,0|y0tac0,52,62,1|yd1y7z,52,62,1|yd1y80,50,66,0|yjjczz,50,66,0|yjjd00,52,62,1|yvs0vz,52,62,1|yvs0w0,50,66,0|z29fnz,50,66,0|z29fo0,52,62,1|zei3jz,52,62,1|zei3k0,50,66,0\",\"America/Eirunepe|,0,93,0|-t85f28,56,63,0|-jyl4w1,56,63,0|-jyl4w0,42,42,1|-jpavk1,42,42,1|-jpavk0,56,63,0|-jfs7g1,56,63,0|-jfs7g0,42,42,1|-j6iy81,42,42,1|-j6iy80,56,63,0|-ahcss1,56,63,0|-ahcss0,42,42,1|-aacy41,42,42,1|-aacy40,56,63,0|-9ykvg1,56,63,0|-9ykvg0,42,42,1|-9scvk1,42,42,1|-9scvk0,56,63,0|-9fsy41,56,63,0|-9fsy40,42,42,1|-99j3k1,42,42,1|-99j3k0,56,63,0|-8wz641,56,63,0|-8wz640,42,42,1|-8sckw1,42,42,1|-8sckw0,56,63,0|-35xgs1,56,63,0|-35xgs0,42,42,1|-31nu81,42,42,1|-31nu80,56,63,0|-2kdjg1,56,63,0|-2kdjg0,42,42,1|-2hccw1,42,42,1|-2hccw0,56,63,0|-24qks1,56,63,0|-24qks0,42,42,1|-203zk1,42,42,1|-203zk0,56,63,0|-1ni7g1,56,63,0|-1ni7g0,42,42,1|-1hc281,42,42,1|-1hc280,56,63,0|-14qa41,56,63,0|-14qa40,42,42,1|-yia81,42,42,1|-yia80,56,63,0|89jhvz,56,63,0|89jhw0,42,42,1|8gdprz,42,42,1|8gdps0,56,63,0|8rwlvz,56,63,0|8rwlw0,42,42,1|8xnxrz,42,42,1|8xnxs0,56,63,0|9aoj7z,56,63,0|9aoj80,42,42,1|9g2wfz,42,42,1|9g2wg0,56,63,0|cf0z7z,56,63,0|cf0z80,42,42,1|cli8fz,42,42,1|cli8g0,56,63,0|k2yb7z,56,63,0|k2yb80,42,42,0|mw14fz,42,42,0|mw14g0,56,63,0\",\"America/El_Salvador|,0,94,0|-pkm4tc,45,62,0|91ojbz,45,62,0|91ojc0,46,63,1|998ojz,46,63,1|998ok0,45,62,0|9kelzz,45,62,0|9kem00,46,63,1|9ryr7z,46,63,1|9ryr80,45,62,0\",\"America/Fort_Nelson|,0,95,0|-18vrvy1,51,40,0|-qzopk1,51,40,0|-qzopk0,57,66,1|-qplto1,57,66,1|-qplto0,51,40,0|-ek1w81,51,40,0|-ek1w80,68,66,1|-cq2tg1,68,66,1|-cq2tg0,69,66,1|-cnov01,69,66,1|-cnov00,51,40,0|-bu5tk1,51,40,0|-bu5tk0,57,66,1|-bm8po1,57,66,1|-bm8po0,51,40,0|-bbfqw1,51,40,0|-bbfqw0,57,66,1|-b3in01,57,66,1|-b3in00,51,40,0|-aspo81,51,40,0|-aspo80,57,66,1|-akskc1,57,66,1|-akskc0,51,40,0|-a9mmw1,51,40,0|-a9mmw0,57,66,1|-a22ho1,57,66,1|-a22ho0,51,40,0|-9qwk81,51,40,0|-9qwk80,57,66,1|-9izgc1,57,66,1|-9izgc0,51,40,0|-986hk1,51,40,0|-986hk0,57,66,1|-909do1,57,66,1|-909do0,51,40,0|-8pgew1,51,40,0|-8pgew0,57,66,1|-8hjb01,57,66,1|-8hjb00,51,40,0|-86qc81,51,40,0|-86qc80,57,66,1|-7yt8c1,57,66,1|-7yt8c0,51,40,0|-7o09k1,51,40,0|-7o09k0,57,66,1|-7g35o1,57,66,1|-7g35o0,51,40,0|-74x881,51,40,0|-74x880,57,66,1|-6x04c1,57,66,1|-6x04c0,51,40,0|-6m75k1,51,40,0|-6m75k0,57,66,1|-6ea1o1,57,66,1|-6ea1o0,51,40,0|-63h2w1,51,40,0|-63h2w0,57,66,1|-5vjz01,57,66,1|-5vjz00,51,40,0|-5kr081,51,40,0|-5kr080,57,66,1|-5ctwc1,57,66,1|-5ctwc0,51,40,0|-520xk1,51,40,0|-520xk0,57,66,1|-4u3to1,57,66,1|-4u3to0,51,40,0|-4ixw81,51,40,0|-4ixw80,57,66,1|-4bdr01,57,66,1|-4bdr00,51,40,0|-407tk1,51,40,0|-407tk0,57,66,1|-3quv01,57,66,1|-3quv00,51,40,0|-3hhqw1,51,40,0|-3hhqw0,57,66,1|-384sc1,57,66,1|-384sc0,51,40,0|-2yro81,51,40,0|-2yro80,57,66,1|-2pepo1,57,66,1|-2pepo0,51,40,0|-2g1lk1,51,40,0|-2g1lk0,57,66,1|-26boc1,57,66,1|-26boc0,51,40,0|-1xbiw1,51,40,0|-1xbiw0,57,66,1|-1nllo1,57,66,1|-1nllo0,51,40,0|-1e8hk1,51,40,0|-1e8hk0,57,66,1|-14vj01,57,66,1|-14vj00,51,40,0|-view1,51,40,0|-view0,57,66,1|-m5gc1,57,66,1|-m5gc0,51,40,0|-csc81,51,40,0|-csc80,57,66,1|-3fdo1,57,66,1|-3fdo0,51,40,0|5xqfz,51,40,0|5xqg0,57,66,1|faozz,57,66,1|fap00,51,40,0|ont3z,51,40,0|ont40,57,66,1|ydqbz,57,66,1|ydqc0,51,40,0|17qufz,51,40,0|17qug0,57,66,1|1h3szz,57,66,1|1h3t00,51,40,0|1qgx3z,51,40,0|1qgx40,57,66,1|1ztvnz,57,66,1|1ztvo0,51,40,0|296zrz,51,40,0|296zs0,57,66,1|2ijybz,57,66,1|2ijyc0,51,40,0|2rx2fz,51,40,0|2rx2g0,57,66,1|31a0zz,57,66,1|31a100,51,40,0|3an53z,51,40,0|3an540,57,66,1|3kd2bz,57,66,1|3kd2c0,51,40,0|3td7rz,51,40,0|3td7s0,57,66,1|4334zz,57,66,1|433500,51,40,0|4cg93z,51,40,0|4cg940,57,66,1|4lt7nz,57,66,1|4lt7o0,51,40,0|4v6brz,51,40,0|4v6bs0,57,66,1|54jabz,57,66,1|54jac0,51,40,0|5dwefz,51,40,0|5dweg0,57,66,1|5n9czz,57,66,1|5n9d00,51,40,0|5wmh3z,51,40,0|5wmh40,57,66,1|65zfnz,57,66,1|65zfo0,51,40,0|6fcjrz,51,40,0|6fcjs0,57,66,1|6p2gzz,57,66,1|6p2h00,51,40,0|6y2mfz,51,40,0|6y2mg0,57,66,1|77sjnz,57,66,1|77sjo0,51,40,0|7h5nrz,51,40,0|7h5ns0,57,66,1|7qimbz,57,66,1|7qimc0,51,40,0|7zvqfz,51,40,0|7zvqg0,57,66,1|898ozz,57,66,1|898p00,51,40,0|8ilt3z,51,40,0|8ilt40,57,66,1|8ryrnz,57,66,1|8ryro0,51,40,0|908zrz,51,40,0|908zs0,57,66,1|9aoubz,57,66,1|9aouc0,51,40,0|9iz2fz,51,40,0|9iz2g0,57,66,1|9trvnz,57,66,1|9trvo0,51,40,0|a1p53z,51,40,0|a1p540,57,66,1|achybz,57,66,1|achyc0,51,40,0|akf7rz,51,40,0|akf7s0,57,66,1|av80zz,57,66,1|av8100,51,40,0|b3i93z,51,40,0|b3i940,57,66,1|bdy3nz,57,66,1|bdy3o0,51,40,0|bm8brz,51,40,0|bm8bs0,57,66,1|bwo6bz,57,66,1|bwo6c0,51,40,0|c4yefz,51,40,0|c4yeg0,57,66,1|cfr7nz,57,66,1|cfr7o0,51,40,0|cnoh3z,51,40,0|cnoh40,57,66,1|cyhabz,57,66,1|cyhac0,51,40,0|d6ejrz,51,40,0|d6ejs0,57,66,1|dh7czz,57,66,1|dh7d00,51,40,0|dphl3z,51,40,0|dphl40,57,66,1|dzxfnz,57,66,1|dzxfo0,51,40,0|e87nrz,51,40,0|e87ns0,57,66,1|einibz,57,66,1|einic0,51,40,0|eqxqfz,51,40,0|eqxqg0,57,66,1|f1dkzz,57,66,1|f1dl00,51,40,0|f9nt3z,51,40,0|f9nt40,57,66,1|fkgmbz,57,66,1|fkgmc0,51,40,0|fsdvrz,51,40,0|fsdvs0,57,66,1|g36ozz,57,66,1|g36p00,51,40,0|gb3yfz,51,40,0|gb3yg0,57,66,1|glwrnz,57,66,1|glwro0,51,40,0|gu6zrz,51,40,0|gu6zs0,57,66,1|h4mubz,57,66,1|h4muc0,51,40,0|hcx2fz,51,40,0|hcx2g0,57,66,1|hncwzz,57,66,1|hncx00,51,40,0|hvn53z,51,40,0|hvn540,57,66,1|i6fybz,57,66,1|i6fyc0,51,40,0|ied7rz,51,40,0|ied7s0,57,66,1|ip60zz,57,66,1|ip6100,51,40,0|ix3afz,51,40,0|ix3ag0,57,66,1|j7w3nz,57,66,1|j7w3o0,51,40,0|jeqh3z,51,40,0|jeqh40,57,66,1|jqz4zz,57,66,1|jqz500,51,40,0|jxgjrz,51,40,0|jxgjs0,57,66,1|k9p7nz,57,66,1|k9p7o0,51,40,0|kg6mfz,51,40,0|kg6mg0,57,66,1|ksfabz,57,66,1|ksfac0,51,40,0|kz9nrz,51,40,0|kz9ns0,57,66,1|lbibnz,57,66,1|lbibo0,51,40,0|lhzqfz,51,40,0|lhzqg0,57,66,1|lu8ebz,57,66,1|lu8ec0,51,40,0|m0pt3z,51,40,0|m0pt40,57,66,1|mcygzz,57,66,1|mcyh00,51,40,0|mjfvrz,51,40,0|mjfvs0,57,66,1|mvojnz,57,66,1|mvojo0,51,40,0|n25yfz,51,40,0|n25yg0,57,66,1|neembz,57,66,1|neemc0,51,40,0|nkw13z,51,40,0|nkw140,50,66,0\",\"America/Fortaleza|,0,96,0|-t85kvc,39,44,0|-jylag1,39,44,0|-jylag0,40,45,1|-jpb141,40,45,1|-jpb140,39,44,0|-jfsd01,39,44,0|-jfsd00,40,45,1|-j6j3s1,40,45,1|-j6j3s0,39,44,0|-ahcyc1,39,44,0|-ahcyc0,40,45,1|-aad3o1,40,45,1|-aad3o0,39,44,0|-9yl101,39,44,0|-9yl100,40,45,1|-9sd141,40,45,1|-9sd140,39,44,0|-9ft3o1,39,44,0|-9ft3o0,40,45,1|-99j941,40,45,1|-99j940,39,44,0|-8wzbo1,39,44,0|-8wzbo0,40,45,1|-8scqg1,40,45,1|-8scqg0,39,44,0|-35xmc1,39,44,0|-35xmc0,40,45,1|-31nzs1,40,45,1|-31nzs0,39,44,0|-2kdp01,39,44,0|-2kdp00,40,45,1|-2hcig1,40,45,1|-2hcig0,39,44,0|-24qqc1,39,44,0|-24qqc0,40,45,1|-204541,40,45,1|-204540,39,44,0|-1nid01,39,44,0|-1nid00,40,45,1|-1hc7s1,40,45,1|-1hc7s0,39,44,0|-14qfo1,39,44,0|-14qfo0,40,45,1|-yifs1,40,45,1|-yifs0,39,44,0|89jcbz,39,44,0|89jcc0,40,45,1|8gdk7z,40,45,1|8gdk80,39,44,0|8rwgbz,39,44,0|8rwgc0,40,45,1|8xns7z,40,45,1|8xns80,39,44,0|9aodnz,39,44,0|9aodo0,40,45,1|9g2qvz,40,45,1|9g2qw0,39,44,0|9t1hnz,39,44,0|9t1ho0,40,45,1|9yfuvz,40,45,1|9yfuw0,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|ahvuvz,40,45,1|ahvuw0,39,44,0|fj0azz,39,44,0|fj0b00,40,45,1|fqkg7z,40,45,1|fqkg80,39,44,0|g23cbz,39,44,0|g23cc0,40,45,1|g2t6vz,40,45,1|g2t6w0,39,44,0|gl6dnz,39,44,0|gl6do0,40,45,1|grnmvz,40,45,1|grnmw0,39,44,0\",\"America/Glace_Bay|,0,97,0|-z94kwc,32,42,0|-qzp0o1,32,42,0|-qzp0o0,54,44,1|-qpm4s1,54,44,1|-qpm4s0,32,42,0|-ek27c1,32,42,0|-ek27c0,33,44,1|-cq2tg1,33,44,1|-cq2tg0,34,44,1|-cnp641,34,44,1|-cnp640,32,42,0|-8pgq01,32,42,0|-8pgq00,54,44,1|-8hjm41,54,44,1|-8hjm40,32,42,0|17qjbz,32,42,0|17qjc0,54,44,1|1h3hvz,54,44,1|1h3hw0,32,42,0|1qglzz,32,42,0|1qgm00,54,44,1|1ztkjz,54,44,1|1ztkk0,32,42,0|296onz,32,42,0|296oo0,54,44,1|2ijn7z,54,44,1|2ijn80,32,42,0|2rwrbz,32,42,0|2rwrc0,54,44,1|319pvz,54,44,1|319pw0,32,42,0|3amtzz,32,42,0|3amu00,54,44,1|3kcr7z,54,44,1|3kcr80,32,42,0|3tcwnz,32,42,0|3tcwo0,54,44,1|432tvz,54,44,1|432tw0,32,42,0|4cfxzz,32,42,0|4cfy00,54,44,1|4lswjz,54,44,1|4lswk0,32,42,0|4v60nz,32,42,0|4v60o0,54,44,1|54iz7z,54,44,1|54iz80,32,42,0|5dw3bz,32,42,0|5dw3c0,54,44,1|5n91vz,54,44,1|5n91w0,32,42,0|5wm5zz,32,42,0|5wm600,54,44,1|65z4jz,54,44,1|65z4k0,32,42,0|6fc8nz,32,42,0|6fc8o0,54,44,1|6p25vz,54,44,1|6p25w0,32,42,0|6y2bbz,32,42,0|6y2bc0,54,44,1|77s8jz,54,44,1|77s8k0,32,42,0|7h5cnz,32,42,0|7h5co0,54,44,1|7qib7z,54,44,1|7qib80,32,42,0|7zvfbz,32,42,0|7zvfc0,54,44,1|898dvz,54,44,1|898dw0,32,42,0|8ilhzz,32,42,0|8ili00,54,44,1|8rygjz,54,44,1|8rygk0,32,42,0|908onz,32,42,0|908oo0,54,44,1|9aoj7z,54,44,1|9aoj80,32,42,0|9iyrbz,32,42,0|9iyrc0,54,44,1|9trkjz,54,44,1|9trkk0,32,42,0|a1otzz,32,42,0|a1ou00,54,44,1|achn7z,54,44,1|achn80,32,42,0|akewnz,32,42,0|akewo0,54,44,1|av7pvz,54,44,1|av7pw0,32,42,0|b3hxzz,32,42,0|b3hy00,54,44,1|bdxsjz,54,44,1|bdxsk0,32,42,0|bm80nz,32,42,0|bm80o0,54,44,1|bwnv7z,54,44,1|bwnv80,32,42,0|c4y3bz,32,42,0|c4y3c0,54,44,1|cfqwjz,54,44,1|cfqwk0,32,42,0|cno5zz,32,42,0|cno600,54,44,1|cygz7z,54,44,1|cygz80,32,42,0|d6e8nz,32,42,0|d6e8o0,54,44,1|dh71vz,54,44,1|dh71w0,32,42,0|dph9zz,32,42,0|dpha00,54,44,1|dzx4jz,54,44,1|dzx4k0,32,42,0|e87cnz,32,42,0|e87co0,54,44,1|ein77z,54,44,1|ein780,32,42,0|eqxfbz,32,42,0|eqxfc0,54,44,1|f1d9vz,54,44,1|f1d9w0,32,42,0|f9nhzz,32,42,0|f9ni00,54,44,1|fkgb7z,54,44,1|fkgb80,32,42,0|fsdknz,32,42,0|fsdko0,54,44,1|g36dvz,54,44,1|g36dw0,32,42,0|gb3nbz,32,42,0|gb3nc0,54,44,1|glwgjz,54,44,1|glwgk0,32,42,0|gu6onz,32,42,0|gu6oo0,54,44,1|h4mj7z,54,44,1|h4mj80,32,42,0|hcwrbz,32,42,0|hcwrc0,54,44,1|hnclvz,54,44,1|hnclw0,32,42,0|hvmtzz,32,42,0|hvmu00,54,44,1|i6fn7z,54,44,1|i6fn80,32,42,0|iecwnz,32,42,0|iecwo0,54,44,1|ip5pvz,54,44,1|ip5pw0,32,42,0|ix2zbz,32,42,0|ix2zc0,54,44,1|j7vsjz,54,44,1|j7vsk0,32,42,0|jeq5zz,32,42,0|jeq600,54,44,1|jqytvz,54,44,1|jqytw0,32,42,0|jxg8nz,32,42,0|jxg8o0,54,44,1|k9owjz,54,44,1|k9owk0,32,42,0|kg6bbz,32,42,0|kg6bc0,54,44,1|ksez7z,54,44,1|ksez80,32,42,0|kz9cnz,32,42,0|kz9co0,54,44,1|lbi0jz,54,44,1|lbi0k0,32,42,0|lhzfbz,32,42,0|lhzfc0,54,44,1|lu837z,54,44,1|lu8380,32,42,0|m0phzz,32,42,0|m0pi00,54,44,1|mcy5vz,54,44,1|mcy5w0,32,42,0|mjfknz,32,42,0|mjfko0,54,44,1|mvo8jz,54,44,1|mvo8k0,32,42,0|n25nbz,32,42,0|n25nc0,54,44,1|neeb7z,54,44,1|neeb80,32,42,0|nkvpzz,32,42,0|nkvq00,54,44,1|nx4dvz,54,44,1|nx4dw0,32,42,0|o3yrbz,32,42,0|o3yrc0,54,44,1|og7f7z,54,44,1|og7f80,32,42,0|omotzz,32,42,0|omou00,54,44,1|oyxhvz,54,44,1|oyxhw0,32,42,0|p5ewnz,32,42,0|p5ewo0,54,44,1|phnkjz,54,44,1|phnkk0,32,42,0|po4zbz,32,42,0|po4zc0,54,44,1|q0dn7z,54,44,1|q0dn80,32,42,0|q6v1zz,32,42,0|q6v200,54,44,1|qj3pvz,54,44,1|qj3pw0,32,42,0|qpy3bz,32,42,0|qpy3c0,54,44,1|r26r7z,54,44,1|r26r80,32,42,0|r8o5zz,32,42,0|r8o600,54,44,1|rkwtvz,54,44,1|rkwtw0,32,42,0|rre8nz,32,42,0|rre8o0,54,44,1|s3mwjz,54,44,1|s3mwk0,32,42,0|sa4bbz,32,42,0|sa4bc0,54,44,1|smcz7z,54,44,1|smcz80,32,42,0|ssudzz,32,42,0|ssue00,54,44,1|t531vz,54,44,1|t531w0,32,42,0|tbkgnz,32,42,0|tbkgo0,54,44,1|tnt4jz,54,44,1|tnt4k0,32,42,0|tunhzz,32,42,0|tuni00,54,44,1|u6w5vz,54,44,1|u6w5w0,32,42,0|uddknz,32,42,0|uddko0,54,44,1|upm8jz,54,44,1|upm8k0,32,42,0|uw3nbz,32,42,0|uw3nc0,54,44,1|v8cb7z,54,44,1|v8cb80,32,42,0|vetpzz,32,42,0|vetq00,54,44,1|vr2dvz,54,44,1|vr2dw0,32,42,0|vxjsnz,32,42,0|vxjso0,54,44,1|w9sgjz,54,44,1|w9sgk0,32,42,0|wgmtzz,32,42,0|wgmu00,54,44,1|wsvhvz,54,44,1|wsvhw0,32,42,0|wzcwnz,32,42,0|wzcwo0,54,44,1|xblkjz,54,44,1|xblkk0,32,42,0|xi2zbz,32,42,0|xi2zc0,54,44,1|xubn7z,54,44,1|xubn80,32,42,0|y0t1zz,32,42,0|y0t200,54,44,1|yd1pvz,54,44,1|yd1pw0,32,42,0|yjj4nz,32,42,0|yjj4o0,54,44,1|yvrsjz,54,44,1|yvrsk0,32,42,0|z297bz,32,42,0|z297c0,54,44,1|zehv7z,54,44,1|zehv80,32,42,0\",\"America/Goose_Bay|,0,98,0|-18vs7h8,24,99,0|-qzp20l,24,99,0|-qzp20k,72,100,1|-qpm64l,72,100,1|-qpm64k,24,99,0|-i52u8l,24,99,0|-i52u8k,24,101,0|-hk3aa1,24,101,0|-hk3aa0,72,102,1|-hcj521,72,102,1|-hcj520,24,101,0|-h1d7m1,24,101,0|-h1d7m0,72,102,1|-gtt2e1,72,102,1|-gtt2e0,24,101,0|-gin4y1,24,101,0|-gin4y0,72,102,1|-gb2zq1,72,102,1|-gb2zq0,24,101,0|-fzk3m1,24,101,0|-fzk3m0,72,102,1|-fscx21,72,102,1|-fscx20,24,101,0|-fgu0y1,24,101,0|-fgu0y0,72,102,1|-f99vq1,72,102,1|-f99vq0,24,101,0|-ey3ya1,24,101,0|-ey3ya0,72,102,1|-eqjt21,72,102,1|-eqjt20,24,101,0|-efdvm1,24,101,0|-efdvm0,25,102,1|-cq2tg1,25,102,1|-cq2tg0,26,102,1|-cnp7i1,26,102,1|-cnp7i0,24,101,0|-cc6be1,24,101,0|-cc6be0,72,102,1|-c4m661,72,102,1|-c4m660,24,101,0|-btg8q1,24,101,0|-btg8q0,72,102,1|-blw3i1,72,102,1|-blw3i0,24,101,0|-baq621,24,101,0|-baq620,72,102,1|-b360u1,72,102,1|-b360u0,24,101,0|-as03e1,24,101,0|-as03e0,72,102,1|-akfy61,72,102,1|-akfy60,24,101,0|-a8x221,24,101,0|-a8x220,72,102,1|-a1cwu1,72,102,1|-a1cwu0,24,101,0|-9qwwq1,24,101,0|-9qwwq0,72,102,1|-9izsu1,72,102,1|-9izsu0,24,101,0|-986u21,24,101,0|-986u20,72,102,1|-909q61,72,102,1|-909q60,24,101,0|-8pgre1,24,101,0|-8pgre0,72,102,1|-8hjni1,72,102,1|-8hjni0,24,101,0|-86qoq1,24,101,0|-86qoq0,72,102,1|-7ytku1,72,102,1|-7ytku0,24,101,0|-7o0m21,24,101,0|-7o0m20,72,102,1|-7g3i61,72,102,1|-7g3i60,24,101,0|-74xkq1,24,101,0|-74xkq0,72,102,1|-6x0gu1,72,102,1|-6x0gu0,24,101,0|-6m7i21,24,101,0|-6m7i20,72,102,1|-6eae61,72,102,1|-6eae60,24,101,0|-63hfe1,24,101,0|-63hfe0,72,102,1|-5vkbi1,72,102,1|-5vkbi0,24,101,0|-5krcq1,24,101,0|-5krcq0,72,102,1|-5cu8u1,72,102,1|-5cu8u0,24,101,0|-521a21,24,101,0|-521a20,72,102,1|-4sbcu1,72,102,1|-4sbcu0,24,101,0|-4iy8q1,24,101,0|-4iy8q0,72,102,1|-49la61,72,102,1|-49la60,24,101,0|-408621,24,101,0|-408620,72,102,1|-3qv7i1,72,102,1|-3qv7i0,24,101,0|-3hi3e1,24,101,0|-3hi3e0,72,102,1|-3854u1,72,102,1|-3854u0,24,101,0|-2ys0q1,24,101,0|-2ys0q0,72,102,1|-2pf261,72,102,1|-2pf260,24,101,0|-2g1y21,24,101,0|-2g1y20,72,102,1|-26c0u1,72,102,1|-26c0u0,24,101,0|-1zdy21,24,101,0|-1zdy20,32,42,0|-1xbu01,32,42,0|-1xbu00,54,44,1|-1nlws1,54,44,1|-1nlws0,32,42,0|-1e8so1,32,42,0|-1e8so0,54,44,1|-14vu41,54,44,1|-14vu40,32,42,0|-viq01,32,42,0|-viq00,54,44,1|-m5rg1,54,44,1|-m5rg0,32,42,0|-csnc1,32,42,0|-csnc0,54,44,1|-3fos1,54,44,1|-3fos0,32,42,0|5xfbz,32,42,0|5xfc0,54,44,1|fadvz,54,44,1|fadw0,32,42,0|onhzz,32,42,0|oni00,54,44,1|ydf7z,54,44,1|ydf80,32,42,0|17qjbz,32,42,0|17qjc0,54,44,1|1h3hvz,54,44,1|1h3hw0,32,42,0|1qglzz,32,42,0|1qgm00,54,44,1|1ztkjz,54,44,1|1ztkk0,32,42,0|296onz,32,42,0|296oo0,54,44,1|2ijn7z,54,44,1|2ijn80,32,42,0|2rwrbz,32,42,0|2rwrc0,54,44,1|319pvz,54,44,1|319pw0,32,42,0|3amtzz,32,42,0|3amu00,54,44,1|3kcr7z,54,44,1|3kcr80,32,42,0|3tcwnz,32,42,0|3tcwo0,54,44,1|432tvz,54,44,1|432tw0,32,42,0|4cfxzz,32,42,0|4cfy00,54,44,1|4lswjz,54,44,1|4lswk0,32,42,0|4v60nz,32,42,0|4v60o0,54,44,1|54iz7z,54,44,1|54iz80,32,42,0|5dw3bz,32,42,0|5dw3c0,54,44,1|5n91vz,54,44,1|5n91w0,32,42,0|5wm5zz,32,42,0|5wm600,54,44,1|65z4jz,54,44,1|65z4k0,32,42,0|6fc8nz,32,42,0|6fc8o0,54,44,1|6p25vz,54,44,1|6p25w0,32,42,0|6y2bbz,32,42,0|6y2bc0,54,44,1|77s8jz,54,44,1|77s8k0,32,42,0|7h5cnz,32,42,0|7h5co0,54,44,1|7qib7z,54,44,1|7qib80,32,42,0|7zvfbz,32,42,0|7zvfc0,54,44,1|898dvz,54,44,1|898dw0,32,42,0|8ilhzz,32,42,0|8ili00,54,44,1|8rygjz,54,44,1|8rygk0,32,42,0|908j5n,32,42,0|908j5o,54,44,1|9aodpn,54,44,1|9aodpo,32,42,0|9iyltn,32,42,0|9iylto,73,45,1|9trc9n,73,45,1|9trc9o,32,42,0|a1oohn,32,42,0|a1ooho,54,44,1|achhpn,54,44,1|achhpo,32,42,0|aker5n,32,42,0|aker5o,54,44,1|av7kdn,54,44,1|av7kdo,32,42,0|b3hshn,32,42,0|b3hsho,54,44,1|bdxn1n,54,44,1|bdxn1o,32,42,0|bm7v5n,32,42,0|bm7v5o,54,44,1|bwnppn,54,44,1|bwnppo,32,42,0|c4xxtn,32,42,0|c4xxto,54,44,1|cfqr1n,54,44,1|cfqr1o,32,42,0|cno0hn,32,42,0|cno0ho,54,44,1|cygtpn,54,44,1|cygtpo,32,42,0|d6e35n,32,42,0|d6e35o,54,44,1|dh6wdn,54,44,1|dh6wdo,32,42,0|dph4hn,32,42,0|dph4ho,54,44,1|dzwz1n,54,44,1|dzwz1o,32,42,0|e8775n,32,42,0|e8775o,54,44,1|ein1pn,54,44,1|ein1po,32,42,0|eqx9tn,32,42,0|eqx9to,54,44,1|f1d4dn,54,44,1|f1d4do,32,42,0|f9nchn,32,42,0|f9ncho,54,44,1|fkg5pn,54,44,1|fkg5po,32,42,0|fsdf5n,32,42,0|fsdf5o,54,44,1|g368dn,54,44,1|g368do,32,42,0|gb3htn,32,42,0|gb3hto,54,44,1|glwb1n,54,44,1|glwb1o,32,42,0|gu6j5n,32,42,0|gu6j5o,54,44,1|h4mdpn,54,44,1|h4mdpo,32,42,0|hcwltn,32,42,0|hcwlto,54,44,1|hncgdn,54,44,1|hncgdo,32,42,0|hvmohn,32,42,0|hvmoho,54,44,1|i6fhpn,54,44,1|i6fhpo,32,42,0|iecr5n,32,42,0|iecr5o,54,44,1|ip5kdn,54,44,1|ip5kdo,32,42,0|ix2ttn,32,42,0|ix2tto,54,44,1|j7vn1n,54,44,1|j7vn1o,32,42,0|jeq0hn,32,42,0|jeq0ho,54,44,1|jqyodn,54,44,1|jqyodo,32,42,0|jxg35n,32,42,0|jxg35o,54,44,1|k9or1n,54,44,1|k9or1o,32,42,0|kg65tn,32,42,0|kg65to,54,44,1|ksetpn,54,44,1|ksetpo,32,42,0|kz975n,32,42,0|kz975o,54,44,1|lbhv1n,54,44,1|lbhv1o,32,42,0|lhz9tn,32,42,0|lhz9to,54,44,1|lu837z,54,44,1|lu8380,32,42,0|m0phzz,32,42,0|m0pi00,54,44,1|mcy5vz,54,44,1|mcy5w0,32,42,0|mjfknz,32,42,0|mjfko0,54,44,1|mvo8jz,54,44,1|mvo8k0,32,42,0|n25nbz,32,42,0|n25nc0,54,44,1|neeb7z,54,44,1|neeb80,32,42,0|nkvpzz,32,42,0|nkvq00,54,44,1|nx4dvz,54,44,1|nx4dw0,32,42,0|o3yrbz,32,42,0|o3yrc0,54,44,1|og7f7z,54,44,1|og7f80,32,42,0|omotzz,32,42,0|omou00,54,44,1|oyxhvz,54,44,1|oyxhw0,32,42,0|p5ewnz,32,42,0|p5ewo0,54,44,1|phnkjz,54,44,1|phnkk0,32,42,0|po4zbz,32,42,0|po4zc0,54,44,1|q0dn7z,54,44,1|q0dn80,32,42,0|q6v1zz,32,42,0|q6v200,54,44,1|qj3pvz,54,44,1|qj3pw0,32,42,0|qpy3bz,32,42,0|qpy3c0,54,44,1|r26r7z,54,44,1|r26r80,32,42,0|r8o5zz,32,42,0|r8o600,54,44,1|rkwtvz,54,44,1|rkwtw0,32,42,0|rre8nz,32,42,0|rre8o0,54,44,1|s3mwjz,54,44,1|s3mwk0,32,42,0|sa4bbz,32,42,0|sa4bc0,54,44,1|smcz7z,54,44,1|smcz80,32,42,0|ssudzz,32,42,0|ssue00,54,44,1|t531vz,54,44,1|t531w0,32,42,0|tbkgnz,32,42,0|tbkgo0,54,44,1|tnt4jz,54,44,1|tnt4k0,32,42,0|tunhzz,32,42,0|tuni00,54,44,1|u6w5vz,54,44,1|u6w5w0,32,42,0|uddknz,32,42,0|uddko0,54,44,1|upm8jz,54,44,1|upm8k0,32,42,0|uw3nbz,32,42,0|uw3nc0,54,44,1|v8cb7z,54,44,1|v8cb80,32,42,0|vetpzz,32,42,0|vetq00,54,44,1|vr2dvz,54,44,1|vr2dw0,32,42,0|vxjsnz,32,42,0|vxjso0,54,44,1|w9sgjz,54,44,1|w9sgk0,32,42,0|wgmtzz,32,42,0|wgmu00,54,44,1|wsvhvz,54,44,1|wsvhw0,32,42,0|wzcwnz,32,42,0|wzcwo0,54,44,1|xblkjz,54,44,1|xblkk0,32,42,0|xi2zbz,32,42,0|xi2zc0,54,44,1|xubn7z,54,44,1|xubn80,32,42,0|y0t1zz,32,42,0|y0t200,54,44,1|yd1pvz,54,44,1|yd1pw0,32,42,0|yjj4nz,32,42,0|yjj4o0,54,44,1|yvrsjz,54,44,1|yvrsk0,32,42,0|z297bz,32,42,0|z297c0,54,44,1|zehv7z,54,44,1|zehv80,32,42,0\",\"America/Grand_Turk|,0,103,0|-15r0w5s,74,104,0|-u85og3,74,104,0|-u85og2,49,63,0|4v63fz,49,63,0|4v63g0,62,42,1|54j1zz,62,42,1|54j200,49,63,0|5dw63z,49,63,0|5dw640,62,42,1|5n94nz,62,42,1|5n94o0,49,63,0|5wm8rz,49,63,0|5wm8s0,62,42,1|65z7bz,62,42,1|65z7c0,49,63,0|6fcbfz,49,63,0|6fcbg0,62,42,1|6p28nz,62,42,1|6p28o0,49,63,0|6y2e3z,49,63,0|6y2e40,62,42,1|77sbbz,62,42,1|77sbc0,49,63,0|7h5ffz,49,63,0|7h5fg0,62,42,1|7qidzz,62,42,1|7qie00,49,63,0|7zvi3z,49,63,0|7zvi40,62,42,1|898gnz,62,42,1|898go0,49,63,0|8ilkrz,49,63,0|8ilks0,62,42,1|8ryjbz,62,42,1|8ryjc0,49,63,0|908rfz,49,63,0|908rg0,62,42,1|9aolzz,62,42,1|9aom00,49,63,0|9iyu3z,49,63,0|9iyu40,62,42,1|9trnbz,62,42,1|9trnc0,49,63,0|a1owrz,49,63,0|a1ows0,62,42,1|achpzz,62,42,1|achq00,49,63,0|akezfz,49,63,0|akezg0,62,42,1|av7snz,62,42,1|av7so0,49,63,0|b3i0rz,49,63,0|b3i0s0,62,42,1|bdxvbz,62,42,1|bdxvc0,49,63,0|bm83fz,49,63,0|bm83g0,62,42,1|bwnxzz,62,42,1|bwny00,49,63,0|c4y63z,49,63,0|c4y640,62,42,1|cfqzbz,62,42,1|cfqzc0,49,63,0|cno8rz,49,63,0|cno8s0,62,42,1|cyh1zz,62,42,1|cyh200,49,63,0|d6ebfz,49,63,0|d6ebg0,62,42,1|dh74nz,62,42,1|dh74o0,49,63,0|dphcrz,49,63,0|dphcs0,62,42,1|dzx7bz,62,42,1|dzx7c0,49,63,0|e87ffz,49,63,0|e87fg0,62,42,1|ein9zz,62,42,1|eina00,49,63,0|eqxi3z,49,63,0|eqxi40,62,42,1|f1dcnz,62,42,1|f1dco0,49,63,0|f9nkrz,49,63,0|f9nks0,62,42,1|fkgdzz,62,42,1|fkge00,49,63,0|fsdnfz,49,63,0|fsdng0,62,42,1|g36gnz,62,42,1|g36go0,49,63,0|gb3q3z,49,63,0|gb3q40,62,42,1|glwjbz,62,42,1|glwjc0,49,63,0|gu6rfz,49,63,0|gu6rg0,62,42,1|h4mlzz,62,42,1|h4mm00,49,63,0|hcwu3z,49,63,0|hcwu40,62,42,1|hnconz,62,42,1|hncoo0,49,63,0|hvmwrz,49,63,0|hvmws0,62,42,1|i6fpzz,62,42,1|i6fq00,49,63,0|ieczfz,49,63,0|ieczg0,62,42,1|ip5snz,62,42,1|ip5so0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,32,42,0|p5ezfz,32,42,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Grenada|,0,41,0|-u6m79w,32,42,0\",\"America/Guadeloupe|,0,41,0|-u6m79w,32,42,0\",\"America/Guatemala|,0,105,0|-qqqskk,45,62,0|219hzz,45,62,0|219i00,46,63,1|25xxvz,46,63,1|25xxw0,45,62,0|6zgbbz,45,62,0|6zgbc0,46,63,1|75tv7z,46,63,1|75tv80,45,62,0|b2q5zz,45,62,0|b2q600,46,63,1|bbd77z,46,63,1|bbd780,45,62,0|iyitzz,45,62,0|iyiu00,46,63,1|j6fxvz,46,63,1|j6fxw0,45,62,0\",\"America/Guayaquil|,0,106,0|-15r0ujs,75,107,0|-kcr84p,75,107,0|-kcr84o,56,63,0|byetvz,56,63,0|byetw0,42,42,1|c1yj3z,42,42,1|c1yj40,56,63,0\",\"America/Guyana|,0,108,0|-smcak8,76,109,0|2wsiez,76,109,0|2wsif0,39,44,0|ayjxnz,39,44,0|ayjxo0,42,42,0\",\"America/Halifax|,0,110,0|-z94k80,32,42,0|-s1x3k1,32,42,0|-s1x3k0,54,44,1|-rsiac1,54,44,1|-rsiac0,32,42,0|-qzp0o1,32,42,0|-qzp0o0,54,44,1|-qpm4s1,54,44,1|-qpm4s0,32,42,0|-pwt681,32,42,0|-pwt680,54,44,1|-pr1uc1,54,44,1|-pr1uc0,32,42,0|-pe6sw1,32,42,0|-pe6sw0,54,44,1|-p7wyc1,54,44,1|-p7wyc0,32,42,0|-ovpzk1,32,42,0|-ovpzk0,54,44,1|-op5101,54,44,1|-op5100,32,42,0|-ocmy81,32,42,0|-ocmy80,54,44,1|-o6eyc1,54,44,1|-o6eyc0,32,42,0|-ntwvk1,32,42,0|-ntwvk0,54,44,1|-nn0t01,54,44,1|-nn0t00,32,42,0|-nb6sw1,32,42,0|-nb6sw0,54,44,1|-n3kt01,54,44,1|-n3kt00,32,42,0|-mrqsw1,32,42,0|-mrqsw0,54,44,1|-mlkno1,54,44,1|-mlkno0,32,42,0|-m9qnk1,32,42,0|-m9qnk0,54,44,1|-m24no1,54,44,1|-m24no0,32,42,0|-lqank1,32,42,0|-lqank0,54,44,1|-lk6d01,54,44,1|-lk6d00,32,42,0|-l7kkw1,32,42,0|-l7kkw0,54,44,1|-l1pjo1,54,44,1|-l1pjo0,32,42,0|-koui81,32,42,0|-koui80,54,44,1|-kibec1,54,44,1|-kibec0,32,42,0|-k64fk1,32,42,0|-k64fk0,54,44,1|-jyvec1,54,44,1|-jyvec0,32,42,0|-jnrbk1,32,42,0|-jnrbk0,54,44,1|-jg5bo1,54,44,1|-jg5bo0,32,42,0|-j518w1,32,42,0|-j518w0,54,44,1|-ix2ac1,54,44,1|-ix2ac0,32,42,0|-il8a81,32,42,0|-il8a80,54,44,1|-if3zo1,54,44,1|-if3zo0,32,42,0|-i1sa81,32,42,0|-i1sa80,54,44,1|-hvm501,54,44,1|-hvm500,32,42,0|-hj0cw1,32,42,0|-hj0cw0,54,44,1|-hdlzo1,54,44,1|-hdlzo0,32,42,0|-h1rzk1,32,42,0|-h1rzk0,54,44,1|-gu5zo1,54,44,1|-gu5zo0,32,42,0|-gj1ww1,32,42,0|-gj1ww0,54,44,1|-gbfx01,54,44,1|-gbfx00,32,42,0|-fyvzk1,32,42,0|-fyvzk0,54,44,1|-fspuc1,54,44,1|-fspuc0,32,42,0|-fh8sw1,32,42,0|-fh8sw0,54,44,1|-f9mt01,54,44,1|-f9mt00,32,42,0|-eyiq81,32,42,0|-eyiq80,54,44,1|-eqwqc1,54,44,1|-eqwqc0,32,42,0|-ek27c1,32,42,0|-ek27c0,33,44,1|-cq2tg1,33,44,1|-cq2tg0,34,44,1|-cnp641,34,44,1|-cnp640,32,42,0|-ccw7c1,32,42,0|-ccw7c0,54,44,1|-c4z3g1,54,44,1|-c4z3g0,32,42,0|-bu64o1,32,42,0|-bu64o0,54,44,1|-bm90s1,54,44,1|-bm90s0,32,42,0|-bbg201,32,42,0|-bbg200,54,44,1|-b3iy41,54,44,1|-b3iy40,32,42,0|-aspzc1,32,42,0|-aspzc0,54,44,1|-aksvg1,54,44,1|-aksvg0,32,42,0|-9qwvc1,32,42,0|-9qwvc0,54,44,1|-9izrg1,54,44,1|-9izrg0,32,42,0|-986so1,32,42,0|-986so0,54,44,1|-909os1,54,44,1|-909os0,32,42,0|-8pgq01,32,42,0|-8pgq00,54,44,1|-8hjm41,54,44,1|-8hjm40,32,42,0|-86qnc1,32,42,0|-86qnc0,54,44,1|-7ytjg1,54,44,1|-7ytjg0,32,42,0|-74xjc1,32,42,0|-74xjc0,54,44,1|-6x0fg1,54,44,1|-6x0fg0,32,42,0|-6m7go1,32,42,0|-6m7go0,54,44,1|-6eacs1,54,44,1|-6eacs0,32,42,0|-63he01,32,42,0|-63he00,54,44,1|-5vka41,54,44,1|-5vka40,32,42,0|-5krbc1,32,42,0|-5krbc0,54,44,1|-5cu7g1,54,44,1|-5cu7g0,32,42,0|-4084o1,32,42,0|-4084o0,54,44,1|-3qv641,54,44,1|-3qv640,32,42,0|-3hi201,32,42,0|-3hi200,54,44,1|-3853g1,54,44,1|-3853g0,32,42,0|-2yrzc1,32,42,0|-2yrzc0,54,44,1|-2pf0s1,54,44,1|-2pf0s0,32,42,0|-2g1wo1,32,42,0|-2g1wo0,54,44,1|-26bzg1,54,44,1|-26bzg0,32,42,0|-1xbu01,32,42,0|-1xbu00,54,44,1|-1nlws1,54,44,1|-1nlws0,32,42,0|-1e8so1,32,42,0|-1e8so0,54,44,1|-14vu41,54,44,1|-14vu40,32,42,0|-viq01,32,42,0|-viq00,54,44,1|-m5rg1,54,44,1|-m5rg0,32,42,0|-csnc1,32,42,0|-csnc0,54,44,1|-3fos1,54,44,1|-3fos0,32,42,0|5xfbz,32,42,0|5xfc0,54,44,1|fadvz,54,44,1|fadw0,32,42,0|onhzz,32,42,0|oni00,54,44,1|ydf7z,54,44,1|ydf80,32,42,0|17qjbz,32,42,0|17qjc0,54,44,1|1h3hvz,54,44,1|1h3hw0,32,42,0|1qglzz,32,42,0|1qgm00,54,44,1|1ztkjz,54,44,1|1ztkk0,32,42,0|296onz,32,42,0|296oo0,54,44,1|2ijn7z,54,44,1|2ijn80,32,42,0|2rwrbz,32,42,0|2rwrc0,54,44,1|319pvz,54,44,1|319pw0,32,42,0|3amtzz,32,42,0|3amu00,54,44,1|3kcr7z,54,44,1|3kcr80,32,42,0|3tcwnz,32,42,0|3tcwo0,54,44,1|432tvz,54,44,1|432tw0,32,42,0|4cfxzz,32,42,0|4cfy00,54,44,1|4lswjz,54,44,1|4lswk0,32,42,0|4v60nz,32,42,0|4v60o0,54,44,1|54iz7z,54,44,1|54iz80,32,42,0|5dw3bz,32,42,0|5dw3c0,54,44,1|5n91vz,54,44,1|5n91w0,32,42,0|5wm5zz,32,42,0|5wm600,54,44,1|65z4jz,54,44,1|65z4k0,32,42,0|6fc8nz,32,42,0|6fc8o0,54,44,1|6p25vz,54,44,1|6p25w0,32,42,0|6y2bbz,32,42,0|6y2bc0,54,44,1|77s8jz,54,44,1|77s8k0,32,42,0|7h5cnz,32,42,0|7h5co0,54,44,1|7qib7z,54,44,1|7qib80,32,42,0|7zvfbz,32,42,0|7zvfc0,54,44,1|898dvz,54,44,1|898dw0,32,42,0|8ilhzz,32,42,0|8ili00,54,44,1|8rygjz,54,44,1|8rygk0,32,42,0|908onz,32,42,0|908oo0,54,44,1|9aoj7z,54,44,1|9aoj80,32,42,0|9iyrbz,32,42,0|9iyrc0,54,44,1|9trkjz,54,44,1|9trkk0,32,42,0|a1otzz,32,42,0|a1ou00,54,44,1|achn7z,54,44,1|achn80,32,42,0|akewnz,32,42,0|akewo0,54,44,1|av7pvz,54,44,1|av7pw0,32,42,0|b3hxzz,32,42,0|b3hy00,54,44,1|bdxsjz,54,44,1|bdxsk0,32,42,0|bm80nz,32,42,0|bm80o0,54,44,1|bwnv7z,54,44,1|bwnv80,32,42,0|c4y3bz,32,42,0|c4y3c0,54,44,1|cfqwjz,54,44,1|cfqwk0,32,42,0|cno5zz,32,42,0|cno600,54,44,1|cygz7z,54,44,1|cygz80,32,42,0|d6e8nz,32,42,0|d6e8o0,54,44,1|dh71vz,54,44,1|dh71w0,32,42,0|dph9zz,32,42,0|dpha00,54,44,1|dzx4jz,54,44,1|dzx4k0,32,42,0|e87cnz,32,42,0|e87co0,54,44,1|ein77z,54,44,1|ein780,32,42,0|eqxfbz,32,42,0|eqxfc0,54,44,1|f1d9vz,54,44,1|f1d9w0,32,42,0|f9nhzz,32,42,0|f9ni00,54,44,1|fkgb7z,54,44,1|fkgb80,32,42,0|fsdknz,32,42,0|fsdko0,54,44,1|g36dvz,54,44,1|g36dw0,32,42,0|gb3nbz,32,42,0|gb3nc0,54,44,1|glwgjz,54,44,1|glwgk0,32,42,0|gu6onz,32,42,0|gu6oo0,54,44,1|h4mj7z,54,44,1|h4mj80,32,42,0|hcwrbz,32,42,0|hcwrc0,54,44,1|hnclvz,54,44,1|hnclw0,32,42,0|hvmtzz,32,42,0|hvmu00,54,44,1|i6fn7z,54,44,1|i6fn80,32,42,0|iecwnz,32,42,0|iecwo0,54,44,1|ip5pvz,54,44,1|ip5pw0,32,42,0|ix2zbz,32,42,0|ix2zc0,54,44,1|j7vsjz,54,44,1|j7vsk0,32,42,0|jeq5zz,32,42,0|jeq600,54,44,1|jqytvz,54,44,1|jqytw0,32,42,0|jxg8nz,32,42,0|jxg8o0,54,44,1|k9owjz,54,44,1|k9owk0,32,42,0|kg6bbz,32,42,0|kg6bc0,54,44,1|ksez7z,54,44,1|ksez80,32,42,0|kz9cnz,32,42,0|kz9co0,54,44,1|lbi0jz,54,44,1|lbi0k0,32,42,0|lhzfbz,32,42,0|lhzfc0,54,44,1|lu837z,54,44,1|lu8380,32,42,0|m0phzz,32,42,0|m0pi00,54,44,1|mcy5vz,54,44,1|mcy5w0,32,42,0|mjfknz,32,42,0|mjfko0,54,44,1|mvo8jz,54,44,1|mvo8k0,32,42,0|n25nbz,32,42,0|n25nc0,54,44,1|neeb7z,54,44,1|neeb80,32,42,0|nkvpzz,32,42,0|nkvq00,54,44,1|nx4dvz,54,44,1|nx4dw0,32,42,0|o3yrbz,32,42,0|o3yrc0,54,44,1|og7f7z,54,44,1|og7f80,32,42,0|omotzz,32,42,0|omou00,54,44,1|oyxhvz,54,44,1|oyxhw0,32,42,0|p5ewnz,32,42,0|p5ewo0,54,44,1|phnkjz,54,44,1|phnkk0,32,42,0|po4zbz,32,42,0|po4zc0,54,44,1|q0dn7z,54,44,1|q0dn80,32,42,0|q6v1zz,32,42,0|q6v200,54,44,1|qj3pvz,54,44,1|qj3pw0,32,42,0|qpy3bz,32,42,0|qpy3c0,54,44,1|r26r7z,54,44,1|r26r80,32,42,0|r8o5zz,32,42,0|r8o600,54,44,1|rkwtvz,54,44,1|rkwtw0,32,42,0|rre8nz,32,42,0|rre8o0,54,44,1|s3mwjz,54,44,1|s3mwk0,32,42,0|sa4bbz,32,42,0|sa4bc0,54,44,1|smcz7z,54,44,1|smcz80,32,42,0|ssudzz,32,42,0|ssue00,54,44,1|t531vz,54,44,1|t531w0,32,42,0|tbkgnz,32,42,0|tbkgo0,54,44,1|tnt4jz,54,44,1|tnt4k0,32,42,0|tunhzz,32,42,0|tuni00,54,44,1|u6w5vz,54,44,1|u6w5w0,32,42,0|uddknz,32,42,0|uddko0,54,44,1|upm8jz,54,44,1|upm8k0,32,42,0|uw3nbz,32,42,0|uw3nc0,54,44,1|v8cb7z,54,44,1|v8cb80,32,42,0|vetpzz,32,42,0|vetq00,54,44,1|vr2dvz,54,44,1|vr2dw0,32,42,0|vxjsnz,32,42,0|vxjso0,54,44,1|w9sgjz,54,44,1|w9sgk0,32,42,0|wgmtzz,32,42,0|wgmu00,54,44,1|wsvhvz,54,44,1|wsvhw0,32,42,0|wzcwnz,32,42,0|wzcwo0,54,44,1|xblkjz,54,44,1|xblkk0,32,42,0|xi2zbz,32,42,0|xi2zc0,54,44,1|xubn7z,54,44,1|xubn80,32,42,0|y0t1zz,32,42,0|y0t200,54,44,1|yd1pvz,54,44,1|yd1pw0,32,42,0|yjj4nz,32,42,0|yjj4o0,54,44,1|yvrsjz,54,44,1|yvrsk0,32,42,0|z297bz,32,42,0|z297c0,54,44,1|zehv7z,54,44,1|zehv80,32,42,0\",\"America/Havana|,0,111,0|-15r0u2w,77,112,0|-n7762p,77,112,0|-n7762o,45,63,0|-louq41,45,63,0|-louq40,46,42,1|-likvk1,46,42,1|-likvk0,45,63,0|-ffsvg1,45,63,0|-ffsvg0,46,42,1|-fb4fk1,46,42,1|-fb4fk0,45,63,0|-ex2ss1,45,63,0|-ex2ss0,46,42,1|-es1e81,46,42,1|-es1e80,45,63,0|-edzrg1,45,63,0|-edzrg0,46,42,1|-e9bbk1,46,42,1|-e9bbk0,45,63,0|-cttjg1,45,63,0|-cttjg0,46,42,1|-cp53k1,46,42,1|-cp53k0,45,63,0|-cb3gs1,45,63,0|-cb3gs0,46,42,1|-c6f0w1,46,42,1|-c6f0w0,45,63,0|-2e5gs1,45,63,0|-2e5gs0,46,42,1|-27xgw1,46,42,1|-27xgw0,45,63,0|-1vj3g1,45,63,0|-1vj3g0,46,42,1|-1p1u81,46,42,1|-1p1u80,45,63,0|-1fdm41,45,63,0|-1fdm40,46,42,1|-17enk1,46,42,1|-17enk0,45,63,0|-w8q41,45,63,0|-w8q40,46,42,1|-ookw1,46,42,1|-ookw0,45,63,0|-csq41,45,63,0|-csq40,46,42,1|-3frk1,46,42,1|-3frk0,45,63,0|5xcjz,45,63,0|5xck0,46,42,1|fab3z,46,42,1|fab40,45,63,0|onf7z,45,63,0|onf80,46,42,1|ydcfz,46,42,1|ydcg0,45,63,0|17qgjz,45,63,0|17qgk0,46,42,1|1g0j3z,46,42,1|1g0j40,45,63,0|1qgj7z,45,63,0|1qgj80,46,42,1|1ysgfz,46,42,1|1ysgg0,45,63,0|296lvz,45,63,0|296lw0,46,42,1|2hkdrz,46,42,1|2hkds0,45,63,0|2rwojz,45,63,0|2rwok0,46,42,1|319n3z,46,42,1|319n40,45,63,0|3amr7z,45,63,0|3amr80,46,42,1|3kcofz,46,42,1|3kcog0,45,63,0|3tctvz,45,63,0|3tctw0,46,42,1|432r3z,46,42,1|432r40,45,63,0|4cstvz,45,63,0|4cstw0,46,42,1|4kpxrz,46,42,1|4kpxs0,45,63,0|4t05vz,45,63,0|4t05w0,46,42,1|53sz3z,46,42,1|53sz40,45,63,0|5bq8jz,45,63,0|5bq8k0,46,42,1|5mj1rz,46,42,1|5mj1s0,45,63,0|5xc0jz,45,63,0|5xc0k0,46,42,1|6594fz,46,42,1|6594g0,45,63,0|6g237z,45,63,0|6g2380,46,42,1|6nz73z,46,42,1|6nz740,45,63,0|6ys5vz,45,63,0|6ys5w0,46,42,1|76p9rz,46,42,1|76p9s0,45,63,0|7hi8jz,45,63,0|7hi8k0,46,42,1|7psb3z,46,42,1|7psb40,45,63,0|808b7z,45,63,0|808b80,46,42,1|88idrz,46,42,1|88ids0,45,63,0|8gfn7z,45,63,0|8gfn80,46,42,1|8r8gfz,46,42,1|8r8gg0,45,63,0|8z5pvz,45,63,0|8z5pw0,46,42,1|99yj3z,46,42,1|99yj40,45,63,0|9i8r7z,45,63,0|9i8r80,46,42,1|9solrz,46,42,1|9sols0,45,63,0|a0ytvz,45,63,0|a0ytw0,46,42,1|abeofz,46,42,1|abeog0,45,63,0|aketvz,45,63,0|aketw0,46,42,1|auhprz,46,42,1|auhps0,45,63,0|b3hv7z,45,63,0|b3hv80,46,42,1|bd7v7z,46,42,1|bd7v80,45,63,0|bm7xvz,45,63,0|bm7xw0,46,42,1|bvxxvz,46,42,1|bvxxw0,45,63,0|c4y0jz,45,63,0|c4y0k0,46,42,1|ceo0jz,46,42,1|ceo0k0,45,63,0|cno37z,45,63,0|cno380,46,42,1|cxe37z,46,42,1|cxe380,45,63,0|d6e5vz,45,63,0|d6e5w0,46,42,1|dg45vz,46,42,1|dg45w0,45,63,0|dph77z,45,63,0|dph780,46,42,1|dyu8jz,46,42,1|dyu8k0,45,63,0|e879vz,45,63,0|e879w0,46,42,1|ehx9vz,46,42,1|ehx9w0,45,63,0|eqkdvz,45,63,0|eqkdw0,46,42,1|f1d9vz,46,42,1|f1d9w0,45,63,0|f9agjz,45,63,0|f9agk0,46,42,1|fkgb7z,46,42,1|fkgb80,45,63,0|fsdhvz,45,63,0|fsdhw0,46,42,1|g36dvz,46,42,1|g36dw0,45,63,0|gb3kjz,45,63,0|gb3kk0,46,42,1|glwgjz,46,42,1|glwgk0,45,63,0|gu6lvz,45,63,0|gu6lw0,46,42,1|h4mj7z,46,42,1|h4mj80,45,63,0|hcwojz,45,63,0|hcwok0,46,42,1|hnclvz,46,42,1|hnclw0,45,63,0|hv9sjz,45,63,0|hv9sk0,46,42,1|j7vsjz,46,42,1|j7vsk0,45,63,0|jeq37z,45,63,0|jeq380,46,42,1|jqlv7z,46,42,1|jqlv80,45,63,0|jxt4jz,45,63,0|jxt4k0,46,42,1|k9bxvz,46,42,1|k9bxw0,45,63,0|kg68jz,45,63,0|kg68k0,46,42,1|ks20jz,46,42,1|ks20k0,45,63,0|kz99vz,45,63,0|kz99w0,46,42,1|lb51vz,46,42,1|lb51w0,45,63,0|licb7z,45,63,0|licb80,46,42,1|lul1vz,46,42,1|lul1w0,45,63,0|m1sb7z,45,63,0|m1sb80,46,42,1|mcy5vz,46,42,1|mcy5w0,45,63,0|mjfhvz,45,63,0|mjfhw0,46,42,1|mvo8jz,46,42,1|mvo8k0,45,63,0|n25kjz,45,63,0|n25kk0,46,42,1|neeb7z,46,42,1|neeb80,45,63,0|nkvn7z,45,63,0|nkvn80,46,42,1|nx4dvz,46,42,1|nx4dw0,45,63,0|o3yojz,45,63,0|o3yok0,46,42,1|og7f7z,46,42,1|og7f80,45,63,0|omor7z,45,63,0|omor80,46,42,1|oyxhvz,46,42,1|oyxhw0,45,63,0|p5etvz,45,63,0|p5etw0,46,42,1|phnkjz,46,42,1|phnkk0,45,63,0|po4wjz,45,63,0|po4wk0,46,42,1|q0dn7z,46,42,1|q0dn80,45,63,0|q6uz7z,45,63,0|q6uz80,46,42,1|qj3pvz,46,42,1|qj3pw0,45,63,0|qpy0jz,45,63,0|qpy0k0,46,42,1|r26r7z,46,42,1|r26r80,45,63,0|r8o37z,45,63,0|r8o380,46,42,1|rkwtvz,46,42,1|rkwtw0,45,63,0|rre5vz,45,63,0|rre5w0,46,42,1|s3mwjz,46,42,1|s3mwk0,45,63,0|sa48jz,45,63,0|sa48k0,46,42,1|smcz7z,46,42,1|smcz80,45,63,0|ssub7z,45,63,0|ssub80,46,42,1|t531vz,46,42,1|t531w0,45,63,0|tbkdvz,45,63,0|tbkdw0,46,42,1|tnt4jz,46,42,1|tnt4k0,45,63,0|tunf7z,45,63,0|tunf80,46,42,1|u6w5vz,46,42,1|u6w5w0,45,63,0|uddhvz,45,63,0|uddhw0,46,42,1|upm8jz,46,42,1|upm8k0,45,63,0|uw3kjz,45,63,0|uw3kk0,46,42,1|v8cb7z,46,42,1|v8cb80,45,63,0|vetn7z,45,63,0|vetn80,46,42,1|vr2dvz,46,42,1|vr2dw0,45,63,0|vxjpvz,45,63,0|vxjpw0,46,42,1|w9sgjz,46,42,1|w9sgk0,45,63,0|wgmr7z,45,63,0|wgmr80,46,42,1|wsvhvz,46,42,1|wsvhw0,45,63,0|wzctvz,45,63,0|wzctw0,46,42,1|xblkjz,46,42,1|xblkk0,45,63,0|xi2wjz,45,63,0|xi2wk0,46,42,1|xubn7z,46,42,1|xubn80,45,63,0|y0sz7z,45,63,0|y0sz80,46,42,1|yd1pvz,46,42,1|yd1pw0,45,63,0|yjj1vz,45,63,0|yjj1w0,46,42,1|yvrsjz,46,42,1|yvrsk0,45,63,0|z294jz,45,63,0|z294k0,46,42,1|zehv7z,46,42,1|zehv80,45,63,0\",\"America/Hermosillo|,0,113,0|-p1u4k0,50,66,0|-m7mko1,50,66,0|-m7mko0,45,62,0|-kf67c1,45,62,0|-kf67c0,50,66,0|-k6j3c1,50,66,0|-k6j3c0,45,62,0|-jypm01,45,62,0|-jypm00,50,66,0|-jpan81,50,66,0|-jpan80,45,62,0|-eg9601,45,62,0|-eg9600,50,66,0|-axv381,50,66,0|-axv380,51,40,0|m7z,51,40,0|m80,50,66,0|dphibz,50,66,0|dphic0,52,62,1|dzxcvz,52,62,1|dzxcw0,50,66,0|e87kzz,50,66,0|e87l00,52,62,1|einfjz,52,62,1|einfk0,50,66,0|eqxnnz,50,66,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0\",\"America/Indiana/Indianapolis|,0,114,0|-18y0m00,45,62,0|-r0esg1,45,62,0|-r0esg0,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-qhops1,45,62,0|-qhops0,46,63,1|-q6vwk1,46,63,1|-q6vwk0,45,62,0|-evzog1,45,62,0|-evzog0,46,63,1|-eqy9w1,46,63,1|-eqy9w0,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-ccw1s1,45,62,0|-ccw1s0,46,63,1|-c4yxw1,46,63,1|-c4yxw0,45,62,0|-bu5z41,45,62,0|-bu5z40,46,63,1|-bm8v81,46,63,1|-bm8v80,45,62,0|-bbfwg1,45,62,0|-bbfwg0,46,63,1|-b3isk1,46,63,1|-b3isk0,45,62,0|-aspts1,45,62,0|-aspts0,46,63,1|-akspw1,46,63,1|-akspw0,45,62,0|-a9msg1,45,62,0|-a9msg0,46,63,1|-a22n81,46,63,1|-a22n80,45,62,0|-9qwps1,45,62,0|-9qwps0,46,63,1|-9izlw1,46,63,1|-9izlw0,45,62,0|-986n41,45,62,0|-986n40,46,63,1|-909j81,46,63,1|-909j80,45,62,0|-8pgkg1,45,62,0|-8pgkg0,46,63,1|-8hjgk1,46,63,1|-8hjgk0,45,62,0|-86qhs1,45,62,0|-86qhs0,46,63,1|-7ytdw1,46,63,1|-7ytdw0,45,62,0|-7o0f41,45,62,0|-7o0f40,49,63,0|-6ea781,49,63,0|-6ea780,45,62,0|-63h8g1,45,62,0|-63h8g0,49,63,0|-cskk1,49,63,0|-cskk0,62,42,1|-3fm01,62,42,1|-3fm00,49,63,0|5xi3z,49,63,0|5xi40,62,42,1|fagnz,62,42,1|fago0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Indiana/Knox|,0,115,0|-18y0m00,45,62,0|-r0esg1,45,62,0|-r0esg0,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-qhops1,45,62,0|-qhops0,46,63,1|-q6vwk1,46,63,1|-q6vwk0,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-bu5z41,45,62,0|-bu5z40,46,63,1|-bm8v81,46,63,1|-bm8v80,45,62,0|-bbfwg1,45,62,0|-bbfwg0,46,63,1|-b3isk1,46,63,1|-b3isk0,45,62,0|-aspts1,45,62,0|-aspts0,46,63,1|-akspw1,46,63,1|-akspw0,45,62,0|-a9msg1,45,62,0|-a9msg0,46,63,1|-a22n81,46,63,1|-a22n80,45,62,0|-9qwps1,45,62,0|-9qwps0,46,63,1|-9izlw1,46,63,1|-9izlw0,45,62,0|-986n41,45,62,0|-986n40,46,63,1|-909j81,46,63,1|-909j80,45,62,0|-8pgkg1,45,62,0|-8pgkg0,46,63,1|-8hjgk1,46,63,1|-8hjgk0,45,62,0|-86qhs1,45,62,0|-86qhs0,46,63,1|-7ytdw1,46,63,1|-7ytdw0,45,62,0|-7o0f41,45,62,0|-7o0f40,46,63,1|-7eahw1,46,63,1|-7eahw0,45,62,0|-74xds1,45,62,0|-74xds0,46,63,1|-6vkf81,46,63,1|-6vkf80,45,62,0|-6m7b41,45,62,0|-6m7b40,46,63,1|-6ea781,46,63,1|-6ea780,45,62,0|-63h8g1,45,62,0|-63h8g0,46,63,1|-5vk4k1,46,63,1|-5vk4k0,45,62,0|-5kr5s1,45,62,0|-5kr5s0,46,63,1|-5be781,46,63,1|-5be780,45,62,0|-521341,45,62,0|-521340,46,63,1|-4sb5w1,46,63,1|-4sb5w0,45,62,0|-4iy1s1,45,62,0|-4iy1s0,46,63,1|-49l381,46,63,1|-49l380,45,62,0|-407z41,45,62,0|-407z40,49,63,0|-384xw1,49,63,0|-384xw0,45,62,0|-1e8n41,45,62,0|-1e8n40,46,63,1|-14vok1,46,63,1|-14vok0,45,62,0|-vikg1,45,62,0|-vikg0,46,63,1|-m5lw1,46,63,1|-m5lw0,45,62,0|-cshs1,45,62,0|-cshs0,46,63,1|-3fj81,46,63,1|-3fj80,45,62,0|5xkvz,45,62,0|5xkw0,46,63,1|fajfz,46,63,1|fajg0,45,62,0|onnjz,45,62,0|onnk0,46,63,1|ydkrz,46,63,1|ydks0,45,62,0|17qovz,45,62,0|17qow0,46,63,1|1h3nfz,46,63,1|1h3ng0,45,62,0|1qgrjz,45,62,0|1qgrk0,46,63,1|1ztq3z,46,63,1|1ztq40,45,62,0|23ffjz,45,62,0|23ffk0,46,63,1|2ijsrz,46,63,1|2ijss0,45,62,0|2oo8vz,45,62,0|2oo8w0,46,63,1|319vfz,46,63,1|319vg0,45,62,0|3amzjz,45,62,0|3amzk0,46,63,1|3kcwrz,46,63,1|3kcws0,45,62,0|3td27z,45,62,0|3td280,46,63,1|432zfz,46,63,1|432zg0,45,62,0|4cg3jz,45,62,0|4cg3k0,46,63,1|4lt23z,46,63,1|4lt240,45,62,0|4v667z,45,62,0|4v6680,46,63,1|54j4rz,46,63,1|54j4s0,45,62,0|5dw8vz,45,62,0|5dw8w0,46,63,1|5n97fz,46,63,1|5n97g0,45,62,0|5wmbjz,45,62,0|5wmbk0,46,63,1|65za3z,46,63,1|65za40,45,62,0|6fce7z,45,62,0|6fce80,46,63,1|6p2bfz,46,63,1|6p2bg0,45,62,0|6y2gvz,45,62,0|6y2gw0,46,63,1|77se3z,46,63,1|77se40,45,62,0|7h5i7z,45,62,0|7h5i80,46,63,1|7qigrz,46,63,1|7qigs0,45,62,0|7zvkvz,45,62,0|7zvkw0,46,63,1|898jfz,46,63,1|898jg0,45,62,0|8ilnjz,45,62,0|8ilnk0,46,63,1|8rym3z,46,63,1|8rym40,45,62,0|908u7z,45,62,0|908u80,46,63,1|9aoorz,46,63,1|9aoos0,45,62,0|9iywvz,45,62,0|9iyww0,46,63,1|9trq3z,46,63,1|9trq40,45,62,0|a1ozjz,45,62,0|a1ozk0,46,63,1|achsrz,46,63,1|achss0,45,62,0|akf27z,45,62,0|akf280,46,63,1|av7vfz,46,63,1|av7vg0,45,62,0|b3i3jz,45,62,0|b3i3k0,46,63,1|bdxy3z,46,63,1|bdxy40,49,63,0|ix323z,49,63,0|ix3240,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jeqbjz,45,62,0|jeqbk0,46,63,1|jqyzfz,46,63,1|jqyzg0,45,62,0|jxge7z,45,62,0|jxge80,46,63,1|k9p23z,46,63,1|k9p240,45,62,0|kg6gvz,45,62,0|kg6gw0,46,63,1|ksf4rz,46,63,1|ksf4s0,45,62,0|kz9i7z,45,62,0|kz9i80,46,63,1|lbi63z,46,63,1|lbi640,45,62,0|lhzkvz,45,62,0|lhzkw0,46,63,1|lu88rz,46,63,1|lu88s0,45,62,0|m0pnjz,45,62,0|m0pnk0,46,63,1|mcybfz,46,63,1|mcybg0,45,62,0|mjfq7z,45,62,0|mjfq80,46,63,1|mvoe3z,46,63,1|mvoe40,45,62,0|n25svz,45,62,0|n25sw0,46,63,1|neegrz,46,63,1|neegs0,45,62,0|nkvvjz,45,62,0|nkvvk0,46,63,1|nx4jfz,46,63,1|nx4jg0,45,62,0|o3ywvz,45,62,0|o3yww0,46,63,1|og7krz,46,63,1|og7ks0,45,62,0|omozjz,45,62,0|omozk0,46,63,1|oyxnfz,46,63,1|oyxng0,45,62,0|p5f27z,45,62,0|p5f280,46,63,1|phnq3z,46,63,1|phnq40,45,62,0|po54vz,45,62,0|po54w0,46,63,1|q0dsrz,46,63,1|q0dss0,45,62,0|q6v7jz,45,62,0|q6v7k0,46,63,1|qj3vfz,46,63,1|qj3vg0,45,62,0|qpy8vz,45,62,0|qpy8w0,46,63,1|r26wrz,46,63,1|r26ws0,45,62,0|r8objz,45,62,0|r8obk0,46,63,1|rkwzfz,46,63,1|rkwzg0,45,62,0|rree7z,45,62,0|rree80,46,63,1|s3n23z,46,63,1|s3n240,45,62,0|sa4gvz,45,62,0|sa4gw0,46,63,1|smd4rz,46,63,1|smd4s0,45,62,0|ssujjz,45,62,0|ssujk0,46,63,1|t537fz,46,63,1|t537g0,45,62,0|tbkm7z,45,62,0|tbkm80,46,63,1|tnta3z,46,63,1|tnta40,45,62,0|tunnjz,45,62,0|tunnk0,46,63,1|u6wbfz,46,63,1|u6wbg0,45,62,0|uddq7z,45,62,0|uddq80,46,63,1|upme3z,46,63,1|upme40,45,62,0|uw3svz,45,62,0|uw3sw0,46,63,1|v8cgrz,46,63,1|v8cgs0,45,62,0|vetvjz,45,62,0|vetvk0,46,63,1|vr2jfz,46,63,1|vr2jg0,45,62,0|vxjy7z,45,62,0|vxjy80,46,63,1|w9sm3z,46,63,1|w9sm40,45,62,0|wgmzjz,45,62,0|wgmzk0,46,63,1|wsvnfz,46,63,1|wsvng0,45,62,0|wzd27z,45,62,0|wzd280,46,63,1|xblq3z,46,63,1|xblq40,45,62,0|xi34vz,45,62,0|xi34w0,46,63,1|xubsrz,46,63,1|xubss0,45,62,0|y0t7jz,45,62,0|y0t7k0,46,63,1|yd1vfz,46,63,1|yd1vg0,45,62,0|yjja7z,45,62,0|yjja80,46,63,1|yvry3z,46,63,1|yvry40,45,62,0|z29cvz,45,62,0|z29cw0,46,63,1|zei0rz,46,63,1|zei0s0,45,62,0\",\"America/Indiana/Marengo|,0,116,0|-18y0m00,45,62,0|-r0esg1,45,62,0|-r0esg0,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-qhops1,45,62,0|-qhops0,46,63,1|-q6vwk1,46,63,1|-q6vwk0,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-9qwps1,45,62,0|-9qwps0,46,63,1|-9izlw1,46,63,1|-9izlw0,45,62,0|-86qhs1,45,62,0|-86qhs0,46,63,1|-7ytdw1,46,63,1|-7ytdw0,45,62,0|-7o0f41,45,62,0|-7o0f40,46,63,1|-7g3b81,46,63,1|-7g3b80,45,62,0|-74xds1,45,62,0|-74xds0,46,63,1|-6x09w1,46,63,1|-6x09w0,45,62,0|-6m7b41,45,62,0|-6m7b40,46,63,1|-6ea781,46,63,1|-6ea780,45,62,0|-63h8g1,45,62,0|-63h8g0,46,63,1|-5vk4k1,46,63,1|-5vk4k0,45,62,0|-5kr5s1,45,62,0|-5kr5s0,46,63,1|-5cu1w1,46,63,1|-5cu1w0,45,62,0|-521341,45,62,0|-521340,46,63,1|-4u3z81,46,63,1|-4u3z80,45,62,0|-4iy1s1,45,62,0|-4iy1s0,49,63,0|-cskk1,49,63,0|-cskk0,62,42,1|-3fm01,62,42,1|-3fm00,49,63,0|5xi3z,49,63,0|5xi40,62,42,1|fagnz,62,42,1|fago0,49,63,0|onkrz,49,63,0|onks0,62,42,1|ydhzz,62,42,1|ydi00,49,63,0|17qm3z,49,63,0|17qm40,62,42,1|1h3knz,62,42,1|1h3ko0,49,63,0|1qgorz,49,63,0|1qgos0,62,42,1|1ztnbz,62,42,1|1ztnc0,49,63,0|23fcrz,49,63,0|23fcs0,46,63,1|2ijsrz,46,63,1|2ijss0,49,63,0|2oo63z,49,63,0|2oo640,62,42,1|319snz,62,42,1|319so0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Indiana/Petersburg|,0,117,0|-18y0m00,45,62,0|-r0esg1,45,62,0|-r0esg0,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-qhops1,45,62,0|-qhops0,46,63,1|-q6vwk1,46,63,1|-q6vwk0,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-7nnm01,45,62,0|-7nnm00,46,63,1|-7g3b81,46,63,1|-7g3b80,45,62,0|-74xds1,45,62,0|-74xds0,46,63,1|-6x09w1,46,63,1|-6x09w0,45,62,0|-6m7b41,45,62,0|-6m7b40,46,63,1|-6ea781,46,63,1|-6ea780,45,62,0|-63h8g1,45,62,0|-63h8g0,46,63,1|-5vk4k1,46,63,1|-5vk4k0,45,62,0|-5kr5s1,45,62,0|-5kr5s0,46,63,1|-5cu1w1,46,63,1|-5cu1w0,45,62,0|-521341,45,62,0|-521340,46,63,1|-4u3z81,46,63,1|-4u3z80,45,62,0|-4iy1s1,45,62,0|-4iy1s0,46,63,1|-49l381,46,63,1|-49l380,45,62,0|-407z41,45,62,0|-407z40,46,63,1|-3qv0k1,46,63,1|-3qv0k0,45,62,0|-3hhwg1,45,62,0|-3hhwg0,46,63,1|-384xw1,46,63,1|-384xw0,45,62,0|-2yrts1,45,62,0|-2yrts0,46,63,1|-2pev81,46,63,1|-2pev80,45,62,0|-2g1r41,45,62,0|-2g1r40,49,63,0|-1nlr81,49,63,0|-1nlr80,45,62,0|-1e8n41,45,62,0|-1e8n40,46,63,1|-14vok1,46,63,1|-14vok0,45,62,0|-vikg1,45,62,0|-vikg0,46,63,1|-m5lw1,46,63,1|-m5lw0,45,62,0|-cshs1,45,62,0|-cshs0,46,63,1|-3fj81,46,63,1|-3fj80,45,62,0|5xkvz,45,62,0|5xkw0,46,63,1|fajfz,46,63,1|fajg0,45,62,0|onnjz,45,62,0|onnk0,46,63,1|ydkrz,46,63,1|ydks0,45,62,0|17qovz,45,62,0|17qow0,46,63,1|1h3nfz,46,63,1|1h3ng0,45,62,0|1qgrjz,45,62,0|1qgrk0,46,63,1|1ztq3z,46,63,1|1ztq40,45,62,0|23ffjz,45,62,0|23ffk0,46,63,1|2ijsrz,46,63,1|2ijss0,45,62,0|2oo8vz,45,62,0|2oo8w0,46,63,1|319vfz,46,63,1|319vg0,45,62,0|3amzjz,45,62,0|3amzk0,46,63,1|3kcwrz,46,63,1|3kcws0,45,62,0|3td27z,45,62,0|3td280,46,63,1|432zfz,46,63,1|432zg0,49,63,0|ix323z,49,63,0|ix3240,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jeqbjz,45,62,0|jeqbk0,46,63,1|jqyzfz,46,63,1|jqyzg0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Indiana/Tell_City|,0,118,0|-18y0m00,45,62,0|-r0esg1,45,62,0|-r0esg0,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-qhops1,45,62,0|-qhops0,46,63,1|-q6vwk1,46,63,1|-q6vwk0,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-7nnm01,45,62,0|-7nnm00,46,63,1|-7g3b81,46,63,1|-7g3b80,45,62,0|-74xds1,45,62,0|-74xds0,46,63,1|-6x09w1,46,63,1|-6x09w0,45,62,0|-6m7b41,45,62,0|-6m7b40,46,63,1|-6ea781,46,63,1|-6ea780,45,62,0|-63h8g1,45,62,0|-63h8g0,46,63,1|-5vk4k1,46,63,1|-5vk4k0,45,62,0|-5kr5s1,45,62,0|-5kr5s0,46,63,1|-5cu1w1,46,63,1|-5cu1w0,45,62,0|-521341,45,62,0|-521340,46,63,1|-4u3z81,46,63,1|-4u3z80,45,62,0|-4iy1s1,45,62,0|-4iy1s0,46,63,1|-49l381,46,63,1|-49l380,45,62,0|-407z41,45,62,0|-407z40,46,63,1|-3qv0k1,46,63,1|-3qv0k0,45,62,0|-3hhwg1,45,62,0|-3hhwg0,46,63,1|-384xw1,46,63,1|-384xw0,45,62,0|-2yrts1,45,62,0|-2yrts0,49,63,0|-14vok1,49,63,0|-14vok0,45,62,0|-vikg1,45,62,0|-vikg0,46,63,1|-m5lw1,46,63,1|-m5lw0,45,62,0|-cshs1,45,62,0|-cshs0,62,42,1|-3fm01,62,42,1|-3fm00,49,63,0|5xi3z,49,63,0|5xi40,62,42,1|fagnz,62,42,1|fago0,49,63,0|ix323z,49,63,0|ix3240,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jeqbjz,45,62,0|jeqbk0,46,63,1|jqyzfz,46,63,1|jqyzg0,45,62,0|jxge7z,45,62,0|jxge80,46,63,1|k9p23z,46,63,1|k9p240,45,62,0|kg6gvz,45,62,0|kg6gw0,46,63,1|ksf4rz,46,63,1|ksf4s0,45,62,0|kz9i7z,45,62,0|kz9i80,46,63,1|lbi63z,46,63,1|lbi640,45,62,0|lhzkvz,45,62,0|lhzkw0,46,63,1|lu88rz,46,63,1|lu88s0,45,62,0|m0pnjz,45,62,0|m0pnk0,46,63,1|mcybfz,46,63,1|mcybg0,45,62,0|mjfq7z,45,62,0|mjfq80,46,63,1|mvoe3z,46,63,1|mvoe40,45,62,0|n25svz,45,62,0|n25sw0,46,63,1|neegrz,46,63,1|neegs0,45,62,0|nkvvjz,45,62,0|nkvvk0,46,63,1|nx4jfz,46,63,1|nx4jg0,45,62,0|o3ywvz,45,62,0|o3yww0,46,63,1|og7krz,46,63,1|og7ks0,45,62,0|omozjz,45,62,0|omozk0,46,63,1|oyxnfz,46,63,1|oyxng0,45,62,0|p5f27z,45,62,0|p5f280,46,63,1|phnq3z,46,63,1|phnq40,45,62,0|po54vz,45,62,0|po54w0,46,63,1|q0dsrz,46,63,1|q0dss0,45,62,0|q6v7jz,45,62,0|q6v7k0,46,63,1|qj3vfz,46,63,1|qj3vg0,45,62,0|qpy8vz,45,62,0|qpy8w0,46,63,1|r26wrz,46,63,1|r26ws0,45,62,0|r8objz,45,62,0|r8obk0,46,63,1|rkwzfz,46,63,1|rkwzg0,45,62,0|rree7z,45,62,0|rree80,46,63,1|s3n23z,46,63,1|s3n240,45,62,0|sa4gvz,45,62,0|sa4gw0,46,63,1|smd4rz,46,63,1|smd4s0,45,62,0|ssujjz,45,62,0|ssujk0,46,63,1|t537fz,46,63,1|t537g0,45,62,0|tbkm7z,45,62,0|tbkm80,46,63,1|tnta3z,46,63,1|tnta40,45,62,0|tunnjz,45,62,0|tunnk0,46,63,1|u6wbfz,46,63,1|u6wbg0,45,62,0|uddq7z,45,62,0|uddq80,46,63,1|upme3z,46,63,1|upme40,45,62,0|uw3svz,45,62,0|uw3sw0,46,63,1|v8cgrz,46,63,1|v8cgs0,45,62,0|vetvjz,45,62,0|vetvk0,46,63,1|vr2jfz,46,63,1|vr2jg0,45,62,0|vxjy7z,45,62,0|vxjy80,46,63,1|w9sm3z,46,63,1|w9sm40,45,62,0|wgmzjz,45,62,0|wgmzk0,46,63,1|wsvnfz,46,63,1|wsvng0,45,62,0|wzd27z,45,62,0|wzd280,46,63,1|xblq3z,46,63,1|xblq40,45,62,0|xi34vz,45,62,0|xi34w0,46,63,1|xubsrz,46,63,1|xubss0,45,62,0|y0t7jz,45,62,0|y0t7k0,46,63,1|yd1vfz,46,63,1|yd1vg0,45,62,0|yjja7z,45,62,0|yjja80,46,63,1|yvry3z,46,63,1|yvry40,45,62,0|z29cvz,45,62,0|z29cw0,46,63,1|zei0rz,46,63,1|zei0s0,45,62,0\",\"America/Indiana/Vevay|,0,119,0|-18y0m00,45,62,0|-r0esg1,45,62,0|-r0esg0,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-qhops1,45,62,0|-qhops0,46,63,1|-q6vwk1,46,63,1|-q6vwk0,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-86qhs1,45,62,0|-86qhs0,49,63,0|-cskk1,49,63,0|-cskk0,62,42,1|-3fm01,62,42,1|-3fm00,49,63,0|5xi3z,49,63,0|5xi40,62,42,1|fagnz,62,42,1|fago0,49,63,0|onkrz,49,63,0|onks0,62,42,1|ydhzz,62,42,1|ydi00,49,63,0|17qm3z,49,63,0|17qm40,62,42,1|1h3knz,62,42,1|1h3ko0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Indiana/Vincennes|,0,120,0|-18y0m00,45,62,0|-r0esg1,45,62,0|-r0esg0,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-qhops1,45,62,0|-qhops0,46,63,1|-q6vwk1,46,63,1|-q6vwk0,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-ccw1s1,45,62,0|-ccw1s0,46,63,1|-c4yxw1,46,63,1|-c4yxw0,45,62,0|-8pgkg1,45,62,0|-8pgkg0,46,63,1|-8hjgk1,46,63,1|-8hjgk0,45,62,0|-86qhs1,45,62,0|-86qhs0,46,63,1|-7ytdw1,46,63,1|-7ytdw0,45,62,0|-7nnm01,45,62,0|-7nnm00,46,63,1|-7g3b81,46,63,1|-7g3b80,45,62,0|-74xds1,45,62,0|-74xds0,46,63,1|-6x09w1,46,63,1|-6x09w0,45,62,0|-6m7b41,45,62,0|-6m7b40,46,63,1|-6ea781,46,63,1|-6ea780,45,62,0|-63h8g1,45,62,0|-63h8g0,46,63,1|-5vk4k1,46,63,1|-5vk4k0,45,62,0|-5kr5s1,45,62,0|-5kr5s0,46,63,1|-5cu1w1,46,63,1|-5cu1w0,45,62,0|-521341,45,62,0|-521340,46,63,1|-4sb5w1,46,63,1|-4sb5w0,45,62,0|-4iy1s1,45,62,0|-4iy1s0,46,63,1|-4bdwk1,46,63,1|-4bdwk0,45,62,0|-407z41,45,62,0|-407z40,46,63,1|-3qv0k1,46,63,1|-3qv0k0,45,62,0|-3hhwg1,45,62,0|-3hhwg0,46,63,1|-384xw1,46,63,1|-384xw0,45,62,0|-2yrts1,45,62,0|-2yrts0,49,63,0|-cskk1,49,63,0|-cskk0,62,42,1|-3fm01,62,42,1|-3fm00,49,63,0|5xi3z,49,63,0|5xi40,62,42,1|fagnz,62,42,1|fago0,49,63,0|ix323z,49,63,0|ix3240,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jeqbjz,45,62,0|jeqbk0,46,63,1|jqyzfz,46,63,1|jqyzg0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Indiana/Winamac|,0,121,0|-18y0m00,45,62,0|-r0esg1,45,62,0|-r0esg0,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-qhops1,45,62,0|-qhops0,46,63,1|-q6vwk1,46,63,1|-q6vwk0,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-ccw1s1,45,62,0|-ccw1s0,46,63,1|-c4yxw1,46,63,1|-c4yxw0,45,62,0|-bu5z41,45,62,0|-bu5z40,46,63,1|-bm8v81,46,63,1|-bm8v80,45,62,0|-bbfwg1,45,62,0|-bbfwg0,46,63,1|-b3isk1,46,63,1|-b3isk0,45,62,0|-aspts1,45,62,0|-aspts0,46,63,1|-akspw1,46,63,1|-akspw0,45,62,0|-a9msg1,45,62,0|-a9msg0,46,63,1|-a22n81,46,63,1|-a22n80,45,62,0|-9qwps1,45,62,0|-9qwps0,46,63,1|-9izlw1,46,63,1|-9izlw0,45,62,0|-986n41,45,62,0|-986n40,46,63,1|-909j81,46,63,1|-909j80,45,62,0|-8pgkg1,45,62,0|-8pgkg0,46,63,1|-8hjgk1,46,63,1|-8hjgk0,45,62,0|-86qhs1,45,62,0|-86qhs0,46,63,1|-7ytdw1,46,63,1|-7ytdw0,45,62,0|-7o0f41,45,62,0|-7o0f40,46,63,1|-7eahw1,46,63,1|-7eahw0,45,62,0|-74xds1,45,62,0|-74xds0,46,63,1|-6vkf81,46,63,1|-6vkf80,45,62,0|-6m7b41,45,62,0|-6m7b40,46,63,1|-6ea781,46,63,1|-6ea780,45,62,0|-63h8g1,45,62,0|-63h8g0,46,63,1|-5vk4k1,46,63,1|-5vk4k0,45,62,0|-5kr5s1,45,62,0|-5kr5s0,46,63,1|-5cu1w1,46,63,1|-5cu1w0,45,62,0|-521341,45,62,0|-521340,46,63,1|-4u3z81,46,63,1|-4u3z80,45,62,0|-4iy1s1,45,62,0|-4iy1s0,49,63,0|-cskk1,49,63,0|-cskk0,62,42,1|-3fm01,62,42,1|-3fm00,49,63,0|5xi3z,49,63,0|5xi40,62,42,1|fagnz,62,42,1|fago0,49,63,0|ix323z,49,63,0|ix3240,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jeqbjz,45,62,0|jeqbk0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Inuvik|,60,1,0|-8ve5c0,51,40,0|-2g1r41,51,40,0|-2g1r40,78,62,1|-26br41,78,62,1|-26br40,51,40,0|4v6brz,51,40,0|4v6bs0,50,66,0|5dwbnz,50,66,0|5dwbo0,52,62,1|5n9a7z,52,62,1|5n9a80,50,66,0|5wmebz,50,66,0|5wmec0,52,62,1|65zcvz,52,62,1|65zcw0,50,66,0|6fcgzz,50,66,0|6fch00,52,62,1|6p2e7z,52,62,1|6p2e80,50,66,0|6y2jnz,50,66,0|6y2jo0,52,62,1|77sgvz,52,62,1|77sgw0,50,66,0|7h5kzz,50,66,0|7h5l00,52,62,1|7qijjz,52,62,1|7qijk0,50,66,0|7zvnnz,50,66,0|7zvno0,52,62,1|898m7z,52,62,1|898m80,50,66,0|8ilqbz,50,66,0|8ilqc0,52,62,1|8ryovz,52,62,1|8ryow0,50,66,0|908wzz,50,66,0|908x00,52,62,1|9aorjz,52,62,1|9aork0,50,66,0|9iyznz,50,66,0|9iyzo0,52,62,1|9trsvz,52,62,1|9trsw0,50,66,0|a1p2bz,50,66,0|a1p2c0,52,62,1|achvjz,52,62,1|achvk0,50,66,0|akf4zz,50,66,0|akf500,52,62,1|av7y7z,52,62,1|av7y80,50,66,0|b3i6bz,50,66,0|b3i6c0,52,62,1|bdy0vz,52,62,1|bdy0w0,50,66,0|bm88zz,50,66,0|bm8900,52,62,1|bwo3jz,52,62,1|bwo3k0,50,66,0|c4ybnz,50,66,0|c4ybo0,52,62,1|cfr4vz,52,62,1|cfr4w0,50,66,0|cnoebz,50,66,0|cnoec0,52,62,1|cyh7jz,52,62,1|cyh7k0,50,66,0|d6egzz,50,66,0|d6eh00,52,62,1|dh7a7z,52,62,1|dh7a80,50,66,0|dphibz,50,66,0|dphic0,52,62,1|dzxcvz,52,62,1|dzxcw0,50,66,0|e87kzz,50,66,0|e87l00,52,62,1|einfjz,52,62,1|einfk0,50,66,0|eqxnnz,50,66,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0|f9nqbz,50,66,0|f9nqc0,52,62,1|fkgjjz,52,62,1|fkgjk0,50,66,0|fsdszz,50,66,0|fsdt00,52,62,1|g36m7z,52,62,1|g36m80,50,66,0|gb3vnz,50,66,0|gb3vo0,52,62,1|glwovz,52,62,1|glwow0,50,66,0|gu6wzz,50,66,0|gu6x00,52,62,1|h4mrjz,52,62,1|h4mrk0,50,66,0|hcwznz,50,66,0|hcwzo0,52,62,1|hncu7z,52,62,1|hncu80,50,66,0|hvn2bz,50,66,0|hvn2c0,52,62,1|i6fvjz,52,62,1|i6fvk0,50,66,0|ied4zz,50,66,0|ied500,52,62,1|ip5y7z,52,62,1|ip5y80,50,66,0|ix37nz,50,66,0|ix37o0,52,62,1|j7w0vz,52,62,1|j7w0w0,50,66,0|jeqebz,50,66,0|jeqec0,52,62,1|jqz27z,52,62,1|jqz280,50,66,0|jxggzz,50,66,0|jxgh00,52,62,1|k9p4vz,52,62,1|k9p4w0,50,66,0|kg6jnz,50,66,0|kg6jo0,52,62,1|ksf7jz,52,62,1|ksf7k0,50,66,0|kz9kzz,50,66,0|kz9l00,52,62,1|lbi8vz,52,62,1|lbi8w0,50,66,0|lhznnz,50,66,0|lhzno0,52,62,1|lu8bjz,52,62,1|lu8bk0,50,66,0|m0pqbz,50,66,0|m0pqc0,52,62,1|mcye7z,52,62,1|mcye80,50,66,0|mjfszz,50,66,0|mjft00,52,62,1|mvogvz,52,62,1|mvogw0,50,66,0|n25vnz,50,66,0|n25vo0,52,62,1|neejjz,52,62,1|neejk0,50,66,0|nkvybz,50,66,0|nkvyc0,52,62,1|nx4m7z,52,62,1|nx4m80,50,66,0|o3yznz,50,66,0|o3yzo0,52,62,1|og7njz,52,62,1|og7nk0,50,66,0|omp2bz,50,66,0|omp2c0,52,62,1|oyxq7z,52,62,1|oyxq80,50,66,0|p5f4zz,50,66,0|p5f500,52,62,1|phnsvz,52,62,1|phnsw0,50,66,0|po57nz,50,66,0|po57o0,52,62,1|q0dvjz,52,62,1|q0dvk0,50,66,0|q6vabz,50,66,0|q6vac0,52,62,1|qj3y7z,52,62,1|qj3y80,50,66,0|qpybnz,50,66,0|qpybo0,52,62,1|r26zjz,52,62,1|r26zk0,50,66,0|r8oebz,50,66,0|r8oec0,52,62,1|rkx27z,52,62,1|rkx280,50,66,0|rregzz,50,66,0|rreh00,52,62,1|s3n4vz,52,62,1|s3n4w0,50,66,0|sa4jnz,50,66,0|sa4jo0,52,62,1|smd7jz,52,62,1|smd7k0,50,66,0|ssumbz,50,66,0|ssumc0,52,62,1|t53a7z,52,62,1|t53a80,50,66,0|tbkozz,50,66,0|tbkp00,52,62,1|tntcvz,52,62,1|tntcw0,50,66,0|tunqbz,50,66,0|tunqc0,52,62,1|u6we7z,52,62,1|u6we80,50,66,0|uddszz,50,66,0|uddt00,52,62,1|upmgvz,52,62,1|upmgw0,50,66,0|uw3vnz,50,66,0|uw3vo0,52,62,1|v8cjjz,52,62,1|v8cjk0,50,66,0|vetybz,50,66,0|vetyc0,52,62,1|vr2m7z,52,62,1|vr2m80,50,66,0|vxk0zz,50,66,0|vxk100,52,62,1|w9sovz,52,62,1|w9sow0,50,66,0|wgn2bz,50,66,0|wgn2c0,52,62,1|wsvq7z,52,62,1|wsvq80,50,66,0|wzd4zz,50,66,0|wzd500,52,62,1|xblsvz,52,62,1|xblsw0,50,66,0|xi37nz,50,66,0|xi37o0,52,62,1|xubvjz,52,62,1|xubvk0,50,66,0|y0tabz,50,66,0|y0tac0,52,62,1|yd1y7z,52,62,1|yd1y80,50,66,0|yjjczz,50,66,0|yjjd00,52,62,1|yvs0vz,52,62,1|yvs0w0,50,66,0|z29fnz,50,66,0|z29fo0,52,62,1|zei3jz,52,62,1|zei3k0,50,66,0\",\"America/Iqaluit|,60,1,0|-eb6ao0,70,42,1|-cq2tg1,70,42,1|-cq2tg0,71,42,1|-cnp3c1,71,42,1|-cnp3c0,49,63,0|-2g1zg1,49,63,0|-2g1zg0,79,44,1|-26bzg1,79,44,1|-26bzg0,49,63,0|5dw63z,49,63,0|5dw640,62,42,1|5n94nz,62,42,1|5n94o0,49,63,0|5wm8rz,49,63,0|5wm8s0,62,42,1|65z7bz,62,42,1|65z7c0,49,63,0|6fcbfz,49,63,0|6fcbg0,62,42,1|6p28nz,62,42,1|6p28o0,49,63,0|6y2e3z,49,63,0|6y2e40,62,42,1|77sbbz,62,42,1|77sbc0,49,63,0|7h5ffz,49,63,0|7h5fg0,62,42,1|7qidzz,62,42,1|7qie00,49,63,0|7zvi3z,49,63,0|7zvi40,62,42,1|898gnz,62,42,1|898go0,49,63,0|8ilkrz,49,63,0|8ilks0,62,42,1|8ryjbz,62,42,1|8ryjc0,49,63,0|908rfz,49,63,0|908rg0,62,42,1|9aolzz,62,42,1|9aom00,49,63,0|9iyu3z,49,63,0|9iyu40,62,42,1|9trnbz,62,42,1|9trnc0,49,63,0|a1owrz,49,63,0|a1ows0,62,42,1|achpzz,62,42,1|achq00,49,63,0|akezfz,49,63,0|akezg0,62,42,1|av7snz,62,42,1|av7so0,49,63,0|b3i0rz,49,63,0|b3i0s0,62,42,1|bdxvbz,62,42,1|bdxvc0,49,63,0|bm83fz,49,63,0|bm83g0,62,42,1|bwnxzz,62,42,1|bwny00,49,63,0|c4y63z,49,63,0|c4y640,62,42,1|cfqzbz,62,42,1|cfqzc0,49,63,0|cno8rz,49,63,0|cno8s0,62,42,1|cyh1zz,62,42,1|cyh200,49,63,0|d6ebfz,49,63,0|d6ebg0,62,42,1|dh74nz,62,42,1|dh74o0,49,63,0|dphcrz,49,63,0|dphcs0,62,42,1|dzx7bz,62,42,1|dzx7c0,49,63,0|e87ffz,49,63,0|e87fg0,62,42,1|ein9zz,62,42,1|eina00,49,63,0|eqxi3z,49,63,0|eqxi40,62,42,1|f1dcnz,62,42,1|f1dco0,49,63,0|f9nkrz,49,63,0|f9nks0,62,42,1|fkgdzz,62,42,1|fkge00,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,49,63,0|gb3q3z,49,63,0|gb3q40,62,42,1|glwjbz,62,42,1|glwjc0,49,63,0|gu6rfz,49,63,0|gu6rg0,62,42,1|h4mlzz,62,42,1|h4mm00,49,63,0|hcwu3z,49,63,0|hcwu40,62,42,1|hnconz,62,42,1|hncoo0,49,63,0|hvmwrz,49,63,0|hvmws0,62,42,1|i6fpzz,62,42,1|i6fq00,49,63,0|ieczfz,49,63,0|ieczg0,62,42,1|ip5snz,62,42,1|ip5so0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Jamaica|,0,104,0|-15r0v42,74,104,0|-u85og3,74,104,0|-u85og2,49,63,0|23fcrz,49,63,0|23fcs0,62,42,1|2ijpzz,62,42,1|2ijq00,49,63,0|2oo63z,49,63,0|2oo640,62,42,1|319snz,62,42,1|319so0,49,63,0|3amwrz,49,63,0|3amws0,62,42,1|3kctzz,62,42,1|3kcu00,49,63,0|3tczfz,49,63,0|3tczg0,62,42,1|432wnz,62,42,1|432wo0,49,63,0|4cg0rz,49,63,0|4cg0s0,62,42,1|4lszbz,62,42,1|4lszc0,49,63,0|4v63fz,49,63,0|4v63g0,62,42,1|54j1zz,62,42,1|54j200,49,63,0|5dw63z,49,63,0|5dw640,62,42,1|5n94nz,62,42,1|5n94o0,49,63,0|5wm8rz,49,63,0|5wm8s0,62,42,1|65z7bz,62,42,1|65z7c0,49,63,0|6fcbfz,49,63,0|6fcbg0,62,42,1|6p28nz,62,42,1|6p28o0,49,63,0|6y2e3z,49,63,0|6y2e40,62,42,1|77sbbz,62,42,1|77sbc0,49,63,0\",\"America/Juneau|,0,122,0|-1hc7qjz,0,123,0|-1078wfw,0,123,0|-1078wfv,51,40,0|-ek1w81,51,40,0|-ek1w80,68,66,1|-cq2tg1,68,66,1|-cq2tg0,69,66,1|-cnov01,69,66,1|-cnov00,51,40,0|-csc81,51,40,0|-csc80,57,66,1|-3fdo1,57,66,1|-3fdo0,51,40,0|5xqfz,51,40,0|5xqg0,57,66,1|faozz,57,66,1|fap00,51,40,0|ont3z,51,40,0|ont40,57,66,1|ydqbz,57,66,1|ydqc0,51,40,0|17qufz,51,40,0|17qug0,57,66,1|1h3szz,57,66,1|1h3t00,51,40,0|1qgx3z,51,40,0|1qgx40,57,66,1|1ztvnz,57,66,1|1ztvo0,51,40,0|23fl3z,51,40,0|23fl40,57,66,1|2ijybz,57,66,1|2ijyc0,51,40,0|2ooefz,51,40,0|2ooeg0,57,66,1|31a0zz,57,66,1|31a100,51,40,0|3an53z,51,40,0|3an540,57,66,1|3kd2bz,57,66,1|3kd2c0,51,40,0|3td7rz,51,40,0|3td7s0,57,66,1|4334zz,57,66,1|433500,51,40,0|4cg93z,51,40,0|4cg940,57,66,1|4lt7nz,57,66,1|4lt7o0,51,40,0|4v6brz,51,40,0|4v6bs0,57,66,1|54jabz,57,66,1|54jac0,51,40,0|5dwefz,51,40,0|5dweg0,64,40,1|5n9frz,64,40,1|5n9fs0,51,40,0|5wmh3z,51,40,0|5wmh40,57,66,1|65zfnz,57,66,1|65zfo0,51,40,0|6fcjrz,51,40,0|6fcjs0,57,66,1|6p2gzz,57,66,1|6p2h00,51,40,0|6y2mfz,51,40,0|6y2mg0,57,66,1|77sjnz,57,66,1|77sjo0,36,37,0|79dybz,36,37,0|79dyc0,37,37,0|7h5qjz,37,37,0|7h5qk0,38,40,1|7qip3z,38,40,1|7qip40,37,37,0|7zvt7z,37,37,0|7zvt80,38,40,1|898rrz,38,40,1|898rs0,37,37,0|8ilvvz,37,37,0|8ilvw0,38,40,1|8ryufz,38,40,1|8ryug0,37,37,0|9092jz,37,37,0|9092k0,38,40,1|9aox3z,38,40,1|9aox40,37,37,0|9iz57z,37,37,0|9iz580,38,40,1|9tryfz,38,40,1|9tryg0,37,37,0|a1p7vz,37,37,0|a1p7w0,38,40,1|aci13z,38,40,1|aci140,37,37,0|akfajz,37,37,0|akfak0,38,40,1|av83rz,38,40,1|av83s0,37,37,0|b3ibvz,37,37,0|b3ibw0,38,40,1|bdy6fz,38,40,1|bdy6g0,37,37,0|bm8ejz,37,37,0|bm8ek0,38,40,1|bwo93z,38,40,1|bwo940,37,37,0|c4yh7z,37,37,0|c4yh80,38,40,1|cfrafz,38,40,1|cfrag0,37,37,0|cnojvz,37,37,0|cnojw0,38,40,1|cyhd3z,38,40,1|cyhd40,37,37,0|d6emjz,37,37,0|d6emk0,38,40,1|dh7frz,38,40,1|dh7fs0,37,37,0|dphnvz,37,37,0|dphnw0,38,40,1|dzxifz,38,40,1|dzxig0,37,37,0|e87qjz,37,37,0|e87qk0,38,40,1|einl3z,38,40,1|einl40,37,37,0|eqxt7z,37,37,0|eqxt80,38,40,1|f1dnrz,38,40,1|f1dns0,37,37,0|f9nvvz,37,37,0|f9nvw0,38,40,1|fkgp3z,38,40,1|fkgp40,37,37,0|fsdyjz,37,37,0|fsdyk0,38,40,1|g36rrz,38,40,1|g36rs0,37,37,0|gb417z,37,37,0|gb4180,38,40,1|glwufz,38,40,1|glwug0,37,37,0|gu72jz,37,37,0|gu72k0,38,40,1|h4mx3z,38,40,1|h4mx40,37,37,0|hcx57z,37,37,0|hcx580,38,40,1|hnczrz,38,40,1|hnczs0,37,37,0|hvn7vz,37,37,0|hvn7w0,38,40,1|i6g13z,38,40,1|i6g140,37,37,0|iedajz,37,37,0|iedak0,38,40,1|ip63rz,38,40,1|ip63s0,37,37,0|ix3d7z,37,37,0|ix3d80,38,40,1|j7w6fz,38,40,1|j7w6g0,37,37,0|jeqjvz,37,37,0|jeqjw0,38,40,1|jqz7rz,38,40,1|jqz7s0,37,37,0|jxgmjz,37,37,0|jxgmk0,38,40,1|k9pafz,38,40,1|k9pag0,37,37,0|kg6p7z,37,37,0|kg6p80,38,40,1|ksfd3z,38,40,1|ksfd40,37,37,0|kz9qjz,37,37,0|kz9qk0,38,40,1|lbiefz,38,40,1|lbieg0,37,37,0|lhzt7z,37,37,0|lhzt80,38,40,1|lu8h3z,38,40,1|lu8h40,37,37,0|m0pvvz,37,37,0|m0pvw0,38,40,1|mcyjrz,38,40,1|mcyjs0,37,37,0|mjfyjz,37,37,0|mjfyk0,38,40,1|mvomfz,38,40,1|mvomg0,37,37,0|n2617z,37,37,0|n26180,38,40,1|neep3z,38,40,1|neep40,37,37,0|nkw3vz,37,37,0|nkw3w0,38,40,1|nx4rrz,38,40,1|nx4rs0,37,37,0|o3z57z,37,37,0|o3z580,38,40,1|og7t3z,38,40,1|og7t40,37,37,0|omp7vz,37,37,0|omp7w0,38,40,1|oyxvrz,38,40,1|oyxvs0,37,37,0|p5fajz,37,37,0|p5fak0,38,40,1|phnyfz,38,40,1|phnyg0,37,37,0|po5d7z,37,37,0|po5d80,38,40,1|q0e13z,38,40,1|q0e140,37,37,0|q6vfvz,37,37,0|q6vfw0,38,40,1|qj43rz,38,40,1|qj43s0,37,37,0|qpyh7z,37,37,0|qpyh80,38,40,1|r2753z,38,40,1|r27540,37,37,0|r8ojvz,37,37,0|r8ojw0,38,40,1|rkx7rz,38,40,1|rkx7s0,37,37,0|rremjz,37,37,0|rremk0,38,40,1|s3nafz,38,40,1|s3nag0,37,37,0|sa4p7z,37,37,0|sa4p80,38,40,1|smdd3z,38,40,1|smdd40,37,37,0|ssurvz,37,37,0|ssurw0,38,40,1|t53frz,38,40,1|t53fs0,37,37,0|tbkujz,37,37,0|tbkuk0,38,40,1|tntifz,38,40,1|tntig0,37,37,0|tunvvz,37,37,0|tunvw0,38,40,1|u6wjrz,38,40,1|u6wjs0,37,37,0|uddyjz,37,37,0|uddyk0,38,40,1|upmmfz,38,40,1|upmmg0,37,37,0|uw417z,37,37,0|uw4180,38,40,1|v8cp3z,38,40,1|v8cp40,37,37,0|veu3vz,37,37,0|veu3w0,38,40,1|vr2rrz,38,40,1|vr2rs0,37,37,0|vxk6jz,37,37,0|vxk6k0,38,40,1|w9sufz,38,40,1|w9sug0,37,37,0|wgn7vz,37,37,0|wgn7w0,38,40,1|wsvvrz,38,40,1|wsvvs0,37,37,0|wzdajz,37,37,0|wzdak0,38,40,1|xblyfz,38,40,1|xblyg0,37,37,0|xi3d7z,37,37,0|xi3d80,38,40,1|xuc13z,38,40,1|xuc140,37,37,0|y0tfvz,37,37,0|y0tfw0,38,40,1|yd23rz,38,40,1|yd23s0,37,37,0|yjjijz,37,37,0|yjjik0,38,40,1|yvs6fz,38,40,1|yvs6g0,37,37,0|z29l7z,37,37,0|z29l80,38,40,1|zei93z,38,40,1|zei940,37,37,0\",\"America/Kentucky/Louisville|,0,124,0|-18y0m00,45,62,0|-r0esg1,45,62,0|-r0esg0,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-qhops1,45,62,0|-qhops0,46,63,1|-q6vwk1,46,63,1|-q6vwk0,45,62,0|-pefr41,45,62,0|-pefr40,46,63,1|-p841w1,46,63,1|-p841w0,45,62,0|-eyvds1,45,62,0|-eyvds0,46,63,1|-eqy9w1,46,63,1|-eqy9w0,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-ccw7ad,45,62,0|-ccw7ac,46,63,1|-cb3b81,46,63,1|-cb3b80,45,62,0|-a9msg1,45,62,0|-a9msg0,46,63,1|-a22n81,46,63,1|-a22n80,45,62,0|-9qwps1,45,62,0|-9qwps0,46,63,1|-9izlw1,46,63,1|-9izlw0,45,62,0|-986n41,45,62,0|-986n40,46,63,1|-909j81,46,63,1|-909j80,45,62,0|-8pgkg1,45,62,0|-8pgkg0,46,63,1|-8hjgk1,46,63,1|-8hjgk0,45,62,0|-86qhs1,45,62,0|-86qhs0,46,63,1|-7ytdw1,46,63,1|-7ytdw0,45,62,0|-7o0f41,45,62,0|-7o0f40,46,63,1|-7g3b81,46,63,1|-7g3b80,45,62,0|-74xds1,45,62,0|-74xds0,46,63,1|-6vkf81,46,63,1|-6vkf80,45,62,0|-6m7b41,45,62,0|-6m7b40,46,63,1|-6cuck1,46,63,1|-6cuck0,45,62,0|-63h8g1,45,62,0|-63h8g0,46,63,1|-5u49w1,46,63,1|-5u49w0,45,62,0|-5kr5s1,45,62,0|-5kr5s0,46,63,1|-5be781,46,63,1|-5be780,45,62,0|-521341,45,62,0|-521340,46,63,1|-4sb5w1,46,63,1|-4sb5w0,45,62,0|-4iy1s1,45,62,0|-4iy1s0,46,63,1|-4emkk1,46,63,1|-4emkk0,49,63,0|-vin81,49,63,0|-vin80,62,42,1|-m5oo1,62,42,1|-m5oo0,49,63,0|-cskk1,49,63,0|-cskk0,62,42,1|-3fm01,62,42,1|-3fm00,49,63,0|5xi3z,49,63,0|5xi40,62,42,1|fagnz,62,42,1|fago0,49,63,0|onkrz,49,63,0|onks0,62,42,1|ydhzz,62,42,1|ydi00,49,63,0|17qm3z,49,63,0|17qm40,62,42,1|1h3knz,62,42,1|1h3ko0,49,63,0|1qgorz,49,63,0|1qgos0,62,42,1|1ztnbz,62,42,1|1ztnc0,49,63,0|23fcrz,49,63,0|23fcs0,46,63,1|2ijsrz,46,63,1|2ijss0,49,63,0|2oo63z,49,63,0|2oo640,62,42,1|319snz,62,42,1|319so0,49,63,0|3amwrz,49,63,0|3amws0,62,42,1|3kctzz,62,42,1|3kcu00,49,63,0|3tczfz,49,63,0|3tczg0,62,42,1|432wnz,62,42,1|432wo0,49,63,0|4cg0rz,49,63,0|4cg0s0,62,42,1|4lszbz,62,42,1|4lszc0,49,63,0|4v63fz,49,63,0|4v63g0,62,42,1|54j1zz,62,42,1|54j200,49,63,0|5dw63z,49,63,0|5dw640,62,42,1|5n94nz,62,42,1|5n94o0,49,63,0|5wm8rz,49,63,0|5wm8s0,62,42,1|65z7bz,62,42,1|65z7c0,49,63,0|6fcbfz,49,63,0|6fcbg0,62,42,1|6p28nz,62,42,1|6p28o0,49,63,0|6y2e3z,49,63,0|6y2e40,62,42,1|77sbbz,62,42,1|77sbc0,49,63,0|7h5ffz,49,63,0|7h5fg0,62,42,1|7qidzz,62,42,1|7qie00,49,63,0|7zvi3z,49,63,0|7zvi40,62,42,1|898gnz,62,42,1|898go0,49,63,0|8ilkrz,49,63,0|8ilks0,62,42,1|8ryjbz,62,42,1|8ryjc0,49,63,0|908rfz,49,63,0|908rg0,62,42,1|9aolzz,62,42,1|9aom00,49,63,0|9iyu3z,49,63,0|9iyu40,62,42,1|9trnbz,62,42,1|9trnc0,49,63,0|a1owrz,49,63,0|a1ows0,62,42,1|achpzz,62,42,1|achq00,49,63,0|akezfz,49,63,0|akezg0,62,42,1|av7snz,62,42,1|av7so0,49,63,0|b3i0rz,49,63,0|b3i0s0,62,42,1|bdxvbz,62,42,1|bdxvc0,49,63,0|bm83fz,49,63,0|bm83g0,62,42,1|bwnxzz,62,42,1|bwny00,49,63,0|c4y63z,49,63,0|c4y640,62,42,1|cfqzbz,62,42,1|cfqzc0,49,63,0|cno8rz,49,63,0|cno8s0,62,42,1|cyh1zz,62,42,1|cyh200,49,63,0|d6ebfz,49,63,0|d6ebg0,62,42,1|dh74nz,62,42,1|dh74o0,49,63,0|dphcrz,49,63,0|dphcs0,62,42,1|dzx7bz,62,42,1|dzx7c0,49,63,0|e87ffz,49,63,0|e87fg0,62,42,1|ein9zz,62,42,1|eina00,49,63,0|eqxi3z,49,63,0|eqxi40,62,42,1|f1dcnz,62,42,1|f1dco0,49,63,0|f9nkrz,49,63,0|f9nks0,62,42,1|fkgdzz,62,42,1|fkge00,49,63,0|fsdnfz,49,63,0|fsdng0,62,42,1|g36gnz,62,42,1|g36go0,49,63,0|gb3q3z,49,63,0|gb3q40,62,42,1|glwjbz,62,42,1|glwjc0,49,63,0|gu6rfz,49,63,0|gu6rg0,62,42,1|h4mlzz,62,42,1|h4mm00,49,63,0|hcwu3z,49,63,0|hcwu40,62,42,1|hnconz,62,42,1|hncoo0,49,63,0|hvmwrz,49,63,0|hvmws0,62,42,1|i6fpzz,62,42,1|i6fq00,49,63,0|ieczfz,49,63,0|ieczg0,62,42,1|ip5snz,62,42,1|ip5so0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Kentucky/Monticello|,0,125,0|-18y0m00,45,62,0|-r0esg1,45,62,0|-r0esg0,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-qhops1,45,62,0|-qhops0,46,63,1|-q6vwk1,46,63,1|-q6vwk0,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-vikg1,45,62,0|-vikg0,46,63,1|-m5lw1,46,63,1|-m5lw0,45,62,0|-cshs1,45,62,0|-cshs0,46,63,1|-3fj81,46,63,1|-3fj80,45,62,0|5xkvz,45,62,0|5xkw0,46,63,1|fajfz,46,63,1|fajg0,45,62,0|onnjz,45,62,0|onnk0,46,63,1|ydkrz,46,63,1|ydks0,45,62,0|17qovz,45,62,0|17qow0,46,63,1|1h3nfz,46,63,1|1h3ng0,45,62,0|1qgrjz,45,62,0|1qgrk0,46,63,1|1ztq3z,46,63,1|1ztq40,45,62,0|23ffjz,45,62,0|23ffk0,46,63,1|2ijsrz,46,63,1|2ijss0,45,62,0|2oo8vz,45,62,0|2oo8w0,46,63,1|319vfz,46,63,1|319vg0,45,62,0|3amzjz,45,62,0|3amzk0,46,63,1|3kcwrz,46,63,1|3kcws0,45,62,0|3td27z,45,62,0|3td280,46,63,1|432zfz,46,63,1|432zg0,45,62,0|4cg3jz,45,62,0|4cg3k0,46,63,1|4lt23z,46,63,1|4lt240,45,62,0|4v667z,45,62,0|4v6680,46,63,1|54j4rz,46,63,1|54j4s0,45,62,0|5dw8vz,45,62,0|5dw8w0,46,63,1|5n97fz,46,63,1|5n97g0,45,62,0|5wmbjz,45,62,0|5wmbk0,46,63,1|65za3z,46,63,1|65za40,45,62,0|6fce7z,45,62,0|6fce80,46,63,1|6p2bfz,46,63,1|6p2bg0,45,62,0|6y2gvz,45,62,0|6y2gw0,46,63,1|77se3z,46,63,1|77se40,45,62,0|7h5i7z,45,62,0|7h5i80,46,63,1|7qigrz,46,63,1|7qigs0,45,62,0|7zvkvz,45,62,0|7zvkw0,46,63,1|898jfz,46,63,1|898jg0,45,62,0|8ilnjz,45,62,0|8ilnk0,46,63,1|8rym3z,46,63,1|8rym40,45,62,0|908u7z,45,62,0|908u80,46,63,1|9aoorz,46,63,1|9aoos0,45,62,0|9iywvz,45,62,0|9iyww0,46,63,1|9trq3z,46,63,1|9trq40,45,62,0|a1ozjz,45,62,0|a1ozk0,46,63,1|achsrz,46,63,1|achss0,45,62,0|akf27z,45,62,0|akf280,46,63,1|av7vfz,46,63,1|av7vg0,45,62,0|b3i3jz,45,62,0|b3i3k0,46,63,1|bdxy3z,46,63,1|bdxy40,45,62,0|bm867z,45,62,0|bm8680,46,63,1|bwo0rz,46,63,1|bwo0s0,45,62,0|c4y8vz,45,62,0|c4y8w0,46,63,1|cfr23z,46,63,1|cfr240,45,62,0|cnobjz,45,62,0|cnobk0,46,63,1|cyh4rz,46,63,1|cyh4s0,45,62,0|d6ee7z,45,62,0|d6ee80,46,63,1|dh77fz,46,63,1|dh77g0,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxkvz,45,62,0|eqxkw0,46,63,1|f1dffz,46,63,1|f1dfg0,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkggrz,46,63,1|fkggs0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,49,63,0|gb3q3z,49,63,0|gb3q40,62,42,1|glwjbz,62,42,1|glwjc0,49,63,0|gu6rfz,49,63,0|gu6rg0,62,42,1|h4mlzz,62,42,1|h4mm00,49,63,0|hcwu3z,49,63,0|hcwu40,62,42,1|hnconz,62,42,1|hncoo0,49,63,0|hvmwrz,49,63,0|hvmws0,62,42,1|i6fpzz,62,42,1|i6fq00,49,63,0|ieczfz,49,63,0|ieczg0,62,42,1|ip5snz,62,42,1|ip5so0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Kralendijk|,0,58,0|-u7lckd,43,59,0|-2lx4u1,43,59,0|-2lx4u0,32,42,0\",\"America/La_Paz|,0,126,0|-15r0wpo,41,126,0|-jxzspp,41,126,0|-jxzspo,27,127,1|-jpva5p,27,127,1|-jpva5o,42,42,0\",\"America/Lima|,0,128,0|-15r0v2c,0,129,0|-w25lpp,0,129,0|-w25lpo,56,63,0|-gp8241,56,63,0|-gp8240,42,42,1|-gklgw1,42,42,1|-gklgw0,56,63,0|-gbhm41,56,63,0|-gbhm40,42,42,1|-g24nk1,42,42,1|-g24nk0,56,63,0|-fsrjg1,56,63,0|-fsrjg0,42,42,1|-fjekw1,42,42,1|-fjekw0,56,63,0|8cmlvz,56,63,0|8cmlw0,42,42,1|8h973z,42,42,1|8h9740,56,63,0|8vej7z,56,63,0|8vej80,42,42,1|9014fz,42,42,1|9014g0,56,63,0|afs5vz,56,63,0|afs5w0,42,42,1|aker3z,42,42,1|aker40,56,63,0|cixpvz,56,63,0|cixpw0,42,42,1|cnkb3z,42,42,1|cnkb40,56,63,0\",\"America/Los_Angeles|,0,130,0|-18y0gg0,51,40,0|-r0emw1,51,40,0|-r0emw0,57,66,1|-qplto1,57,66,1|-qplto0,51,40,0|-qhok81,51,40,0|-qhok80,57,66,1|-q6vr01,57,66,1|-q6vr00,51,40,0|-ek1w81,51,40,0|-ek1w80,68,66,1|-cq2tg1,68,66,1|-cq2tg0,69,66,1|-cnov01,69,66,1|-cnov00,51,40,0|-bdliud,51,40,0|-bdliuc,57,66,1|-ayj0c1,57,66,1|-ayj0c0,51,40,0|-a9mpo1,51,40,0|-a9mpo0,57,66,1|-a22ho1,57,66,1|-a22ho0,51,40,0|-9qwn01,51,40,0|-9qwn00,57,66,1|-9izgc1,57,66,1|-9izgc0,51,40,0|-986kc1,51,40,0|-986kc0,57,66,1|-909do1,57,66,1|-909do0,51,40,0|-8pgho1,51,40,0|-8pgho0,57,66,1|-8hjb01,57,66,1|-8hjb00,51,40,0|-86qf01,51,40,0|-86qf00,57,66,1|-7yt8c1,57,66,1|-7yt8c0,51,40,0|-7o0cc1,51,40,0|-7o0cc0,57,66,1|-7g35o1,57,66,1|-7g35o0,51,40,0|-74xb01,51,40,0|-74xb00,57,66,1|-6x04c1,57,66,1|-6x04c0,51,40,0|-6m78c1,51,40,0|-6m78c0,57,66,1|-6ea1o1,57,66,1|-6ea1o0,51,40,0|-63h5o1,51,40,0|-63h5o0,57,66,1|-5vjz01,57,66,1|-5vjz00,51,40,0|-5kr301,51,40,0|-5kr300,57,66,1|-5ctwc1,57,66,1|-5ctwc0,51,40,0|-5210c1,51,40,0|-5210c0,57,66,1|-4u3to1,57,66,1|-4u3to0,51,40,0|-4ixz01,51,40,0|-4ixz00,57,66,1|-4bdr01,57,66,1|-4bdr00,51,40,0|-407wc1,51,40,0|-407wc0,57,66,1|-3quv01,57,66,1|-3quv00,51,40,0|-3hhto1,51,40,0|-3hhto0,57,66,1|-384sc1,57,66,1|-384sc0,51,40,0|-2yrr01,51,40,0|-2yrr00,57,66,1|-2pepo1,57,66,1|-2pepo0,51,40,0|-2g1oc1,51,40,0|-2g1oc0,57,66,1|-26boc1,57,66,1|-26boc0,51,40,0|-1xblo1,51,40,0|-1xblo0,57,66,1|-1nllo1,57,66,1|-1nllo0,51,40,0|-1e8hk1,51,40,0|-1e8hk0,57,66,1|-14vj01,57,66,1|-14vj00,51,40,0|-view1,51,40,0|-view0,57,66,1|-m5gc1,57,66,1|-m5gc0,51,40,0|-csc81,51,40,0|-csc80,57,66,1|-3fdo1,57,66,1|-3fdo0,51,40,0|5xqfz,51,40,0|5xqg0,57,66,1|faozz,57,66,1|fap00,51,40,0|ont3z,51,40,0|ont40,57,66,1|ydqbz,57,66,1|ydqc0,51,40,0|17qufz,51,40,0|17qug0,57,66,1|1h3szz,57,66,1|1h3t00,51,40,0|1qgx3z,51,40,0|1qgx40,57,66,1|1ztvnz,57,66,1|1ztvo0,51,40,0|23fl3z,51,40,0|23fl40,57,66,1|2ijybz,57,66,1|2ijyc0,51,40,0|2ooefz,51,40,0|2ooeg0,57,66,1|31a0zz,57,66,1|31a100,51,40,0|3an53z,51,40,0|3an540,57,66,1|3kd2bz,57,66,1|3kd2c0,51,40,0|3td7rz,51,40,0|3td7s0,57,66,1|4334zz,57,66,1|433500,51,40,0|4cg93z,51,40,0|4cg940,57,66,1|4lt7nz,57,66,1|4lt7o0,51,40,0|4v6brz,51,40,0|4v6bs0,57,66,1|54jabz,57,66,1|54jac0,51,40,0|5dwefz,51,40,0|5dweg0,57,66,1|5n9czz,57,66,1|5n9d00,51,40,0|5wmh3z,51,40,0|5wmh40,57,66,1|65zfnz,57,66,1|65zfo0,51,40,0|6fcjrz,51,40,0|6fcjs0,57,66,1|6p2gzz,57,66,1|6p2h00,51,40,0|6y2mfz,51,40,0|6y2mg0,57,66,1|77sjnz,57,66,1|77sjo0,51,40,0|7h5nrz,51,40,0|7h5ns0,57,66,1|7qimbz,57,66,1|7qimc0,51,40,0|7zvqfz,51,40,0|7zvqg0,57,66,1|898ozz,57,66,1|898p00,51,40,0|8ilt3z,51,40,0|8ilt40,57,66,1|8ryrnz,57,66,1|8ryro0,51,40,0|908zrz,51,40,0|908zs0,57,66,1|9aoubz,57,66,1|9aouc0,51,40,0|9iz2fz,51,40,0|9iz2g0,57,66,1|9trvnz,57,66,1|9trvo0,51,40,0|a1p53z,51,40,0|a1p540,57,66,1|achybz,57,66,1|achyc0,51,40,0|akf7rz,51,40,0|akf7s0,57,66,1|av80zz,57,66,1|av8100,51,40,0|b3i93z,51,40,0|b3i940,57,66,1|bdy3nz,57,66,1|bdy3o0,51,40,0|bm8brz,51,40,0|bm8bs0,57,66,1|bwo6bz,57,66,1|bwo6c0,51,40,0|c4yefz,51,40,0|c4yeg0,57,66,1|cfr7nz,57,66,1|cfr7o0,51,40,0|cnoh3z,51,40,0|cnoh40,57,66,1|cyhabz,57,66,1|cyhac0,51,40,0|d6ejrz,51,40,0|d6ejs0,57,66,1|dh7czz,57,66,1|dh7d00,51,40,0|dphl3z,51,40,0|dphl40,57,66,1|dzxfnz,57,66,1|dzxfo0,51,40,0|e87nrz,51,40,0|e87ns0,57,66,1|einibz,57,66,1|einic0,51,40,0|eqxqfz,51,40,0|eqxqg0,57,66,1|f1dkzz,57,66,1|f1dl00,51,40,0|f9nt3z,51,40,0|f9nt40,57,66,1|fkgmbz,57,66,1|fkgmc0,51,40,0|fsdvrz,51,40,0|fsdvs0,57,66,1|g36ozz,57,66,1|g36p00,51,40,0|gb3yfz,51,40,0|gb3yg0,57,66,1|glwrnz,57,66,1|glwro0,51,40,0|gu6zrz,51,40,0|gu6zs0,57,66,1|h4mubz,57,66,1|h4muc0,51,40,0|hcx2fz,51,40,0|hcx2g0,57,66,1|hncwzz,57,66,1|hncx00,51,40,0|hvn53z,51,40,0|hvn540,57,66,1|i6fybz,57,66,1|i6fyc0,51,40,0|ied7rz,51,40,0|ied7s0,57,66,1|ip60zz,57,66,1|ip6100,51,40,0|ix3afz,51,40,0|ix3ag0,57,66,1|j7w3nz,57,66,1|j7w3o0,51,40,0|jeqh3z,51,40,0|jeqh40,57,66,1|jqz4zz,57,66,1|jqz500,51,40,0|jxgjrz,51,40,0|jxgjs0,57,66,1|k9p7nz,57,66,1|k9p7o0,51,40,0|kg6mfz,51,40,0|kg6mg0,57,66,1|ksfabz,57,66,1|ksfac0,51,40,0|kz9nrz,51,40,0|kz9ns0,57,66,1|lbibnz,57,66,1|lbibo0,51,40,0|lhzqfz,51,40,0|lhzqg0,57,66,1|lu8ebz,57,66,1|lu8ec0,51,40,0|m0pt3z,51,40,0|m0pt40,57,66,1|mcygzz,57,66,1|mcyh00,51,40,0|mjfvrz,51,40,0|mjfvs0,57,66,1|mvojnz,57,66,1|mvojo0,51,40,0|n25yfz,51,40,0|n25yg0,57,66,1|neembz,57,66,1|neemc0,51,40,0|nkw13z,51,40,0|nkw140,57,66,1|nx4ozz,57,66,1|nx4p00,51,40,0|o3z2fz,51,40,0|o3z2g0,57,66,1|og7qbz,57,66,1|og7qc0,51,40,0|omp53z,51,40,0|omp540,57,66,1|oyxszz,57,66,1|oyxt00,51,40,0|p5f7rz,51,40,0|p5f7s0,57,66,1|phnvnz,57,66,1|phnvo0,51,40,0|po5afz,51,40,0|po5ag0,57,66,1|q0dybz,57,66,1|q0dyc0,51,40,0|q6vd3z,51,40,0|q6vd40,57,66,1|qj40zz,57,66,1|qj4100,51,40,0|qpyefz,51,40,0|qpyeg0,57,66,1|r272bz,57,66,1|r272c0,51,40,0|r8oh3z,51,40,0|r8oh40,57,66,1|rkx4zz,57,66,1|rkx500,51,40,0|rrejrz,51,40,0|rrejs0,57,66,1|s3n7nz,57,66,1|s3n7o0,51,40,0|sa4mfz,51,40,0|sa4mg0,57,66,1|smdabz,57,66,1|smdac0,51,40,0|ssup3z,51,40,0|ssup40,57,66,1|t53czz,57,66,1|t53d00,51,40,0|tbkrrz,51,40,0|tbkrs0,57,66,1|tntfnz,57,66,1|tntfo0,51,40,0|tunt3z,51,40,0|tunt40,57,66,1|u6wgzz,57,66,1|u6wh00,51,40,0|uddvrz,51,40,0|uddvs0,57,66,1|upmjnz,57,66,1|upmjo0,51,40,0|uw3yfz,51,40,0|uw3yg0,57,66,1|v8cmbz,57,66,1|v8cmc0,51,40,0|veu13z,51,40,0|veu140,57,66,1|vr2ozz,57,66,1|vr2p00,51,40,0|vxk3rz,51,40,0|vxk3s0,57,66,1|w9srnz,57,66,1|w9sro0,51,40,0|wgn53z,51,40,0|wgn540,57,66,1|wsvszz,57,66,1|wsvt00,51,40,0|wzd7rz,51,40,0|wzd7s0,57,66,1|xblvnz,57,66,1|xblvo0,51,40,0|xi3afz,51,40,0|xi3ag0,57,66,1|xubybz,57,66,1|xubyc0,51,40,0|y0td3z,51,40,0|y0td40,57,66,1|yd20zz,57,66,1|yd2100,51,40,0|yjjfrz,51,40,0|yjjfs0,57,66,1|yvs3nz,57,66,1|yvs3o0,51,40,0|z29ifz,51,40,0|z29ig0,57,66,1|zei6bz,57,66,1|zei6c0,51,40,0\",\"America/Lower_Princes|,0,58,0|-u7lckd,43,59,0|-2lx4u1,43,59,0|-2lx4u0,32,42,0\",\"America/Maceio|,0,131,0|-t85ldw,39,44,0|-jylag1,39,44,0|-jylag0,40,45,1|-jpb141,40,45,1|-jpb140,39,44,0|-jfsd01,39,44,0|-jfsd00,40,45,1|-j6j3s1,40,45,1|-j6j3s0,39,44,0|-ahcyc1,39,44,0|-ahcyc0,40,45,1|-aad3o1,40,45,1|-aad3o0,39,44,0|-9yl101,39,44,0|-9yl100,40,45,1|-9sd141,40,45,1|-9sd140,39,44,0|-9ft3o1,39,44,0|-9ft3o0,40,45,1|-99j941,40,45,1|-99j940,39,44,0|-8wzbo1,39,44,0|-8wzbo0,40,45,1|-8scqg1,40,45,1|-8scqg0,39,44,0|-35xmc1,39,44,0|-35xmc0,40,45,1|-31nzs1,40,45,1|-31nzs0,39,44,0|-2kdp01,39,44,0|-2kdp00,40,45,1|-2hcig1,40,45,1|-2hcig0,39,44,0|-24qqc1,39,44,0|-24qqc0,40,45,1|-204541,40,45,1|-204540,39,44,0|-1nid01,39,44,0|-1nid00,40,45,1|-1hc7s1,40,45,1|-1hc7s0,39,44,0|-14qfo1,39,44,0|-14qfo0,40,45,1|-yifs1,40,45,1|-yifs0,39,44,0|89jcbz,39,44,0|89jcc0,40,45,1|8gdk7z,40,45,1|8gdk80,39,44,0|8rwgbz,39,44,0|8rwgc0,40,45,1|8xns7z,40,45,1|8xns80,39,44,0|9aodnz,39,44,0|9aodo0,40,45,1|9g2qvz,40,45,1|9g2qw0,39,44,0|9t1hnz,39,44,0|9t1ho0,40,45,1|9yfuvz,40,45,1|9yfuw0,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|ahvuvz,40,45,1|ahvuw0,39,44,0|dggyzz,39,44,0|dggz00,40,45,1|dml9jz,40,45,1|dml9k0,39,44,0|fj0azz,39,44,0|fj0b00,40,45,1|fqkg7z,40,45,1|fqkg80,39,44,0|g23cbz,39,44,0|g23cc0,40,45,1|g2t6vz,40,45,1|g2t6w0,39,44,0|gl6dnz,39,44,0|gl6do0,40,45,1|grnmvz,40,45,1|grnmw0,39,44,0\",\"America/Managua|,0,132,0|-15r0tcs,21,133,0|-ijh6op,21,133,0|-ijh6oo,45,62,0|1qkbbz,45,62,0|1qkbc0,49,63,0|2ob1vz,49,63,0|2ob1w0,45,62,0|4t08nz,45,62,0|4t08o0,46,63,1|4y3hvz,46,63,1|4y3hw0,45,62,0|5bqbbz,45,62,0|5bqbc0,46,63,1|5gtkjz,46,63,1|5gtkk0,45,62,0|bhcefz,45,62,0|bhceg0,49,63,0|bv2gjz,49,63,0|bv2gk0,45,62,0|c05vbz,45,62,0|c05vc0,49,63,0|e3bcjz,49,63,0|e3bck0,45,62,0|iepvbz,45,62,0|iepvc0,46,63,1|inpv7z,46,63,1|inpv80,45,62,0|iyizjz,45,62,0|iyizk0,46,63,1|j6g0nz,46,63,1|j6g0o0,45,62,0\",\"America/Manaus|,0,134,0|-t85gvw,42,42,0|-jyl7o1,42,42,0|-jyl7o0,39,44,1|-jpayc1,39,44,1|-jpayc0,42,42,0|-jfsa81,42,42,0|-jfsa80,39,44,1|-j6j101,39,44,1|-j6j100,42,42,0|-ahcvk1,42,42,0|-ahcvk0,39,44,1|-aad0w1,39,44,1|-aad0w0,42,42,0|-9yky81,42,42,0|-9yky80,39,44,1|-9scyc1,39,44,1|-9scyc0,42,42,0|-9ft0w1,42,42,0|-9ft0w0,39,44,1|-99j6c1,39,44,1|-99j6c0,42,42,0|-8wz8w1,42,42,0|-8wz8w0,39,44,1|-8scno1,39,44,1|-8scno0,42,42,0|-35xjk1,42,42,0|-35xjk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2kdm81,42,42,0|-2kdm80,39,44,1|-2hcfo1,39,44,1|-2hcfo0,42,42,0|-24qnk1,42,42,0|-24qnk0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1nia81,42,42,0|-1nia80,39,44,1|-1hc501,39,44,1|-1hc500,42,42,0|-14qcw1,42,42,0|-14qcw0,39,44,1|-yid01,39,44,1|-yid00,42,42,0|89jf3z,42,42,0|89jf40,39,44,1|8gdmzz,39,44,1|8gdn00,42,42,0|8rwj3z,42,42,0|8rwj40,39,44,1|8xnuzz,39,44,1|8xnv00,42,42,0|9aogfz,42,42,0|9aogg0,39,44,1|9g2tnz,39,44,1|9g2to0,42,42,0|cf0wfz,42,42,0|cf0wg0,39,44,1|cli5nz,39,44,1|cli5o0,42,42,0\",\"America/Marigot|,0,41,0|-u6m79w,32,42,0\",\"America/Martinique|,0,135,0|-15r0y0s,80,135,0|-umcvct,80,135,0|-umcvcs,32,42,0|5ct1rz,32,42,0|5ct1s0,54,44,1|5lt1nz,54,44,1|5lt1o0,32,42,0\",\"America/Matamoros|,0,136,0|-p1u7c0,45,62,0|9iywvz,45,62,0|9iyww0,46,63,1|9trq3z,46,63,1|9trq40,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxkvz,45,62,0|eqxkw0,46,63,1|f1dffz,46,63,1|f1dfg0,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkggrz,46,63,1|fkggs0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,45,62,0|gcwm7z,45,62,0|gcwm80,46,63,1|gkgrfz,46,63,1|gkgrg0,45,62,0|gu6u7z,45,62,0|gu6u80,46,63,1|h4morz,46,63,1|h4mos0,45,62,0|hcwwvz,45,62,0|hcwww0,46,63,1|hncrfz,46,63,1|hncrg0,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fsrz,46,63,1|i6fss0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5vfz,46,63,1|ip5vg0,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jft7jz,45,62,0|jft7k0,46,63,1|jqm0rz,46,63,1|jqm0s0,45,62,0|jyw8vz,45,62,0|jyw8w0,46,63,1|k9c3fz,46,63,1|k9c3g0,45,62,0|khmbjz,45,62,0|khmbk0,46,63,1|ks263z,46,63,1|ks2640,45,62,0|kz9i7z,45,62,0|kz9i80,46,63,1|lbi63z,46,63,1|lbi640,45,62,0|lhzkvz,45,62,0|lhzkw0,46,63,1|lu88rz,46,63,1|lu88s0,45,62,0|m0pnjz,45,62,0|m0pnk0,46,63,1|mcybfz,46,63,1|mcybg0,45,62,0|mjfq7z,45,62,0|mjfq80,46,63,1|mvoe3z,46,63,1|mvoe40,45,62,0|n25svz,45,62,0|n25sw0,46,63,1|neegrz,46,63,1|neegs0,45,62,0|nkvvjz,45,62,0|nkvvk0,46,63,1|nx4jfz,46,63,1|nx4jg0,45,62,0|o3ywvz,45,62,0|o3yww0,46,63,1|og7krz,46,63,1|og7ks0,45,62,0|omozjz,45,62,0|omozk0,46,63,1|oyxnfz,46,63,1|oyxng0,45,62,0|p5f27z,45,62,0|p5f280,46,63,1|phnq3z,46,63,1|phnq40,45,62,0|po54vz,45,62,0|po54w0,46,63,1|q0dsrz,46,63,1|q0dss0,45,62,0|q6v7jz,45,62,0|q6v7k0,46,63,1|qj3vfz,46,63,1|qj3vg0,45,62,0|qpy8vz,45,62,0|qpy8w0,46,63,1|r26wrz,46,63,1|r26ws0,45,62,0|r8objz,45,62,0|r8obk0,46,63,1|rkwzfz,46,63,1|rkwzg0,45,62,0|rree7z,45,62,0|rree80,46,63,1|s3n23z,46,63,1|s3n240,45,62,0|sa4gvz,45,62,0|sa4gw0,46,63,1|smd4rz,46,63,1|smd4s0,45,62,0|ssujjz,45,62,0|ssujk0,46,63,1|t537fz,46,63,1|t537g0,45,62,0|tbkm7z,45,62,0|tbkm80,46,63,1|tnta3z,46,63,1|tnta40,45,62,0|tunnjz,45,62,0|tunnk0,46,63,1|u6wbfz,46,63,1|u6wbg0,45,62,0|uddq7z,45,62,0|uddq80,46,63,1|upme3z,46,63,1|upme40,45,62,0|uw3svz,45,62,0|uw3sw0,46,63,1|v8cgrz,46,63,1|v8cgs0,45,62,0|vetvjz,45,62,0|vetvk0,46,63,1|vr2jfz,46,63,1|vr2jg0,45,62,0|vxjy7z,45,62,0|vxjy80,46,63,1|w9sm3z,46,63,1|w9sm40,45,62,0|wgmzjz,45,62,0|wgmzk0,46,63,1|wsvnfz,46,63,1|wsvng0,45,62,0|wzd27z,45,62,0|wzd280,46,63,1|xblq3z,46,63,1|xblq40,45,62,0|xi34vz,45,62,0|xi34w0,46,63,1|xubsrz,46,63,1|xubss0,45,62,0|y0t7jz,45,62,0|y0t7k0,46,63,1|yd1vfz,46,63,1|yd1vg0,45,62,0|yjja7z,45,62,0|yjja80,46,63,1|yvry3z,46,63,1|yvry40,45,62,0|z29cvz,45,62,0|z29cw0,46,63,1|zei0rz,46,63,1|zei0s0,45,62,0\",\"America/Mazatlan|,0,137,0|-p1u4k0,50,66,0|-m7mko1,50,66,0|-m7mko0,45,62,0|-kf67c1,45,62,0|-kf67c0,50,66,0|-k6j3c1,50,66,0|-k6j3c0,45,62,0|-jypm01,45,62,0|-jypm00,50,66,0|-jpan81,50,66,0|-jpan80,45,62,0|-eg9601,45,62,0|-eg9600,50,66,0|-axv381,50,66,0|-axv380,51,40,0|m7z,51,40,0|m80,50,66,0|dphibz,50,66,0|dphic0,52,62,1|dzxcvz,52,62,1|dzxcw0,50,66,0|e87kzz,50,66,0|e87l00,52,62,1|einfjz,52,62,1|einfk0,50,66,0|eqxnnz,50,66,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0|f9nqbz,50,66,0|f9nqc0,52,62,1|fkgjjz,52,62,1|fkgjk0,50,66,0|fsdszz,50,66,0|fsdt00,52,62,1|g36m7z,52,62,1|g36m80,50,66,0|gcwozz,50,66,0|gcwp00,52,62,1|gkgu7z,52,62,1|gkgu80,50,66,0|gu6wzz,50,66,0|gu6x00,52,62,1|h4mrjz,52,62,1|h4mrk0,50,66,0|hcwznz,50,66,0|hcwzo0,52,62,1|hncu7z,52,62,1|hncu80,50,66,0|hvn2bz,50,66,0|hvn2c0,52,62,1|i6fvjz,52,62,1|i6fvk0,50,66,0|ied4zz,50,66,0|ied500,52,62,1|ip5y7z,52,62,1|ip5y80,50,66,0|ix37nz,50,66,0|ix37o0,52,62,1|j7w0vz,52,62,1|j7w0w0,50,66,0|jftabz,50,66,0|jftac0,52,62,1|jqm3jz,52,62,1|jqm3k0,50,66,0|jywbnz,50,66,0|jywbo0,52,62,1|k9c67z,52,62,1|k9c680,50,66,0|khmebz,50,66,0|khmec0,52,62,1|ks28vz,52,62,1|ks28w0,50,66,0|l0cgzz,50,66,0|l0ch00,52,62,1|lb5a7z,52,62,1|lb5a80,50,66,0|lj2jnz,50,66,0|lj2jo0,52,62,1|ltvcvz,52,62,1|ltvcw0,50,66,0|m1smbz,50,66,0|m1smc0,52,62,1|mclfjz,52,62,1|mclfk0,50,66,0|mkvnnz,50,66,0|mkvno0,52,62,1|mvbi7z,52,62,1|mvbi80,50,66,0|n3lqbz,50,66,0|n3lqc0,52,62,1|ne1kvz,52,62,1|ne1kw0,50,66,0|nmbszz,50,66,0|nmbt00,52,62,1|nwrnjz,52,62,1|nwrnk0,50,66,0|o51vnz,50,66,0|o51vo0,52,62,1|ofuovz,52,62,1|ofuow0,50,66,0|onrybz,50,66,0|onryc0,52,62,1|oykrjz,52,62,1|oykrk0,50,66,0|p6i0zz,50,66,0|p6i100,52,62,1|phau7z,52,62,1|phau80,50,66,0|ppl2bz,50,66,0|ppl2c0,52,62,1|q00wvz,52,62,1|q00ww0,50,66,0|q8b4zz,50,66,0|q8b500,52,62,1|qiqzjz,52,62,1|qiqzk0,50,66,0|qr17nz,50,66,0|qr17o0,52,62,1|r1u0vz,52,62,1|r1u0w0,50,66,0|r9rabz,50,66,0|r9rac0,52,62,1|rkk3jz,52,62,1|rkk3k0,50,66,0|rshczz,50,66,0|rshd00,52,62,1|s3a67z,52,62,1|s3a680,50,66,0|sbkebz,50,66,0|sbkec0,52,62,1|sm08vz,52,62,1|sm08w0,50,66,0|suagzz,50,66,0|suah00,52,62,1|t4qbjz,52,62,1|t4qbk0,50,66,0|td0jnz,50,66,0|td0jo0,52,62,1|tnge7z,52,62,1|tnge80,50,66,0|tvqmbz,50,66,0|tvqmc0,52,62,1|u6jfjz,52,62,1|u6jfk0,50,66,0|uegozz,50,66,0|uegp00,52,62,1|up9i7z,52,62,1|up9i80,50,66,0|ux6rnz,50,66,0|ux6ro0,52,62,1|v7zkvz,52,62,1|v7zkw0,50,66,0|vg9szz,50,66,0|vg9t00,52,62,1|vqpnjz,52,62,1|vqpnk0,50,66,0|vyzvnz,50,66,0|vyzvo0,52,62,1|w9fq7z,52,62,1|w9fq80,50,66,0|whpybz,50,66,0|whpyc0,52,62,1|wsirjz,52,62,1|wsirk0,50,66,0|x0g0zz,50,66,0|x0g100,52,62,1|xb8u7z,52,62,1|xb8u80,50,66,0|xj63nz,50,66,0|xj63o0,52,62,1|xtywvz,52,62,1|xtyww0,50,66,0|y1w6bz,50,66,0|y1w6c0,52,62,1|ycozjz,52,62,1|ycozk0,50,66,0|ykz7nz,50,66,0|ykz7o0,52,62,1|yvf27z,52,62,1|yvf280,50,66,0|z3pabz,50,66,0|z3pac0,52,62,1|ze54vz,52,62,1|ze54w0,50,66,0\",\"America/Menominee|,0,138,0|-17zjvrx,45,62,0|-r0esg1,45,62,0|-r0esg0,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-qhops1,45,62,0|-qhops0,46,63,1|-q6vwk1,46,63,1|-q6vwk0,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-ccw1s1,45,62,0|-ccw1s0,46,63,1|-c4yxw1,46,63,1|-c4yxw0,45,62,0|-1xbog1,45,62,0|-1xbog0,46,63,1|-1nlr81,46,63,1|-1nlr80,45,62,0|-cshs1,45,62,0|-cshs0,49,63,0|1qgorz,49,63,0|1qgos0,46,63,1|1ztq3z,46,63,1|1ztq40,45,62,0|23ffjz,45,62,0|23ffk0,46,63,1|2ijsrz,46,63,1|2ijss0,45,62,0|2oo8vz,45,62,0|2oo8w0,46,63,1|319vfz,46,63,1|319vg0,45,62,0|3amzjz,45,62,0|3amzk0,46,63,1|3kcwrz,46,63,1|3kcws0,45,62,0|3td27z,45,62,0|3td280,46,63,1|432zfz,46,63,1|432zg0,45,62,0|4cg3jz,45,62,0|4cg3k0,46,63,1|4lt23z,46,63,1|4lt240,45,62,0|4v667z,45,62,0|4v6680,46,63,1|54j4rz,46,63,1|54j4s0,45,62,0|5dw8vz,45,62,0|5dw8w0,46,63,1|5n97fz,46,63,1|5n97g0,45,62,0|5wmbjz,45,62,0|5wmbk0,46,63,1|65za3z,46,63,1|65za40,45,62,0|6fce7z,45,62,0|6fce80,46,63,1|6p2bfz,46,63,1|6p2bg0,45,62,0|6y2gvz,45,62,0|6y2gw0,46,63,1|77se3z,46,63,1|77se40,45,62,0|7h5i7z,45,62,0|7h5i80,46,63,1|7qigrz,46,63,1|7qigs0,45,62,0|7zvkvz,45,62,0|7zvkw0,46,63,1|898jfz,46,63,1|898jg0,45,62,0|8ilnjz,45,62,0|8ilnk0,46,63,1|8rym3z,46,63,1|8rym40,45,62,0|908u7z,45,62,0|908u80,46,63,1|9aoorz,46,63,1|9aoos0,45,62,0|9iywvz,45,62,0|9iyww0,46,63,1|9trq3z,46,63,1|9trq40,45,62,0|a1ozjz,45,62,0|a1ozk0,46,63,1|achsrz,46,63,1|achss0,45,62,0|akf27z,45,62,0|akf280,46,63,1|av7vfz,46,63,1|av7vg0,45,62,0|b3i3jz,45,62,0|b3i3k0,46,63,1|bdxy3z,46,63,1|bdxy40,45,62,0|bm867z,45,62,0|bm8680,46,63,1|bwo0rz,46,63,1|bwo0s0,45,62,0|c4y8vz,45,62,0|c4y8w0,46,63,1|cfr23z,46,63,1|cfr240,45,62,0|cnobjz,45,62,0|cnobk0,46,63,1|cyh4rz,46,63,1|cyh4s0,45,62,0|d6ee7z,45,62,0|d6ee80,46,63,1|dh77fz,46,63,1|dh77g0,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxkvz,45,62,0|eqxkw0,46,63,1|f1dffz,46,63,1|f1dfg0,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkggrz,46,63,1|fkggs0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,45,62,0|gb3svz,45,62,0|gb3sw0,46,63,1|glwm3z,46,63,1|glwm40,45,62,0|gu6u7z,45,62,0|gu6u80,46,63,1|h4morz,46,63,1|h4mos0,45,62,0|hcwwvz,45,62,0|hcwww0,46,63,1|hncrfz,46,63,1|hncrg0,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fsrz,46,63,1|i6fss0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5vfz,46,63,1|ip5vg0,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jeqbjz,45,62,0|jeqbk0,46,63,1|jqyzfz,46,63,1|jqyzg0,45,62,0|jxge7z,45,62,0|jxge80,46,63,1|k9p23z,46,63,1|k9p240,45,62,0|kg6gvz,45,62,0|kg6gw0,46,63,1|ksf4rz,46,63,1|ksf4s0,45,62,0|kz9i7z,45,62,0|kz9i80,46,63,1|lbi63z,46,63,1|lbi640,45,62,0|lhzkvz,45,62,0|lhzkw0,46,63,1|lu88rz,46,63,1|lu88s0,45,62,0|m0pnjz,45,62,0|m0pnk0,46,63,1|mcybfz,46,63,1|mcybg0,45,62,0|mjfq7z,45,62,0|mjfq80,46,63,1|mvoe3z,46,63,1|mvoe40,45,62,0|n25svz,45,62,0|n25sw0,46,63,1|neegrz,46,63,1|neegs0,45,62,0|nkvvjz,45,62,0|nkvvk0,46,63,1|nx4jfz,46,63,1|nx4jg0,45,62,0|o3ywvz,45,62,0|o3yww0,46,63,1|og7krz,46,63,1|og7ks0,45,62,0|omozjz,45,62,0|omozk0,46,63,1|oyxnfz,46,63,1|oyxng0,45,62,0|p5f27z,45,62,0|p5f280,46,63,1|phnq3z,46,63,1|phnq40,45,62,0|po54vz,45,62,0|po54w0,46,63,1|q0dsrz,46,63,1|q0dss0,45,62,0|q6v7jz,45,62,0|q6v7k0,46,63,1|qj3vfz,46,63,1|qj3vg0,45,62,0|qpy8vz,45,62,0|qpy8w0,46,63,1|r26wrz,46,63,1|r26ws0,45,62,0|r8objz,45,62,0|r8obk0,46,63,1|rkwzfz,46,63,1|rkwzg0,45,62,0|rree7z,45,62,0|rree80,46,63,1|s3n23z,46,63,1|s3n240,45,62,0|sa4gvz,45,62,0|sa4gw0,46,63,1|smd4rz,46,63,1|smd4s0,45,62,0|ssujjz,45,62,0|ssujk0,46,63,1|t537fz,46,63,1|t537g0,45,62,0|tbkm7z,45,62,0|tbkm80,46,63,1|tnta3z,46,63,1|tnta40,45,62,0|tunnjz,45,62,0|tunnk0,46,63,1|u6wbfz,46,63,1|u6wbg0,45,62,0|uddq7z,45,62,0|uddq80,46,63,1|upme3z,46,63,1|upme40,45,62,0|uw3svz,45,62,0|uw3sw0,46,63,1|v8cgrz,46,63,1|v8cgs0,45,62,0|vetvjz,45,62,0|vetvk0,46,63,1|vr2jfz,46,63,1|vr2jg0,45,62,0|vxjy7z,45,62,0|vxjy80,46,63,1|w9sm3z,46,63,1|w9sm40,45,62,0|wgmzjz,45,62,0|wgmzk0,46,63,1|wsvnfz,46,63,1|wsvng0,45,62,0|wzd27z,45,62,0|wzd280,46,63,1|xblq3z,46,63,1|xblq40,45,62,0|xi34vz,45,62,0|xi34w0,46,63,1|xubsrz,46,63,1|xubss0,45,62,0|y0t7jz,45,62,0|y0t7k0,46,63,1|yd1vfz,46,63,1|yd1vg0,45,62,0|yjja7z,45,62,0|yjja80,46,63,1|yvry3z,46,63,1|yvry40,45,62,0|z29cvz,45,62,0|z29cw0,46,63,1|zei0rz,46,63,1|zei0s0,45,62,0\",\"America/Merida|,0,139,0|-p1u7c0,45,62,0|690gnz,45,62,0|690go0,49,63,0|6qpf7z,49,63,0|6qpf80,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxkvz,45,62,0|eqxkw0,46,63,1|f1dffz,46,63,1|f1dfg0,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkggrz,46,63,1|fkggs0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,45,62,0|gcwm7z,45,62,0|gcwm80,46,63,1|gkgrfz,46,63,1|gkgrg0,45,62,0|gu6u7z,45,62,0|gu6u80,46,63,1|h4morz,46,63,1|h4mos0,45,62,0|hcwwvz,45,62,0|hcwww0,46,63,1|hncrfz,46,63,1|hncrg0,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fsrz,46,63,1|i6fss0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5vfz,46,63,1|ip5vg0,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jft7jz,45,62,0|jft7k0,46,63,1|jqm0rz,46,63,1|jqm0s0,45,62,0|jyw8vz,45,62,0|jyw8w0,46,63,1|k9c3fz,46,63,1|k9c3g0,45,62,0|khmbjz,45,62,0|khmbk0,46,63,1|ks263z,46,63,1|ks2640,45,62,0|l0ce7z,45,62,0|l0ce80,46,63,1|lb57fz,46,63,1|lb57g0,45,62,0|lj2gvz,45,62,0|lj2gw0,46,63,1|ltva3z,46,63,1|ltva40,45,62,0|m1sjjz,45,62,0|m1sjk0,46,63,1|mclcrz,46,63,1|mclcs0,45,62,0|mkvkvz,45,62,0|mkvkw0,46,63,1|mvbffz,46,63,1|mvbfg0,45,62,0|n3lnjz,45,62,0|n3lnk0,46,63,1|ne1i3z,46,63,1|ne1i40,45,62,0|nmbq7z,45,62,0|nmbq80,46,63,1|nwrkrz,46,63,1|nwrks0,45,62,0|o51svz,45,62,0|o51sw0,46,63,1|ofum3z,46,63,1|ofum40,45,62,0|onrvjz,45,62,0|onrvk0,46,63,1|oykorz,46,63,1|oykos0,45,62,0|p6hy7z,45,62,0|p6hy80,46,63,1|pharfz,46,63,1|pharg0,45,62,0|ppkzjz,45,62,0|ppkzk0,46,63,1|q00u3z,46,63,1|q00u40,45,62,0|q8b27z,45,62,0|q8b280,46,63,1|qiqwrz,46,63,1|qiqws0,45,62,0|qr14vz,45,62,0|qr14w0,46,63,1|r1ty3z,46,63,1|r1ty40,45,62,0|r9r7jz,45,62,0|r9r7k0,46,63,1|rkk0rz,46,63,1|rkk0s0,45,62,0|rsha7z,45,62,0|rsha80,46,63,1|s3a3fz,46,63,1|s3a3g0,45,62,0|sbkbjz,45,62,0|sbkbk0,46,63,1|sm063z,46,63,1|sm0640,45,62,0|suae7z,45,62,0|suae80,46,63,1|t4q8rz,46,63,1|t4q8s0,45,62,0|td0gvz,45,62,0|td0gw0,46,63,1|tngbfz,46,63,1|tngbg0,45,62,0|tvqjjz,45,62,0|tvqjk0,46,63,1|u6jcrz,46,63,1|u6jcs0,45,62,0|uegm7z,45,62,0|uegm80,46,63,1|up9ffz,46,63,1|up9fg0,45,62,0|ux6ovz,45,62,0|ux6ow0,46,63,1|v7zi3z,46,63,1|v7zi40,45,62,0|vg9q7z,45,62,0|vg9q80,46,63,1|vqpkrz,46,63,1|vqpks0,45,62,0|vyzsvz,45,62,0|vyzsw0,46,63,1|w9fnfz,46,63,1|w9fng0,45,62,0|whpvjz,45,62,0|whpvk0,46,63,1|wsiorz,46,63,1|wsios0,45,62,0|x0fy7z,45,62,0|x0fy80,46,63,1|xb8rfz,46,63,1|xb8rg0,45,62,0|xj60vz,45,62,0|xj60w0,46,63,1|xtyu3z,46,63,1|xtyu40,45,62,0|y1w3jz,45,62,0|y1w3k0,46,63,1|ycowrz,46,63,1|ycows0,45,62,0|ykz4vz,45,62,0|ykz4w0,46,63,1|yvezfz,46,63,1|yvezg0,45,62,0|z3p7jz,45,62,0|z3p7k0,46,63,1|ze523z,46,63,1|ze5240,45,62,0\",\"America/Metlakatla|,0,140,0|-1hc7qjz,0,141,0|-1078wyv,0,141,0|-1078wyu,51,40,0|-ek1w81,51,40,0|-ek1w80,68,66,1|-cq2tg1,68,66,1|-cq2tg0,69,66,1|-cnov01,69,66,1|-cnov00,51,40,0|-csc81,51,40,0|-csc80,57,66,1|-3fdo1,57,66,1|-3fdo0,51,40,0|5xqfz,51,40,0|5xqg0,57,66,1|faozz,57,66,1|fap00,51,40,0|ont3z,51,40,0|ont40,57,66,1|ydqbz,57,66,1|ydqc0,51,40,0|17qufz,51,40,0|17qug0,57,66,1|1h3szz,57,66,1|1h3t00,51,40,0|1qgx3z,51,40,0|1qgx40,57,66,1|1ztvnz,57,66,1|1ztvo0,51,40,0|23fl3z,51,40,0|23fl40,57,66,1|2ijybz,57,66,1|2ijyc0,51,40,0|2ooefz,51,40,0|2ooeg0,57,66,1|31a0zz,57,66,1|31a100,51,40,0|3an53z,51,40,0|3an540,57,66,1|3kd2bz,57,66,1|3kd2c0,51,40,0|3td7rz,51,40,0|3td7s0,57,66,1|4334zz,57,66,1|433500,51,40,0|4cg93z,51,40,0|4cg940,57,66,1|4lt7nz,57,66,1|4lt7o0,51,40,0|4v6brz,51,40,0|4v6bs0,57,66,1|54jabz,57,66,1|54jac0,51,40,0|5dwefz,51,40,0|5dweg0,57,66,1|5n9czz,57,66,1|5n9d00,51,40,0|5wmh3z,51,40,0|5wmh40,57,66,1|65zfnz,57,66,1|65zfo0,51,40,0|6fcjrz,51,40,0|6fcjs0,57,66,1|6p2gzz,57,66,1|6p2h00,51,40,0|6y2mfz,51,40,0|6y2mg0,57,66,1|77sjnz,57,66,1|77sjo0,51,40,0|nx4rrz,51,40,0|nx4rs0,37,37,0|o3z57z,37,37,0|o3z580,38,40,1|og7t3z,38,40,1|og7t40,37,37,0|omp7vz,37,37,0|omp7w0,38,40,1|oyxvrz,38,40,1|oyxvs0,37,37,0|p5fajz,37,37,0|p5fak0,38,40,1|phnyfz,38,40,1|phnyg0,51,40,0|plmjrz,51,40,0|plmjs0,37,37,0|po5d7z,37,37,0|po5d80,38,40,1|q0e13z,38,40,1|q0e140,37,37,0|q6vfvz,37,37,0|q6vfw0,38,40,1|qj43rz,38,40,1|qj43s0,37,37,0|qpyh7z,37,37,0|qpyh80,38,40,1|r2753z,38,40,1|r27540,37,37,0|r8ojvz,37,37,0|r8ojw0,38,40,1|rkx7rz,38,40,1|rkx7s0,37,37,0|rremjz,37,37,0|rremk0,38,40,1|s3nafz,38,40,1|s3nag0,37,37,0|sa4p7z,37,37,0|sa4p80,38,40,1|smdd3z,38,40,1|smdd40,37,37,0|ssurvz,37,37,0|ssurw0,38,40,1|t53frz,38,40,1|t53fs0,37,37,0|tbkujz,37,37,0|tbkuk0,38,40,1|tntifz,38,40,1|tntig0,37,37,0|tunvvz,37,37,0|tunvw0,38,40,1|u6wjrz,38,40,1|u6wjs0,37,37,0|uddyjz,37,37,0|uddyk0,38,40,1|upmmfz,38,40,1|upmmg0,37,37,0|uw417z,37,37,0|uw4180,38,40,1|v8cp3z,38,40,1|v8cp40,37,37,0|veu3vz,37,37,0|veu3w0,38,40,1|vr2rrz,38,40,1|vr2rs0,37,37,0|vxk6jz,37,37,0|vxk6k0,38,40,1|w9sufz,38,40,1|w9sug0,37,37,0|wgn7vz,37,37,0|wgn7w0,38,40,1|wsvvrz,38,40,1|wsvvs0,37,37,0|wzdajz,37,37,0|wzdak0,38,40,1|xblyfz,38,40,1|xblyg0,37,37,0|xi3d7z,37,37,0|xi3d80,38,40,1|xuc13z,38,40,1|xuc140,37,37,0|y0tfvz,37,37,0|y0tfw0,38,40,1|yd23rz,38,40,1|yd23s0,37,37,0|yjjijz,37,37,0|yjjik0,38,40,1|yvs6fz,38,40,1|yvs6g0,37,37,0|z29l7z,37,37,0|z29l80,38,40,1|zei93z,38,40,1|zei940,37,37,0\",\"America/Mexico_City|,0,142,0|-p1u4k0,50,66,0|-m7mko1,50,66,0|-m7mko0,45,62,0|-kf67c1,45,62,0|-kf67c0,50,66,0|-k6j3c1,50,66,0|-k6j3c0,45,62,0|-jypm01,45,62,0|-jypm00,50,66,0|-jpan81,50,66,0|-jpan80,45,62,0|-g4n8o1,45,62,0|-g4n8o0,46,63,1|-fxg241,46,63,1|-fxg240,45,62,0|-f60y01,45,62,0|-f60y00,46,63,1|-f07rg1,46,63,1|-f07rg0,45,62,0|-dlc7c1,45,62,0|-dlc7c0,47,63,1|-deaks1,47,63,1|-deaks0,45,62,0|-adljc1,45,62,0|-adljc0,46,63,1|-a4yi41,46,63,1|-a4yi40,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxkvz,45,62,0|eqxkw0,46,63,1|f1dffz,46,63,1|f1dfg0,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkggrz,46,63,1|fkggs0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,45,62,0|gcwm7z,45,62,0|gcwm80,46,63,1|gkgrfz,46,63,1|gkgrg0,45,62,0|gu6u7z,45,62,0|gu6u80,46,63,1|h4morz,46,63,1|h4mos0,45,62,0|hcwwvz,45,62,0|hcwww0,46,63,1|hncrfz,46,63,1|hncrg0,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fsrz,46,63,1|i6fss0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5vfz,46,63,1|ip5vg0,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jft7jz,45,62,0|jft7k0,46,63,1|jqm0rz,46,63,1|jqm0s0,45,62,0|jyw8vz,45,62,0|jyw8w0,46,63,1|k9c3fz,46,63,1|k9c3g0,45,62,0|khmbjz,45,62,0|khmbk0,46,63,1|ks263z,46,63,1|ks2640,45,62,0|l0ce7z,45,62,0|l0ce80,46,63,1|lb57fz,46,63,1|lb57g0,45,62,0|lj2gvz,45,62,0|lj2gw0,46,63,1|ltva3z,46,63,1|ltva40,45,62,0|m1sjjz,45,62,0|m1sjk0,46,63,1|mclcrz,46,63,1|mclcs0,45,62,0|mkvkvz,45,62,0|mkvkw0,46,63,1|mvbffz,46,63,1|mvbfg0,45,62,0|n3lnjz,45,62,0|n3lnk0,46,63,1|ne1i3z,46,63,1|ne1i40,45,62,0|nmbq7z,45,62,0|nmbq80,46,63,1|nwrkrz,46,63,1|nwrks0,45,62,0|o51svz,45,62,0|o51sw0,46,63,1|ofum3z,46,63,1|ofum40,45,62,0|onrvjz,45,62,0|onrvk0,46,63,1|oykorz,46,63,1|oykos0,45,62,0|p6hy7z,45,62,0|p6hy80,46,63,1|pharfz,46,63,1|pharg0,45,62,0|ppkzjz,45,62,0|ppkzk0,46,63,1|q00u3z,46,63,1|q00u40,45,62,0|q8b27z,45,62,0|q8b280,46,63,1|qiqwrz,46,63,1|qiqws0,45,62,0|qr14vz,45,62,0|qr14w0,46,63,1|r1ty3z,46,63,1|r1ty40,45,62,0|r9r7jz,45,62,0|r9r7k0,46,63,1|rkk0rz,46,63,1|rkk0s0,45,62,0|rsha7z,45,62,0|rsha80,46,63,1|s3a3fz,46,63,1|s3a3g0,45,62,0|sbkbjz,45,62,0|sbkbk0,46,63,1|sm063z,46,63,1|sm0640,45,62,0|suae7z,45,62,0|suae80,46,63,1|t4q8rz,46,63,1|t4q8s0,45,62,0|td0gvz,45,62,0|td0gw0,46,63,1|tngbfz,46,63,1|tngbg0,45,62,0|tvqjjz,45,62,0|tvqjk0,46,63,1|u6jcrz,46,63,1|u6jcs0,45,62,0|uegm7z,45,62,0|uegm80,46,63,1|up9ffz,46,63,1|up9fg0,45,62,0|ux6ovz,45,62,0|ux6ow0,46,63,1|v7zi3z,46,63,1|v7zi40,45,62,0|vg9q7z,45,62,0|vg9q80,46,63,1|vqpkrz,46,63,1|vqpks0,45,62,0|vyzsvz,45,62,0|vyzsw0,46,63,1|w9fnfz,46,63,1|w9fng0,45,62,0|whpvjz,45,62,0|whpvk0,46,63,1|wsiorz,46,63,1|wsios0,45,62,0|x0fy7z,45,62,0|x0fy80,46,63,1|xb8rfz,46,63,1|xb8rg0,45,62,0|xj60vz,45,62,0|xj60w0,46,63,1|xtyu3z,46,63,1|xtyu40,45,62,0|y1w3jz,45,62,0|y1w3k0,46,63,1|ycowrz,46,63,1|ycows0,45,62,0|ykz4vz,45,62,0|ykz4w0,46,63,1|yvezfz,46,63,1|yvezg0,45,62,0|z3p7jz,45,62,0|z3p7k0,46,63,1|ze523z,46,63,1|ze5240,45,62,0\",\"America/Miquelon|,0,143,0|-ulmyxk,32,42,0|5e3cfz,32,42,0|5e3cg0,39,44,0|908lvz,39,44,0|908lw0,40,45,1|9aogfz,40,45,1|9aogg0,39,44,0|9iyojz,39,44,0|9iyok0,40,45,1|9trhrz,40,45,1|9trhs0,39,44,0|a1or7z,39,44,0|a1or80,40,45,1|achkfz,40,45,1|achkg0,39,44,0|aketvz,39,44,0|aketw0,40,45,1|av7n3z,40,45,1|av7n40,39,44,0|b3hv7z,39,44,0|b3hv80,40,45,1|bdxprz,40,45,1|bdxps0,39,44,0|bm7xvz,39,44,0|bm7xw0,40,45,1|bwnsfz,40,45,1|bwnsg0,39,44,0|c4y0jz,39,44,0|c4y0k0,40,45,1|cfqtrz,40,45,1|cfqts0,39,44,0|cno37z,39,44,0|cno380,40,45,1|cygwfz,40,45,1|cygwg0,39,44,0|d6e5vz,39,44,0|d6e5w0,40,45,1|dh6z3z,40,45,1|dh6z40,39,44,0|dph77z,39,44,0|dph780,40,45,1|dzx1rz,40,45,1|dzx1s0,39,44,0|e879vz,39,44,0|e879w0,40,45,1|ein4fz,40,45,1|ein4g0,39,44,0|eqxcjz,39,44,0|eqxck0,40,45,1|f1d73z,40,45,1|f1d740,39,44,0|f9nf7z,39,44,0|f9nf80,40,45,1|fkg8fz,40,45,1|fkg8g0,39,44,0|fsdhvz,39,44,0|fsdhw0,40,45,1|g36b3z,40,45,1|g36b40,39,44,0|gb3kjz,39,44,0|gb3kk0,40,45,1|glwdrz,40,45,1|glwds0,39,44,0|gu6lvz,39,44,0|gu6lw0,40,45,1|h4mgfz,40,45,1|h4mgg0,39,44,0|hcwojz,39,44,0|hcwok0,40,45,1|hncj3z,40,45,1|hncj40,39,44,0|hvmr7z,39,44,0|hvmr80,40,45,1|i6fkfz,40,45,1|i6fkg0,39,44,0|iectvz,39,44,0|iectw0,40,45,1|ip5n3z,40,45,1|ip5n40,39,44,0|ix2wjz,39,44,0|ix2wk0,40,45,1|j7vprz,40,45,1|j7vps0,39,44,0|jeq37z,39,44,0|jeq380,40,45,1|jqyr3z,40,45,1|jqyr40,39,44,0|jxg5vz,39,44,0|jxg5w0,40,45,1|k9otrz,40,45,1|k9ots0,39,44,0|kg68jz,39,44,0|kg68k0,40,45,1|ksewfz,40,45,1|ksewg0,39,44,0|kz99vz,39,44,0|kz99w0,40,45,1|lbhxrz,40,45,1|lbhxs0,39,44,0|lhzcjz,39,44,0|lhzck0,40,45,1|lu80fz,40,45,1|lu80g0,39,44,0|m0pf7z,39,44,0|m0pf80,40,45,1|mcy33z,40,45,1|mcy340,39,44,0|mjfhvz,39,44,0|mjfhw0,40,45,1|mvo5rz,40,45,1|mvo5s0,39,44,0|n25kjz,39,44,0|n25kk0,40,45,1|nee8fz,40,45,1|nee8g0,39,44,0|nkvn7z,39,44,0|nkvn80,40,45,1|nx4b3z,40,45,1|nx4b40,39,44,0|o3yojz,39,44,0|o3yok0,40,45,1|og7cfz,40,45,1|og7cg0,39,44,0|omor7z,39,44,0|omor80,40,45,1|oyxf3z,40,45,1|oyxf40,39,44,0|p5etvz,39,44,0|p5etw0,40,45,1|phnhrz,40,45,1|phnhs0,39,44,0|po4wjz,39,44,0|po4wk0,40,45,1|q0dkfz,40,45,1|q0dkg0,39,44,0|q6uz7z,39,44,0|q6uz80,40,45,1|qj3n3z,40,45,1|qj3n40,39,44,0|qpy0jz,39,44,0|qpy0k0,40,45,1|r26ofz,40,45,1|r26og0,39,44,0|r8o37z,39,44,0|r8o380,40,45,1|rkwr3z,40,45,1|rkwr40,39,44,0|rre5vz,39,44,0|rre5w0,40,45,1|s3mtrz,40,45,1|s3mts0,39,44,0|sa48jz,39,44,0|sa48k0,40,45,1|smcwfz,40,45,1|smcwg0,39,44,0|ssub7z,39,44,0|ssub80,40,45,1|t52z3z,40,45,1|t52z40,39,44,0|tbkdvz,39,44,0|tbkdw0,40,45,1|tnt1rz,40,45,1|tnt1s0,39,44,0|tunf7z,39,44,0|tunf80,40,45,1|u6w33z,40,45,1|u6w340,39,44,0|uddhvz,39,44,0|uddhw0,40,45,1|upm5rz,40,45,1|upm5s0,39,44,0|uw3kjz,39,44,0|uw3kk0,40,45,1|v8c8fz,40,45,1|v8c8g0,39,44,0|vetn7z,39,44,0|vetn80,40,45,1|vr2b3z,40,45,1|vr2b40,39,44,0|vxjpvz,39,44,0|vxjpw0,40,45,1|w9sdrz,40,45,1|w9sds0,39,44,0|wgmr7z,39,44,0|wgmr80,40,45,1|wsvf3z,40,45,1|wsvf40,39,44,0|wzctvz,39,44,0|wzctw0,40,45,1|xblhrz,40,45,1|xblhs0,39,44,0|xi2wjz,39,44,0|xi2wk0,40,45,1|xubkfz,40,45,1|xubkg0,39,44,0|y0sz7z,39,44,0|y0sz80,40,45,1|yd1n3z,40,45,1|yd1n40,39,44,0|yjj1vz,39,44,0|yjj1w0,40,45,1|yvrprz,40,45,1|yvrps0,39,44,0|z294jz,39,44,0|z294k0,40,45,1|zehsfz,40,45,1|zehsg0,39,44,0\",\"America/Moncton|,0,144,0|-18wys04,49,63,0|-z94i41,49,63,0|-z94i40,32,42,0|-qzp0o1,32,42,0|-qzp0o0,54,44,1|-qpm4s1,54,44,1|-qpm4s0,32,42,0|-j2ve41,32,42,0|-j2ve40,54,44,1|-iy6y81,54,44,1|-iy6y80,32,42,0|-ik5bg1,32,42,0|-ik5bg0,54,44,1|-ifgvk1,54,44,1|-ifgvk0,32,42,0|-i1f8s1,32,42,0|-i1f8s0,54,44,1|-hwqsw1,54,44,1|-hwqsw0,32,42,0|-hip641,32,42,0|-hip640,54,44,1|-he0q81,54,44,1|-he0q80,32,42,0|-gzz3g1,32,42,0|-gzz3g0,54,44,1|-gvank1,54,44,1|-gvank0,32,42,0|-gh90s1,32,42,0|-gh90s0,54,44,1|-gckkw1,54,44,1|-gckkw0,32,42,0|-fyxrg1,32,42,0|-fyxrg0,54,44,1|-fstgw1,54,44,1|-fstgw0,32,42,0|-fgiss1,32,42,0|-fgiss0,54,44,1|-fa3e81,54,44,1|-fa3e80,32,42,0|-eying1,32,42,0|-eying0,54,44,1|-er0cw1,54,44,1|-er0cw0,32,42,0|-ek27c1,32,42,0|-ek27c0,33,44,1|-cq2tg1,33,44,1|-cq2tg0,34,44,1|-cnp641,34,44,1|-cnp640,32,42,0|-ccw7c1,32,42,0|-ccw7c0,54,44,1|-c4z3g1,54,44,1|-c4z3g0,32,42,0|-bu64o1,32,42,0|-bu64o0,54,44,1|-bm90s1,54,44,1|-bm90s0,32,42,0|-bbg201,32,42,0|-bbg200,54,44,1|-b3iy41,54,44,1|-b3iy40,32,42,0|-aspzc1,32,42,0|-aspzc0,54,44,1|-aksvg1,54,44,1|-aksvg0,32,42,0|-a9my01,32,42,0|-a9my00,54,44,1|-a22ss1,54,44,1|-a22ss0,32,42,0|-9qwvc1,32,42,0|-9qwvc0,54,44,1|-9izrg1,54,44,1|-9izrg0,32,42,0|-986so1,32,42,0|-986so0,54,44,1|-909os1,54,44,1|-909os0,32,42,0|-8pgq01,32,42,0|-8pgq00,54,44,1|-8hjm41,54,44,1|-8hjm40,32,42,0|-86qnc1,32,42,0|-86qnc0,54,44,1|-7ytjg1,54,44,1|-7ytjg0,32,42,0|-7o0ko1,32,42,0|-7o0ko0,54,44,1|-7g3gs1,54,44,1|-7g3gs0,32,42,0|-74xjc1,32,42,0|-74xjc0,54,44,1|-6x0fg1,54,44,1|-6x0fg0,32,42,0|-6m7go1,32,42,0|-6m7go0,54,44,1|-6cui41,54,44,1|-6cui40,32,42,0|-63he01,32,42,0|-63he00,54,44,1|-5u4fg1,54,44,1|-5u4fg0,32,42,0|-5krbc1,32,42,0|-5krbc0,54,44,1|-5becs1,54,44,1|-5becs0,32,42,0|-5218o1,32,42,0|-5218o0,54,44,1|-4sbbg1,54,44,1|-4sbbg0,32,42,0|-4iy7c1,32,42,0|-4iy7c0,54,44,1|-49l8s1,54,44,1|-49l8s0,32,42,0|-4084o1,32,42,0|-4084o0,54,44,1|-3qv641,54,44,1|-3qv640,32,42,0|-3hi201,32,42,0|-3hi200,54,44,1|-3853g1,54,44,1|-3853g0,32,42,0|-2yrzc1,32,42,0|-2yrzc0,54,44,1|-2pf0s1,54,44,1|-2pf0s0,32,42,0|-2g1wo1,32,42,0|-2g1wo0,54,44,1|-26bzg1,54,44,1|-26bzg0,32,42,0|-1xbu01,32,42,0|-1xbu00,54,44,1|-1nlws1,54,44,1|-1nlws0,32,42,0|-1e8so1,32,42,0|-1e8so0,54,44,1|-14vu41,54,44,1|-14vu40,32,42,0|-viq01,32,42,0|-viq00,54,44,1|-m5rg1,54,44,1|-m5rg0,32,42,0|-csnc1,32,42,0|-csnc0,54,44,1|-3fos1,54,44,1|-3fos0,32,42,0|5xfbz,32,42,0|5xfc0,54,44,1|fadvz,54,44,1|fadw0,32,42,0|onhzz,32,42,0|oni00,54,44,1|ydf7z,54,44,1|ydf80,32,42,0|17qjbz,32,42,0|17qjc0,54,44,1|1h3hvz,54,44,1|1h3hw0,32,42,0|296onz,32,42,0|296oo0,54,44,1|2ijn7z,54,44,1|2ijn80,32,42,0|2rwrbz,32,42,0|2rwrc0,54,44,1|319pvz,54,44,1|319pw0,32,42,0|3amtzz,32,42,0|3amu00,54,44,1|3kcr7z,54,44,1|3kcr80,32,42,0|3tcwnz,32,42,0|3tcwo0,54,44,1|432tvz,54,44,1|432tw0,32,42,0|4cfxzz,32,42,0|4cfy00,54,44,1|4lswjz,54,44,1|4lswk0,32,42,0|4v60nz,32,42,0|4v60o0,54,44,1|54iz7z,54,44,1|54iz80,32,42,0|5dw3bz,32,42,0|5dw3c0,54,44,1|5n91vz,54,44,1|5n91w0,32,42,0|5wm5zz,32,42,0|5wm600,54,44,1|65z4jz,54,44,1|65z4k0,32,42,0|6fc8nz,32,42,0|6fc8o0,54,44,1|6p25vz,54,44,1|6p25w0,32,42,0|6y2bbz,32,42,0|6y2bc0,54,44,1|77s8jz,54,44,1|77s8k0,32,42,0|7h5cnz,32,42,0|7h5co0,54,44,1|7qib7z,54,44,1|7qib80,32,42,0|7zvfbz,32,42,0|7zvfc0,54,44,1|898dvz,54,44,1|898dw0,32,42,0|8ilhzz,32,42,0|8ili00,54,44,1|8rygjz,54,44,1|8rygk0,32,42,0|908onz,32,42,0|908oo0,54,44,1|9aoj7z,54,44,1|9aoj80,32,42,0|9iyrbz,32,42,0|9iyrc0,54,44,1|9trkjz,54,44,1|9trkk0,32,42,0|a1otzz,32,42,0|a1ou00,54,44,1|achn7z,54,44,1|achn80,32,42,0|akewnz,32,42,0|akewo0,54,44,1|av7pvz,54,44,1|av7pw0,32,42,0|b3hxzz,32,42,0|b3hy00,54,44,1|bdxsjz,54,44,1|bdxsk0,32,42,0|bm80nz,32,42,0|bm80o0,54,44,1|bwnv7z,54,44,1|bwnv80,32,42,0|c4xxtn,32,42,0|c4xxto,54,44,1|cfqr1n,54,44,1|cfqr1o,32,42,0|cno0hn,32,42,0|cno0ho,54,44,1|cygtpn,54,44,1|cygtpo,32,42,0|d6e35n,32,42,0|d6e35o,54,44,1|dh6wdn,54,44,1|dh6wdo,32,42,0|dph4hn,32,42,0|dph4ho,54,44,1|dzwz1n,54,44,1|dzwz1o,32,42,0|e8775n,32,42,0|e8775o,54,44,1|ein1pn,54,44,1|ein1po,32,42,0|eqx9tn,32,42,0|eqx9to,54,44,1|f1d4dn,54,44,1|f1d4do,32,42,0|f9nchn,32,42,0|f9ncho,54,44,1|fkg5pn,54,44,1|fkg5po,32,42,0|fsdf5n,32,42,0|fsdf5o,54,44,1|g368dn,54,44,1|g368do,32,42,0|gb3htn,32,42,0|gb3hto,54,44,1|glwb1n,54,44,1|glwb1o,32,42,0|gu6j5n,32,42,0|gu6j5o,54,44,1|h4mdpn,54,44,1|h4mdpo,32,42,0|hcwltn,32,42,0|hcwlto,54,44,1|hncgdn,54,44,1|hncgdo,32,42,0|hvmohn,32,42,0|hvmoho,54,44,1|i6fhpn,54,44,1|i6fhpo,32,42,0|iecr5n,32,42,0|iecr5o,54,44,1|ip5kdn,54,44,1|ip5kdo,32,42,0|ix2ttn,32,42,0|ix2tto,54,44,1|j7vn1n,54,44,1|j7vn1o,32,42,0|jeq5zz,32,42,0|jeq600,54,44,1|jqytvz,54,44,1|jqytw0,32,42,0|jxg8nz,32,42,0|jxg8o0,54,44,1|k9owjz,54,44,1|k9owk0,32,42,0|kg6bbz,32,42,0|kg6bc0,54,44,1|ksez7z,54,44,1|ksez80,32,42,0|kz9cnz,32,42,0|kz9co0,54,44,1|lbi0jz,54,44,1|lbi0k0,32,42,0|lhzfbz,32,42,0|lhzfc0,54,44,1|lu837z,54,44,1|lu8380,32,42,0|m0phzz,32,42,0|m0pi00,54,44,1|mcy5vz,54,44,1|mcy5w0,32,42,0|mjfknz,32,42,0|mjfko0,54,44,1|mvo8jz,54,44,1|mvo8k0,32,42,0|n25nbz,32,42,0|n25nc0,54,44,1|neeb7z,54,44,1|neeb80,32,42,0|nkvpzz,32,42,0|nkvq00,54,44,1|nx4dvz,54,44,1|nx4dw0,32,42,0|o3yrbz,32,42,0|o3yrc0,54,44,1|og7f7z,54,44,1|og7f80,32,42,0|omotzz,32,42,0|omou00,54,44,1|oyxhvz,54,44,1|oyxhw0,32,42,0|p5ewnz,32,42,0|p5ewo0,54,44,1|phnkjz,54,44,1|phnkk0,32,42,0|po4zbz,32,42,0|po4zc0,54,44,1|q0dn7z,54,44,1|q0dn80,32,42,0|q6v1zz,32,42,0|q6v200,54,44,1|qj3pvz,54,44,1|qj3pw0,32,42,0|qpy3bz,32,42,0|qpy3c0,54,44,1|r26r7z,54,44,1|r26r80,32,42,0|r8o5zz,32,42,0|r8o600,54,44,1|rkwtvz,54,44,1|rkwtw0,32,42,0|rre8nz,32,42,0|rre8o0,54,44,1|s3mwjz,54,44,1|s3mwk0,32,42,0|sa4bbz,32,42,0|sa4bc0,54,44,1|smcz7z,54,44,1|smcz80,32,42,0|ssudzz,32,42,0|ssue00,54,44,1|t531vz,54,44,1|t531w0,32,42,0|tbkgnz,32,42,0|tbkgo0,54,44,1|tnt4jz,54,44,1|tnt4k0,32,42,0|tunhzz,32,42,0|tuni00,54,44,1|u6w5vz,54,44,1|u6w5w0,32,42,0|uddknz,32,42,0|uddko0,54,44,1|upm8jz,54,44,1|upm8k0,32,42,0|uw3nbz,32,42,0|uw3nc0,54,44,1|v8cb7z,54,44,1|v8cb80,32,42,0|vetpzz,32,42,0|vetq00,54,44,1|vr2dvz,54,44,1|vr2dw0,32,42,0|vxjsnz,32,42,0|vxjso0,54,44,1|w9sgjz,54,44,1|w9sgk0,32,42,0|wgmtzz,32,42,0|wgmu00,54,44,1|wsvhvz,54,44,1|wsvhw0,32,42,0|wzcwnz,32,42,0|wzcwo0,54,44,1|xblkjz,54,44,1|xblkk0,32,42,0|xi2zbz,32,42,0|xi2zc0,54,44,1|xubn7z,54,44,1|xubn80,32,42,0|y0t1zz,32,42,0|y0t200,54,44,1|yd1pvz,54,44,1|yd1pw0,32,42,0|yjj4nz,32,42,0|yjj4o0,54,44,1|yvrsjz,54,44,1|yvrsk0,32,42,0|z297bz,32,42,0|z297c0,54,44,1|zehv7z,54,44,1|zehv80,32,42,0\",\"America/Monterrey|,0,145,0|-p1u7c0,45,62,0|9iywvz,45,62,0|9iyww0,46,63,1|9trq3z,46,63,1|9trq40,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxkvz,45,62,0|eqxkw0,46,63,1|f1dffz,46,63,1|f1dfg0,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkggrz,46,63,1|fkggs0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,45,62,0|gcwm7z,45,62,0|gcwm80,46,63,1|gkgrfz,46,63,1|gkgrg0,45,62,0|gu6u7z,45,62,0|gu6u80,46,63,1|h4morz,46,63,1|h4mos0,45,62,0|hcwwvz,45,62,0|hcwww0,46,63,1|hncrfz,46,63,1|hncrg0,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fsrz,46,63,1|i6fss0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5vfz,46,63,1|ip5vg0,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jft7jz,45,62,0|jft7k0,46,63,1|jqm0rz,46,63,1|jqm0s0,45,62,0|jyw8vz,45,62,0|jyw8w0,46,63,1|k9c3fz,46,63,1|k9c3g0,45,62,0|khmbjz,45,62,0|khmbk0,46,63,1|ks263z,46,63,1|ks2640,45,62,0|l0ce7z,45,62,0|l0ce80,46,63,1|lb57fz,46,63,1|lb57g0,45,62,0|lj2gvz,45,62,0|lj2gw0,46,63,1|ltva3z,46,63,1|ltva40,45,62,0|m1sjjz,45,62,0|m1sjk0,46,63,1|mclcrz,46,63,1|mclcs0,45,62,0|mkvkvz,45,62,0|mkvkw0,46,63,1|mvbffz,46,63,1|mvbfg0,45,62,0|n3lnjz,45,62,0|n3lnk0,46,63,1|ne1i3z,46,63,1|ne1i40,45,62,0|nmbq7z,45,62,0|nmbq80,46,63,1|nwrkrz,46,63,1|nwrks0,45,62,0|o51svz,45,62,0|o51sw0,46,63,1|ofum3z,46,63,1|ofum40,45,62,0|onrvjz,45,62,0|onrvk0,46,63,1|oykorz,46,63,1|oykos0,45,62,0|p6hy7z,45,62,0|p6hy80,46,63,1|pharfz,46,63,1|pharg0,45,62,0|ppkzjz,45,62,0|ppkzk0,46,63,1|q00u3z,46,63,1|q00u40,45,62,0|q8b27z,45,62,0|q8b280,46,63,1|qiqwrz,46,63,1|qiqws0,45,62,0|qr14vz,45,62,0|qr14w0,46,63,1|r1ty3z,46,63,1|r1ty40,45,62,0|r9r7jz,45,62,0|r9r7k0,46,63,1|rkk0rz,46,63,1|rkk0s0,45,62,0|rsha7z,45,62,0|rsha80,46,63,1|s3a3fz,46,63,1|s3a3g0,45,62,0|sbkbjz,45,62,0|sbkbk0,46,63,1|sm063z,46,63,1|sm0640,45,62,0|suae7z,45,62,0|suae80,46,63,1|t4q8rz,46,63,1|t4q8s0,45,62,0|td0gvz,45,62,0|td0gw0,46,63,1|tngbfz,46,63,1|tngbg0,45,62,0|tvqjjz,45,62,0|tvqjk0,46,63,1|u6jcrz,46,63,1|u6jcs0,45,62,0|uegm7z,45,62,0|uegm80,46,63,1|up9ffz,46,63,1|up9fg0,45,62,0|ux6ovz,45,62,0|ux6ow0,46,63,1|v7zi3z,46,63,1|v7zi40,45,62,0|vg9q7z,45,62,0|vg9q80,46,63,1|vqpkrz,46,63,1|vqpks0,45,62,0|vyzsvz,45,62,0|vyzsw0,46,63,1|w9fnfz,46,63,1|w9fng0,45,62,0|whpvjz,45,62,0|whpvk0,46,63,1|wsiorz,46,63,1|wsios0,45,62,0|x0fy7z,45,62,0|x0fy80,46,63,1|xb8rfz,46,63,1|xb8rg0,45,62,0|xj60vz,45,62,0|xj60w0,46,63,1|xtyu3z,46,63,1|xtyu40,45,62,0|y1w3jz,45,62,0|y1w3k0,46,63,1|ycowrz,46,63,1|ycows0,45,62,0|ykz4vz,45,62,0|ykz4w0,46,63,1|yvezfz,46,63,1|yvezg0,45,62,0|z3p7jz,45,62,0|z3p7k0,46,63,1|ze523z,46,63,1|ze5240,45,62,0\",\"America/Montevideo|,0,146,0|-w4mll9,21,146,0|-px809a,21,146,0|-px8099,42,42,0|-o50vk1,42,42,0|-o50vk0,39,44,1|-nvm2c1,39,44,1|-nvm2c0,81,101,0|-nm74y1,81,101,0|-nm74y0,39,44,1|-ncu501,39,44,1|-ncu500,81,101,0|-n3f7m1,81,101,0|-n3f7m0,39,44,1|-mu27o1,39,44,1|-mu27o0,81,101,0|-ivo8y1,81,101,0|-ivo8y0,39,44,1|-inr3o1,39,44,1|-inr3o0,81,101,0|-icy6a1,81,101,0|-icy6a0,39,44,1|-i51101,39,44,1|-i51100,81,101,0|-hu83m1,81,101,0|-hu83m0,39,44,1|-hmayc1,39,44,1|-hmayc0,81,101,0|-hbi0y1,81,101,0|-hbi0y0,39,44,1|-h3kvo1,39,44,1|-h3kvo0,81,101,0|-gsezm1,81,101,0|-gsezm0,39,44,1|-gkut01,39,44,1|-gkut00,81,101,0|-g9owy1,81,101,0|-g9owy0,39,44,1|-g24qc1,39,44,1|-g24qc0,81,101,0|-fseoy1,81,101,0|-fseoy0,39,44,1|-fj1p01,39,44,1|-fj1p00,81,101,0|-f88rm1,81,101,0|-f88rm0,39,44,1|-f0bmc1,39,44,1|-f0bmc0,81,101,0|-etxya1,81,101,0|-etxya0,39,44,1|-e482c1,39,44,1|-e482c0,82,102,1|-dzlfq1,82,102,1|-dzlfq0,39,44,0|-5jbp01,39,44,0|-5jbp00,82,102,1|-5abnq1,82,102,1|-5abnq0,39,44,0|-572yc1,39,44,0|-572yc0,40,45,1|-54kag1,40,45,1|-54kag0,39,44,0|-2h5101,39,44,0|-2h5100,40,45,1|-285141,40,45,1|-285140,39,44,0|-u1901,39,44,0|-u1900,82,102,1|-kd521,82,102,1|-kd520,39,44,0|5vcbz,39,44,0|5vcc0,40,45,1|8fuvz,40,45,1|8fuw0,39,44,0|17dcbz,39,44,0|17dcc0,40,45,1|1botjz,40,45,1|1botk0,39,44,0|23s0bz,39,44,0|23s0c0,83,147,1|26nlhz,83,147,1|26nli0,82,102,1|2fnqxz,82,102,1|2fnqy0,39,44,0|2lf6zz,39,44,0|2lf700,40,45,1|2qgljz,40,45,1|2qglk0,39,44,0|3mvcbz,39,44,0|3mvcc0,40,45,1|3qtuvz,40,45,1|3qtuw0,39,44,0|44vhnz,39,44,0|44vho0,40,45,1|49jxjz,40,45,1|49jxk0,39,44,0|4obhnz,39,44,0|4obho0,40,45,1|4sa07z,40,45,1|4sa080,39,44,0|4v5sbz,39,44,0|4v5sc0,40,45,1|5bq07z,40,45,1|5bq080,39,44,0|9d8yzz,39,44,0|9d8z00,40,45,1|9h5mvz,40,45,1|9h5mw0,39,44,0|9vx6zz,39,44,0|9vx700,40,45,1|a08o7z,40,45,1|a08o80,39,44,0|achhnz,39,44,0|achho0,40,45,1|ails7z,40,45,1|ails80,39,44,0|auulnz,39,44,0|auulo0,40,45,1|b1otjz,40,45,1|b1otk0,39,44,0|bdxmzz,39,44,0|bdxn00,40,45,1|bkew7z,40,45,1|bkew80,39,44,0|bwaqzz,39,44,0|bwar00,40,45,1|c34yvz,40,45,1|c34yw0,39,44,0|i49pnz,39,44,0|i49po0,40,45,1|idzsfz,40,45,1|idzsg0,39,44,0|io2tvz,39,44,0|io2tw0,40,45,1|ivzxrz,40,45,1|ivzxs0,39,44,0|j6fxvz,39,44,0|j6fxw0,40,45,1|jeq0fz,40,45,1|jeq0g0,39,44,0|jpiz7z,39,44,0|jpiz80,40,45,1|jxg33z,40,45,1|jxg340,39,44,0|k891vz,39,44,0|k891w0,40,45,1|kg65rz,40,45,1|kg65s0,39,44,0|kqz4jz,39,44,0|kqz4k0,40,45,1|kz973z,40,45,1|kz9740,39,44,0|l9p77z,39,44,0|l9p780,40,45,1|lhz9rz,40,45,1|lhz9s0,39,44,0|lsf9vz,39,44,0|lsf9w0,40,45,1|m0pcfz,40,45,1|m0pcg0,39,44,0|mbib7z,39,44,0|mbib80,40,45,1|mjff3z,40,45,1|mjff40,39,44,0|mu8dvz,39,44,0|mu8dw0,40,45,1|n25hrz,40,45,1|n25hs0,39,44,0|ncygjz,39,44,0|ncygk0,40,45,1|nkvkfz,40,45,1|nkvkg0,39,44,0\",\"America/Montserrat|,0,41,0|-u6m79w,32,42,0\",\"America/Nassau|,0,148,0|-u6m4c6,49,63,0|-efufg1,49,63,0|-efufg0,70,42,1|-d1oy81,70,42,1|-d1oy80,49,63,0|-d03gs1,49,63,0|-d03gs0,70,42,1|-cq2tg1,70,42,1|-cq2tg0,71,42,1|-cmrww1,71,42,1|-cmrww0,49,63,0|-2yrwk1,49,63,0|-2yrwk0,62,42,1|-2pey01,62,42,1|-2pey00,49,63,0|-2g1tw1,49,63,0|-2g1tw0,62,42,1|-26bwo1,62,42,1|-26bwo0,49,63,0|-1xbr81,49,63,0|-1xbr80,62,42,1|-1nlu01,62,42,1|-1nlu00,49,63,0|-1e8pw1,49,63,0|-1e8pw0,62,42,1|-14vrc1,62,42,1|-14vrc0,49,63,0|-vin81,49,63,0|-vin80,62,42,1|-m5oo1,62,42,1|-m5oo0,49,63,0|-cskk1,49,63,0|-cskk0,62,42,1|-3fm01,62,42,1|-3fm00,49,63,0|5xi3z,49,63,0|5xi40,62,42,1|fagnz,62,42,1|fago0,49,63,0|onkrz,49,63,0|onks0,62,42,1|ydhzz,62,42,1|ydi00,49,63,0|17qm3z,49,63,0|17qm40,62,42,1|1h3knz,62,42,1|1h3ko0,49,63,0|1qgorz,49,63,0|1qgos0,62,42,1|1ztnbz,62,42,1|1ztnc0,49,63,0|296rfz,49,63,0|296rg0,62,42,1|2ijpzz,62,42,1|2ijq00,49,63,0|2rwu3z,49,63,0|2rwu40,62,42,1|319snz,62,42,1|319so0,49,63,0|3amwrz,49,63,0|3amws0,62,42,1|3kctzz,62,42,1|3kcu00,49,63,0|3tczfz,49,63,0|3tczg0,62,42,1|432wnz,62,42,1|432wo0,49,63,0|4cg0rz,49,63,0|4cg0s0,62,42,1|4lszbz,62,42,1|4lszc0,49,63,0|4v63fz,49,63,0|4v63g0,62,42,1|54j1zz,62,42,1|54j200,49,63,0|5dw63z,49,63,0|5dw640,62,42,1|5n94nz,62,42,1|5n94o0,49,63,0|5wm8rz,49,63,0|5wm8s0,62,42,1|65z7bz,62,42,1|65z7c0,49,63,0|6fcbfz,49,63,0|6fcbg0,62,42,1|6p28nz,62,42,1|6p28o0,49,63,0|6y2e3z,49,63,0|6y2e40,62,42,1|77sbbz,62,42,1|77sbc0,49,63,0|7h5ffz,49,63,0|7h5fg0,62,42,1|7qidzz,62,42,1|7qie00,49,63,0|7zvi3z,49,63,0|7zvi40,62,42,1|898gnz,62,42,1|898go0,49,63,0|8ilkrz,49,63,0|8ilks0,62,42,1|8ryjbz,62,42,1|8ryjc0,49,63,0|908rfz,49,63,0|908rg0,62,42,1|9aolzz,62,42,1|9aom00,49,63,0|9iyu3z,49,63,0|9iyu40,62,42,1|9trnbz,62,42,1|9trnc0,49,63,0|a1owrz,49,63,0|a1ows0,62,42,1|achpzz,62,42,1|achq00,49,63,0|akezfz,49,63,0|akezg0,62,42,1|av7snz,62,42,1|av7so0,49,63,0|b3i0rz,49,63,0|b3i0s0,62,42,1|bdxvbz,62,42,1|bdxvc0,49,63,0|bm83fz,49,63,0|bm83g0,62,42,1|bwnxzz,62,42,1|bwny00,49,63,0|c4y63z,49,63,0|c4y640,62,42,1|cfqzbz,62,42,1|cfqzc0,49,63,0|cno8rz,49,63,0|cno8s0,62,42,1|cyh1zz,62,42,1|cyh200,49,63,0|d6ebfz,49,63,0|d6ebg0,62,42,1|dh74nz,62,42,1|dh74o0,49,63,0|dphcrz,49,63,0|dphcs0,62,42,1|dzx7bz,62,42,1|dzx7c0,49,63,0|e87ffz,49,63,0|e87fg0,62,42,1|ein9zz,62,42,1|eina00,49,63,0|eqxi3z,49,63,0|eqxi40,62,42,1|f1dcnz,62,42,1|f1dco0,49,63,0|f9nkrz,49,63,0|f9nks0,62,42,1|fkgdzz,62,42,1|fkge00,49,63,0|fsdnfz,49,63,0|fsdng0,62,42,1|g36gnz,62,42,1|g36go0,49,63,0|gb3q3z,49,63,0|gb3q40,62,42,1|glwjbz,62,42,1|glwjc0,49,63,0|gu6rfz,49,63,0|gu6rg0,62,42,1|h4mlzz,62,42,1|h4mm00,49,63,0|hcwu3z,49,63,0|hcwu40,62,42,1|hnconz,62,42,1|hncoo0,49,63,0|hvmwrz,49,63,0|hvmws0,62,42,1|i6fpzz,62,42,1|i6fq00,49,63,0|ieczfz,49,63,0|ieczg0,62,42,1|ip5snz,62,42,1|ip5so0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/New_York|,0,149,0|-18y0os0,49,63,0|-r0ev81,49,63,0|-r0ev80,62,42,1|-qpm201,62,42,1|-qpm200,49,63,0|-qhosk1,49,63,0|-qhosk0,62,42,1|-q6vzc1,62,42,1|-q6vzc0,49,63,0|-pyypw1,49,63,0|-pyypw0,62,42,1|-pnsy01,62,42,1|-pnsy00,49,63,0|-pessk1,49,63,0|-pessk0,62,42,1|-p6voo1,62,42,1|-p6voo0,49,63,0|-ovpr81,49,63,0|-ovpr80,62,42,1|-oo5m01,62,42,1|-oo5m00,49,63,0|-oczok1,49,63,0|-oczok0,62,42,1|-o52ko1,62,42,1|-o52ko0,49,63,0|-nu9lw1,49,63,0|-nu9lw0,62,42,1|-nmci01,62,42,1|-nmci00,49,63,0|-nbjj81,49,63,0|-nbjj80,62,42,1|-n3mfc1,62,42,1|-n3mfc0,49,63,0|-mstgk1,49,63,0|-mstgk0,62,42,1|-mkwco1,62,42,1|-mkwco0,49,63,0|-ma3dw1,49,63,0|-ma3dw0,62,42,1|-m26a01,62,42,1|-m26a00,49,63,0|-lr0ck1,49,63,0|-lr0ck0,62,42,1|-lj38o1,62,42,1|-lj38o0,49,63,0|-l8a9w1,49,63,0|-l8a9w0,62,42,1|-l0d601,62,42,1|-l0d600,49,63,0|-kpk781,49,63,0|-kpk780,62,42,1|-khn3c1,62,42,1|-khn3c0,49,63,0|-k6u4k1,49,63,0|-k6u4k0,62,42,1|-jyx0o1,62,42,1|-jyx0o0,49,63,0|-jo41w1,49,63,0|-jo41w0,62,42,1|-jg6y01,62,42,1|-jg6y00,49,63,0|-j510k1,49,63,0|-j510k0,62,42,1|-ixgvc1,62,42,1|-ixgvc0,49,63,0|-imaxw1,49,63,0|-imaxw0,62,42,1|-iedu01,62,42,1|-iedu00,49,63,0|-i3kv81,49,63,0|-i3kv80,62,42,1|-hvnrc1,62,42,1|-hvnrc0,49,63,0|-hkusk1,49,63,0|-hkusk0,62,42,1|-hcxoo1,62,42,1|-hcxoo0,49,63,0|-h24pw1,49,63,0|-h24pw0,62,42,1|-gu7m01,62,42,1|-gu7m00,49,63,0|-gjen81,49,63,0|-gjen80,62,42,1|-gbhjc1,62,42,1|-gbhjc0,49,63,0|-g0blw1,49,63,0|-g0blw0,62,42,1|-fsrgo1,62,42,1|-fsrgo0,49,63,0|-fhlj81,49,63,0|-fhlj80,62,42,1|-f9ofc1,62,42,1|-f9ofc0,49,63,0|-eyvgk1,49,63,0|-eyvgk0,62,42,1|-eqyco1,62,42,1|-eqyco0,49,63,0|-ek24k1,49,63,0|-ek24k0,70,42,1|-cq2tg1,70,42,1|-cq2tg0,71,42,1|-cnp3c1,71,42,1|-cnp3c0,49,63,0|-ccw4k1,49,63,0|-ccw4k0,62,42,1|-c4z0o1,62,42,1|-c4z0o0,49,63,0|-bu61w1,49,63,0|-bu61w0,62,42,1|-bm8y01,62,42,1|-bm8y00,49,63,0|-bbfz81,49,63,0|-bbfz80,62,42,1|-b3ivc1,62,42,1|-b3ivc0,49,63,0|-aspwk1,49,63,0|-aspwk0,62,42,1|-aksso1,62,42,1|-aksso0,49,63,0|-a9mv81,49,63,0|-a9mv80,62,42,1|-a22q01,62,42,1|-a22q00,49,63,0|-9qwsk1,49,63,0|-9qwsk0,62,42,1|-9izoo1,62,42,1|-9izoo0,49,63,0|-986pw1,49,63,0|-986pw0,62,42,1|-909m01,62,42,1|-909m00,49,63,0|-8pgn81,49,63,0|-8pgn80,62,42,1|-8hjjc1,62,42,1|-8hjjc0,49,63,0|-86qkk1,49,63,0|-86qkk0,62,42,1|-7ytgo1,62,42,1|-7ytgo0,49,63,0|-7o0hw1,49,63,0|-7o0hw0,62,42,1|-7eako1,62,42,1|-7eako0,49,63,0|-74xgk1,49,63,0|-74xgk0,62,42,1|-6vki01,62,42,1|-6vki00,49,63,0|-6m7dw1,49,63,0|-6m7dw0,62,42,1|-6cufc1,62,42,1|-6cufc0,49,63,0|-63hb81,49,63,0|-63hb80,62,42,1|-5u4co1,62,42,1|-5u4co0,49,63,0|-5kr8k1,49,63,0|-5kr8k0,62,42,1|-5bea01,62,42,1|-5bea00,49,63,0|-5215w1,49,63,0|-5215w0,62,42,1|-4sb8o1,62,42,1|-4sb8o0,49,63,0|-4iy4k1,49,63,0|-4iy4k0,62,42,1|-49l601,62,42,1|-49l600,49,63,0|-4081w1,49,63,0|-4081w0,62,42,1|-3qv3c1,62,42,1|-3qv3c0,49,63,0|-3hhz81,49,63,0|-3hhz80,62,42,1|-3850o1,62,42,1|-3850o0,49,63,0|-2yrwk1,49,63,0|-2yrwk0,62,42,1|-2pey01,62,42,1|-2pey00,49,63,0|-2g1tw1,49,63,0|-2g1tw0,62,42,1|-26bwo1,62,42,1|-26bwo0,49,63,0|-1xbr81,49,63,0|-1xbr80,62,42,1|-1nlu01,62,42,1|-1nlu00,49,63,0|-1e8pw1,49,63,0|-1e8pw0,62,42,1|-14vrc1,62,42,1|-14vrc0,49,63,0|-vin81,49,63,0|-vin80,62,42,1|-m5oo1,62,42,1|-m5oo0,49,63,0|-cskk1,49,63,0|-cskk0,62,42,1|-3fm01,62,42,1|-3fm00,49,63,0|5xi3z,49,63,0|5xi40,62,42,1|fagnz,62,42,1|fago0,49,63,0|onkrz,49,63,0|onks0,62,42,1|ydhzz,62,42,1|ydi00,49,63,0|17qm3z,49,63,0|17qm40,62,42,1|1h3knz,62,42,1|1h3ko0,49,63,0|1qgorz,49,63,0|1qgos0,62,42,1|1ztnbz,62,42,1|1ztnc0,49,63,0|23fcrz,49,63,0|23fcs0,62,42,1|2ijpzz,62,42,1|2ijq00,49,63,0|2oo63z,49,63,0|2oo640,62,42,1|319snz,62,42,1|319so0,49,63,0|3amwrz,49,63,0|3amws0,62,42,1|3kctzz,62,42,1|3kcu00,49,63,0|3tczfz,49,63,0|3tczg0,62,42,1|432wnz,62,42,1|432wo0,49,63,0|4cg0rz,49,63,0|4cg0s0,62,42,1|4lszbz,62,42,1|4lszc0,49,63,0|4v63fz,49,63,0|4v63g0,62,42,1|54j1zz,62,42,1|54j200,49,63,0|5dw63z,49,63,0|5dw640,62,42,1|5n94nz,62,42,1|5n94o0,49,63,0|5wm8rz,49,63,0|5wm8s0,62,42,1|65z7bz,62,42,1|65z7c0,49,63,0|6fcbfz,49,63,0|6fcbg0,62,42,1|6p28nz,62,42,1|6p28o0,49,63,0|6y2e3z,49,63,0|6y2e40,62,42,1|77sbbz,62,42,1|77sbc0,49,63,0|7h5ffz,49,63,0|7h5fg0,62,42,1|7qidzz,62,42,1|7qie00,49,63,0|7zvi3z,49,63,0|7zvi40,62,42,1|898gnz,62,42,1|898go0,49,63,0|8ilkrz,49,63,0|8ilks0,62,42,1|8ryjbz,62,42,1|8ryjc0,49,63,0|908rfz,49,63,0|908rg0,62,42,1|9aolzz,62,42,1|9aom00,49,63,0|9iyu3z,49,63,0|9iyu40,62,42,1|9trnbz,62,42,1|9trnc0,49,63,0|a1owrz,49,63,0|a1ows0,62,42,1|achpzz,62,42,1|achq00,49,63,0|akezfz,49,63,0|akezg0,62,42,1|av7snz,62,42,1|av7so0,49,63,0|b3i0rz,49,63,0|b3i0s0,62,42,1|bdxvbz,62,42,1|bdxvc0,49,63,0|bm83fz,49,63,0|bm83g0,62,42,1|bwnxzz,62,42,1|bwny00,49,63,0|c4y63z,49,63,0|c4y640,62,42,1|cfqzbz,62,42,1|cfqzc0,49,63,0|cno8rz,49,63,0|cno8s0,62,42,1|cyh1zz,62,42,1|cyh200,49,63,0|d6ebfz,49,63,0|d6ebg0,62,42,1|dh74nz,62,42,1|dh74o0,49,63,0|dphcrz,49,63,0|dphcs0,62,42,1|dzx7bz,62,42,1|dzx7c0,49,63,0|e87ffz,49,63,0|e87fg0,62,42,1|ein9zz,62,42,1|eina00,49,63,0|eqxi3z,49,63,0|eqxi40,62,42,1|f1dcnz,62,42,1|f1dco0,49,63,0|f9nkrz,49,63,0|f9nks0,62,42,1|fkgdzz,62,42,1|fkge00,49,63,0|fsdnfz,49,63,0|fsdng0,62,42,1|g36gnz,62,42,1|g36go0,49,63,0|gb3q3z,49,63,0|gb3q40,62,42,1|glwjbz,62,42,1|glwjc0,49,63,0|gu6rfz,49,63,0|gu6rg0,62,42,1|h4mlzz,62,42,1|h4mm00,49,63,0|hcwu3z,49,63,0|hcwu40,62,42,1|hnconz,62,42,1|hncoo0,49,63,0|hvmwrz,49,63,0|hvmws0,62,42,1|i6fpzz,62,42,1|i6fq00,49,63,0|ieczfz,49,63,0|ieczg0,62,42,1|ip5snz,62,42,1|ip5so0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Nipigon|,0,150,0|-1353bnk,49,63,0|-qzoxw1,49,63,0|-qzoxw0,62,42,1|-qpm201,62,42,1|-qpm200,49,63,0|-f9oi41,49,63,0|-f9oi40,62,42,1|-ek24k1,62,42,1|-ek24k0,70,42,1|-cq2tg1,70,42,1|-cq2tg0,71,42,1|-cnp3c1,71,42,1|-cnp3c0,49,63,0|296rfz,49,63,0|296rg0,62,42,1|2ijpzz,62,42,1|2ijq00,49,63,0|2rwu3z,49,63,0|2rwu40,62,42,1|319snz,62,42,1|319so0,49,63,0|3amwrz,49,63,0|3amws0,62,42,1|3kctzz,62,42,1|3kcu00,49,63,0|3tczfz,49,63,0|3tczg0,62,42,1|432wnz,62,42,1|432wo0,49,63,0|4cg0rz,49,63,0|4cg0s0,62,42,1|4lszbz,62,42,1|4lszc0,49,63,0|4v63fz,49,63,0|4v63g0,62,42,1|54j1zz,62,42,1|54j200,49,63,0|5dw63z,49,63,0|5dw640,62,42,1|5n94nz,62,42,1|5n94o0,49,63,0|5wm8rz,49,63,0|5wm8s0,62,42,1|65z7bz,62,42,1|65z7c0,49,63,0|6fcbfz,49,63,0|6fcbg0,62,42,1|6p28nz,62,42,1|6p28o0,49,63,0|6y2e3z,49,63,0|6y2e40,62,42,1|77sbbz,62,42,1|77sbc0,49,63,0|7h5ffz,49,63,0|7h5fg0,62,42,1|7qidzz,62,42,1|7qie00,49,63,0|7zvi3z,49,63,0|7zvi40,62,42,1|898gnz,62,42,1|898go0,49,63,0|8ilkrz,49,63,0|8ilks0,62,42,1|8ryjbz,62,42,1|8ryjc0,49,63,0|908rfz,49,63,0|908rg0,62,42,1|9aolzz,62,42,1|9aom00,49,63,0|9iyu3z,49,63,0|9iyu40,62,42,1|9trnbz,62,42,1|9trnc0,49,63,0|a1owrz,49,63,0|a1ows0,62,42,1|achpzz,62,42,1|achq00,49,63,0|akezfz,49,63,0|akezg0,62,42,1|av7snz,62,42,1|av7so0,49,63,0|b3i0rz,49,63,0|b3i0s0,62,42,1|bdxvbz,62,42,1|bdxvc0,49,63,0|bm83fz,49,63,0|bm83g0,62,42,1|bwnxzz,62,42,1|bwny00,49,63,0|c4y63z,49,63,0|c4y640,62,42,1|cfqzbz,62,42,1|cfqzc0,49,63,0|cno8rz,49,63,0|cno8s0,62,42,1|cyh1zz,62,42,1|cyh200,49,63,0|d6ebfz,49,63,0|d6ebg0,62,42,1|dh74nz,62,42,1|dh74o0,49,63,0|dphcrz,49,63,0|dphcs0,62,42,1|dzx7bz,62,42,1|dzx7c0,49,63,0|e87ffz,49,63,0|e87fg0,62,42,1|ein9zz,62,42,1|eina00,49,63,0|eqxi3z,49,63,0|eqxi40,62,42,1|f1dcnz,62,42,1|f1dco0,49,63,0|f9nkrz,49,63,0|f9nks0,62,42,1|fkgdzz,62,42,1|fkge00,49,63,0|fsdnfz,49,63,0|fsdng0,62,42,1|g36gnz,62,42,1|g36go0,49,63,0|gb3q3z,49,63,0|gb3q40,62,42,1|glwjbz,62,42,1|glwjc0,49,63,0|gu6rfz,49,63,0|gu6rg0,62,42,1|h4mlzz,62,42,1|h4mm00,49,63,0|hcwu3z,49,63,0|hcwu40,62,42,1|hnconz,62,42,1|hncoo0,49,63,0|hvmwrz,49,63,0|hvmws0,62,42,1|i6fpzz,62,42,1|i6fq00,49,63,0|ieczfz,49,63,0|ieczg0,62,42,1|ip5snz,62,42,1|ip5so0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Nome|,0,151,0|-1hc7qjz,0,152,0|-1078qpb,0,152,0|-1078qpa,24,35,0|-ek1nw1,24,35,0|-ek1nw0,25,36,1|-cq2tg1,25,36,1|-cq2tg0,26,36,1|-cnomo1,26,36,1|-cnomo0,24,35,0|-1fq441,24,35,0|-1fq440,27,35,0|-cs3w1,27,35,0|-cs3w0,28,36,1|-3f5c1,28,36,1|-3f5c0,27,35,0|5xyrz,27,35,0|5xys0,28,36,1|faxbz,28,36,1|faxc0,27,35,0|oo1fz,27,35,0|oo1g0,28,36,1|ydynz,28,36,1|ydyo0,27,35,0|17r2rz,27,35,0|17r2s0,28,36,1|1h41bz,28,36,1|1h41c0,27,35,0|1qh5fz,27,35,0|1qh5g0,28,36,1|1zu3zz,28,36,1|1zu400,27,35,0|23ftfz,27,35,0|23ftg0,28,36,1|2ik6nz,28,36,1|2ik6o0,27,35,0|2oomrz,27,35,0|2ooms0,28,36,1|31a9bz,28,36,1|31a9c0,27,35,0|3andfz,27,35,0|3andg0,28,36,1|3kdanz,28,36,1|3kdao0,27,35,0|3tdg3z,27,35,0|3tdg40,28,36,1|433dbz,28,36,1|433dc0,27,35,0|4cghfz,27,35,0|4cghg0,28,36,1|4ltfzz,28,36,1|4ltg00,27,35,0|4v6k3z,27,35,0|4v6k40,28,36,1|54jinz,28,36,1|54jio0,27,35,0|5dwmrz,27,35,0|5dwms0,28,36,1|5n9lbz,28,36,1|5n9lc0,27,35,0|5wmpfz,27,35,0|5wmpg0,28,36,1|65znzz,28,36,1|65zo00,27,35,0|6fcs3z,27,35,0|6fcs40,28,36,1|6p2pbz,28,36,1|6p2pc0,27,35,0|6y2urz,27,35,0|6y2us0,28,36,1|77srzz,28,36,1|77ss00,36,37,0|79dybz,36,37,0|79dyc0,37,37,0|7h5qjz,37,37,0|7h5qk0,38,40,1|7qip3z,38,40,1|7qip40,37,37,0|7zvt7z,37,37,0|7zvt80,38,40,1|898rrz,38,40,1|898rs0,37,37,0|8ilvvz,37,37,0|8ilvw0,38,40,1|8ryufz,38,40,1|8ryug0,37,37,0|9092jz,37,37,0|9092k0,38,40,1|9aox3z,38,40,1|9aox40,37,37,0|9iz57z,37,37,0|9iz580,38,40,1|9tryfz,38,40,1|9tryg0,37,37,0|a1p7vz,37,37,0|a1p7w0,38,40,1|aci13z,38,40,1|aci140,37,37,0|akfajz,37,37,0|akfak0,38,40,1|av83rz,38,40,1|av83s0,37,37,0|b3ibvz,37,37,0|b3ibw0,38,40,1|bdy6fz,38,40,1|bdy6g0,37,37,0|bm8ejz,37,37,0|bm8ek0,38,40,1|bwo93z,38,40,1|bwo940,37,37,0|c4yh7z,37,37,0|c4yh80,38,40,1|cfrafz,38,40,1|cfrag0,37,37,0|cnojvz,37,37,0|cnojw0,38,40,1|cyhd3z,38,40,1|cyhd40,37,37,0|d6emjz,37,37,0|d6emk0,38,40,1|dh7frz,38,40,1|dh7fs0,37,37,0|dphnvz,37,37,0|dphnw0,38,40,1|dzxifz,38,40,1|dzxig0,37,37,0|e87qjz,37,37,0|e87qk0,38,40,1|einl3z,38,40,1|einl40,37,37,0|eqxt7z,37,37,0|eqxt80,38,40,1|f1dnrz,38,40,1|f1dns0,37,37,0|f9nvvz,37,37,0|f9nvw0,38,40,1|fkgp3z,38,40,1|fkgp40,37,37,0|fsdyjz,37,37,0|fsdyk0,38,40,1|g36rrz,38,40,1|g36rs0,37,37,0|gb417z,37,37,0|gb4180,38,40,1|glwufz,38,40,1|glwug0,37,37,0|gu72jz,37,37,0|gu72k0,38,40,1|h4mx3z,38,40,1|h4mx40,37,37,0|hcx57z,37,37,0|hcx580,38,40,1|hnczrz,38,40,1|hnczs0,37,37,0|hvn7vz,37,37,0|hvn7w0,38,40,1|i6g13z,38,40,1|i6g140,37,37,0|iedajz,37,37,0|iedak0,38,40,1|ip63rz,38,40,1|ip63s0,37,37,0|ix3d7z,37,37,0|ix3d80,38,40,1|j7w6fz,38,40,1|j7w6g0,37,37,0|jeqjvz,37,37,0|jeqjw0,38,40,1|jqz7rz,38,40,1|jqz7s0,37,37,0|jxgmjz,37,37,0|jxgmk0,38,40,1|k9pafz,38,40,1|k9pag0,37,37,0|kg6p7z,37,37,0|kg6p80,38,40,1|ksfd3z,38,40,1|ksfd40,37,37,0|kz9qjz,37,37,0|kz9qk0,38,40,1|lbiefz,38,40,1|lbieg0,37,37,0|lhzt7z,37,37,0|lhzt80,38,40,1|lu8h3z,38,40,1|lu8h40,37,37,0|m0pvvz,37,37,0|m0pvw0,38,40,1|mcyjrz,38,40,1|mcyjs0,37,37,0|mjfyjz,37,37,0|mjfyk0,38,40,1|mvomfz,38,40,1|mvomg0,37,37,0|n2617z,37,37,0|n26180,38,40,1|neep3z,38,40,1|neep40,37,37,0|nkw3vz,37,37,0|nkw3w0,38,40,1|nx4rrz,38,40,1|nx4rs0,37,37,0|o3z57z,37,37,0|o3z580,38,40,1|og7t3z,38,40,1|og7t40,37,37,0|omp7vz,37,37,0|omp7w0,38,40,1|oyxvrz,38,40,1|oyxvs0,37,37,0|p5fajz,37,37,0|p5fak0,38,40,1|phnyfz,38,40,1|phnyg0,37,37,0|po5d7z,37,37,0|po5d80,38,40,1|q0e13z,38,40,1|q0e140,37,37,0|q6vfvz,37,37,0|q6vfw0,38,40,1|qj43rz,38,40,1|qj43s0,37,37,0|qpyh7z,37,37,0|qpyh80,38,40,1|r2753z,38,40,1|r27540,37,37,0|r8ojvz,37,37,0|r8ojw0,38,40,1|rkx7rz,38,40,1|rkx7s0,37,37,0|rremjz,37,37,0|rremk0,38,40,1|s3nafz,38,40,1|s3nag0,37,37,0|sa4p7z,37,37,0|sa4p80,38,40,1|smdd3z,38,40,1|smdd40,37,37,0|ssurvz,37,37,0|ssurw0,38,40,1|t53frz,38,40,1|t53fs0,37,37,0|tbkujz,37,37,0|tbkuk0,38,40,1|tntifz,38,40,1|tntig0,37,37,0|tunvvz,37,37,0|tunvw0,38,40,1|u6wjrz,38,40,1|u6wjs0,37,37,0|uddyjz,37,37,0|uddyk0,38,40,1|upmmfz,38,40,1|upmmg0,37,37,0|uw417z,37,37,0|uw4180,38,40,1|v8cp3z,38,40,1|v8cp40,37,37,0|veu3vz,37,37,0|veu3w0,38,40,1|vr2rrz,38,40,1|vr2rs0,37,37,0|vxk6jz,37,37,0|vxk6k0,38,40,1|w9sufz,38,40,1|w9sug0,37,37,0|wgn7vz,37,37,0|wgn7w0,38,40,1|wsvvrz,38,40,1|wsvvs0,37,37,0|wzdajz,37,37,0|wzdak0,38,40,1|xblyfz,38,40,1|xblyg0,37,37,0|xi3d7z,37,37,0|xi3d80,38,40,1|xuc13z,38,40,1|xuc140,37,37,0|y0tfvz,37,37,0|y0tfw0,38,40,1|yd23rz,38,40,1|yd23s0,37,37,0|yjjijz,37,37,0|yjjik0,38,40,1|yvs6fz,38,40,1|yvs6g0,37,37,0|z29l7z,37,37,0|z29l80,38,40,1|zei93z,38,40,1|zei940,37,37,0\",\"America/Noronha|,0,153,0|-t85lzw,40,45,0|-jyld81,40,45,0|-jyld80,13,15,1|-jpb3w1,13,15,1|-jpb3w0,40,45,0|-jfsfs1,40,45,0|-jfsfs0,13,15,1|-j6j6k1,13,15,1|-j6j6k0,40,45,0|-ahd141,40,45,0|-ahd140,13,15,1|-aad6g1,13,15,1|-aad6g0,40,45,0|-9yl3s1,40,45,0|-9yl3s0,13,15,1|-9sd3w1,13,15,1|-9sd3w0,40,45,0|-9ft6g1,40,45,0|-9ft6g0,13,15,1|-99jbw1,13,15,1|-99jbw0,40,45,0|-8wzeg1,40,45,0|-8wzeg0,13,15,1|-8sct81,13,15,1|-8sct80,40,45,0|-35xp41,40,45,0|-35xp40,13,15,1|-31o2k1,13,15,1|-31o2k0,40,45,0|-2kdrs1,40,45,0|-2kdrs0,13,15,1|-2hcl81,13,15,1|-2hcl80,40,45,0|-24qt41,40,45,0|-24qt40,13,15,1|-2047w1,13,15,1|-2047w0,40,45,0|-1nifs1,40,45,0|-1nifs0,13,15,1|-1hcak1,13,15,1|-1hcak0,40,45,0|-14qig1,40,45,0|-14qig0,13,15,1|-yiik1,13,15,1|-yiik0,40,45,0|89j9jz,40,45,0|89j9k0,13,15,1|8gdhfz,13,15,1|8gdhg0,40,45,0|8rwdjz,40,45,0|8rwdk0,13,15,1|8xnpfz,13,15,1|8xnpg0,40,45,0|9aoavz,40,45,0|9aoaw0,13,15,1|9g2o3z,13,15,1|9g2o40,40,45,0|9t1evz,40,45,0|9t1ew0,13,15,1|9yfs3z,13,15,1|9yfs40,40,45,0|abrhjz,40,45,0|abrhk0,13,15,1|ahvs3z,13,15,1|ahvs40,40,45,0|fj087z,40,45,0|fj0880,13,15,1|fqkdfz,13,15,1|fqkdg0,40,45,0|g239jz,40,45,0|g239k0,13,15,1|g2g5fz,13,15,1|g2g5g0,40,45,0|gl6avz,40,45,0|gl6aw0,13,15,1|grnk3z,13,15,1|grnk40,40,45,0\",\"America/North_Dakota/Beulah|,0,154,0|-18y0j80,50,66,0|-r0epo1,50,66,0|-r0epo0,52,62,1|-qplwg1,52,62,1|-qplwg0,50,66,0|-qhon01,50,66,0|-qhon00,52,62,1|-q6vts1,52,62,1|-q6vts0,50,66,0|-ek1z01,50,66,0|-ek1z00,58,62,1|-cq2tg1,58,62,1|-cq2tg0,59,62,1|-cnoxs1,59,62,1|-cnoxs0,50,66,0|-1e8kc1,50,66,0|-1e8kc0,52,62,1|-14vls1,52,62,1|-14vls0,50,66,0|-viho1,50,66,0|-viho0,52,62,1|-m5j41,52,62,1|-m5j40,50,66,0|-csf01,50,66,0|-csf00,52,62,1|-3fgg1,52,62,1|-3fgg0,50,66,0|5xnnz,50,66,0|5xno0,52,62,1|fam7z,52,62,1|fam80,50,66,0|onqbz,50,66,0|onqc0,52,62,1|ydnjz,52,62,1|ydnk0,50,66,0|17qrnz,50,66,0|17qro0,52,62,1|1h3q7z,52,62,1|1h3q80,50,66,0|1qgubz,50,66,0|1qguc0,52,62,1|1ztsvz,52,62,1|1ztsw0,50,66,0|23fibz,50,66,0|23fic0,52,62,1|2ijvjz,52,62,1|2ijvk0,50,66,0|2oobnz,50,66,0|2oobo0,52,62,1|319y7z,52,62,1|319y80,50,66,0|3an2bz,50,66,0|3an2c0,52,62,1|3kczjz,52,62,1|3kczk0,50,66,0|3td4zz,50,66,0|3td500,52,62,1|43327z,52,62,1|433280,50,66,0|4cg6bz,50,66,0|4cg6c0,52,62,1|4lt4vz,52,62,1|4lt4w0,50,66,0|4v68zz,50,66,0|4v6900,52,62,1|54j7jz,52,62,1|54j7k0,50,66,0|5dwbnz,50,66,0|5dwbo0,52,62,1|5n9a7z,52,62,1|5n9a80,50,66,0|5wmebz,50,66,0|5wmec0,52,62,1|65zcvz,52,62,1|65zcw0,50,66,0|6fcgzz,50,66,0|6fch00,52,62,1|6p2e7z,52,62,1|6p2e80,50,66,0|6y2jnz,50,66,0|6y2jo0,52,62,1|77sgvz,52,62,1|77sgw0,50,66,0|7h5kzz,50,66,0|7h5l00,52,62,1|7qijjz,52,62,1|7qijk0,50,66,0|7zvnnz,50,66,0|7zvno0,52,62,1|898m7z,52,62,1|898m80,50,66,0|8ilqbz,50,66,0|8ilqc0,52,62,1|8ryovz,52,62,1|8ryow0,50,66,0|908wzz,50,66,0|908x00,52,62,1|9aorjz,52,62,1|9aork0,50,66,0|9iyznz,50,66,0|9iyzo0,52,62,1|9trsvz,52,62,1|9trsw0,50,66,0|a1p2bz,50,66,0|a1p2c0,52,62,1|achvjz,52,62,1|achvk0,50,66,0|akf4zz,50,66,0|akf500,52,62,1|av7y7z,52,62,1|av7y80,50,66,0|b3i6bz,50,66,0|b3i6c0,52,62,1|bdy0vz,52,62,1|bdy0w0,50,66,0|bm88zz,50,66,0|bm8900,52,62,1|bwo3jz,52,62,1|bwo3k0,50,66,0|c4ybnz,50,66,0|c4ybo0,52,62,1|cfr4vz,52,62,1|cfr4w0,50,66,0|cnoebz,50,66,0|cnoec0,52,62,1|cyh7jz,52,62,1|cyh7k0,50,66,0|d6egzz,50,66,0|d6eh00,52,62,1|dh7a7z,52,62,1|dh7a80,50,66,0|dphibz,50,66,0|dphic0,52,62,1|dzxcvz,52,62,1|dzxcw0,50,66,0|e87kzz,50,66,0|e87l00,52,62,1|einfjz,52,62,1|einfk0,50,66,0|eqxnnz,50,66,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0|f9nqbz,50,66,0|f9nqc0,52,62,1|fkgjjz,52,62,1|fkgjk0,50,66,0|fsdszz,50,66,0|fsdt00,52,62,1|g36m7z,52,62,1|g36m80,50,66,0|gb3vnz,50,66,0|gb3vo0,52,62,1|glwovz,52,62,1|glwow0,50,66,0|gu6wzz,50,66,0|gu6x00,52,62,1|h4mrjz,52,62,1|h4mrk0,50,66,0|hcwznz,50,66,0|hcwzo0,52,62,1|hncu7z,52,62,1|hncu80,50,66,0|hvn2bz,50,66,0|hvn2c0,52,62,1|i6fvjz,52,62,1|i6fvk0,50,66,0|ied4zz,50,66,0|ied500,52,62,1|ip5y7z,52,62,1|ip5y80,50,66,0|ix37nz,50,66,0|ix37o0,52,62,1|j7w0vz,52,62,1|j7w0w0,50,66,0|jeqebz,50,66,0|jeqec0,52,62,1|jqz27z,52,62,1|jqz280,50,66,0|jxggzz,50,66,0|jxgh00,52,62,1|k9p4vz,52,62,1|k9p4w0,50,66,0|kg6jnz,50,66,0|kg6jo0,52,62,1|ksf7jz,52,62,1|ksf7k0,50,66,0|kz9kzz,50,66,0|kz9l00,52,62,1|lbi8vz,52,62,1|lbi8w0,45,62,0|lhzkvz,45,62,0|lhzkw0,46,63,1|lu88rz,46,63,1|lu88s0,45,62,0|m0pnjz,45,62,0|m0pnk0,46,63,1|mcybfz,46,63,1|mcybg0,45,62,0|mjfq7z,45,62,0|mjfq80,46,63,1|mvoe3z,46,63,1|mvoe40,45,62,0|n25svz,45,62,0|n25sw0,46,63,1|neegrz,46,63,1|neegs0,45,62,0|nkvvjz,45,62,0|nkvvk0,46,63,1|nx4jfz,46,63,1|nx4jg0,45,62,0|o3ywvz,45,62,0|o3yww0,46,63,1|og7krz,46,63,1|og7ks0,45,62,0|omozjz,45,62,0|omozk0,46,63,1|oyxnfz,46,63,1|oyxng0,45,62,0|p5f27z,45,62,0|p5f280,46,63,1|phnq3z,46,63,1|phnq40,45,62,0|po54vz,45,62,0|po54w0,46,63,1|q0dsrz,46,63,1|q0dss0,45,62,0|q6v7jz,45,62,0|q6v7k0,46,63,1|qj3vfz,46,63,1|qj3vg0,45,62,0|qpy8vz,45,62,0|qpy8w0,46,63,1|r26wrz,46,63,1|r26ws0,45,62,0|r8objz,45,62,0|r8obk0,46,63,1|rkwzfz,46,63,1|rkwzg0,45,62,0|rree7z,45,62,0|rree80,46,63,1|s3n23z,46,63,1|s3n240,45,62,0|sa4gvz,45,62,0|sa4gw0,46,63,1|smd4rz,46,63,1|smd4s0,45,62,0|ssujjz,45,62,0|ssujk0,46,63,1|t537fz,46,63,1|t537g0,45,62,0|tbkm7z,45,62,0|tbkm80,46,63,1|tnta3z,46,63,1|tnta40,45,62,0|tunnjz,45,62,0|tunnk0,46,63,1|u6wbfz,46,63,1|u6wbg0,45,62,0|uddq7z,45,62,0|uddq80,46,63,1|upme3z,46,63,1|upme40,45,62,0|uw3svz,45,62,0|uw3sw0,46,63,1|v8cgrz,46,63,1|v8cgs0,45,62,0|vetvjz,45,62,0|vetvk0,46,63,1|vr2jfz,46,63,1|vr2jg0,45,62,0|vxjy7z,45,62,0|vxjy80,46,63,1|w9sm3z,46,63,1|w9sm40,45,62,0|wgmzjz,45,62,0|wgmzk0,46,63,1|wsvnfz,46,63,1|wsvng0,45,62,0|wzd27z,45,62,0|wzd280,46,63,1|xblq3z,46,63,1|xblq40,45,62,0|xi34vz,45,62,0|xi34w0,46,63,1|xubsrz,46,63,1|xubss0,45,62,0|y0t7jz,45,62,0|y0t7k0,46,63,1|yd1vfz,46,63,1|yd1vg0,45,62,0|yjja7z,45,62,0|yjja80,46,63,1|yvry3z,46,63,1|yvry40,45,62,0|z29cvz,45,62,0|z29cw0,46,63,1|zei0rz,46,63,1|zei0s0,45,62,0\",\"America/North_Dakota/Center|,0,155,0|-18y0j80,50,66,0|-r0epo1,50,66,0|-r0epo0,52,62,1|-qplwg1,52,62,1|-qplwg0,50,66,0|-qhon01,50,66,0|-qhon00,52,62,1|-q6vts1,52,62,1|-q6vts0,50,66,0|-ek1z01,50,66,0|-ek1z00,58,62,1|-cq2tg1,58,62,1|-cq2tg0,59,62,1|-cnoxs1,59,62,1|-cnoxs0,50,66,0|-1e8kc1,50,66,0|-1e8kc0,52,62,1|-14vls1,52,62,1|-14vls0,50,66,0|-viho1,50,66,0|-viho0,52,62,1|-m5j41,52,62,1|-m5j40,50,66,0|-csf01,50,66,0|-csf00,52,62,1|-3fgg1,52,62,1|-3fgg0,50,66,0|5xnnz,50,66,0|5xno0,52,62,1|fam7z,52,62,1|fam80,50,66,0|onqbz,50,66,0|onqc0,52,62,1|ydnjz,52,62,1|ydnk0,50,66,0|17qrnz,50,66,0|17qro0,52,62,1|1h3q7z,52,62,1|1h3q80,50,66,0|1qgubz,50,66,0|1qguc0,52,62,1|1ztsvz,52,62,1|1ztsw0,50,66,0|23fibz,50,66,0|23fic0,52,62,1|2ijvjz,52,62,1|2ijvk0,50,66,0|2oobnz,50,66,0|2oobo0,52,62,1|319y7z,52,62,1|319y80,50,66,0|3an2bz,50,66,0|3an2c0,52,62,1|3kczjz,52,62,1|3kczk0,50,66,0|3td4zz,50,66,0|3td500,52,62,1|43327z,52,62,1|433280,50,66,0|4cg6bz,50,66,0|4cg6c0,52,62,1|4lt4vz,52,62,1|4lt4w0,50,66,0|4v68zz,50,66,0|4v6900,52,62,1|54j7jz,52,62,1|54j7k0,50,66,0|5dwbnz,50,66,0|5dwbo0,52,62,1|5n9a7z,52,62,1|5n9a80,50,66,0|5wmebz,50,66,0|5wmec0,52,62,1|65zcvz,52,62,1|65zcw0,50,66,0|6fcgzz,50,66,0|6fch00,52,62,1|6p2e7z,52,62,1|6p2e80,50,66,0|6y2jnz,50,66,0|6y2jo0,52,62,1|77sgvz,52,62,1|77sgw0,50,66,0|7h5kzz,50,66,0|7h5l00,52,62,1|7qijjz,52,62,1|7qijk0,50,66,0|7zvnnz,50,66,0|7zvno0,52,62,1|898m7z,52,62,1|898m80,50,66,0|8ilqbz,50,66,0|8ilqc0,52,62,1|8ryovz,52,62,1|8ryow0,50,66,0|908wzz,50,66,0|908x00,52,62,1|9aorjz,52,62,1|9aork0,50,66,0|9iyznz,50,66,0|9iyzo0,52,62,1|9trsvz,52,62,1|9trsw0,50,66,0|a1p2bz,50,66,0|a1p2c0,52,62,1|achvjz,52,62,1|achvk0,50,66,0|akf4zz,50,66,0|akf500,52,62,1|av7y7z,52,62,1|av7y80,50,66,0|b3i6bz,50,66,0|b3i6c0,52,62,1|bdy0vz,52,62,1|bdy0w0,50,66,0|bm88zz,50,66,0|bm8900,52,62,1|bwo3jz,52,62,1|bwo3k0,45,62,0|c4y8vz,45,62,0|c4y8w0,46,63,1|cfr23z,46,63,1|cfr240,45,62,0|cnobjz,45,62,0|cnobk0,46,63,1|cyh4rz,46,63,1|cyh4s0,45,62,0|d6ee7z,45,62,0|d6ee80,46,63,1|dh77fz,46,63,1|dh77g0,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxkvz,45,62,0|eqxkw0,46,63,1|f1dffz,46,63,1|f1dfg0,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkggrz,46,63,1|fkggs0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,45,62,0|gb3svz,45,62,0|gb3sw0,46,63,1|glwm3z,46,63,1|glwm40,45,62,0|gu6u7z,45,62,0|gu6u80,46,63,1|h4morz,46,63,1|h4mos0,45,62,0|hcwwvz,45,62,0|hcwww0,46,63,1|hncrfz,46,63,1|hncrg0,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fsrz,46,63,1|i6fss0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5vfz,46,63,1|ip5vg0,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jeqbjz,45,62,0|jeqbk0,46,63,1|jqyzfz,46,63,1|jqyzg0,45,62,0|jxge7z,45,62,0|jxge80,46,63,1|k9p23z,46,63,1|k9p240,45,62,0|kg6gvz,45,62,0|kg6gw0,46,63,1|ksf4rz,46,63,1|ksf4s0,45,62,0|kz9i7z,45,62,0|kz9i80,46,63,1|lbi63z,46,63,1|lbi640,45,62,0|lhzkvz,45,62,0|lhzkw0,46,63,1|lu88rz,46,63,1|lu88s0,45,62,0|m0pnjz,45,62,0|m0pnk0,46,63,1|mcybfz,46,63,1|mcybg0,45,62,0|mjfq7z,45,62,0|mjfq80,46,63,1|mvoe3z,46,63,1|mvoe40,45,62,0|n25svz,45,62,0|n25sw0,46,63,1|neegrz,46,63,1|neegs0,45,62,0|nkvvjz,45,62,0|nkvvk0,46,63,1|nx4jfz,46,63,1|nx4jg0,45,62,0|o3ywvz,45,62,0|o3yww0,46,63,1|og7krz,46,63,1|og7ks0,45,62,0|omozjz,45,62,0|omozk0,46,63,1|oyxnfz,46,63,1|oyxng0,45,62,0|p5f27z,45,62,0|p5f280,46,63,1|phnq3z,46,63,1|phnq40,45,62,0|po54vz,45,62,0|po54w0,46,63,1|q0dsrz,46,63,1|q0dss0,45,62,0|q6v7jz,45,62,0|q6v7k0,46,63,1|qj3vfz,46,63,1|qj3vg0,45,62,0|qpy8vz,45,62,0|qpy8w0,46,63,1|r26wrz,46,63,1|r26ws0,45,62,0|r8objz,45,62,0|r8obk0,46,63,1|rkwzfz,46,63,1|rkwzg0,45,62,0|rree7z,45,62,0|rree80,46,63,1|s3n23z,46,63,1|s3n240,45,62,0|sa4gvz,45,62,0|sa4gw0,46,63,1|smd4rz,46,63,1|smd4s0,45,62,0|ssujjz,45,62,0|ssujk0,46,63,1|t537fz,46,63,1|t537g0,45,62,0|tbkm7z,45,62,0|tbkm80,46,63,1|tnta3z,46,63,1|tnta40,45,62,0|tunnjz,45,62,0|tunnk0,46,63,1|u6wbfz,46,63,1|u6wbg0,45,62,0|uddq7z,45,62,0|uddq80,46,63,1|upme3z,46,63,1|upme40,45,62,0|uw3svz,45,62,0|uw3sw0,46,63,1|v8cgrz,46,63,1|v8cgs0,45,62,0|vetvjz,45,62,0|vetvk0,46,63,1|vr2jfz,46,63,1|vr2jg0,45,62,0|vxjy7z,45,62,0|vxjy80,46,63,1|w9sm3z,46,63,1|w9sm40,45,62,0|wgmzjz,45,62,0|wgmzk0,46,63,1|wsvnfz,46,63,1|wsvng0,45,62,0|wzd27z,45,62,0|wzd280,46,63,1|xblq3z,46,63,1|xblq40,45,62,0|xi34vz,45,62,0|xi34w0,46,63,1|xubsrz,46,63,1|xubss0,45,62,0|y0t7jz,45,62,0|y0t7k0,46,63,1|yd1vfz,46,63,1|yd1vg0,45,62,0|yjja7z,45,62,0|yjja80,46,63,1|yvry3z,46,63,1|yvry40,45,62,0|z29cvz,45,62,0|z29cw0,46,63,1|zei0rz,46,63,1|zei0s0,45,62,0\",\"America/North_Dakota/New_Salem|,0,156,0|-18y0j80,50,66,0|-r0epo1,50,66,0|-r0epo0,52,62,1|-qplwg1,52,62,1|-qplwg0,50,66,0|-qhon01,50,66,0|-qhon00,52,62,1|-q6vts1,52,62,1|-q6vts0,50,66,0|-ek1z01,50,66,0|-ek1z00,58,62,1|-cq2tg1,58,62,1|-cq2tg0,59,62,1|-cnoxs1,59,62,1|-cnoxs0,50,66,0|-1e8kc1,50,66,0|-1e8kc0,52,62,1|-14vls1,52,62,1|-14vls0,50,66,0|-viho1,50,66,0|-viho0,52,62,1|-m5j41,52,62,1|-m5j40,50,66,0|-csf01,50,66,0|-csf00,52,62,1|-3fgg1,52,62,1|-3fgg0,50,66,0|5xnnz,50,66,0|5xno0,52,62,1|fam7z,52,62,1|fam80,50,66,0|onqbz,50,66,0|onqc0,52,62,1|ydnjz,52,62,1|ydnk0,50,66,0|17qrnz,50,66,0|17qro0,52,62,1|1h3q7z,52,62,1|1h3q80,50,66,0|1qgubz,50,66,0|1qguc0,52,62,1|1ztsvz,52,62,1|1ztsw0,50,66,0|23fibz,50,66,0|23fic0,52,62,1|2ijvjz,52,62,1|2ijvk0,50,66,0|2oobnz,50,66,0|2oobo0,52,62,1|319y7z,52,62,1|319y80,50,66,0|3an2bz,50,66,0|3an2c0,52,62,1|3kczjz,52,62,1|3kczk0,50,66,0|3td4zz,50,66,0|3td500,52,62,1|43327z,52,62,1|433280,50,66,0|4cg6bz,50,66,0|4cg6c0,52,62,1|4lt4vz,52,62,1|4lt4w0,50,66,0|4v68zz,50,66,0|4v6900,52,62,1|54j7jz,52,62,1|54j7k0,50,66,0|5dwbnz,50,66,0|5dwbo0,52,62,1|5n9a7z,52,62,1|5n9a80,50,66,0|5wmebz,50,66,0|5wmec0,52,62,1|65zcvz,52,62,1|65zcw0,50,66,0|6fcgzz,50,66,0|6fch00,52,62,1|6p2e7z,52,62,1|6p2e80,50,66,0|6y2jnz,50,66,0|6y2jo0,52,62,1|77sgvz,52,62,1|77sgw0,50,66,0|7h5kzz,50,66,0|7h5l00,52,62,1|7qijjz,52,62,1|7qijk0,50,66,0|7zvnnz,50,66,0|7zvno0,52,62,1|898m7z,52,62,1|898m80,50,66,0|8ilqbz,50,66,0|8ilqc0,52,62,1|8ryovz,52,62,1|8ryow0,50,66,0|908wzz,50,66,0|908x00,52,62,1|9aorjz,52,62,1|9aork0,50,66,0|9iyznz,50,66,0|9iyzo0,52,62,1|9trsvz,52,62,1|9trsw0,50,66,0|a1p2bz,50,66,0|a1p2c0,52,62,1|achvjz,52,62,1|achvk0,50,66,0|akf4zz,50,66,0|akf500,52,62,1|av7y7z,52,62,1|av7y80,50,66,0|b3i6bz,50,66,0|b3i6c0,52,62,1|bdy0vz,52,62,1|bdy0w0,50,66,0|bm88zz,50,66,0|bm8900,52,62,1|bwo3jz,52,62,1|bwo3k0,50,66,0|c4ybnz,50,66,0|c4ybo0,52,62,1|cfr4vz,52,62,1|cfr4w0,50,66,0|cnoebz,50,66,0|cnoec0,52,62,1|cyh7jz,52,62,1|cyh7k0,50,66,0|d6egzz,50,66,0|d6eh00,52,62,1|dh7a7z,52,62,1|dh7a80,50,66,0|dphibz,50,66,0|dphic0,52,62,1|dzxcvz,52,62,1|dzxcw0,50,66,0|e87kzz,50,66,0|e87l00,52,62,1|einfjz,52,62,1|einfk0,50,66,0|eqxnnz,50,66,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0|f9nqbz,50,66,0|f9nqc0,52,62,1|fkgjjz,52,62,1|fkgjk0,50,66,0|fsdszz,50,66,0|fsdt00,52,62,1|g36m7z,52,62,1|g36m80,50,66,0|gb3vnz,50,66,0|gb3vo0,52,62,1|glwovz,52,62,1|glwow0,50,66,0|gu6wzz,50,66,0|gu6x00,52,62,1|h4mrjz,52,62,1|h4mrk0,50,66,0|hcwznz,50,66,0|hcwzo0,52,62,1|hncu7z,52,62,1|hncu80,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fsrz,46,63,1|i6fss0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5vfz,46,63,1|ip5vg0,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jeqbjz,45,62,0|jeqbk0,46,63,1|jqyzfz,46,63,1|jqyzg0,45,62,0|jxge7z,45,62,0|jxge80,46,63,1|k9p23z,46,63,1|k9p240,45,62,0|kg6gvz,45,62,0|kg6gw0,46,63,1|ksf4rz,46,63,1|ksf4s0,45,62,0|kz9i7z,45,62,0|kz9i80,46,63,1|lbi63z,46,63,1|lbi640,45,62,0|lhzkvz,45,62,0|lhzkw0,46,63,1|lu88rz,46,63,1|lu88s0,45,62,0|m0pnjz,45,62,0|m0pnk0,46,63,1|mcybfz,46,63,1|mcybg0,45,62,0|mjfq7z,45,62,0|mjfq80,46,63,1|mvoe3z,46,63,1|mvoe40,45,62,0|n25svz,45,62,0|n25sw0,46,63,1|neegrz,46,63,1|neegs0,45,62,0|nkvvjz,45,62,0|nkvvk0,46,63,1|nx4jfz,46,63,1|nx4jg0,45,62,0|o3ywvz,45,62,0|o3yww0,46,63,1|og7krz,46,63,1|og7ks0,45,62,0|omozjz,45,62,0|omozk0,46,63,1|oyxnfz,46,63,1|oyxng0,45,62,0|p5f27z,45,62,0|p5f280,46,63,1|phnq3z,46,63,1|phnq40,45,62,0|po54vz,45,62,0|po54w0,46,63,1|q0dsrz,46,63,1|q0dss0,45,62,0|q6v7jz,45,62,0|q6v7k0,46,63,1|qj3vfz,46,63,1|qj3vg0,45,62,0|qpy8vz,45,62,0|qpy8w0,46,63,1|r26wrz,46,63,1|r26ws0,45,62,0|r8objz,45,62,0|r8obk0,46,63,1|rkwzfz,46,63,1|rkwzg0,45,62,0|rree7z,45,62,0|rree80,46,63,1|s3n23z,46,63,1|s3n240,45,62,0|sa4gvz,45,62,0|sa4gw0,46,63,1|smd4rz,46,63,1|smd4s0,45,62,0|ssujjz,45,62,0|ssujk0,46,63,1|t537fz,46,63,1|t537g0,45,62,0|tbkm7z,45,62,0|tbkm80,46,63,1|tnta3z,46,63,1|tnta40,45,62,0|tunnjz,45,62,0|tunnk0,46,63,1|u6wbfz,46,63,1|u6wbg0,45,62,0|uddq7z,45,62,0|uddq80,46,63,1|upme3z,46,63,1|upme40,45,62,0|uw3svz,45,62,0|uw3sw0,46,63,1|v8cgrz,46,63,1|v8cgs0,45,62,0|vetvjz,45,62,0|vetvk0,46,63,1|vr2jfz,46,63,1|vr2jg0,45,62,0|vxjy7z,45,62,0|vxjy80,46,63,1|w9sm3z,46,63,1|w9sm40,45,62,0|wgmzjz,45,62,0|wgmzk0,46,63,1|wsvnfz,46,63,1|wsvng0,45,62,0|wzd27z,45,62,0|wzd280,46,63,1|xblq3z,46,63,1|xblq40,45,62,0|xi34vz,45,62,0|xi34w0,46,63,1|xubsrz,46,63,1|xubss0,45,62,0|y0t7jz,45,62,0|y0t7k0,46,63,1|yd1vfz,46,63,1|yd1vg0,45,62,0|yjja7z,45,62,0|yjja80,46,63,1|yvry3z,46,63,1|yvry40,45,62,0|z29cvz,45,62,0|z29cw0,46,63,1|zei0rz,46,63,1|zei0s0,45,62,0\",\"America/Nuuk|,0,157,0|-rvumf4,39,44,0|5ct4jz,39,44,0|5ct4k0,40,45,1|5lsw3z,40,45,1|5lsw40,39,44,0|5v5xfz,39,44,0|5v5xg0,40,45,1|64iyrz,40,45,1|64iys0,39,44,0|6dw03z,39,44,0|6dw040,40,45,1|6n91fz,40,45,1|6n91g0,39,44,0|6wm2rz,39,44,0|6wm2s0,40,45,1|75z43z,40,45,1|75z440,39,44,0|7fc5fz,39,44,0|7fc5g0,40,45,1|7p25fz,40,45,1|7p25g0,39,44,0|7yf6rz,39,44,0|7yf6s0,40,45,1|87s83z,40,45,1|87s840,39,44,0|8h59fz,39,44,0|8h59g0,40,45,1|8qiarz,40,45,1|8qias0,39,44,0|8zvc3z,39,44,0|8zvc40,40,45,1|998dfz,40,45,1|998dg0,39,44,0|9ilerz,39,44,0|9iles0,40,45,1|9ryg3z,40,45,1|9ryg40,39,44,0|a1bhfz,39,44,0|a1bhg0,40,45,1|aaoirz,40,45,1|aaois0,39,44,0|ak1k3z,39,44,0|ak1k40,40,45,1|atrk3z,40,45,1|atrk40,39,44,0|b34lfz,39,44,0|b34lg0,40,45,1|bchmrz,40,45,1|bchms0,39,44,0|bluo3z,39,44,0|bluo40,40,45,1|bv7pfz,40,45,1|bv7pg0,39,44,0|c4kqrz,39,44,0|c4kqs0,40,45,1|cdxs3z,40,45,1|cdxs40,39,44,0|cnatfz,39,44,0|cnatg0,40,45,1|cwnurz,40,45,1|cwnus0,39,44,0|d60w3z,39,44,0|d60w40,40,45,1|dfdxfz,40,45,1|dfdxg0,39,44,0|dp3xfz,39,44,0|dp3xg0,40,45,1|dzwtfz,40,45,1|dzwtg0,39,44,0|e7u03z,39,44,0|e7u040,40,45,1|eimw3z,40,45,1|eimw40,39,44,0|eqk2rz,39,44,0|eqk2s0,40,45,1|f1cyrz,40,45,1|f1cys0,39,44,0|f9a5fz,39,44,0|f9a5g0,40,45,1|fkg03z,40,45,1|fkg040,39,44,0|fs083z,39,44,0|fs0840,40,45,1|g362rz,40,45,1|g362s0,39,44,0|gaqarz,39,44,0|gaqas0,40,45,1|glw5fz,40,45,1|glw5g0,39,44,0|gttc3z,39,44,0|gttc40,40,45,1|h4m83z,40,45,1|h4m840,39,44,0|hcjerz,39,44,0|hcjes0,40,45,1|hncarz,40,45,1|hncas0,39,44,0|hv9hfz,39,44,0|hv9hg0,40,45,1|i6fc3z,40,45,1|i6fc40,39,44,0|idzk3z,39,44,0|idzk40,40,45,1|ip5erz,40,45,1|ip5es0,39,44,0|iwpmrz,39,44,0|iwpms0,40,45,1|j7vhfz,40,45,1|j7vhg0,39,44,0|jffpfz,39,44,0|jffpg0,40,45,1|jqlk3z,40,45,1|jqlk40,39,44,0|jyiqrz,39,44,0|jyiqs0,40,45,1|k9bmrz,40,45,1|k9bms0,39,44,0|kh8tfz,39,44,0|kh8tg0,40,45,1|ks1pfz,40,45,1|ks1pg0,39,44,0|kzyw3z,39,44,0|kzyw40,40,45,1|lb4qrz,40,45,1|lb4qs0,39,44,0|lioyrz,39,44,0|lioys0,40,45,1|ltutfz,40,45,1|ltutg0,39,44,0|m1f1fz,39,44,0|m1f1g0,40,45,1|mckw3z,40,45,1|mckw40,39,44,0|mki2rz,39,44,0|mki2s0,40,45,1|mvayrz,40,45,1|mvays0,39,44,0|n385fz,39,44,0|n385g0,40,45,1|ne11fz,40,45,1|ne11g0,39,44,0|nly83z,39,44,0|nly840,40,45,1|nwr43z,40,45,1|nwr440,39,44,0|o4oarz,39,44,0|o4oas0,40,45,1|ofu5fz,40,45,1|ofu5g0,39,44,0|onedfz,39,44,0|onedg0,40,45,1|oyk83z,40,45,1|oyk840,39,44,0|p64g3z,39,44,0|p64g40,40,45,1|phaarz,40,45,1|phaas0,39,44,0|pp7hfz,39,44,0|pp7hg0,40,45,1|q00dfz,40,45,1|q00dg0,39,44,0|q7xk3z,39,44,0|q7xk40,40,45,1|qiqg3z,40,45,1|qiqg40,39,44,0|qqnmrz,39,44,0|qqnms0,40,45,1|r1thfz,40,45,1|r1thg0,39,44,0|r9dpfz,39,44,0|r9dpg0,40,45,1|rkjk3z,40,45,1|rkjk40,39,44,0|rs3s3z,39,44,0|rs3s40,40,45,1|s39mrz,40,45,1|s39ms0,39,44,0|sb6tfz,39,44,0|sb6tg0,40,45,1|slzpfz,40,45,1|slzpg0,39,44,0|stww3z,39,44,0|stww40,40,45,1|t4ps3z,40,45,1|t4ps40,39,44,0|tcmyrz,39,44,0|tcmys0,40,45,1|tnfurz,40,45,1|tnfus0,39,44,0|tvd1fz,39,44,0|tvd1g0,40,45,1|u6iw3z,40,45,1|u6iw40,39,44,0|ue343z,39,44,0|ue3440,40,45,1|up8yrz,40,45,1|up8ys0,39,44,0|uwt6rz,39,44,0|uwt6s0,40,45,1|v7z1fz,40,45,1|v7z1g0,39,44,0|vfw83z,39,44,0|vfw840,40,45,1|vqp43z,40,45,1|vqp440,39,44,0|vymarz,39,44,0|vymas0,40,45,1|w9f6rz,40,45,1|w9f6s0,39,44,0|whcdfz,39,44,0|whcdg0,40,45,1|wsi83z,40,45,1|wsi840,39,44,0|x02g3z,39,44,0|x02g40,40,45,1|xb8arz,40,45,1|xb8as0,39,44,0|xisirz,39,44,0|xisis0,40,45,1|xtydfz,40,45,1|xtydg0,39,44,0|y1ilfz,39,44,0|y1ilg0,40,45,1|ycog3z,40,45,1|ycog40,39,44,0|yklmrz,39,44,0|yklms0,40,45,1|yveirz,40,45,1|yveis0,39,44,0|z3bpfz,39,44,0|z3bpg0,40,45,1|ze4lfz,40,45,1|ze4lg0,39,44,0\",\"America/Ojinaga|,0,158,0|-p1u4k0,50,66,0|-m7mko1,50,66,0|-m7mko0,45,62,0|-kf67c1,45,62,0|-kf67c0,50,66,0|-k6j3c1,50,66,0|-k6j3c0,45,62,0|-jypm01,45,62,0|-jypm00,50,66,0|-jpan81,50,66,0|-jpan80,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxnnz,45,62,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0|f9nqbz,50,66,0|f9nqc0,52,62,1|fkgjjz,52,62,1|fkgjk0,50,66,0|fsdszz,50,66,0|fsdt00,52,62,1|g36m7z,52,62,1|g36m80,50,66,0|gcwozz,50,66,0|gcwp00,52,62,1|gkgu7z,52,62,1|gkgu80,50,66,0|gu6wzz,50,66,0|gu6x00,52,62,1|h4mrjz,52,62,1|h4mrk0,50,66,0|hcwznz,50,66,0|hcwzo0,52,62,1|hncu7z,52,62,1|hncu80,50,66,0|hvn2bz,50,66,0|hvn2c0,52,62,1|i6fvjz,52,62,1|i6fvk0,50,66,0|ied4zz,50,66,0|ied500,52,62,1|ip5y7z,52,62,1|ip5y80,50,66,0|ix37nz,50,66,0|ix37o0,52,62,1|j7w0vz,52,62,1|j7w0w0,50,66,0|jftabz,50,66,0|jftac0,52,62,1|jqm3jz,52,62,1|jqm3k0,50,66,0|jywbnz,50,66,0|jywbo0,52,62,1|k9c67z,52,62,1|k9c680,50,66,0|khmebz,50,66,0|khmec0,52,62,1|ks28vz,52,62,1|ks28w0,50,66,0|kz9kzz,50,66,0|kz9l00,52,62,1|lbi8vz,52,62,1|lbi8w0,50,66,0|lhznnz,50,66,0|lhzno0,52,62,1|lu8bjz,52,62,1|lu8bk0,50,66,0|m0pqbz,50,66,0|m0pqc0,52,62,1|mcye7z,52,62,1|mcye80,50,66,0|mjfszz,50,66,0|mjft00,52,62,1|mvogvz,52,62,1|mvogw0,50,66,0|n25vnz,50,66,0|n25vo0,52,62,1|neejjz,52,62,1|neejk0,50,66,0|nkvybz,50,66,0|nkvyc0,52,62,1|nx4m7z,52,62,1|nx4m80,50,66,0|o3yznz,50,66,0|o3yzo0,52,62,1|og7njz,52,62,1|og7nk0,50,66,0|omp2bz,50,66,0|omp2c0,52,62,1|oyxq7z,52,62,1|oyxq80,50,66,0|p5f4zz,50,66,0|p5f500,52,62,1|phnsvz,52,62,1|phnsw0,50,66,0|po57nz,50,66,0|po57o0,52,62,1|q0dvjz,52,62,1|q0dvk0,50,66,0|q6vabz,50,66,0|q6vac0,52,62,1|qj3y7z,52,62,1|qj3y80,50,66,0|qpybnz,50,66,0|qpybo0,52,62,1|r26zjz,52,62,1|r26zk0,50,66,0|r8oebz,50,66,0|r8oec0,52,62,1|rkx27z,52,62,1|rkx280,50,66,0|rregzz,50,66,0|rreh00,52,62,1|s3n4vz,52,62,1|s3n4w0,50,66,0|sa4jnz,50,66,0|sa4jo0,52,62,1|smd7jz,52,62,1|smd7k0,50,66,0|ssumbz,50,66,0|ssumc0,52,62,1|t53a7z,52,62,1|t53a80,50,66,0|tbkozz,50,66,0|tbkp00,52,62,1|tntcvz,52,62,1|tntcw0,50,66,0|tunqbz,50,66,0|tunqc0,52,62,1|u6we7z,52,62,1|u6we80,50,66,0|uddszz,50,66,0|uddt00,52,62,1|upmgvz,52,62,1|upmgw0,50,66,0|uw3vnz,50,66,0|uw3vo0,52,62,1|v8cjjz,52,62,1|v8cjk0,50,66,0|vetybz,50,66,0|vetyc0,52,62,1|vr2m7z,52,62,1|vr2m80,50,66,0|vxk0zz,50,66,0|vxk100,52,62,1|w9sovz,52,62,1|w9sow0,50,66,0|wgn2bz,50,66,0|wgn2c0,52,62,1|wsvq7z,52,62,1|wsvq80,50,66,0|wzd4zz,50,66,0|wzd500,52,62,1|xblsvz,52,62,1|xblsw0,50,66,0|xi37nz,50,66,0|xi37o0,52,62,1|xubvjz,52,62,1|xubvk0,50,66,0|y0tabz,50,66,0|y0tac0,52,62,1|yd1y7z,52,62,1|yd1y80,50,66,0|yjjczz,50,66,0|yjjd00,52,62,1|yvs0vz,52,62,1|yvs0w0,50,66,0|z29fnz,50,66,0|z29fo0,52,62,1|zei3jz,52,62,1|zei3k0,50,66,0\",\"America/Panama|,0,80,0|-15r0uls,41,81,0|-w757vd,41,81,0|-w757vc,49,63,0\",\"America/Pangnirtung|,60,1,0|-pkmlc0,32,42,0|-ek27c1,32,42,0|-ek27c0,33,44,1|-cq2tg1,33,44,1|-cq2tg0,34,44,1|-cnp641,34,44,1|-cnp640,32,42,0|-2g2281,32,42,0|-2g2280,73,45,1|-26c281,73,45,1|-26c280,32,42,0|5dw3bz,32,42,0|5dw3c0,54,44,1|5n91vz,54,44,1|5n91w0,32,42,0|5wm5zz,32,42,0|5wm600,54,44,1|65z4jz,54,44,1|65z4k0,32,42,0|6fc8nz,32,42,0|6fc8o0,54,44,1|6p25vz,54,44,1|6p25w0,32,42,0|6y2bbz,32,42,0|6y2bc0,54,44,1|77s8jz,54,44,1|77s8k0,32,42,0|7h5cnz,32,42,0|7h5co0,54,44,1|7qib7z,54,44,1|7qib80,32,42,0|7zvfbz,32,42,0|7zvfc0,54,44,1|898dvz,54,44,1|898dw0,32,42,0|8ilhzz,32,42,0|8ili00,54,44,1|8rygjz,54,44,1|8rygk0,32,42,0|908onz,32,42,0|908oo0,54,44,1|9aoj7z,54,44,1|9aoj80,32,42,0|9iyrbz,32,42,0|9iyrc0,54,44,1|9trkjz,54,44,1|9trkk0,32,42,0|a1otzz,32,42,0|a1ou00,54,44,1|achn7z,54,44,1|achn80,32,42,0|akewnz,32,42,0|akewo0,54,44,1|av7pvz,54,44,1|av7pw0,32,42,0|b3hxzz,32,42,0|b3hy00,54,44,1|bdxsjz,54,44,1|bdxsk0,32,42,0|bm80nz,32,42,0|bm80o0,54,44,1|bwnv7z,54,44,1|bwnv80,32,42,0|c4y3bz,32,42,0|c4y3c0,54,44,1|cfqwjz,54,44,1|cfqwk0,32,42,0|cno5zz,32,42,0|cno600,54,44,1|cygz7z,54,44,1|cygz80,32,42,0|d6e8nz,32,42,0|d6e8o0,62,42,1|dh74nz,62,42,1|dh74o0,49,63,0|dphcrz,49,63,0|dphcs0,62,42,1|dzx7bz,62,42,1|dzx7c0,49,63,0|e87ffz,49,63,0|e87fg0,62,42,1|ein9zz,62,42,1|eina00,49,63,0|eqxi3z,49,63,0|eqxi40,62,42,1|f1dcnz,62,42,1|f1dco0,49,63,0|f9nkrz,49,63,0|f9nks0,62,42,1|fkgdzz,62,42,1|fkge00,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,49,63,0|gb3q3z,49,63,0|gb3q40,62,42,1|glwjbz,62,42,1|glwjc0,49,63,0|gu6rfz,49,63,0|gu6rg0,62,42,1|h4mlzz,62,42,1|h4mm00,49,63,0|hcwu3z,49,63,0|hcwu40,62,42,1|hnconz,62,42,1|hncoo0,49,63,0|hvmwrz,49,63,0|hvmws0,62,42,1|i6fpzz,62,42,1|i6fq00,49,63,0|ieczfz,49,63,0|ieczg0,62,42,1|ip5snz,62,42,1|ip5so0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Paramaribo|,0,159,0|-usj4g8,7,160,0|-i9lsfx,7,160,0|-i9lsfw,7,161,0|-cnnf4d,7,161,0|-cnnf4c,81,101,0|7p471z,81,101,0|7p4720,39,44,0\",\"America/Phoenix|,0,162,0|-18y0j80,50,66,0|-r0epo1,50,66,0|-r0epo0,52,62,1|-qplwg1,52,62,1|-qplwg0,50,66,0|-qhon01,50,66,0|-qhon00,52,62,1|-q6vts1,52,62,1|-q6vts0,50,66,0|-ek1z01,50,66,0|-ek1z00,58,62,1|-dkikmd,58,62,1|-dkikmc,50,66,0|-dftz6d,50,66,0|-dftz6c,58,62,1|-d6f5yd,58,62,1|-d6f5yc,50,66,0|-1e8kc1,50,66,0|-1e8kc0,52,62,1|-14vls1,52,62,1|-14vls0,50,66,0\",\"America/Port-au-Prince|,0,163,0|-15r0vxs,84,164,0|-rmk9ad,84,164,0|-rmk9ac,49,63,0|6ys5vz,49,63,0|6ys5w0,62,42,1|77s5rz,62,42,1|77s5s0,49,63,0|7h59vz,49,63,0|7h59w0,62,42,1|7qi8fz,62,42,1|7qi8g0,49,63,0|7zvcjz,49,63,0|7zvck0,62,42,1|898b3z,62,42,1|898b40,49,63,0|8ilf7z,49,63,0|8ilf80,62,42,1|8rydrz,62,42,1|8ryds0,49,63,0|91bhvz,49,63,0|91bhw0,62,42,1|9aogfz,62,42,1|9aogg0,49,63,0|9iyrbz,49,63,0|9iyrc0,62,42,1|9trnbz,62,42,1|9trnc0,49,63,0|a1otzz,49,63,0|a1ou00,62,42,1|achpzz,62,42,1|achq00,49,63,0|akewnz,49,63,0|akewo0,62,42,1|av7snz,62,42,1|av7so0,49,63,0|b3hxzz,49,63,0|b3hy00,62,42,1|bdxvbz,62,42,1|bdxvc0,49,63,0|bm80nz,49,63,0|bm80o0,62,42,1|bwnxzz,62,42,1|bwny00,49,63,0|c4y3bz,49,63,0|c4y3c0,62,42,1|cfqzbz,62,42,1|cfqzc0,49,63,0|cno5zz,49,63,0|cno600,62,42,1|cyh1zz,62,42,1|cyh200,49,63,0|d6e8nz,49,63,0|d6e8o0,62,42,1|dh74nz,62,42,1|dh74o0,49,63,0|dph9zz,49,63,0|dpha00,62,42,1|dzx7bz,62,42,1|dzx7c0,49,63,0|e87cnz,49,63,0|e87co0,62,42,1|ein9zz,62,42,1|eina00,49,63,0|iectvz,49,63,0|iectw0,62,42,1|ip5n3z,62,42,1|ip5n40,49,63,0|ix2wjz,49,63,0|ix2wk0,62,42,1|j7vprz,62,42,1|j7vps0,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Port_of_Spain|,0,41,0|-u6m79w,32,42,0\",\"America/Porto_Velho|,0,165,0|-t85g60,42,42,0|-jyl7o1,42,42,0|-jyl7o0,39,44,1|-jpayc1,39,44,1|-jpayc0,42,42,0|-jfsa81,42,42,0|-jfsa80,39,44,1|-j6j101,39,44,1|-j6j100,42,42,0|-ahcvk1,42,42,0|-ahcvk0,39,44,1|-aad0w1,39,44,1|-aad0w0,42,42,0|-9yky81,42,42,0|-9yky80,39,44,1|-9scyc1,39,44,1|-9scyc0,42,42,0|-9ft0w1,42,42,0|-9ft0w0,39,44,1|-99j6c1,39,44,1|-99j6c0,42,42,0|-8wz8w1,42,42,0|-8wz8w0,39,44,1|-8scno1,39,44,1|-8scno0,42,42,0|-35xjk1,42,42,0|-35xjk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2kdm81,42,42,0|-2kdm80,39,44,1|-2hcfo1,39,44,1|-2hcfo0,42,42,0|-24qnk1,42,42,0|-24qnk0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1nia81,42,42,0|-1nia80,39,44,1|-1hc501,39,44,1|-1hc500,42,42,0|-14qcw1,42,42,0|-14qcw0,39,44,1|-yid01,39,44,1|-yid00,42,42,0|89jf3z,42,42,0|89jf40,39,44,1|8gdmzz,39,44,1|8gdn00,42,42,0|8rwj3z,42,42,0|8rwj40,39,44,1|8xnuzz,39,44,1|8xnv00,42,42,0|9aogfz,42,42,0|9aogg0,39,44,1|9g2tnz,39,44,1|9g2to0,42,42,0\",\"America/Puerto_Rico|,0,166,0|-10xhp3b,32,42,0|-efsnk1,32,42,0|-efsnk0,33,44,1|-cq2tg1,33,44,1|-cq2tg0,34,44,1|-cnp641,34,44,1|-cnp640,32,42,0\",\"America/Punta_Arenas|,0,167,0|-15r0w78,85,168,0|-vauawr,85,168,0|-vauawq,56,63,0|-rx8i41,56,63,0|-rx8i40,85,168,0|-qs16wr,85,168,0|-qs16wq,42,42,0|-qcwsw1,42,42,0|-qcwsw0,85,168,0|-m3etkr,85,168,0|-m3etkq,42,42,1|-lsgfk1,42,42,1|-lsgfk0,56,63,0|-lkl0s1,56,63,0|-lkl0s0,42,42,1|-l9oi81,42,42,1|-l9oi80,56,63,0|-l1t3g1,56,63,0|-l1t3g0,42,42,1|-kqwkw1,42,42,1|-kqwkw0,56,63,0|-kj1641,56,63,0|-kj1640,42,42,1|-k84nk1,42,42,1|-k84nk0,56,63,0|-k098s1,56,63,0|-k098s0,42,42,1|-jpavk1,42,42,1|-jpavk0,56,63,0|-jhfgs1,56,63,0|-jhfgs0,42,42,0|-eeay81,42,42,0|-eeay80,56,63,0|-eb5ws1,56,63,0|-eb5ws0,42,42,0|-bvifk1,42,42,0|-bvifk0,56,63,0|-bsvzk1,56,63,0|-bsvzk0,42,42,0|-lsvk1,42,42,0|-lsvk0,39,44,1|-e8qc1,39,44,1|-e8qc0,42,42,0|-1zww1,42,42,0|-1zww0,39,44,1|4hcbz,39,44,1|4hcc0,42,42,0|ekdrz,42,42,0|ekds0,39,44,1|mhhnz,39,44,1|mhho0,42,42,0|xagfz,42,42,0|xagg0,39,44,1|157kbz,39,44,1|157kc0,42,42,0|1gdhrz,42,42,0|1gdhs0,39,44,1|1nxmzz,39,44,1|1nxn00,42,42,0|1ydn3z,42,42,0|1ydn40,39,44,1|26npnz,39,44,1|26npo0,42,42,0|2htn3z,42,42,0|2htn40,39,44,1|2pdsbz,39,44,1|2pdsc0,42,42,0|30jprz,42,42,0|30jps0,39,44,1|38gtnz,39,44,1|38gto0,42,42,0|3j9sfz,42,42,0|3j9sg0,39,44,1|3r6wbz,39,44,1|3r6wc0,42,42,0|41zv3z,42,42,0|41zv40,39,44,1|49wyzz,39,44,1|49wz00,42,42,0|4l2wfz,42,42,0|4l2wg0,39,44,1|4sn1nz,39,44,1|4sn1o0,42,42,0|53sz3z,42,42,0|53sz40,39,44,1|5bd4bz,39,44,1|5bd4c0,42,42,0|5mj1rz,42,42,0|5mj1s0,39,44,1|5ug5nz,39,44,1|5ug5o0,42,42,0|6594fz,42,42,0|6594g0,39,44,1|6d68bz,39,44,1|6d68c0,42,42,0|6nz73z,42,42,0|6nz740,39,44,1|6vwazz,39,44,1|6vwb00,42,42,0|76p9rz,42,42,0|76p9s0,39,44,1|7emdnz,39,44,1|7emdo0,42,42,0|7psb3z,42,42,0|7psb40,39,44,1|7xcgbz,39,44,1|7xcgc0,42,42,0|88idrz,42,42,0|88ids0,39,44,1|8g2izz,39,44,1|8g2j00,42,42,0|8r8gfz,42,42,0|8r8gg0,39,44,1|90lezz,39,44,1|90lf00,42,42,0|99yj3z,42,42,0|99yj40,39,44,1|9hvmzz,39,44,1|9hvn00,42,42,0|9solrz,42,42,0|9sols0,39,44,1|a0lpnz,39,44,1|a0lpo0,42,42,0|abrn3z,42,42,0|abrn40,39,44,1|ajbsbz,39,44,1|ajbsc0,42,42,0|at1v3z,42,42,0|at1v40,39,44,1|b21uzz,39,44,1|b21v00,42,42,0|bd7sfz,42,42,0|bd7sg0,39,44,1|bl4wbz,39,44,1|bl4wc0,42,42,0|bvxv3z,42,42,0|bvxv40,39,44,1|c3uyzz,39,44,1|c3uz00,42,42,0|cenxrz,42,42,0|cenxs0,39,44,1|cml1nz,39,44,1|cml1o0,42,42,0|cxe0fz,42,42,0|cxe0g0,39,44,1|d5b4bz,39,44,1|d5b4c0,42,42,0|dgh1rz,42,42,0|dgh1s0,39,44,1|do16zz,39,44,1|do1700,42,42,0|dz74fz,42,42,0|dz74g0,39,44,1|e7u5nz,39,44,1|e7u5o0,42,42,0|ehx73z,42,42,0|ehx740,39,44,1|epuazz,39,44,1|epub00,42,42,0|ezxcfz,42,42,0|ezxcg0,39,44,1|f9n9nz,39,44,1|f9n9o0,42,42,0|fjdcfz,42,42,0|fjdcg0,39,44,1|fragbz,39,44,1|fragc0,42,42,0|g2gdrz,42,42,0|g2gds0,39,44,1|ga0izz,39,44,1|ga0j00,42,42,0|gl6gfz,42,42,0|gl6gg0,39,44,1|gsqlnz,39,44,1|gsqlo0,42,42,0|h3wj3z,42,42,0|h3wj40,39,44,1|hbgobz,39,44,1|hbgoc0,42,42,0|hmmlrz,42,42,0|hmmls0,39,44,1|hujpnz,39,44,1|hujpo0,42,42,0|i5cofz,42,42,0|i5cog0,39,44,1|id9sbz,39,44,1|id9sc0,42,42,0|io2r3z,42,42,0|io2r40,39,44,1|ivzuzz,39,44,1|ivzv00,42,42,0|j75sfz,42,42,0|j75sg0,39,44,1|jepxnz,39,44,1|jepxo0,42,42,0|jpvv3z,42,42,0|jpvv40,39,44,1|jyiwbz,39,44,1|jyiwc0,42,42,0|k8lxrz,42,42,0|k8lxs0,39,44,1|kgj1nz,39,44,1|kgj1o0,42,42,0|krc0fz,42,42,0|krc0g0,39,44,1|l0c0bz,39,44,1|l0c0c0,42,42,0|la233z,42,42,0|la2340,39,44,1|lkuwbz,39,44,1|lkuwc0,42,42,0|lq9f3z,42,42,0|lq9f40,39,44,1|m380bz,39,44,1|m380c0,42,42,0|m9pf3z,42,42,0|m9pf40,39,44,1|mly2zz,39,44,1|mly300,42,42,0|mssgfz,42,42,0|mssgg0,39,44,1|n4o5nz,39,44,1|n4o5o0,42,42,0|nbij3z,42,42,0|nbij40,39,44,1|o776zz,39,44,1|o77700,42,42,0|obvsfz,42,42,0|obvsg0,39,44,1|ohn4bz,39,44,1|ohn4c0,39,44,0\",\"America/Rainy_River|,0,169,0|-1353ahk,45,62,0|-qzov41,45,62,0|-qzov40,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-f9ofc1,45,62,0|-f9ofc0,46,63,1|-ek21s1,46,63,1|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|296u7z,45,62,0|296u80,46,63,1|2ijsrz,46,63,1|2ijss0,45,62,0|2rwwvz,45,62,0|2rwww0,46,63,1|319vfz,46,63,1|319vg0,45,62,0|3amzjz,45,62,0|3amzk0,46,63,1|3kcwrz,46,63,1|3kcws0,45,62,0|3td27z,45,62,0|3td280,46,63,1|432zfz,46,63,1|432zg0,45,62,0|4cg3jz,45,62,0|4cg3k0,46,63,1|4lt23z,46,63,1|4lt240,45,62,0|4v667z,45,62,0|4v6680,46,63,1|54j4rz,46,63,1|54j4s0,45,62,0|5dw8vz,45,62,0|5dw8w0,46,63,1|5n97fz,46,63,1|5n97g0,45,62,0|5wmbjz,45,62,0|5wmbk0,46,63,1|65za3z,46,63,1|65za40,45,62,0|6fce7z,45,62,0|6fce80,46,63,1|6p2bfz,46,63,1|6p2bg0,45,62,0|6y2gvz,45,62,0|6y2gw0,46,63,1|77se3z,46,63,1|77se40,45,62,0|7h5i7z,45,62,0|7h5i80,46,63,1|7qigrz,46,63,1|7qigs0,45,62,0|7zvkvz,45,62,0|7zvkw0,46,63,1|898jfz,46,63,1|898jg0,45,62,0|8ilnjz,45,62,0|8ilnk0,46,63,1|8rym3z,46,63,1|8rym40,45,62,0|908u7z,45,62,0|908u80,46,63,1|9aoorz,46,63,1|9aoos0,45,62,0|9iywvz,45,62,0|9iyww0,46,63,1|9trq3z,46,63,1|9trq40,45,62,0|a1ozjz,45,62,0|a1ozk0,46,63,1|achsrz,46,63,1|achss0,45,62,0|akf27z,45,62,0|akf280,46,63,1|av7vfz,46,63,1|av7vg0,45,62,0|b3i3jz,45,62,0|b3i3k0,46,63,1|bdxy3z,46,63,1|bdxy40,45,62,0|bm867z,45,62,0|bm8680,46,63,1|bwo0rz,46,63,1|bwo0s0,45,62,0|c4y8vz,45,62,0|c4y8w0,46,63,1|cfr23z,46,63,1|cfr240,45,62,0|cnobjz,45,62,0|cnobk0,46,63,1|cyh4rz,46,63,1|cyh4s0,45,62,0|d6ee7z,45,62,0|d6ee80,46,63,1|dh77fz,46,63,1|dh77g0,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxkvz,45,62,0|eqxkw0,46,63,1|f1dffz,46,63,1|f1dfg0,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkggrz,46,63,1|fkggs0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,45,62,0|gb3svz,45,62,0|gb3sw0,46,63,1|glwm3z,46,63,1|glwm40,45,62,0|gu6u7z,45,62,0|gu6u80,46,63,1|h4morz,46,63,1|h4mos0,45,62,0|hcwwvz,45,62,0|hcwww0,46,63,1|hncrfz,46,63,1|hncrg0,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fsrz,46,63,1|i6fss0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5vfz,46,63,1|ip5vg0,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jeqbjz,45,62,0|jeqbk0,46,63,1|jqyzfz,46,63,1|jqyzg0,45,62,0|jxge7z,45,62,0|jxge80,46,63,1|k9p23z,46,63,1|k9p240,45,62,0|kg6gvz,45,62,0|kg6gw0,46,63,1|ksf4rz,46,63,1|ksf4s0,45,62,0|kz9i7z,45,62,0|kz9i80,46,63,1|lbi63z,46,63,1|lbi640,45,62,0|lhzkvz,45,62,0|lhzkw0,46,63,1|lu88rz,46,63,1|lu88s0,45,62,0|m0pnjz,45,62,0|m0pnk0,46,63,1|mcybfz,46,63,1|mcybg0,45,62,0|mjfq7z,45,62,0|mjfq80,46,63,1|mvoe3z,46,63,1|mvoe40,45,62,0|n25svz,45,62,0|n25sw0,46,63,1|neegrz,46,63,1|neegs0,45,62,0|nkvvjz,45,62,0|nkvvk0,46,63,1|nx4jfz,46,63,1|nx4jg0,45,62,0|o3ywvz,45,62,0|o3yww0,46,63,1|og7krz,46,63,1|og7ks0,45,62,0|omozjz,45,62,0|omozk0,46,63,1|oyxnfz,46,63,1|oyxng0,45,62,0|p5f27z,45,62,0|p5f280,46,63,1|phnq3z,46,63,1|phnq40,45,62,0|po54vz,45,62,0|po54w0,46,63,1|q0dsrz,46,63,1|q0dss0,45,62,0|q6v7jz,45,62,0|q6v7k0,46,63,1|qj3vfz,46,63,1|qj3vg0,45,62,0|qpy8vz,45,62,0|qpy8w0,46,63,1|r26wrz,46,63,1|r26ws0,45,62,0|r8objz,45,62,0|r8obk0,46,63,1|rkwzfz,46,63,1|rkwzg0,45,62,0|rree7z,45,62,0|rree80,46,63,1|s3n23z,46,63,1|s3n240,45,62,0|sa4gvz,45,62,0|sa4gw0,46,63,1|smd4rz,46,63,1|smd4s0,45,62,0|ssujjz,45,62,0|ssujk0,46,63,1|t537fz,46,63,1|t537g0,45,62,0|tbkm7z,45,62,0|tbkm80,46,63,1|tnta3z,46,63,1|tnta40,45,62,0|tunnjz,45,62,0|tunnk0,46,63,1|u6wbfz,46,63,1|u6wbg0,45,62,0|uddq7z,45,62,0|uddq80,46,63,1|upme3z,46,63,1|upme40,45,62,0|uw3svz,45,62,0|uw3sw0,46,63,1|v8cgrz,46,63,1|v8cgs0,45,62,0|vetvjz,45,62,0|vetvk0,46,63,1|vr2jfz,46,63,1|vr2jg0,45,62,0|vxjy7z,45,62,0|vxjy80,46,63,1|w9sm3z,46,63,1|w9sm40,45,62,0|wgmzjz,45,62,0|wgmzk0,46,63,1|wsvnfz,46,63,1|wsvng0,45,62,0|wzd27z,45,62,0|wzd280,46,63,1|xblq3z,46,63,1|xblq40,45,62,0|xi34vz,45,62,0|xi34w0,46,63,1|xubsrz,46,63,1|xubss0,45,62,0|y0t7jz,45,62,0|y0t7k0,46,63,1|yd1vfz,46,63,1|yd1vg0,45,62,0|yjja7z,45,62,0|yjja80,46,63,1|yvry3z,46,63,1|yvry40,45,62,0|z29cvz,45,62,0|z29cw0,46,63,1|zei0rz,46,63,1|zei0s0,45,62,0\",\"America/Rankin_Inlet|,60,1,0|-6s8lc0,45,62,0|-2g1wo1,45,62,0|-2g1wo0,86,42,1|-26bwo1,86,42,1|-26bwo0,45,62,0|5dw8vz,45,62,0|5dw8w0,46,63,1|5n97fz,46,63,1|5n97g0,45,62,0|5wmbjz,45,62,0|5wmbk0,46,63,1|65za3z,46,63,1|65za40,45,62,0|6fce7z,45,62,0|6fce80,46,63,1|6p2bfz,46,63,1|6p2bg0,45,62,0|6y2gvz,45,62,0|6y2gw0,46,63,1|77se3z,46,63,1|77se40,45,62,0|7h5i7z,45,62,0|7h5i80,46,63,1|7qigrz,46,63,1|7qigs0,45,62,0|7zvkvz,45,62,0|7zvkw0,46,63,1|898jfz,46,63,1|898jg0,45,62,0|8ilnjz,45,62,0|8ilnk0,46,63,1|8rym3z,46,63,1|8rym40,45,62,0|908u7z,45,62,0|908u80,46,63,1|9aoorz,46,63,1|9aoos0,45,62,0|9iywvz,45,62,0|9iyww0,46,63,1|9trq3z,46,63,1|9trq40,45,62,0|a1ozjz,45,62,0|a1ozk0,46,63,1|achsrz,46,63,1|achss0,45,62,0|akf27z,45,62,0|akf280,46,63,1|av7vfz,46,63,1|av7vg0,45,62,0|b3i3jz,45,62,0|b3i3k0,46,63,1|bdxy3z,46,63,1|bdxy40,45,62,0|bm867z,45,62,0|bm8680,46,63,1|bwo0rz,46,63,1|bwo0s0,45,62,0|c4y8vz,45,62,0|c4y8w0,46,63,1|cfr23z,46,63,1|cfr240,45,62,0|cnobjz,45,62,0|cnobk0,46,63,1|cyh4rz,46,63,1|cyh4s0,45,62,0|d6ee7z,45,62,0|d6ee80,46,63,1|dh77fz,46,63,1|dh77g0,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxkvz,45,62,0|eqxkw0,46,63,1|f1dffz,46,63,1|f1dfg0,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkggrz,46,63,1|fkggs0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,49,63,0|gb3svz,49,63,0|gb3sw0,46,63,1|glwm3z,46,63,1|glwm40,45,62,0|gu6u7z,45,62,0|gu6u80,46,63,1|h4morz,46,63,1|h4mos0,45,62,0|hcwwvz,45,62,0|hcwww0,46,63,1|hncrfz,46,63,1|hncrg0,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fsrz,46,63,1|i6fss0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5vfz,46,63,1|ip5vg0,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jeqbjz,45,62,0|jeqbk0,46,63,1|jqyzfz,46,63,1|jqyzg0,45,62,0|jxge7z,45,62,0|jxge80,46,63,1|k9p23z,46,63,1|k9p240,45,62,0|kg6gvz,45,62,0|kg6gw0,46,63,1|ksf4rz,46,63,1|ksf4s0,45,62,0|kz9i7z,45,62,0|kz9i80,46,63,1|lbi63z,46,63,1|lbi640,45,62,0|lhzkvz,45,62,0|lhzkw0,46,63,1|lu88rz,46,63,1|lu88s0,45,62,0|m0pnjz,45,62,0|m0pnk0,46,63,1|mcybfz,46,63,1|mcybg0,45,62,0|mjfq7z,45,62,0|mjfq80,46,63,1|mvoe3z,46,63,1|mvoe40,45,62,0|n25svz,45,62,0|n25sw0,46,63,1|neegrz,46,63,1|neegs0,45,62,0|nkvvjz,45,62,0|nkvvk0,46,63,1|nx4jfz,46,63,1|nx4jg0,45,62,0|o3ywvz,45,62,0|o3yww0,46,63,1|og7krz,46,63,1|og7ks0,45,62,0|omozjz,45,62,0|omozk0,46,63,1|oyxnfz,46,63,1|oyxng0,45,62,0|p5f27z,45,62,0|p5f280,46,63,1|phnq3z,46,63,1|phnq40,45,62,0|po54vz,45,62,0|po54w0,46,63,1|q0dsrz,46,63,1|q0dss0,45,62,0|q6v7jz,45,62,0|q6v7k0,46,63,1|qj3vfz,46,63,1|qj3vg0,45,62,0|qpy8vz,45,62,0|qpy8w0,46,63,1|r26wrz,46,63,1|r26ws0,45,62,0|r8objz,45,62,0|r8obk0,46,63,1|rkwzfz,46,63,1|rkwzg0,45,62,0|rree7z,45,62,0|rree80,46,63,1|s3n23z,46,63,1|s3n240,45,62,0|sa4gvz,45,62,0|sa4gw0,46,63,1|smd4rz,46,63,1|smd4s0,45,62,0|ssujjz,45,62,0|ssujk0,46,63,1|t537fz,46,63,1|t537g0,45,62,0|tbkm7z,45,62,0|tbkm80,46,63,1|tnta3z,46,63,1|tnta40,45,62,0|tunnjz,45,62,0|tunnk0,46,63,1|u6wbfz,46,63,1|u6wbg0,45,62,0|uddq7z,45,62,0|uddq80,46,63,1|upme3z,46,63,1|upme40,45,62,0|uw3svz,45,62,0|uw3sw0,46,63,1|v8cgrz,46,63,1|v8cgs0,45,62,0|vetvjz,45,62,0|vetvk0,46,63,1|vr2jfz,46,63,1|vr2jg0,45,62,0|vxjy7z,45,62,0|vxjy80,46,63,1|w9sm3z,46,63,1|w9sm40,45,62,0|wgmzjz,45,62,0|wgmzk0,46,63,1|wsvnfz,46,63,1|wsvng0,45,62,0|wzd27z,45,62,0|wzd280,46,63,1|xblq3z,46,63,1|xblq40,45,62,0|xi34vz,45,62,0|xi34w0,46,63,1|xubsrz,46,63,1|xubss0,45,62,0|y0t7jz,45,62,0|y0t7k0,46,63,1|yd1vfz,46,63,1|yd1vg0,45,62,0|yjja7z,45,62,0|yjja80,46,63,1|yvry3z,46,63,1|yvry40,45,62,0|z29cvz,45,62,0|z29cw0,46,63,1|zei0rz,46,63,1|zei0s0,45,62,0\",\"America/Recife|,0,170,0|-t85ljc,39,44,0|-jylag1,39,44,0|-jylag0,40,45,1|-jpb141,40,45,1|-jpb140,39,44,0|-jfsd01,39,44,0|-jfsd00,40,45,1|-j6j3s1,40,45,1|-j6j3s0,39,44,0|-ahcyc1,39,44,0|-ahcyc0,40,45,1|-aad3o1,40,45,1|-aad3o0,39,44,0|-9yl101,39,44,0|-9yl100,40,45,1|-9sd141,40,45,1|-9sd140,39,44,0|-9ft3o1,39,44,0|-9ft3o0,40,45,1|-99j941,40,45,1|-99j940,39,44,0|-8wzbo1,39,44,0|-8wzbo0,40,45,1|-8scqg1,40,45,1|-8scqg0,39,44,0|-35xmc1,39,44,0|-35xmc0,40,45,1|-31nzs1,40,45,1|-31nzs0,39,44,0|-2kdp01,39,44,0|-2kdp00,40,45,1|-2hcig1,40,45,1|-2hcig0,39,44,0|-24qqc1,39,44,0|-24qqc0,40,45,1|-204541,40,45,1|-204540,39,44,0|-1nid01,39,44,0|-1nid00,40,45,1|-1hc7s1,40,45,1|-1hc7s0,39,44,0|-14qfo1,39,44,0|-14qfo0,40,45,1|-yifs1,40,45,1|-yifs0,39,44,0|89jcbz,39,44,0|89jcc0,40,45,1|8gdk7z,40,45,1|8gdk80,39,44,0|8rwgbz,39,44,0|8rwgc0,40,45,1|8xns7z,40,45,1|8xns80,39,44,0|9aodnz,39,44,0|9aodo0,40,45,1|9g2qvz,40,45,1|9g2qw0,39,44,0|9t1hnz,39,44,0|9t1ho0,40,45,1|9yfuvz,40,45,1|9yfuw0,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|ahvuvz,40,45,1|ahvuw0,39,44,0|fj0azz,39,44,0|fj0b00,40,45,1|fqkg7z,40,45,1|fqkg80,39,44,0|g23cbz,39,44,0|g23cc0,40,45,1|g2g87z,40,45,1|g2g880,39,44,0|gl6dnz,39,44,0|gl6do0,40,45,1|grnmvz,40,45,1|grnmw0,39,44,0\",\"America/Regina|,0,171,0|-xkq9yc,50,66,0|-qzosc1,50,66,0|-qzosc0,52,62,1|-qplwg1,52,62,1|-qplwg0,50,66,0|-kp78k1,50,66,0|-kp78k0,52,62,1|-kha4o1,52,62,1|-kha4o0,50,66,0|-k6h5w1,50,66,0|-k6h5w0,52,62,1|-jyk201,52,62,1|-jyk200,50,66,0|-jnr381,50,66,0|-jnr380,52,62,1|-jftzc1,52,62,1|-jftzc0,50,66,0|-j4o1w1,50,66,0|-j4o1w0,52,62,1|-ix3wo1,52,62,1|-ix3wo0,50,66,0|-ilxz81,50,66,0|-ilxz80,52,62,1|-ie0vc1,52,62,1|-ie0vc0,50,66,0|-h2un81,50,66,0|-h2un80,52,62,1|-gthoo1,52,62,1|-gthoo0,50,66,0|-gk4kk1,50,66,0|-gk4kk0,52,62,1|-gb4ko1,52,62,1|-gb4ko0,50,66,0|-g1ehw1,50,66,0|-g1ehw0,52,62,1|-fs1jc1,52,62,1|-fs1jc0,50,66,0|-fibgk1,50,66,0|-fibgk0,52,62,1|-f8yi01,52,62,1|-f8yi00,50,66,0|-ezldw1,50,66,0|-ezldw0,52,62,1|-eq8fc1,52,62,1|-eq8fc0,50,66,0|-ek1z01,50,66,0|-ek1z00,58,62,1|-cq2tg1,58,62,1|-cq2tg0,59,62,1|-cnoxs1,59,62,1|-cnoxs0,50,66,0|-cdlwc1,50,66,0|-cdlwc0,52,62,1|-c48xs1,52,62,1|-c48xs0,50,66,0|-bu5wc1,50,66,0|-bu5wc0,52,62,1|-bm8sg1,52,62,1|-bm8sg0,50,66,0|-bbfto1,50,66,0|-bbfto0,52,62,1|-b3ips1,52,62,1|-b3ips0,50,66,0|-aspr01,50,66,0|-aspr00,52,62,1|-aksn41,52,62,1|-aksn40,50,66,0|-a9mpo1,50,66,0|-a9mpo0,52,62,1|-a22kg1,52,62,1|-a22kg0,50,66,0|-9qwn01,50,66,0|-9qwn00,52,62,1|-9izj41,52,62,1|-9izj40,50,66,0|-986kc1,50,66,0|-986kc0,52,62,1|-909gg1,52,62,1|-909gg0,50,66,0|-8pgho1,50,66,0|-8pgho0,52,62,1|-8hjds1,52,62,1|-8hjds0,50,66,0|-86qf01,50,66,0|-86qf00,52,62,1|-7ytb41,52,62,1|-7ytb40,50,66,0|-7o0cc1,50,66,0|-7o0cc0,52,62,1|-7g38g1,52,62,1|-7g38g0,50,66,0|-74xb01,50,66,0|-74xb00,52,62,1|-6x0741,52,62,1|-6x0740,50,66,0|-6m78c1,50,66,0|-6m78c0,52,62,1|-6ea4g1,52,62,1|-6ea4g0,50,66,0|-5kr301,50,66,0|-5kr300,52,62,1|-5be4g1,52,62,1|-5be4g0,50,66,0|-5210c1,50,66,0|-5210c0,45,62,0\",\"America/Resolute|,60,1,0|-bnp9c0,45,62,0|-2g1wo1,45,62,0|-2g1wo0,86,42,1|-26bwo1,86,42,1|-26bwo0,45,62,0|5dw8vz,45,62,0|5dw8w0,46,63,1|5n97fz,46,63,1|5n97g0,45,62,0|5wmbjz,45,62,0|5wmbk0,46,63,1|65za3z,46,63,1|65za40,45,62,0|6fce7z,45,62,0|6fce80,46,63,1|6p2bfz,46,63,1|6p2bg0,45,62,0|6y2gvz,45,62,0|6y2gw0,46,63,1|77se3z,46,63,1|77se40,45,62,0|7h5i7z,45,62,0|7h5i80,46,63,1|7qigrz,46,63,1|7qigs0,45,62,0|7zvkvz,45,62,0|7zvkw0,46,63,1|898jfz,46,63,1|898jg0,45,62,0|8ilnjz,45,62,0|8ilnk0,46,63,1|8rym3z,46,63,1|8rym40,45,62,0|908u7z,45,62,0|908u80,46,63,1|9aoorz,46,63,1|9aoos0,45,62,0|9iywvz,45,62,0|9iyww0,46,63,1|9trq3z,46,63,1|9trq40,45,62,0|a1ozjz,45,62,0|a1ozk0,46,63,1|achsrz,46,63,1|achss0,45,62,0|akf27z,45,62,0|akf280,46,63,1|av7vfz,46,63,1|av7vg0,45,62,0|b3i3jz,45,62,0|b3i3k0,46,63,1|bdxy3z,46,63,1|bdxy40,45,62,0|bm867z,45,62,0|bm8680,46,63,1|bwo0rz,46,63,1|bwo0s0,45,62,0|c4y8vz,45,62,0|c4y8w0,46,63,1|cfr23z,46,63,1|cfr240,45,62,0|cnobjz,45,62,0|cnobk0,46,63,1|cyh4rz,46,63,1|cyh4s0,45,62,0|d6ee7z,45,62,0|d6ee80,46,63,1|dh77fz,46,63,1|dh77g0,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxa3z,46,63,1|dzxa40,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|eincrz,46,63,1|eincs0,45,62,0|eqxkvz,45,62,0|eqxkw0,46,63,1|f1dffz,46,63,1|f1dfg0,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkggrz,46,63,1|fkggs0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36jfz,46,63,1|g36jg0,49,63,0|gb3svz,49,63,0|gb3sw0,46,63,1|glwm3z,46,63,1|glwm40,45,62,0|gu6u7z,45,62,0|gu6u80,46,63,1|h4morz,46,63,1|h4mos0,45,62,0|hcwwvz,45,62,0|hcwww0,46,63,1|hncrfz,46,63,1|hncrg0,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fsrz,46,63,1|i6fss0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5vfz,46,63,1|ip5vg0,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,49,63,0|jeqbjz,49,63,0|jeqbk0,46,63,1|jqyzfz,46,63,1|jqyzg0,45,62,0|jxge7z,45,62,0|jxge80,46,63,1|k9p23z,46,63,1|k9p240,45,62,0|kg6gvz,45,62,0|kg6gw0,46,63,1|ksf4rz,46,63,1|ksf4s0,45,62,0|kz9i7z,45,62,0|kz9i80,46,63,1|lbi63z,46,63,1|lbi640,45,62,0|lhzkvz,45,62,0|lhzkw0,46,63,1|lu88rz,46,63,1|lu88s0,45,62,0|m0pnjz,45,62,0|m0pnk0,46,63,1|mcybfz,46,63,1|mcybg0,45,62,0|mjfq7z,45,62,0|mjfq80,46,63,1|mvoe3z,46,63,1|mvoe40,45,62,0|n25svz,45,62,0|n25sw0,46,63,1|neegrz,46,63,1|neegs0,45,62,0|nkvvjz,45,62,0|nkvvk0,46,63,1|nx4jfz,46,63,1|nx4jg0,45,62,0|o3ywvz,45,62,0|o3yww0,46,63,1|og7krz,46,63,1|og7ks0,45,62,0|omozjz,45,62,0|omozk0,46,63,1|oyxnfz,46,63,1|oyxng0,45,62,0|p5f27z,45,62,0|p5f280,46,63,1|phnq3z,46,63,1|phnq40,45,62,0|po54vz,45,62,0|po54w0,46,63,1|q0dsrz,46,63,1|q0dss0,45,62,0|q6v7jz,45,62,0|q6v7k0,46,63,1|qj3vfz,46,63,1|qj3vg0,45,62,0|qpy8vz,45,62,0|qpy8w0,46,63,1|r26wrz,46,63,1|r26ws0,45,62,0|r8objz,45,62,0|r8obk0,46,63,1|rkwzfz,46,63,1|rkwzg0,45,62,0|rree7z,45,62,0|rree80,46,63,1|s3n23z,46,63,1|s3n240,45,62,0|sa4gvz,45,62,0|sa4gw0,46,63,1|smd4rz,46,63,1|smd4s0,45,62,0|ssujjz,45,62,0|ssujk0,46,63,1|t537fz,46,63,1|t537g0,45,62,0|tbkm7z,45,62,0|tbkm80,46,63,1|tnta3z,46,63,1|tnta40,45,62,0|tunnjz,45,62,0|tunnk0,46,63,1|u6wbfz,46,63,1|u6wbg0,45,62,0|uddq7z,45,62,0|uddq80,46,63,1|upme3z,46,63,1|upme40,45,62,0|uw3svz,45,62,0|uw3sw0,46,63,1|v8cgrz,46,63,1|v8cgs0,45,62,0|vetvjz,45,62,0|vetvk0,46,63,1|vr2jfz,46,63,1|vr2jg0,45,62,0|vxjy7z,45,62,0|vxjy80,46,63,1|w9sm3z,46,63,1|w9sm40,45,62,0|wgmzjz,45,62,0|wgmzk0,46,63,1|wsvnfz,46,63,1|wsvng0,45,62,0|wzd27z,45,62,0|wzd280,46,63,1|xblq3z,46,63,1|xblq40,45,62,0|xi34vz,45,62,0|xi34w0,46,63,1|xubsrz,46,63,1|xubss0,45,62,0|y0t7jz,45,62,0|y0t7k0,46,63,1|yd1vfz,46,63,1|yd1vg0,45,62,0|yjja7z,45,62,0|yjja80,46,63,1|yvry3z,46,63,1|yvry40,45,62,0|z29cvz,45,62,0|z29cw0,46,63,1|zei0rz,46,63,1|zei0s0,45,62,0\",\"America/Rio_Branco|,0,172,0|-t85fg0,56,63,0|-jyl4w1,56,63,0|-jyl4w0,42,42,1|-jpavk1,42,42,1|-jpavk0,56,63,0|-jfs7g1,56,63,0|-jfs7g0,42,42,1|-j6iy81,42,42,1|-j6iy80,56,63,0|-ahcss1,56,63,0|-ahcss0,42,42,1|-aacy41,42,42,1|-aacy40,56,63,0|-9ykvg1,56,63,0|-9ykvg0,42,42,1|-9scvk1,42,42,1|-9scvk0,56,63,0|-9fsy41,56,63,0|-9fsy40,42,42,1|-99j3k1,42,42,1|-99j3k0,56,63,0|-8wz641,56,63,0|-8wz640,42,42,1|-8sckw1,42,42,1|-8sckw0,56,63,0|-35xgs1,56,63,0|-35xgs0,42,42,1|-31nu81,42,42,1|-31nu80,56,63,0|-2kdjg1,56,63,0|-2kdjg0,42,42,1|-2hccw1,42,42,1|-2hccw0,56,63,0|-24qks1,56,63,0|-24qks0,42,42,1|-203zk1,42,42,1|-203zk0,56,63,0|-1ni7g1,56,63,0|-1ni7g0,42,42,1|-1hc281,42,42,1|-1hc280,56,63,0|-14qa41,56,63,0|-14qa40,42,42,1|-yia81,42,42,1|-yia80,56,63,0|89jhvz,56,63,0|89jhw0,42,42,1|8gdprz,42,42,1|8gdps0,56,63,0|8rwlvz,56,63,0|8rwlw0,42,42,1|8xnxrz,42,42,1|8xnxs0,56,63,0|9aoj7z,56,63,0|9aoj80,42,42,1|9g2wfz,42,42,1|9g2wg0,56,63,0|k2yb7z,56,63,0|k2yb80,42,42,0|mw14fz,42,42,0|mw14g0,56,63,0\",\"America/Santarem|,0,173,0|-t85hvc,42,42,0|-jyl7o1,42,42,0|-jyl7o0,39,44,1|-jpayc1,39,44,1|-jpayc0,42,42,0|-jfsa81,42,42,0|-jfsa80,39,44,1|-j6j101,39,44,1|-j6j100,42,42,0|-ahcvk1,42,42,0|-ahcvk0,39,44,1|-aad0w1,39,44,1|-aad0w0,42,42,0|-9yky81,42,42,0|-9yky80,39,44,1|-9scyc1,39,44,1|-9scyc0,42,42,0|-9ft0w1,42,42,0|-9ft0w0,39,44,1|-99j6c1,39,44,1|-99j6c0,42,42,0|-8wz8w1,42,42,0|-8wz8w0,39,44,1|-8scno1,39,44,1|-8scno0,42,42,0|-35xjk1,42,42,0|-35xjk0,39,44,1|-31nx01,39,44,1|-31nx00,42,42,0|-2kdm81,42,42,0|-2kdm80,39,44,1|-2hcfo1,39,44,1|-2hcfo0,42,42,0|-24qnk1,42,42,0|-24qnk0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1nia81,42,42,0|-1nia80,39,44,1|-1hc501,39,44,1|-1hc500,42,42,0|-14qcw1,42,42,0|-14qcw0,39,44,1|-yid01,39,44,1|-yid00,42,42,0|89jf3z,42,42,0|89jf40,39,44,1|8gdmzz,39,44,1|8gdn00,42,42,0|8rwj3z,42,42,0|8rwj40,39,44,1|8xnuzz,39,44,1|8xnv00,42,42,0|9aogfz,42,42,0|9aogg0,39,44,1|9g2tnz,39,44,1|9g2to0,42,42,0|k2y8fz,42,42,0|k2y8g0,39,44,0\",\"America/Santiago|,0,168,0|-15r0w8q,85,168,0|-vauawr,85,168,0|-vauawq,56,63,0|-rx8i41,56,63,0|-rx8i40,85,168,0|-qs16wr,85,168,0|-qs16wq,42,42,0|-qcwsw1,42,42,0|-qcwsw0,85,168,0|-m3etkr,85,168,0|-m3etkq,42,42,1|-lsgfk1,42,42,1|-lsgfk0,56,63,0|-lkl0s1,56,63,0|-lkl0s0,42,42,1|-l9oi81,42,42,1|-l9oi80,56,63,0|-l1t3g1,56,63,0|-l1t3g0,42,42,1|-kqwkw1,42,42,1|-kqwkw0,56,63,0|-kj1641,56,63,0|-kj1640,42,42,1|-k84nk1,42,42,1|-k84nk0,56,63,0|-k098s1,56,63,0|-k098s0,42,42,1|-jpavk1,42,42,1|-jpavk0,56,63,0|-jhfgs1,56,63,0|-jhfgs0,42,42,0|-eeay81,42,42,0|-eeay80,56,63,0|-eb5ws1,56,63,0|-eb5ws0,42,42,0|-c8vww1,42,42,0|-c8vww0,39,44,1|-c6f3o1,39,44,1|-c6f3o0,42,42,0|-bvifk1,42,42,0|-bvifk0,56,63,0|-bsvzk1,56,63,0|-bsvzk0,42,42,0|-lsvk1,42,42,0|-lsvk0,39,44,1|-e8qc1,39,44,1|-e8qc0,42,42,0|-1zww1,42,42,0|-1zww0,39,44,1|4hcbz,39,44,1|4hcc0,42,42,0|ekdrz,42,42,0|ekds0,39,44,1|mhhnz,39,44,1|mhho0,42,42,0|xagfz,42,42,0|xagg0,39,44,1|157kbz,39,44,1|157kc0,42,42,0|1gdhrz,42,42,0|1gdhs0,39,44,1|1nxmzz,39,44,1|1nxn00,42,42,0|1ydn3z,42,42,0|1ydn40,39,44,1|26npnz,39,44,1|26npo0,42,42,0|2htn3z,42,42,0|2htn40,39,44,1|2pdsbz,39,44,1|2pdsc0,42,42,0|30jprz,42,42,0|30jps0,39,44,1|38gtnz,39,44,1|38gto0,42,42,0|3j9sfz,42,42,0|3j9sg0,39,44,1|3r6wbz,39,44,1|3r6wc0,42,42,0|41zv3z,42,42,0|41zv40,39,44,1|49wyzz,39,44,1|49wz00,42,42,0|4l2wfz,42,42,0|4l2wg0,39,44,1|4sn1nz,39,44,1|4sn1o0,42,42,0|53sz3z,42,42,0|53sz40,39,44,1|5bd4bz,39,44,1|5bd4c0,42,42,0|5mj1rz,42,42,0|5mj1s0,39,44,1|5ug5nz,39,44,1|5ug5o0,42,42,0|6594fz,42,42,0|6594g0,39,44,1|6d68bz,39,44,1|6d68c0,42,42,0|6nz73z,42,42,0|6nz740,39,44,1|6vwazz,39,44,1|6vwb00,42,42,0|76p9rz,42,42,0|76p9s0,39,44,1|7emdnz,39,44,1|7emdo0,42,42,0|7psb3z,42,42,0|7psb40,39,44,1|7xcgbz,39,44,1|7xcgc0,42,42,0|88idrz,42,42,0|88ids0,39,44,1|8g2izz,39,44,1|8g2j00,42,42,0|8r8gfz,42,42,0|8r8gg0,39,44,1|90lezz,39,44,1|90lf00,42,42,0|99yj3z,42,42,0|99yj40,39,44,1|9hvmzz,39,44,1|9hvn00,42,42,0|9solrz,42,42,0|9sols0,39,44,1|a0lpnz,39,44,1|a0lpo0,42,42,0|abrn3z,42,42,0|abrn40,39,44,1|ajbsbz,39,44,1|ajbsc0,42,42,0|at1v3z,42,42,0|at1v40,39,44,1|b21uzz,39,44,1|b21v00,42,42,0|bd7sfz,42,42,0|bd7sg0,39,44,1|bl4wbz,39,44,1|bl4wc0,42,42,0|bvxv3z,42,42,0|bvxv40,39,44,1|c3uyzz,39,44,1|c3uz00,42,42,0|cenxrz,42,42,0|cenxs0,39,44,1|cml1nz,39,44,1|cml1o0,42,42,0|cxe0fz,42,42,0|cxe0g0,39,44,1|d5b4bz,39,44,1|d5b4c0,42,42,0|dgh1rz,42,42,0|dgh1s0,39,44,1|do16zz,39,44,1|do1700,42,42,0|dz74fz,42,42,0|dz74g0,39,44,1|e7u5nz,39,44,1|e7u5o0,42,42,0|ehx73z,42,42,0|ehx740,39,44,1|epuazz,39,44,1|epub00,42,42,0|ezxcfz,42,42,0|ezxcg0,39,44,1|f9n9nz,39,44,1|f9n9o0,42,42,0|fjdcfz,42,42,0|fjdcg0,39,44,1|fragbz,39,44,1|fragc0,42,42,0|g2gdrz,42,42,0|g2gds0,39,44,1|ga0izz,39,44,1|ga0j00,42,42,0|gl6gfz,42,42,0|gl6gg0,39,44,1|gsqlnz,39,44,1|gsqlo0,42,42,0|h3wj3z,42,42,0|h3wj40,39,44,1|hbgobz,39,44,1|hbgoc0,42,42,0|hmmlrz,42,42,0|hmmls0,39,44,1|hujpnz,39,44,1|hujpo0,42,42,0|i5cofz,42,42,0|i5cog0,39,44,1|id9sbz,39,44,1|id9sc0,42,42,0|io2r3z,42,42,0|io2r40,39,44,1|ivzuzz,39,44,1|ivzv00,42,42,0|j75sfz,42,42,0|j75sg0,39,44,1|jepxnz,39,44,1|jepxo0,42,42,0|jpvv3z,42,42,0|jpvv40,39,44,1|jyiwbz,39,44,1|jyiwc0,42,42,0|k8lxrz,42,42,0|k8lxs0,39,44,1|kgj1nz,39,44,1|kgj1o0,42,42,0|krc0fz,42,42,0|krc0g0,39,44,1|l0c0bz,39,44,1|l0c0c0,42,42,0|la233z,42,42,0|la2340,39,44,1|lkuwbz,39,44,1|lkuwc0,42,42,0|lq9f3z,42,42,0|lq9f40,39,44,1|m380bz,39,44,1|m380c0,42,42,0|m9pf3z,42,42,0|m9pf40,39,44,1|mly2zz,39,44,1|mly300,42,42,0|mssgfz,42,42,0|mssgg0,39,44,1|n4o5nz,39,44,1|n4o5o0,42,42,0|nbij3z,42,42,0|nbij40,39,44,1|o776zz,39,44,1|o77700,42,42,0|obvsfz,42,42,0|obvsg0,39,44,1|opx9nz,39,44,1|opx9o0,42,42,0|oulv3z,42,42,0|oulv40,39,44,1|p8ncbz,39,44,1|p8ncc0,42,42,0|pdbxrz,42,42,0|pdbxs0,39,44,1|ppklnz,39,44,1|ppklo0,42,42,0|pxhv3z,42,42,0|pxhv40,39,44,1|q8aobz,39,44,1|q8aoc0,42,42,0|qg7xrz,42,42,0|qg7xs0,39,44,1|qr0qzz,39,44,1|qr0r00,42,42,0|qyy0fz,42,42,0|qyy0g0,39,44,1|r9qtnz,39,44,1|r9qto0,42,42,0|rho33z,42,42,0|rho340,39,44,1|rsgwbz,39,44,1|rsgwc0,42,42,0|s0e5rz,42,42,0|s0e5s0,39,44,1|sbjxnz,39,44,1|sbjxo0,42,42,0|sjh73z,42,42,0|sjh740,39,44,1|sua0bz,39,44,1|sua0c0,42,42,0|t279rz,42,42,0|t279s0,39,44,1|td02zz,39,44,1|td0300,42,42,0|tkxcfz,42,42,0|tkxcg0,39,44,1|tvq5nz,39,44,1|tvq5o0,42,42,0|u3nf3z,42,42,0|u3nf40,39,44,1|ueg8bz,39,44,1|ueg8c0,42,42,0|umdhrz,42,42,0|umdhs0,39,44,1|uxj9nz,39,44,1|uxj9o0,42,42,0|v53kfz,42,42,0|v53kg0,39,44,1|vg9cbz,39,44,1|vg9cc0,42,42,0|vo6lrz,42,42,0|vo6ls0,39,44,1|vyzezz,39,44,1|vyzf00,42,42,0|w6wofz,42,42,0|w6wog0,39,44,1|whphnz,39,44,1|whpho0,42,42,0|wpmr3z,42,42,0|wpmr40,39,44,1|x0fkbz,39,44,1|x0fkc0,42,42,0|x8ctrz,42,42,0|x8cts0,39,44,1|xj5mzz,39,44,1|xj5n00,42,42,0|xr2wfz,42,42,0|xr2wg0,39,44,1|y28obz,39,44,1|y28oc0,42,42,0|y9sz3z,42,42,0|y9sz40,39,44,1|ykyqzz,39,44,1|ykyr00,42,42,0|ysw0fz,42,42,0|ysw0g0,39,44,1|z3otnz,39,44,1|z3oto0,42,42,0|zbm33z,42,42,0|zbm340,39,44,1\",\"America/Santo_Domingo|,0,174,0|-15r0we0,87,175,0|-j6hz1d,87,175,0|-j6hz1c,49,63,0|-1nlws1,49,63,0|-1nlws0,62,42,1|-1hdww1,62,42,1|-1hdww0,49,63,0|-3fos1,49,63,0|-3fos0,43,59,1|2mshz,43,59,1|2msi0,49,63,0|fadvz,49,63,0|fadw0,43,59,1|jrghz,43,59,1|jrgi0,49,63,0|ydf7z,49,63,0|ydf80,43,59,1|12l8hz,43,59,1|12l8i0,49,63,0|1h3hvz,49,63,0|1h3hw0,43,59,1|1lf0hz,43,59,1|1lf0i0,49,63,0|1ztkjz,49,63,0|1ztkk0,43,59,1|246xtz,43,59,1|246xu0,49,63,0|2ijn7z,49,63,0|2ijn80,32,42,0|g36gnz,32,42,0|g36go0,49,63,0|g4z9zz,49,63,0|g4za00,32,42,0\",\"America/Sao_Paulo|,0,176,0|-t85jd8,39,44,0|-jylag1,39,44,0|-jylag0,40,45,1|-jpb141,40,45,1|-jpb140,39,44,0|-jfsd01,39,44,0|-jfsd00,40,45,1|-j6j3s1,40,45,1|-j6j3s0,39,44,0|-ahcyc1,39,44,0|-ahcyc0,40,45,1|-aad3o1,40,45,1|-aad3o0,39,44,0|-9yl101,39,44,0|-9yl100,40,45,1|-9sd141,40,45,1|-9sd140,39,44,0|-9ft3o1,39,44,0|-9ft3o0,40,45,1|-99j941,40,45,1|-99j940,39,44,0|-8wzbo1,39,44,0|-8wzbo0,40,45,1|-8scqg1,40,45,1|-8scqg0,39,44,0|-38cno1,39,44,0|-38cno0,40,45,1|-31nzs1,40,45,1|-31nzs0,39,44,0|-2kdp01,39,44,0|-2kdp00,40,45,1|-2hcig1,40,45,1|-2hcig0,39,44,0|-24qqc1,39,44,0|-24qqc0,40,45,1|-204541,40,45,1|-204540,39,44,0|-1nid01,39,44,0|-1nid00,40,45,1|-1hc7s1,40,45,1|-1hc7s0,39,44,0|-14qfo1,39,44,0|-14qfo0,40,45,1|-yifs1,40,45,1|-yifs0,39,44,0|89jcbz,39,44,0|89jcc0,40,45,1|8gdk7z,40,45,1|8gdk80,39,44,0|8rwgbz,39,44,0|8rwgc0,40,45,1|8xns7z,40,45,1|8xns80,39,44,0|9aodnz,39,44,0|9aodo0,40,45,1|9g2qvz,40,45,1|9g2qw0,39,44,0|9t1hnz,39,44,0|9t1ho0,40,45,1|9yfuvz,40,45,1|9yfuw0,39,44,0|abrkbz,39,44,0|abrkc0,40,45,1|ahvuvz,40,45,1|ahvuw0,39,44,0|auulnz,39,44,0|auulo0,40,45,1|b0yw7z,40,45,1|b0yw80,39,44,0|bdkobz,39,44,0|bdkoc0,40,45,1|bjc07z,40,45,1|bjc080,39,44,0|bwnpnz,39,44,0|bwnpo0,40,45,1|c1p47z,40,45,1|c1p480,39,44,0|cf0tnz,39,44,0|cf0to0,40,45,1|cli2vz,40,45,1|cli2w0,39,44,0|cxqwbz,39,44,0|cxqwc0,40,45,1|d485jz,40,45,1|d485k0,39,44,0|dggyzz,39,44,0|dggz00,40,45,1|dml9jz,40,45,1|dml9k0,39,44,0|dyu2zz,39,44,0|dyu300,40,45,1|e5oavz,40,45,1|e5oaw0,39,44,0|ehm0bz,39,44,0|ehm0c0,40,45,1|ep4avz,40,45,1|ep4aw0,39,44,0|f0n6zz,39,44,0|f0n700,40,45,1|f7hevz,40,45,1|f7hew0,39,44,0|fj0azz,39,44,0|fj0b00,40,45,1|fqkg7z,40,45,1|fqkg80,39,44,0|g23cbz,39,44,0|g23cc0,40,45,1|g8xk7z,40,45,1|g8xk80,39,44,0|gl6dnz,39,44,0|gl6do0,40,45,1|grnmvz,40,45,1|grnmw0,39,44,0|h4zcbz,39,44,0|h4zcc0,40,45,1|hadpjz,40,45,1|hadpk0,39,44,0|hmzhnz,39,44,0|hmzho0,40,45,1|ht3s7z,40,45,1|ht3s80,39,44,0|i6j6zz,39,44,0|i6j700,40,45,1|ic6tjz,40,45,1|ic6tk0,39,44,0|iofmzz,39,44,0|iofn00,40,45,1|iuww7z,40,45,1|iuww80,39,44,0|j88lnz,39,44,0|j88lo0,40,45,1|jdzxjz,40,45,1|jdzxk0,39,44,0|jpvsbz,39,44,0|jpvsc0,40,45,1|jwd1jz,40,45,1|jwd1k0,39,44,0|k8ytnz,39,44,0|k8yto0,40,45,1|kf347z,40,45,1|kf3480,39,44,0|krowbz,39,44,0|krowc0,40,45,1|ky65jz,40,45,1|ky65k0,39,44,0|laeyzz,39,44,0|laez00,40,45,1|lgw87z,40,45,1|lgw880,39,44,0|lt51nz,39,44,0|lt51o0,40,45,1|lzz9jz,40,45,1|lzz9k0,39,44,0|mc82zz,39,44,0|mc8300,40,45,1|micdjz,40,45,1|micdk0,39,44,0|muy5nz,39,44,0|muy5o0,40,45,1|n12g7z,40,45,1|n12g80,39,44,0|ndo8bz,39,44,0|ndo8c0,40,45,1|nk5hjz,40,45,1|nk5hk0,39,44,0|nweazz,39,44,0|nweb00,40,45,1|o2vk7z,40,45,1|o2vk80,39,44,0|of4dnz,39,44,0|of4do0,40,45,1|ollmvz,40,45,1|ollmw0,39,44,0|oxugbz,39,44,0|oxugc0,40,45,1|p4bpjz,40,45,1|p4bpk0,39,44,0|phnezz,39,44,0|phnf00,40,45,1|pn1s7z,40,45,1|pn1s80,39,44,0\",\"America/Scoresbysund|,0,177,0|-rvurxk,40,45,0|5ct1rz,40,45,0|5ct1s0,13,15,1|5lt4fz,13,15,1|5lt4g0,40,45,0|5v607z,40,45,0|5v6080,17,1,1|64iyrz,17,1,1|64iys0,13,15,0|6dw03z,13,15,0|6dw040,17,1,1|6n91fz,17,1,1|6n91g0,13,15,0|6wm2rz,13,15,0|6wm2s0,17,1,1|75z43z,17,1,1|75z440,13,15,0|7fc5fz,13,15,0|7fc5g0,17,1,1|7p25fz,17,1,1|7p25g0,13,15,0|7yf6rz,13,15,0|7yf6s0,17,1,1|87s83z,17,1,1|87s840,13,15,0|8h59fz,13,15,0|8h59g0,17,1,1|8qiarz,17,1,1|8qias0,13,15,0|8zvc3z,13,15,0|8zvc40,17,1,1|998dfz,17,1,1|998dg0,13,15,0|9ilerz,13,15,0|9iles0,17,1,1|9ryg3z,17,1,1|9ryg40,13,15,0|a1bhfz,13,15,0|a1bhg0,17,1,1|aaoirz,17,1,1|aaois0,13,15,0|ak1k3z,13,15,0|ak1k40,17,1,1|atrk3z,17,1,1|atrk40,13,15,0|b34lfz,13,15,0|b34lg0,17,1,1|bchmrz,17,1,1|bchms0,13,15,0|bluo3z,13,15,0|bluo40,17,1,1|bv7pfz,17,1,1|bv7pg0,13,15,0|c4kqrz,13,15,0|c4kqs0,17,1,1|cdxs3z,17,1,1|cdxs40,13,15,0|cnatfz,13,15,0|cnatg0,17,1,1|cwnurz,17,1,1|cwnus0,13,15,0|d60w3z,13,15,0|d60w40,17,1,1|dfdxfz,17,1,1|dfdxg0,13,15,0|dp3xfz,13,15,0|dp3xg0,17,1,1|dzwtfz,17,1,1|dzwtg0,13,15,0|e7u03z,13,15,0|e7u040,17,1,1|eimw3z,17,1,1|eimw40,13,15,0|eqk2rz,13,15,0|eqk2s0,17,1,1|f1cyrz,17,1,1|f1cys0,13,15,0|f9a5fz,13,15,0|f9a5g0,17,1,1|fkg03z,17,1,1|fkg040,13,15,0|fs083z,13,15,0|fs0840,17,1,1|g362rz,17,1,1|g362s0,13,15,0|gaqarz,13,15,0|gaqas0,17,1,1|glw5fz,17,1,1|glw5g0,13,15,0|gttc3z,13,15,0|gttc40,17,1,1|h4m83z,17,1,1|h4m840,13,15,0|hcjerz,13,15,0|hcjes0,17,1,1|hncarz,17,1,1|hncas0,13,15,0|hv9hfz,13,15,0|hv9hg0,17,1,1|i6fc3z,17,1,1|i6fc40,13,15,0|idzk3z,13,15,0|idzk40,17,1,1|ip5erz,17,1,1|ip5es0,13,15,0|iwpmrz,13,15,0|iwpms0,17,1,1|j7vhfz,17,1,1|j7vhg0,13,15,0|jffpfz,13,15,0|jffpg0,17,1,1|jqlk3z,17,1,1|jqlk40,13,15,0|jyiqrz,13,15,0|jyiqs0,17,1,1|k9bmrz,17,1,1|k9bms0,13,15,0|kh8tfz,13,15,0|kh8tg0,17,1,1|ks1pfz,17,1,1|ks1pg0,13,15,0|kzyw3z,13,15,0|kzyw40,17,1,1|lb4qrz,17,1,1|lb4qs0,13,15,0|lioyrz,13,15,0|lioys0,17,1,1|ltutfz,17,1,1|ltutg0,13,15,0|m1f1fz,13,15,0|m1f1g0,17,1,1|mckw3z,17,1,1|mckw40,13,15,0|mki2rz,13,15,0|mki2s0,17,1,1|mvayrz,17,1,1|mvays0,13,15,0|n385fz,13,15,0|n385g0,17,1,1|ne11fz,17,1,1|ne11g0,13,15,0|nly83z,13,15,0|nly840,17,1,1|nwr43z,17,1,1|nwr440,13,15,0|o4oarz,13,15,0|o4oas0,17,1,1|ofu5fz,17,1,1|ofu5g0,13,15,0|onedfz,13,15,0|onedg0,17,1,1|oyk83z,17,1,1|oyk840,13,15,0|p64g3z,13,15,0|p64g40,17,1,1|phaarz,17,1,1|phaas0,13,15,0|pp7hfz,13,15,0|pp7hg0,17,1,1|q00dfz,17,1,1|q00dg0,13,15,0|q7xk3z,13,15,0|q7xk40,17,1,1|qiqg3z,17,1,1|qiqg40,13,15,0|qqnmrz,13,15,0|qqnms0,17,1,1|r1thfz,17,1,1|r1thg0,13,15,0|r9dpfz,13,15,0|r9dpg0,17,1,1|rkjk3z,17,1,1|rkjk40,13,15,0|rs3s3z,13,15,0|rs3s40,17,1,1|s39mrz,17,1,1|s39ms0,13,15,0|sb6tfz,13,15,0|sb6tg0,17,1,1|slzpfz,17,1,1|slzpg0,13,15,0|stww3z,13,15,0|stww40,17,1,1|t4ps3z,17,1,1|t4ps40,13,15,0|tcmyrz,13,15,0|tcmys0,17,1,1|tnfurz,17,1,1|tnfus0,13,15,0|tvd1fz,13,15,0|tvd1g0,17,1,1|u6iw3z,17,1,1|u6iw40,13,15,0|ue343z,13,15,0|ue3440,17,1,1|up8yrz,17,1,1|up8ys0,13,15,0|uwt6rz,13,15,0|uwt6s0,17,1,1|v7z1fz,17,1,1|v7z1g0,13,15,0|vfw83z,13,15,0|vfw840,17,1,1|vqp43z,17,1,1|vqp440,13,15,0|vymarz,13,15,0|vymas0,17,1,1|w9f6rz,17,1,1|w9f6s0,13,15,0|whcdfz,13,15,0|whcdg0,17,1,1|wsi83z,17,1,1|wsi840,13,15,0|x02g3z,13,15,0|x02g40,17,1,1|xb8arz,17,1,1|xb8as0,13,15,0|xisirz,13,15,0|xisis0,17,1,1|xtydfz,17,1,1|xtydg0,13,15,0|y1ilfz,13,15,0|y1ilg0,17,1,1|ycog3z,17,1,1|ycog40,13,15,0|yklmrz,13,15,0|yklms0,17,1,1|yveirz,17,1,1|yveis0,13,15,0|z3bpfz,13,15,0|z3bpg0,17,1,1|ze4lfz,17,1,1|ze4lg0,13,15,0\",\"America/Sitka|,0,178,0|-1hc7qjz,0,179,0|-1078wa0,0,179,0|-1078w9z,51,40,0|-ek1w81,51,40,0|-ek1w80,68,66,1|-cq2tg1,68,66,1|-cq2tg0,69,66,1|-cnov01,69,66,1|-cnov00,51,40,0|-csc81,51,40,0|-csc80,57,66,1|-3fdo1,57,66,1|-3fdo0,51,40,0|5xqfz,51,40,0|5xqg0,57,66,1|faozz,57,66,1|fap00,51,40,0|ont3z,51,40,0|ont40,57,66,1|ydqbz,57,66,1|ydqc0,51,40,0|17qufz,51,40,0|17qug0,57,66,1|1h3szz,57,66,1|1h3t00,51,40,0|1qgx3z,51,40,0|1qgx40,57,66,1|1ztvnz,57,66,1|1ztvo0,51,40,0|23fl3z,51,40,0|23fl40,57,66,1|2ijybz,57,66,1|2ijyc0,51,40,0|2ooefz,51,40,0|2ooeg0,57,66,1|31a0zz,57,66,1|31a100,51,40,0|3an53z,51,40,0|3an540,57,66,1|3kd2bz,57,66,1|3kd2c0,51,40,0|3td7rz,51,40,0|3td7s0,57,66,1|4334zz,57,66,1|433500,51,40,0|4cg93z,51,40,0|4cg940,57,66,1|4lt7nz,57,66,1|4lt7o0,51,40,0|4v6brz,51,40,0|4v6bs0,57,66,1|54jabz,57,66,1|54jac0,51,40,0|5dwefz,51,40,0|5dweg0,57,66,1|5n9czz,57,66,1|5n9d00,51,40,0|5wmh3z,51,40,0|5wmh40,57,66,1|65zfnz,57,66,1|65zfo0,51,40,0|6fcjrz,51,40,0|6fcjs0,57,66,1|6p2gzz,57,66,1|6p2h00,51,40,0|6y2mfz,51,40,0|6y2mg0,57,66,1|77sjnz,57,66,1|77sjo0,36,37,0|79dybz,36,37,0|79dyc0,37,37,0|7h5qjz,37,37,0|7h5qk0,38,40,1|7qip3z,38,40,1|7qip40,37,37,0|7zvt7z,37,37,0|7zvt80,38,40,1|898rrz,38,40,1|898rs0,37,37,0|8ilvvz,37,37,0|8ilvw0,38,40,1|8ryufz,38,40,1|8ryug0,37,37,0|9092jz,37,37,0|9092k0,38,40,1|9aox3z,38,40,1|9aox40,37,37,0|9iz57z,37,37,0|9iz580,38,40,1|9tryfz,38,40,1|9tryg0,37,37,0|a1p7vz,37,37,0|a1p7w0,38,40,1|aci13z,38,40,1|aci140,37,37,0|akfajz,37,37,0|akfak0,38,40,1|av83rz,38,40,1|av83s0,37,37,0|b3ibvz,37,37,0|b3ibw0,38,40,1|bdy6fz,38,40,1|bdy6g0,37,37,0|bm8ejz,37,37,0|bm8ek0,38,40,1|bwo93z,38,40,1|bwo940,37,37,0|c4yh7z,37,37,0|c4yh80,38,40,1|cfrafz,38,40,1|cfrag0,37,37,0|cnojvz,37,37,0|cnojw0,38,40,1|cyhd3z,38,40,1|cyhd40,37,37,0|d6emjz,37,37,0|d6emk0,38,40,1|dh7frz,38,40,1|dh7fs0,37,37,0|dphnvz,37,37,0|dphnw0,38,40,1|dzxifz,38,40,1|dzxig0,37,37,0|e87qjz,37,37,0|e87qk0,38,40,1|einl3z,38,40,1|einl40,37,37,0|eqxt7z,37,37,0|eqxt80,38,40,1|f1dnrz,38,40,1|f1dns0,37,37,0|f9nvvz,37,37,0|f9nvw0,38,40,1|fkgp3z,38,40,1|fkgp40,37,37,0|fsdyjz,37,37,0|fsdyk0,38,40,1|g36rrz,38,40,1|g36rs0,37,37,0|gb417z,37,37,0|gb4180,38,40,1|glwufz,38,40,1|glwug0,37,37,0|gu72jz,37,37,0|gu72k0,38,40,1|h4mx3z,38,40,1|h4mx40,37,37,0|hcx57z,37,37,0|hcx580,38,40,1|hnczrz,38,40,1|hnczs0,37,37,0|hvn7vz,37,37,0|hvn7w0,38,40,1|i6g13z,38,40,1|i6g140,37,37,0|iedajz,37,37,0|iedak0,38,40,1|ip63rz,38,40,1|ip63s0,37,37,0|ix3d7z,37,37,0|ix3d80,38,40,1|j7w6fz,38,40,1|j7w6g0,37,37,0|jeqjvz,37,37,0|jeqjw0,38,40,1|jqz7rz,38,40,1|jqz7s0,37,37,0|jxgmjz,37,37,0|jxgmk0,38,40,1|k9pafz,38,40,1|k9pag0,37,37,0|kg6p7z,37,37,0|kg6p80,38,40,1|ksfd3z,38,40,1|ksfd40,37,37,0|kz9qjz,37,37,0|kz9qk0,38,40,1|lbiefz,38,40,1|lbieg0,37,37,0|lhzt7z,37,37,0|lhzt80,38,40,1|lu8h3z,38,40,1|lu8h40,37,37,0|m0pvvz,37,37,0|m0pvw0,38,40,1|mcyjrz,38,40,1|mcyjs0,37,37,0|mjfyjz,37,37,0|mjfyk0,38,40,1|mvomfz,38,40,1|mvomg0,37,37,0|n2617z,37,37,0|n26180,38,40,1|neep3z,38,40,1|neep40,37,37,0|nkw3vz,37,37,0|nkw3w0,38,40,1|nx4rrz,38,40,1|nx4rs0,37,37,0|o3z57z,37,37,0|o3z580,38,40,1|og7t3z,38,40,1|og7t40,37,37,0|omp7vz,37,37,0|omp7w0,38,40,1|oyxvrz,38,40,1|oyxvs0,37,37,0|p5fajz,37,37,0|p5fak0,38,40,1|phnyfz,38,40,1|phnyg0,37,37,0|po5d7z,37,37,0|po5d80,38,40,1|q0e13z,38,40,1|q0e140,37,37,0|q6vfvz,37,37,0|q6vfw0,38,40,1|qj43rz,38,40,1|qj43s0,37,37,0|qpyh7z,37,37,0|qpyh80,38,40,1|r2753z,38,40,1|r27540,37,37,0|r8ojvz,37,37,0|r8ojw0,38,40,1|rkx7rz,38,40,1|rkx7s0,37,37,0|rremjz,37,37,0|rremk0,38,40,1|s3nafz,38,40,1|s3nag0,37,37,0|sa4p7z,37,37,0|sa4p80,38,40,1|smdd3z,38,40,1|smdd40,37,37,0|ssurvz,37,37,0|ssurw0,38,40,1|t53frz,38,40,1|t53fs0,37,37,0|tbkujz,37,37,0|tbkuk0,38,40,1|tntifz,38,40,1|tntig0,37,37,0|tunvvz,37,37,0|tunvw0,38,40,1|u6wjrz,38,40,1|u6wjs0,37,37,0|uddyjz,37,37,0|uddyk0,38,40,1|upmmfz,38,40,1|upmmg0,37,37,0|uw417z,37,37,0|uw4180,38,40,1|v8cp3z,38,40,1|v8cp40,37,37,0|veu3vz,37,37,0|veu3w0,38,40,1|vr2rrz,38,40,1|vr2rs0,37,37,0|vxk6jz,37,37,0|vxk6k0,38,40,1|w9sufz,38,40,1|w9sug0,37,37,0|wgn7vz,37,37,0|wgn7w0,38,40,1|wsvvrz,38,40,1|wsvvs0,37,37,0|wzdajz,37,37,0|wzdak0,38,40,1|xblyfz,38,40,1|xblyg0,37,37,0|xi3d7z,37,37,0|xi3d80,38,40,1|xuc13z,38,40,1|xuc140,37,37,0|y0tfvz,37,37,0|y0tfw0,38,40,1|yd23rz,38,40,1|yd23s0,37,37,0|yjjijz,37,37,0|yjjik0,38,40,1|yvs6fz,38,40,1|yvs6g0,37,37,0|z29l7z,37,37,0|z29l80,38,40,1|zei93z,38,40,1|zei940,37,37,0\",\"America/St_Barthelemy|,0,41,0|-u6m79w,32,42,0\",\"America/St_Johns|,0,99,0|-18vs8wk,24,99,0|-ris3cl,24,99,0|-ris3ck,72,100,1|-rag64l,72,100,1|-rag64k,24,99,0|-qzp20l,24,99,0|-qzp20k,72,100,1|-qpm64l,72,100,1|-qpm64k,24,99,0|-qfsmcl,24,99,0|-qfsmck,72,100,1|-qapd4l,72,100,1|-qapd4k,24,99,0|-px4ecl,24,99,0|-px4eck,72,100,1|-pnrfsl,72,100,1|-pnrfsk,24,99,0|-peebol,24,99,0|-peebok,72,100,1|-p51d4l,72,100,1|-p51d4k,24,99,0|-ovbacl,24,99,0|-ovback,72,100,1|-ombagl,72,100,1|-ombagk,24,99,0|-ocl7ol,24,99,0|-ocl7ok,72,100,1|-o3l7sl,72,100,1|-o3l7sk,24,99,0|-ntv50l,24,99,0|-ntv50k,72,100,1|-nkv54l,72,100,1|-nkv54k,24,99,0|-nb52cl,24,99,0|-nb52ck,72,100,1|-n252gl,72,100,1|-n252gk,24,99,0|-msezol,24,99,0|-msezok,72,100,1|-mj214l,72,100,1|-mj214k,24,99,0|-m9ox0l,24,99,0|-m9ox0k,72,100,1|-m0bygl,72,100,1|-m0bygk,24,99,0|-lqlvol,24,99,0|-lqlvok,72,100,1|-lhlvsl,72,100,1|-lhlvsk,24,99,0|-l7vt0l,24,99,0|-l7vt0k,72,100,1|-kyvt4l,72,100,1|-kyvt4k,24,99,0|-kp5qcl,24,99,0|-kp5qck,72,100,1|-kg5qgl,72,100,1|-kg5qgk,24,99,0|-k6fnol,24,99,0|-k6fnok,72,100,1|-jxfnsl,72,100,1|-jxfnsk,24,99,0|-jnpl0l,24,99,0|-jnpl0k,72,100,1|-jecmgl,72,100,1|-jecmgk,24,99,0|-j4mjol,24,99,0|-j4mjok,72,100,1|-ivmjsl,72,100,1|-ivmjsk,24,99,0|-ilwh0l,24,99,0|-ilwh0k,72,100,1|-icwh4l,72,100,1|-icwh4k,24,99,0|-i52u8l,24,99,0|-i52u8k,24,101,0|-i36ee1,24,101,0|-i36ee0,72,102,1|-hu6ei1,72,102,1|-hu6ei0,24,101,0|-hk3aa1,24,101,0|-hk3aa0,72,102,1|-hcj521,72,102,1|-hcj520,24,101,0|-h1d7m1,24,101,0|-h1d7m0,72,102,1|-gtt2e1,72,102,1|-gtt2e0,24,101,0|-gin4y1,24,101,0|-gin4y0,72,102,1|-gb2zq1,72,102,1|-gb2zq0,24,101,0|-fzk3m1,24,101,0|-fzk3m0,72,102,1|-fscx21,72,102,1|-fscx20,24,101,0|-fgu0y1,24,101,0|-fgu0y0,72,102,1|-f99vq1,72,102,1|-f99vq0,24,101,0|-ey3ya1,24,101,0|-ey3ya0,72,102,1|-eqjt21,72,102,1|-eqjt20,24,101,0|-efdvm1,24,101,0|-efdvm0,25,102,1|-cq2tg1,25,102,1|-cq2tg0,26,102,1|-cnp7i1,26,102,1|-cnp7i0,24,101,0|-cc6be1,24,101,0|-cc6be0,72,102,1|-c4m661,72,102,1|-c4m660,24,101,0|-btg8q1,24,101,0|-btg8q0,72,102,1|-blw3i1,72,102,1|-blw3i0,24,101,0|-baq621,24,101,0|-baq620,72,102,1|-b360u1,72,102,1|-b360u0,24,101,0|-as03e1,24,101,0|-as03e0,72,102,1|-akfy61,72,102,1|-akfy60,24,101,0|-a8x221,24,101,0|-a8x220,72,102,1|-a1cwu1,72,102,1|-a1cwu0,24,101,0|-9qwwq1,24,101,0|-9qwwq0,72,102,1|-9izsu1,72,102,1|-9izsu0,24,101,0|-986u21,24,101,0|-986u20,72,102,1|-909q61,72,102,1|-909q60,24,101,0|-8pgre1,24,101,0|-8pgre0,72,102,1|-8hjni1,72,102,1|-8hjni0,24,101,0|-86qoq1,24,101,0|-86qoq0,72,102,1|-7ytku1,72,102,1|-7ytku0,24,101,0|-7o0m21,24,101,0|-7o0m20,72,102,1|-7g3i61,72,102,1|-7g3i60,24,101,0|-74xkq1,24,101,0|-74xkq0,72,102,1|-6x0gu1,72,102,1|-6x0gu0,24,101,0|-6m7i21,24,101,0|-6m7i20,72,102,1|-6eae61,72,102,1|-6eae60,24,101,0|-63hfe1,24,101,0|-63hfe0,72,102,1|-5vkbi1,72,102,1|-5vkbi0,24,101,0|-5krcq1,24,101,0|-5krcq0,72,102,1|-5cu8u1,72,102,1|-5cu8u0,24,101,0|-521a21,24,101,0|-521a20,72,102,1|-4sbcu1,72,102,1|-4sbcu0,24,101,0|-4iy8q1,24,101,0|-4iy8q0,72,102,1|-49la61,72,102,1|-49la60,24,101,0|-408621,24,101,0|-408620,72,102,1|-3qv7i1,72,102,1|-3qv7i0,24,101,0|-3hi3e1,24,101,0|-3hi3e0,72,102,1|-3854u1,72,102,1|-3854u0,24,101,0|-2ys0q1,24,101,0|-2ys0q0,72,102,1|-2pf261,72,102,1|-2pf260,24,101,0|-2g1y21,24,101,0|-2g1y20,72,102,1|-26c0u1,72,102,1|-26c0u0,24,101,0|-1xbve1,24,101,0|-1xbve0,72,102,1|-1nly61,72,102,1|-1nly60,24,101,0|-1e8u21,24,101,0|-1e8u20,72,102,1|-14vvi1,72,102,1|-14vvi0,24,101,0|-vire1,24,101,0|-vire0,72,102,1|-m5su1,72,102,1|-m5su0,24,101,0|-csoq1,24,101,0|-csoq0,72,102,1|-3fq61,72,102,1|-3fq60,24,101,0|5xdxz,24,101,0|5xdy0,72,102,1|fachz,72,102,1|faci0,24,101,0|onglz,24,101,0|ongm0,72,102,1|yddtz,72,102,1|yddu0,24,101,0|17qhxz,24,101,0|17qhy0,72,102,1|1h3ghz,72,102,1|1h3gi0,24,101,0|1qgklz,24,101,0|1qgkm0,72,102,1|1ztj5z,72,102,1|1ztj60,24,101,0|296n9z,24,101,0|296na0,72,102,1|2ijltz,72,102,1|2ijlu0,24,101,0|2rwpxz,24,101,0|2rwpy0,72,102,1|319ohz,72,102,1|319oi0,24,101,0|3amslz,24,101,0|3amsm0,72,102,1|3kcptz,72,102,1|3kcpu0,24,101,0|3tcv9z,24,101,0|3tcva0,72,102,1|432shz,72,102,1|432si0,24,101,0|4cfwlz,24,101,0|4cfwm0,72,102,1|4lsv5z,72,102,1|4lsv60,24,101,0|4v5z9z,24,101,0|4v5za0,72,102,1|54ixtz,72,102,1|54ixu0,24,101,0|5dw1xz,24,101,0|5dw1y0,72,102,1|5n90hz,72,102,1|5n90i0,24,101,0|5wm4lz,24,101,0|5wm4m0,72,102,1|65z35z,72,102,1|65z360,24,101,0|6fc79z,24,101,0|6fc7a0,72,102,1|6p24hz,72,102,1|6p24i0,24,101,0|6y29xz,24,101,0|6y29y0,72,102,1|77s75z,72,102,1|77s760,24,101,0|7h5b9z,24,101,0|7h5ba0,72,102,1|7qi9tz,72,102,1|7qi9u0,24,101,0|7zvdxz,24,101,0|7zvdy0,72,102,1|898chz,72,102,1|898ci0,24,101,0|8ilglz,24,101,0|8ilgm0,72,102,1|8ryf5z,72,102,1|8ryf60,24,101,0|908hrn,24,101,0|908hro,72,102,1|9aocbn,72,102,1|9aocbo,24,101,0|9iykfn,24,101,0|9iykfo,88,147,1|9travn,88,147,1|9travo,24,101,0|a1on3n,24,101,0|a1on3o,72,102,1|achgbn,72,102,1|achgbo,24,101,0|akeprn,24,101,0|akepro,72,102,1|av7izn,72,102,1|av7izo,24,101,0|b3hr3n,24,101,0|b3hr3o,72,102,1|bdxlnn,72,102,1|bdxlno,24,101,0|bm7trn,24,101,0|bm7tro,72,102,1|bwnobn,72,102,1|bwnobo,24,101,0|c4xwfn,24,101,0|c4xwfo,72,102,1|cfqpnn,72,102,1|cfqpno,24,101,0|cnnz3n,24,101,0|cnnz3o,72,102,1|cygsbn,72,102,1|cygsbo,24,101,0|d6e1rn,24,101,0|d6e1ro,72,102,1|dh6uzn,72,102,1|dh6uzo,24,101,0|dph33n,24,101,0|dph33o,72,102,1|dzwxnn,72,102,1|dzwxno,24,101,0|e875rn,24,101,0|e875ro,72,102,1|ein0bn,72,102,1|ein0bo,24,101,0|eqx8fn,24,101,0|eqx8fo,72,102,1|f1d2zn,72,102,1|f1d2zo,24,101,0|f9nb3n,24,101,0|f9nb3o,72,102,1|fkg4bn,72,102,1|fkg4bo,24,101,0|fsddrn,24,101,0|fsddro,72,102,1|g366zn,72,102,1|g366zo,24,101,0|gb3gfn,24,101,0|gb3gfo,72,102,1|glw9nn,72,102,1|glw9no,24,101,0|gu6hrn,24,101,0|gu6hro,72,102,1|h4mcbn,72,102,1|h4mcbo,24,101,0|hcwkfn,24,101,0|hcwkfo,72,102,1|hncezn,72,102,1|hncezo,24,101,0|hvmn3n,24,101,0|hvmn3o,72,102,1|i6fgbn,72,102,1|i6fgbo,24,101,0|iecprn,24,101,0|iecpro,72,102,1|ip5izn,72,102,1|ip5izo,24,101,0|ix2sfn,24,101,0|ix2sfo,72,102,1|j7vlnn,72,102,1|j7vlno,24,101,0|jepz3n,24,101,0|jepz3o,72,102,1|jqymzn,72,102,1|jqymzo,24,101,0|jxg1rn,24,101,0|jxg1ro,72,102,1|k9opnn,72,102,1|k9opno,24,101,0|kg64fn,24,101,0|kg64fo,72,102,1|ksesbn,72,102,1|ksesbo,24,101,0|kz95rn,24,101,0|kz95ro,72,102,1|lbhtnn,72,102,1|lbhtno,24,101,0|lhz8fn,24,101,0|lhz8fo,72,102,1|lu81tz,72,102,1|lu81u0,24,101,0|m0pglz,24,101,0|m0pgm0,72,102,1|mcy4hz,72,102,1|mcy4i0,24,101,0|mjfj9z,24,101,0|mjfja0,72,102,1|mvo75z,72,102,1|mvo760,24,101,0|n25lxz,24,101,0|n25ly0,72,102,1|nee9tz,72,102,1|nee9u0,24,101,0|nkvolz,24,101,0|nkvom0,72,102,1|nx4chz,72,102,1|nx4ci0,24,101,0|o3ypxz,24,101,0|o3ypy0,72,102,1|og7dtz,72,102,1|og7du0,24,101,0|omoslz,24,101,0|omosm0,72,102,1|oyxghz,72,102,1|oyxgi0,24,101,0|p5ev9z,24,101,0|p5eva0,72,102,1|phnj5z,72,102,1|phnj60,24,101,0|po4xxz,24,101,0|po4xy0,72,102,1|q0dltz,72,102,1|q0dlu0,24,101,0|q6v0lz,24,101,0|q6v0m0,72,102,1|qj3ohz,72,102,1|qj3oi0,24,101,0|qpy1xz,24,101,0|qpy1y0,72,102,1|r26ptz,72,102,1|r26pu0,24,101,0|r8o4lz,24,101,0|r8o4m0,72,102,1|rkwshz,72,102,1|rkwsi0,24,101,0|rre79z,24,101,0|rre7a0,72,102,1|s3mv5z,72,102,1|s3mv60,24,101,0|sa49xz,24,101,0|sa49y0,72,102,1|smcxtz,72,102,1|smcxu0,24,101,0|ssuclz,24,101,0|ssucm0,72,102,1|t530hz,72,102,1|t530i0,24,101,0|tbkf9z,24,101,0|tbkfa0,72,102,1|tnt35z,72,102,1|tnt360,24,101,0|tunglz,24,101,0|tungm0,72,102,1|u6w4hz,72,102,1|u6w4i0,24,101,0|uddj9z,24,101,0|uddja0,72,102,1|upm75z,72,102,1|upm760,24,101,0|uw3lxz,24,101,0|uw3ly0,72,102,1|v8c9tz,72,102,1|v8c9u0,24,101,0|vetolz,24,101,0|vetom0,72,102,1|vr2chz,72,102,1|vr2ci0,24,101,0|vxjr9z,24,101,0|vxjra0,72,102,1|w9sf5z,72,102,1|w9sf60,24,101,0|wgmslz,24,101,0|wgmsm0,72,102,1|wsvghz,72,102,1|wsvgi0,24,101,0|wzcv9z,24,101,0|wzcva0,72,102,1|xblj5z,72,102,1|xblj60,24,101,0|xi2xxz,24,101,0|xi2xy0,72,102,1|xubltz,72,102,1|xublu0,24,101,0|y0t0lz,24,101,0|y0t0m0,72,102,1|yd1ohz,72,102,1|yd1oi0,24,101,0|yjj39z,24,101,0|yjj3a0,72,102,1|yvrr5z,72,102,1|yvrr60,24,101,0|z295xz,24,101,0|z295y0,72,102,1|zehttz,72,102,1|zehtu0,24,101,0\",\"America/St_Kitts|,0,41,0|-u6m79w,32,42,0\",\"America/St_Lucia|,0,41,0|-u6m79w,32,42,0\",\"America/St_Thomas|,0,41,0|-u6m79w,32,42,0\",\"America/St_Vincent|,0,41,0|-u6m79w,32,42,0\",\"America/Swift_Current|,0,180,0|-xkq9d4,50,66,0|-qzosc1,50,66,0|-qzosc0,52,62,1|-qplwg1,52,62,1|-qplwg0,50,66,0|-ek1z01,50,66,0|-ek1z00,58,62,1|-cq2tg1,58,62,1|-cq2tg0,59,62,1|-cnoxs1,59,62,1|-cnoxs0,50,66,0|-ccvz01,50,66,0|-ccvz00,52,62,1|-c48xs1,52,62,1|-c48xs0,50,66,0|-bu5wc1,50,66,0|-bu5wc0,52,62,1|-bm8sg1,52,62,1|-bm8sg0,50,66,0|-bbfto1,50,66,0|-bbfto0,52,62,1|-b3ips1,52,62,1|-b3ips0,50,66,0|-aspr01,50,66,0|-aspr00,52,62,1|-aksn41,52,62,1|-aksn40,50,66,0|-6m78c1,50,66,0|-6m78c0,52,62,1|-6cu9s1,52,62,1|-6cu9s0,50,66,0|-5kr301,50,66,0|-5kr300,52,62,1|-5be4g1,52,62,1|-5be4g0,50,66,0|-5210c1,50,66,0|-5210c0,52,62,1|-4u3wg1,52,62,1|-4u3wg0,50,66,0|-4ixz01,50,66,0|-4ixz00,52,62,1|-4bdts1,52,62,1|-4bdts0,50,66,0|17qrnz,50,66,0|17qro0,45,62,0\",\"America/Tegucigalpa|,0,181,0|-pfzh6k,45,62,0|91ojbz,45,62,0|91ojc0,46,63,1|998ojz,46,63,1|998ok0,45,62,0|9kelzz,45,62,0|9kem00,46,63,1|9ryr7z,46,63,1|9ryr80,45,62,0|iyvsnz,45,62,0|iyvso0,46,63,1|j3m37z,46,63,1|j3m380,45,62,0\",\"America/Thule|,0,182,0|-rvuj9g,32,42,0|b34zbz,32,42,0|b34zc0,54,44,1|bchxvz,54,44,1|bchxw0,32,42,0|blv1zz,32,42,0|blv200,54,44,1|bv80jz,54,44,1|bv80k0,32,42,0|c4y3bz,32,42,0|c4y3c0,54,44,1|cfqwjz,54,44,1|cfqwk0,32,42,0|cno5zz,32,42,0|cno600,54,44,1|cygz7z,54,44,1|cygz80,32,42,0|d6e8nz,32,42,0|d6e8o0,54,44,1|dh71vz,54,44,1|dh71w0,32,42,0|dph9zz,32,42,0|dpha00,54,44,1|dzx4jz,54,44,1|dzx4k0,32,42,0|e87cnz,32,42,0|e87co0,54,44,1|ein77z,54,44,1|ein780,32,42,0|eqxfbz,32,42,0|eqxfc0,54,44,1|f1d9vz,54,44,1|f1d9w0,32,42,0|f9nhzz,32,42,0|f9ni00,54,44,1|fkgb7z,54,44,1|fkgb80,32,42,0|fsdknz,32,42,0|fsdko0,54,44,1|g36dvz,54,44,1|g36dw0,32,42,0|gb3nbz,32,42,0|gb3nc0,54,44,1|glwgjz,54,44,1|glwgk0,32,42,0|gu6onz,32,42,0|gu6oo0,54,44,1|h4mj7z,54,44,1|h4mj80,32,42,0|hcwrbz,32,42,0|hcwrc0,54,44,1|hnclvz,54,44,1|hnclw0,32,42,0|hvmtzz,32,42,0|hvmu00,54,44,1|i6fn7z,54,44,1|i6fn80,32,42,0|iecwnz,32,42,0|iecwo0,54,44,1|ip5pvz,54,44,1|ip5pw0,32,42,0|ix2zbz,32,42,0|ix2zc0,54,44,1|j7vsjz,54,44,1|j7vsk0,32,42,0|jeq5zz,32,42,0|jeq600,54,44,1|jqytvz,54,44,1|jqytw0,32,42,0|jxg8nz,32,42,0|jxg8o0,54,44,1|k9owjz,54,44,1|k9owk0,32,42,0|kg6bbz,32,42,0|kg6bc0,54,44,1|ksez7z,54,44,1|ksez80,32,42,0|kz9cnz,32,42,0|kz9co0,54,44,1|lbi0jz,54,44,1|lbi0k0,32,42,0|lhzfbz,32,42,0|lhzfc0,54,44,1|lu837z,54,44,1|lu8380,32,42,0|m0phzz,32,42,0|m0pi00,54,44,1|mcy5vz,54,44,1|mcy5w0,32,42,0|mjfknz,32,42,0|mjfko0,54,44,1|mvo8jz,54,44,1|mvo8k0,32,42,0|n25nbz,32,42,0|n25nc0,54,44,1|neeb7z,54,44,1|neeb80,32,42,0|nkvpzz,32,42,0|nkvq00,54,44,1|nx4dvz,54,44,1|nx4dw0,32,42,0|o3yrbz,32,42,0|o3yrc0,54,44,1|og7f7z,54,44,1|og7f80,32,42,0|omotzz,32,42,0|omou00,54,44,1|oyxhvz,54,44,1|oyxhw0,32,42,0|p5ewnz,32,42,0|p5ewo0,54,44,1|phnkjz,54,44,1|phnkk0,32,42,0|po4zbz,32,42,0|po4zc0,54,44,1|q0dn7z,54,44,1|q0dn80,32,42,0|q6v1zz,32,42,0|q6v200,54,44,1|qj3pvz,54,44,1|qj3pw0,32,42,0|qpy3bz,32,42,0|qpy3c0,54,44,1|r26r7z,54,44,1|r26r80,32,42,0|r8o5zz,32,42,0|r8o600,54,44,1|rkwtvz,54,44,1|rkwtw0,32,42,0|rre8nz,32,42,0|rre8o0,54,44,1|s3mwjz,54,44,1|s3mwk0,32,42,0|sa4bbz,32,42,0|sa4bc0,54,44,1|smcz7z,54,44,1|smcz80,32,42,0|ssudzz,32,42,0|ssue00,54,44,1|t531vz,54,44,1|t531w0,32,42,0|tbkgnz,32,42,0|tbkgo0,54,44,1|tnt4jz,54,44,1|tnt4k0,32,42,0|tunhzz,32,42,0|tuni00,54,44,1|u6w5vz,54,44,1|u6w5w0,32,42,0|uddknz,32,42,0|uddko0,54,44,1|upm8jz,54,44,1|upm8k0,32,42,0|uw3nbz,32,42,0|uw3nc0,54,44,1|v8cb7z,54,44,1|v8cb80,32,42,0|vetpzz,32,42,0|vetq00,54,44,1|vr2dvz,54,44,1|vr2dw0,32,42,0|vxjsnz,32,42,0|vxjso0,54,44,1|w9sgjz,54,44,1|w9sgk0,32,42,0|wgmtzz,32,42,0|wgmu00,54,44,1|wsvhvz,54,44,1|wsvhw0,32,42,0|wzcwnz,32,42,0|wzcwo0,54,44,1|xblkjz,54,44,1|xblkk0,32,42,0|xi2zbz,32,42,0|xi2zc0,54,44,1|xubn7z,54,44,1|xubn80,32,42,0|y0t1zz,32,42,0|y0t200,54,44,1|yd1pvz,54,44,1|yd1pw0,32,42,0|yjj4nz,32,42,0|yjj4o0,54,44,1|yvrsjz,54,44,1|yvrsk0,32,42,0|z297bz,32,42,0|z297c0,54,44,1|zehv7z,54,44,1|zehv80,32,42,0\",\"America/Thunder_Bay|,0,183,0|-1353bh0,45,62,0|-vbavc1,45,62,0|-vbavc0,49,63,0|-ek24k1,49,63,0|-ek24k0,70,42,1|-cq2tg1,70,42,1|-cq2tg0,71,42,1|-cnp3c1,71,42,1|-cnp3c0,49,63,0|5xi3z,49,63,0|5xi40,62,42,1|fagnz,62,42,1|fago0,49,63,0|onkrz,49,63,0|onks0,62,42,1|ydhzz,62,42,1|ydi00,49,63,0|17qm3z,49,63,0|17qm40,62,42,1|1h3knz,62,42,1|1h3ko0,49,63,0|296rfz,49,63,0|296rg0,62,42,1|2ijpzz,62,42,1|2ijq00,49,63,0|2rwu3z,49,63,0|2rwu40,62,42,1|319snz,62,42,1|319so0,49,63,0|3amwrz,49,63,0|3amws0,62,42,1|3kctzz,62,42,1|3kcu00,49,63,0|3tczfz,49,63,0|3tczg0,62,42,1|432wnz,62,42,1|432wo0,49,63,0|4cg0rz,49,63,0|4cg0s0,62,42,1|4lszbz,62,42,1|4lszc0,49,63,0|4v63fz,49,63,0|4v63g0,62,42,1|54j1zz,62,42,1|54j200,49,63,0|5dw63z,49,63,0|5dw640,62,42,1|5n94nz,62,42,1|5n94o0,49,63,0|5wm8rz,49,63,0|5wm8s0,62,42,1|65z7bz,62,42,1|65z7c0,49,63,0|6fcbfz,49,63,0|6fcbg0,62,42,1|6p28nz,62,42,1|6p28o0,49,63,0|6y2e3z,49,63,0|6y2e40,62,42,1|77sbbz,62,42,1|77sbc0,49,63,0|7h5ffz,49,63,0|7h5fg0,62,42,1|7qidzz,62,42,1|7qie00,49,63,0|7zvi3z,49,63,0|7zvi40,62,42,1|898gnz,62,42,1|898go0,49,63,0|8ilkrz,49,63,0|8ilks0,62,42,1|8ryjbz,62,42,1|8ryjc0,49,63,0|908rfz,49,63,0|908rg0,62,42,1|9aolzz,62,42,1|9aom00,49,63,0|9iyu3z,49,63,0|9iyu40,62,42,1|9trnbz,62,42,1|9trnc0,49,63,0|a1owrz,49,63,0|a1ows0,62,42,1|achpzz,62,42,1|achq00,49,63,0|akezfz,49,63,0|akezg0,62,42,1|av7snz,62,42,1|av7so0,49,63,0|b3i0rz,49,63,0|b3i0s0,62,42,1|bdxvbz,62,42,1|bdxvc0,49,63,0|bm83fz,49,63,0|bm83g0,62,42,1|bwnxzz,62,42,1|bwny00,49,63,0|c4y63z,49,63,0|c4y640,62,42,1|cfqzbz,62,42,1|cfqzc0,49,63,0|cno8rz,49,63,0|cno8s0,62,42,1|cyh1zz,62,42,1|cyh200,49,63,0|d6ebfz,49,63,0|d6ebg0,62,42,1|dh74nz,62,42,1|dh74o0,49,63,0|dphcrz,49,63,0|dphcs0,62,42,1|dzx7bz,62,42,1|dzx7c0,49,63,0|e87ffz,49,63,0|e87fg0,62,42,1|ein9zz,62,42,1|eina00,49,63,0|eqxi3z,49,63,0|eqxi40,62,42,1|f1dcnz,62,42,1|f1dco0,49,63,0|f9nkrz,49,63,0|f9nks0,62,42,1|fkgdzz,62,42,1|fkge00,49,63,0|fsdnfz,49,63,0|fsdng0,62,42,1|g36gnz,62,42,1|g36go0,49,63,0|gb3q3z,49,63,0|gb3q40,62,42,1|glwjbz,62,42,1|glwjc0,49,63,0|gu6rfz,49,63,0|gu6rg0,62,42,1|h4mlzz,62,42,1|h4mm00,49,63,0|hcwu3z,49,63,0|hcwu40,62,42,1|hnconz,62,42,1|hncoo0,49,63,0|hvmwrz,49,63,0|hvmws0,62,42,1|i6fpzz,62,42,1|i6fq00,49,63,0|ieczfz,49,63,0|ieczg0,62,42,1|ip5snz,62,42,1|ip5so0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Tijuana|,0,184,0|-p1u1s0,50,66,0|-o0a9w1,50,66,0|-o0a9w0,51,40,0|-m7mhw1,51,40,0|-m7mhw0,50,66,0|-kf64k1,50,66,0|-kf64k0,51,40,0|-k84cg1,51,40,0|-k84cg0,57,66,1|-jyrdw1,57,66,1|-jyrdw0,51,40,0|-eg90g1,51,40,0|-eg90g0,68,66,1|-cq2tg1,68,66,1|-cq2tg0,69,66,1|-clhdw1,69,66,1|-clhdw0,51,40,0|-bcgxs1,51,40,0|-bcgxs0,57,66,1|-axv381,57,66,1|-axv380,51,40,0|-86qf01,51,40,0|-86qf00,57,66,1|-7yt8c1,57,66,1|-7yt8c0,51,40,0|-7o0cc1,51,40,0|-7o0cc0,57,66,1|-7g35o1,57,66,1|-7g35o0,51,40,0|-74xb01,51,40,0|-74xb00,57,66,1|-6x04c1,57,66,1|-6x04c0,51,40,0|-6m78c1,51,40,0|-6m78c0,57,66,1|-6ea1o1,57,66,1|-6ea1o0,51,40,0|-63h5o1,51,40,0|-63h5o0,57,66,1|-5vjz01,57,66,1|-5vjz00,51,40,0|-5kr301,51,40,0|-5kr300,57,66,1|-5ctwc1,57,66,1|-5ctwc0,51,40,0|-5210c1,51,40,0|-5210c0,57,66,1|-4u3to1,57,66,1|-4u3to0,51,40,0|3an53z,51,40,0|3an540,57,66,1|3kd2bz,57,66,1|3kd2c0,51,40,0|3td7rz,51,40,0|3td7s0,57,66,1|4334zz,57,66,1|433500,51,40,0|4cg93z,51,40,0|4cg940,57,66,1|4lt7nz,57,66,1|4lt7o0,51,40,0|4v6brz,51,40,0|4v6bs0,57,66,1|54jabz,57,66,1|54jac0,51,40,0|5dwefz,51,40,0|5dweg0,57,66,1|5n9czz,57,66,1|5n9d00,51,40,0|5wmh3z,51,40,0|5wmh40,57,66,1|65zfnz,57,66,1|65zfo0,51,40,0|6fcjrz,51,40,0|6fcjs0,57,66,1|6p2gzz,57,66,1|6p2h00,51,40,0|6y2mfz,51,40,0|6y2mg0,57,66,1|77sjnz,57,66,1|77sjo0,51,40,0|7h5nrz,51,40,0|7h5ns0,57,66,1|7qimbz,57,66,1|7qimc0,51,40,0|7zvqfz,51,40,0|7zvqg0,57,66,1|898ozz,57,66,1|898p00,51,40,0|8ilt3z,51,40,0|8ilt40,57,66,1|8ryrnz,57,66,1|8ryro0,51,40,0|908zrz,51,40,0|908zs0,57,66,1|9aoubz,57,66,1|9aouc0,51,40,0|9iz2fz,51,40,0|9iz2g0,57,66,1|9trvnz,57,66,1|9trvo0,51,40,0|a1p53z,51,40,0|a1p540,57,66,1|achybz,57,66,1|achyc0,51,40,0|akf7rz,51,40,0|akf7s0,57,66,1|av80zz,57,66,1|av8100,51,40,0|b3i93z,51,40,0|b3i940,57,66,1|bdy3nz,57,66,1|bdy3o0,51,40,0|bm8brz,51,40,0|bm8bs0,57,66,1|bwo6bz,57,66,1|bwo6c0,51,40,0|c4yefz,51,40,0|c4yeg0,57,66,1|cfr7nz,57,66,1|cfr7o0,51,40,0|cnoh3z,51,40,0|cnoh40,57,66,1|cyhabz,57,66,1|cyhac0,51,40,0|d6ejrz,51,40,0|d6ejs0,57,66,1|dh7czz,57,66,1|dh7d00,51,40,0|dphl3z,51,40,0|dphl40,57,66,1|dzxfnz,57,66,1|dzxfo0,51,40,0|e87nrz,51,40,0|e87ns0,57,66,1|einibz,57,66,1|einic0,51,40,0|eqxqfz,51,40,0|eqxqg0,57,66,1|f1dkzz,57,66,1|f1dl00,51,40,0|f9nt3z,51,40,0|f9nt40,57,66,1|fkgmbz,57,66,1|fkgmc0,51,40,0|fsdvrz,51,40,0|fsdvs0,57,66,1|g36ozz,57,66,1|g36p00,51,40,0|gb3yfz,51,40,0|gb3yg0,57,66,1|glwrnz,57,66,1|glwro0,51,40,0|gu6zrz,51,40,0|gu6zs0,57,66,1|h4mubz,57,66,1|h4muc0,51,40,0|hcx2fz,51,40,0|hcx2g0,57,66,1|hncwzz,57,66,1|hncx00,51,40,0|hvn53z,51,40,0|hvn540,57,66,1|i6fybz,57,66,1|i6fyc0,51,40,0|ied7rz,51,40,0|ied7s0,57,66,1|ip60zz,57,66,1|ip6100,51,40,0|ix3afz,51,40,0|ix3ag0,57,66,1|j7w3nz,57,66,1|j7w3o0,51,40,0|jftd3z,51,40,0|jftd40,57,66,1|jqm6bz,57,66,1|jqm6c0,51,40,0|jywefz,51,40,0|jyweg0,57,66,1|k9c8zz,57,66,1|k9c900,51,40,0|khmh3z,51,40,0|khmh40,57,66,1|ks2bnz,57,66,1|ks2bo0,51,40,0|kz9nrz,51,40,0|kz9ns0,57,66,1|lbibnz,57,66,1|lbibo0,51,40,0|lhzqfz,51,40,0|lhzqg0,57,66,1|lu8ebz,57,66,1|lu8ec0,51,40,0|m0pt3z,51,40,0|m0pt40,57,66,1|mcygzz,57,66,1|mcyh00,51,40,0|mjfvrz,51,40,0|mjfvs0,57,66,1|mvojnz,57,66,1|mvojo0,51,40,0|n25yfz,51,40,0|n25yg0,57,66,1|neembz,57,66,1|neemc0,51,40,0|nkw13z,51,40,0|nkw140,57,66,1|nx4ozz,57,66,1|nx4p00,51,40,0|o3z2fz,51,40,0|o3z2g0,57,66,1|og7qbz,57,66,1|og7qc0,51,40,0|omp53z,51,40,0|omp540,57,66,1|oyxszz,57,66,1|oyxt00,51,40,0|p5f7rz,51,40,0|p5f7s0,57,66,1|phnvnz,57,66,1|phnvo0,51,40,0|po5afz,51,40,0|po5ag0,57,66,1|q0dybz,57,66,1|q0dyc0,51,40,0|q6vd3z,51,40,0|q6vd40,57,66,1|qj40zz,57,66,1|qj4100,51,40,0|qpyefz,51,40,0|qpyeg0,57,66,1|r272bz,57,66,1|r272c0,51,40,0|r8oh3z,51,40,0|r8oh40,57,66,1|rkx4zz,57,66,1|rkx500,51,40,0|rrejrz,51,40,0|rrejs0,57,66,1|s3n7nz,57,66,1|s3n7o0,51,40,0|sa4mfz,51,40,0|sa4mg0,57,66,1|smdabz,57,66,1|smdac0,51,40,0|ssup3z,51,40,0|ssup40,57,66,1|t53czz,57,66,1|t53d00,51,40,0|tbkrrz,51,40,0|tbkrs0,57,66,1|tntfnz,57,66,1|tntfo0,51,40,0|tunt3z,51,40,0|tunt40,57,66,1|u6wgzz,57,66,1|u6wh00,51,40,0|uddvrz,51,40,0|uddvs0,57,66,1|upmjnz,57,66,1|upmjo0,51,40,0|uw3yfz,51,40,0|uw3yg0,57,66,1|v8cmbz,57,66,1|v8cmc0,51,40,0|veu13z,51,40,0|veu140,57,66,1|vr2ozz,57,66,1|vr2p00,51,40,0|vxk3rz,51,40,0|vxk3s0,57,66,1|w9srnz,57,66,1|w9sro0,51,40,0|wgn53z,51,40,0|wgn540,57,66,1|wsvszz,57,66,1|wsvt00,51,40,0|wzd7rz,51,40,0|wzd7s0,57,66,1|xblvnz,57,66,1|xblvo0,51,40,0|xi3afz,51,40,0|xi3ag0,57,66,1|xubybz,57,66,1|xubyc0,51,40,0|y0td3z,51,40,0|y0td40,57,66,1|yd20zz,57,66,1|yd2100,51,40,0|yjjfrz,51,40,0|yjjfs0,57,66,1|yvs3nz,57,66,1|yvs3o0,51,40,0|z29ifz,51,40,0|z29ig0,57,66,1|zei6bz,57,66,1|zei6c0,51,40,0\",\"America/Toronto|,0,185,0|-1353das,49,63,0|-qzoxw1,49,63,0|-qzoxw0,62,42,1|-qpm201,62,42,1|-qpm200,49,63,0|-qhn4u1,49,63,0|-qhn4u0,62,42,1|-q6w4w1,62,42,1|-q6w4w0,49,63,0|-px5wk1,49,63,0|-px5wk0,62,42,1|-pplww1,62,42,1|-pplww0,49,63,0|-pdpwk1,49,63,0|-pdpwk0,62,42,1|-p7e7c1,62,42,1|-p7e7c0,49,63,0|-ouztw1,49,63,0|-ouztw0,62,42,1|-ooiko1,62,42,1|-ooiko0,49,63,0|-oc9r81,49,63,0|-oc9r80,62,42,1|-o5si01,62,42,1|-o5si00,49,63,0|-ntwn81,49,63,0|-ntwn80,62,42,1|-nmpgo1,62,42,1|-nmpgo0,49,63,0|-nb6kk1,49,63,0|-nb6kk0,62,42,1|-n3ze01,62,42,1|-n3ze00,49,63,0|-msghw1,49,63,0|-msghw0,62,42,1|-ml9bc1,62,42,1|-ml9bc0,49,63,0|-m9qf81,49,63,0|-m9qf80,62,42,1|-m26a01,62,42,1|-m26a00,49,63,0|-lr0ck1,49,63,0|-lr0ck0,62,42,1|-lj38o1,62,42,1|-lj38o0,49,63,0|-l8a9w1,49,63,0|-l8a9w0,62,42,1|-l0d601,62,42,1|-l0d600,49,63,0|-kpk781,49,63,0|-kpk780,62,42,1|-khn3c1,62,42,1|-khn3c0,49,63,0|-k6u4k1,49,63,0|-k6u4k0,62,42,1|-jyx0o1,62,42,1|-jyx0o0,49,63,0|-jnr381,49,63,0|-jnr380,62,42,1|-jg6y01,62,42,1|-jg6y00,49,63,0|-j510k1,49,63,0|-j510k0,62,42,1|-ix3wo1,62,42,1|-ix3wo0,49,63,0|-imaxw1,49,63,0|-imaxw0,62,42,1|-iedu01,62,42,1|-iedu00,49,63,0|-i3kv81,49,63,0|-i3kv80,62,42,1|-hvnrc1,62,42,1|-hvnrc0,49,63,0|-hkusk1,49,63,0|-hkusk0,62,42,1|-hcxoo1,62,42,1|-hcxoo0,49,63,0|-h24pw1,49,63,0|-h24pw0,62,42,1|-gu7m01,62,42,1|-gu7m00,49,63,0|-gjen81,49,63,0|-gjen80,62,42,1|-gbhjc1,62,42,1|-gbhjc0,49,63,0|-g0blw1,49,63,0|-g0blw0,62,42,1|-fsrgo1,62,42,1|-fsrgo0,49,63,0|-fhlj81,49,63,0|-fhlj80,62,42,1|-ek24k1,62,42,1|-ek24k0,70,42,1|-cq2tg1,70,42,1|-cq2tg0,71,42,1|-cnp3c1,71,42,1|-cnp3c0,49,63,0|-ccw4k1,49,63,0|-ccw4k0,62,42,1|-c4z0o1,62,42,1|-c4z0o0,49,63,0|-bu67g1,49,63,0|-bu67g0,62,42,1|-bm93k1,62,42,1|-bm93k0,49,63,0|-bbg4s1,49,63,0|-bbg4s0,62,42,1|-b3j0w1,62,42,1|-b3j0w0,49,63,0|-asq241,49,63,0|-asq240,62,42,1|-ahka81,62,42,1|-ahka80,49,63,0|-a9mv81,49,63,0|-a9mv80,62,42,1|-9yu201,62,42,1|-9yu200,49,63,0|-9qwsk1,49,63,0|-9qwsk0,62,42,1|-9izoo1,62,42,1|-9izoo0,49,63,0|-986pw1,49,63,0|-986pw0,62,42,1|-909m01,62,42,1|-909m00,49,63,0|-8pgn81,49,63,0|-8pgn80,62,42,1|-8hjjc1,62,42,1|-8hjjc0,49,63,0|-86qkk1,49,63,0|-86qkk0,62,42,1|-7ytgo1,62,42,1|-7ytgo0,49,63,0|-7o0hw1,49,63,0|-7o0hw0,62,42,1|-7g3e01,62,42,1|-7g3e00,49,63,0|-74xgk1,49,63,0|-74xgk0,62,42,1|-6x0co1,62,42,1|-6x0co0,49,63,0|-6m7dw1,49,63,0|-6m7dw0,62,42,1|-6cufc1,62,42,1|-6cufc0,49,63,0|-63hb81,49,63,0|-63hb80,62,42,1|-5u4co1,62,42,1|-5u4co0,49,63,0|-5kr8k1,49,63,0|-5kr8k0,62,42,1|-5bea01,62,42,1|-5bea00,49,63,0|-5215w1,49,63,0|-5215w0,62,42,1|-4sb8o1,62,42,1|-4sb8o0,49,63,0|-4iy4k1,49,63,0|-4iy4k0,62,42,1|-49l601,62,42,1|-49l600,49,63,0|-4081w1,49,63,0|-4081w0,62,42,1|-3qv3c1,62,42,1|-3qv3c0,49,63,0|-3hhz81,49,63,0|-3hhz80,62,42,1|-3850o1,62,42,1|-3850o0,49,63,0|-2yrwk1,49,63,0|-2yrwk0,62,42,1|-2pey01,62,42,1|-2pey00,49,63,0|-2g1tw1,49,63,0|-2g1tw0,62,42,1|-26bwo1,62,42,1|-26bwo0,49,63,0|-1xbr81,49,63,0|-1xbr80,62,42,1|-1nlu01,62,42,1|-1nlu00,49,63,0|-1e8pw1,49,63,0|-1e8pw0,62,42,1|-14vrc1,62,42,1|-14vrc0,49,63,0|-vin81,49,63,0|-vin80,62,42,1|-m5oo1,62,42,1|-m5oo0,49,63,0|-cskk1,49,63,0|-cskk0,62,42,1|-3fm01,62,42,1|-3fm00,49,63,0|5xi3z,49,63,0|5xi40,62,42,1|fagnz,62,42,1|fago0,49,63,0|onkrz,49,63,0|onks0,62,42,1|ydhzz,62,42,1|ydi00,49,63,0|17qm3z,49,63,0|17qm40,62,42,1|1h3knz,62,42,1|1h3ko0,49,63,0|1qgorz,49,63,0|1qgos0,62,42,1|1ztnbz,62,42,1|1ztnc0,49,63,0|296rfz,49,63,0|296rg0,62,42,1|2ijpzz,62,42,1|2ijq00,49,63,0|2rwu3z,49,63,0|2rwu40,62,42,1|319snz,62,42,1|319so0,49,63,0|3amwrz,49,63,0|3amws0,62,42,1|3kctzz,62,42,1|3kcu00,49,63,0|3tczfz,49,63,0|3tczg0,62,42,1|432wnz,62,42,1|432wo0,49,63,0|4cg0rz,49,63,0|4cg0s0,62,42,1|4lszbz,62,42,1|4lszc0,49,63,0|4v63fz,49,63,0|4v63g0,62,42,1|54j1zz,62,42,1|54j200,49,63,0|5dw63z,49,63,0|5dw640,62,42,1|5n94nz,62,42,1|5n94o0,49,63,0|5wm8rz,49,63,0|5wm8s0,62,42,1|65z7bz,62,42,1|65z7c0,49,63,0|6fcbfz,49,63,0|6fcbg0,62,42,1|6p28nz,62,42,1|6p28o0,49,63,0|6y2e3z,49,63,0|6y2e40,62,42,1|77sbbz,62,42,1|77sbc0,49,63,0|7h5ffz,49,63,0|7h5fg0,62,42,1|7qidzz,62,42,1|7qie00,49,63,0|7zvi3z,49,63,0|7zvi40,62,42,1|898gnz,62,42,1|898go0,49,63,0|8ilkrz,49,63,0|8ilks0,62,42,1|8ryjbz,62,42,1|8ryjc0,49,63,0|908rfz,49,63,0|908rg0,62,42,1|9aolzz,62,42,1|9aom00,49,63,0|9iyu3z,49,63,0|9iyu40,62,42,1|9trnbz,62,42,1|9trnc0,49,63,0|a1owrz,49,63,0|a1ows0,62,42,1|achpzz,62,42,1|achq00,49,63,0|akezfz,49,63,0|akezg0,62,42,1|av7snz,62,42,1|av7so0,49,63,0|b3i0rz,49,63,0|b3i0s0,62,42,1|bdxvbz,62,42,1|bdxvc0,49,63,0|bm83fz,49,63,0|bm83g0,62,42,1|bwnxzz,62,42,1|bwny00,49,63,0|c4y63z,49,63,0|c4y640,62,42,1|cfqzbz,62,42,1|cfqzc0,49,63,0|cno8rz,49,63,0|cno8s0,62,42,1|cyh1zz,62,42,1|cyh200,49,63,0|d6ebfz,49,63,0|d6ebg0,62,42,1|dh74nz,62,42,1|dh74o0,49,63,0|dphcrz,49,63,0|dphcs0,62,42,1|dzx7bz,62,42,1|dzx7c0,49,63,0|e87ffz,49,63,0|e87fg0,62,42,1|ein9zz,62,42,1|eina00,49,63,0|eqxi3z,49,63,0|eqxi40,62,42,1|f1dcnz,62,42,1|f1dco0,49,63,0|f9nkrz,49,63,0|f9nks0,62,42,1|fkgdzz,62,42,1|fkge00,49,63,0|fsdnfz,49,63,0|fsdng0,62,42,1|g36gnz,62,42,1|g36go0,49,63,0|gb3q3z,49,63,0|gb3q40,62,42,1|glwjbz,62,42,1|glwjc0,49,63,0|gu6rfz,49,63,0|gu6rg0,62,42,1|h4mlzz,62,42,1|h4mm00,49,63,0|hcwu3z,49,63,0|hcwu40,62,42,1|hnconz,62,42,1|hncoo0,49,63,0|hvmwrz,49,63,0|hvmws0,62,42,1|i6fpzz,62,42,1|i6fq00,49,63,0|ieczfz,49,63,0|ieczg0,62,42,1|ip5snz,62,42,1|ip5so0,49,63,0|ix323z,49,63,0|ix3240,62,42,1|j7vvbz,62,42,1|j7vvc0,49,63,0|jeq8rz,49,63,0|jeq8s0,62,42,1|jqywnz,62,42,1|jqywo0,49,63,0|jxgbfz,49,63,0|jxgbg0,62,42,1|k9ozbz,62,42,1|k9ozc0,49,63,0|kg6e3z,49,63,0|kg6e40,62,42,1|ksf1zz,62,42,1|ksf200,49,63,0|kz9ffz,49,63,0|kz9fg0,62,42,1|lbi3bz,62,42,1|lbi3c0,49,63,0|lhzi3z,49,63,0|lhzi40,62,42,1|lu85zz,62,42,1|lu8600,49,63,0|m0pkrz,49,63,0|m0pks0,62,42,1|mcy8nz,62,42,1|mcy8o0,49,63,0|mjfnfz,49,63,0|mjfng0,62,42,1|mvobbz,62,42,1|mvobc0,49,63,0|n25q3z,49,63,0|n25q40,62,42,1|needzz,62,42,1|neee00,49,63,0|nkvsrz,49,63,0|nkvss0,62,42,1|nx4gnz,62,42,1|nx4go0,49,63,0|o3yu3z,49,63,0|o3yu40,62,42,1|og7hzz,62,42,1|og7i00,49,63,0|omowrz,49,63,0|omows0,62,42,1|oyxknz,62,42,1|oyxko0,49,63,0|p5ezfz,49,63,0|p5ezg0,62,42,1|phnnbz,62,42,1|phnnc0,49,63,0|po523z,49,63,0|po5240,62,42,1|q0dpzz,62,42,1|q0dq00,49,63,0|q6v4rz,49,63,0|q6v4s0,62,42,1|qj3snz,62,42,1|qj3so0,49,63,0|qpy63z,49,63,0|qpy640,62,42,1|r26tzz,62,42,1|r26u00,49,63,0|r8o8rz,49,63,0|r8o8s0,62,42,1|rkwwnz,62,42,1|rkwwo0,49,63,0|rrebfz,49,63,0|rrebg0,62,42,1|s3mzbz,62,42,1|s3mzc0,49,63,0|sa4e3z,49,63,0|sa4e40,62,42,1|smd1zz,62,42,1|smd200,49,63,0|ssugrz,49,63,0|ssugs0,62,42,1|t534nz,62,42,1|t534o0,49,63,0|tbkjfz,49,63,0|tbkjg0,62,42,1|tnt7bz,62,42,1|tnt7c0,49,63,0|tunkrz,49,63,0|tunks0,62,42,1|u6w8nz,62,42,1|u6w8o0,49,63,0|uddnfz,49,63,0|uddng0,62,42,1|upmbbz,62,42,1|upmbc0,49,63,0|uw3q3z,49,63,0|uw3q40,62,42,1|v8cdzz,62,42,1|v8ce00,49,63,0|vetsrz,49,63,0|vetss0,62,42,1|vr2gnz,62,42,1|vr2go0,49,63,0|vxjvfz,49,63,0|vxjvg0,62,42,1|w9sjbz,62,42,1|w9sjc0,49,63,0|wgmwrz,49,63,0|wgmws0,62,42,1|wsvknz,62,42,1|wsvko0,49,63,0|wzczfz,49,63,0|wzczg0,62,42,1|xblnbz,62,42,1|xblnc0,49,63,0|xi323z,49,63,0|xi3240,62,42,1|xubpzz,62,42,1|xubq00,49,63,0|y0t4rz,49,63,0|y0t4s0,62,42,1|yd1snz,62,42,1|yd1so0,49,63,0|yjj7fz,49,63,0|yjj7g0,62,42,1|yvrvbz,62,42,1|yvrvc0,49,63,0|z29a3z,49,63,0|z29a40,62,42,1|zehxzz,62,42,1|zehy00,49,63,0\",\"America/Tortola|,0,41,0|-u6m79w,32,42,0\",\"America/Vancouver|,0,186,0|-18vrvv8,51,40,0|-qzopk1,51,40,0|-qzopk0,57,66,1|-qplto1,57,66,1|-qplto0,51,40,0|-ek1w81,51,40,0|-ek1w80,68,66,1|-cq2tg1,68,66,1|-cq2tg0,69,66,1|-cnov01,69,66,1|-cnov00,51,40,0|-ccvw81,51,40,0|-ccvw80,57,66,1|-c4ysc1,57,66,1|-c4ysc0,51,40,0|-bu5tk1,51,40,0|-bu5tk0,57,66,1|-bm8po1,57,66,1|-bm8po0,51,40,0|-bbfqw1,51,40,0|-bbfqw0,57,66,1|-b3in01,57,66,1|-b3in00,51,40,0|-aspo81,51,40,0|-aspo80,57,66,1|-akskc1,57,66,1|-akskc0,51,40,0|-a9mmw1,51,40,0|-a9mmw0,57,66,1|-a22ho1,57,66,1|-a22ho0,51,40,0|-9qwk81,51,40,0|-9qwk80,57,66,1|-9izgc1,57,66,1|-9izgc0,51,40,0|-986hk1,51,40,0|-986hk0,57,66,1|-909do1,57,66,1|-909do0,51,40,0|-8pgew1,51,40,0|-8pgew0,57,66,1|-8hjb01,57,66,1|-8hjb00,51,40,0|-86qc81,51,40,0|-86qc80,57,66,1|-7yt8c1,57,66,1|-7yt8c0,51,40,0|-7o09k1,51,40,0|-7o09k0,57,66,1|-7g35o1,57,66,1|-7g35o0,51,40,0|-74x881,51,40,0|-74x880,57,66,1|-6x04c1,57,66,1|-6x04c0,51,40,0|-6m75k1,51,40,0|-6m75k0,57,66,1|-6ea1o1,57,66,1|-6ea1o0,51,40,0|-63h2w1,51,40,0|-63h2w0,57,66,1|-5vjz01,57,66,1|-5vjz00,51,40,0|-5kr081,51,40,0|-5kr080,57,66,1|-5ctwc1,57,66,1|-5ctwc0,51,40,0|-520xk1,51,40,0|-520xk0,57,66,1|-4u3to1,57,66,1|-4u3to0,51,40,0|-4ixw81,51,40,0|-4ixw80,57,66,1|-4bdr01,57,66,1|-4bdr00,51,40,0|-407tk1,51,40,0|-407tk0,57,66,1|-3quv01,57,66,1|-3quv00,51,40,0|-3hhqw1,51,40,0|-3hhqw0,57,66,1|-384sc1,57,66,1|-384sc0,51,40,0|-2yro81,51,40,0|-2yro80,57,66,1|-2pepo1,57,66,1|-2pepo0,51,40,0|-2g1lk1,51,40,0|-2g1lk0,57,66,1|-26boc1,57,66,1|-26boc0,51,40,0|-1xbiw1,51,40,0|-1xbiw0,57,66,1|-1nllo1,57,66,1|-1nllo0,51,40,0|-1e8hk1,51,40,0|-1e8hk0,57,66,1|-14vj01,57,66,1|-14vj00,51,40,0|-view1,51,40,0|-view0,57,66,1|-m5gc1,57,66,1|-m5gc0,51,40,0|-csc81,51,40,0|-csc80,57,66,1|-3fdo1,57,66,1|-3fdo0,51,40,0|5xqfz,51,40,0|5xqg0,57,66,1|faozz,57,66,1|fap00,51,40,0|ont3z,51,40,0|ont40,57,66,1|ydqbz,57,66,1|ydqc0,51,40,0|17qufz,51,40,0|17qug0,57,66,1|1h3szz,57,66,1|1h3t00,51,40,0|1qgx3z,51,40,0|1qgx40,57,66,1|1ztvnz,57,66,1|1ztvo0,51,40,0|296zrz,51,40,0|296zs0,57,66,1|2ijybz,57,66,1|2ijyc0,51,40,0|2rx2fz,51,40,0|2rx2g0,57,66,1|31a0zz,57,66,1|31a100,51,40,0|3an53z,51,40,0|3an540,57,66,1|3kd2bz,57,66,1|3kd2c0,51,40,0|3td7rz,51,40,0|3td7s0,57,66,1|4334zz,57,66,1|433500,51,40,0|4cg93z,51,40,0|4cg940,57,66,1|4lt7nz,57,66,1|4lt7o0,51,40,0|4v6brz,51,40,0|4v6bs0,57,66,1|54jabz,57,66,1|54jac0,51,40,0|5dwefz,51,40,0|5dweg0,57,66,1|5n9czz,57,66,1|5n9d00,51,40,0|5wmh3z,51,40,0|5wmh40,57,66,1|65zfnz,57,66,1|65zfo0,51,40,0|6fcjrz,51,40,0|6fcjs0,57,66,1|6p2gzz,57,66,1|6p2h00,51,40,0|6y2mfz,51,40,0|6y2mg0,57,66,1|77sjnz,57,66,1|77sjo0,51,40,0|7h5nrz,51,40,0|7h5ns0,57,66,1|7qimbz,57,66,1|7qimc0,51,40,0|7zvqfz,51,40,0|7zvqg0,57,66,1|898ozz,57,66,1|898p00,51,40,0|8ilt3z,51,40,0|8ilt40,57,66,1|8ryrnz,57,66,1|8ryro0,51,40,0|908zrz,51,40,0|908zs0,57,66,1|9aoubz,57,66,1|9aouc0,51,40,0|9iz2fz,51,40,0|9iz2g0,57,66,1|9trvnz,57,66,1|9trvo0,51,40,0|a1p53z,51,40,0|a1p540,57,66,1|achybz,57,66,1|achyc0,51,40,0|akf7rz,51,40,0|akf7s0,57,66,1|av80zz,57,66,1|av8100,51,40,0|b3i93z,51,40,0|b3i940,57,66,1|bdy3nz,57,66,1|bdy3o0,51,40,0|bm8brz,51,40,0|bm8bs0,57,66,1|bwo6bz,57,66,1|bwo6c0,51,40,0|c4yefz,51,40,0|c4yeg0,57,66,1|cfr7nz,57,66,1|cfr7o0,51,40,0|cnoh3z,51,40,0|cnoh40,57,66,1|cyhabz,57,66,1|cyhac0,51,40,0|d6ejrz,51,40,0|d6ejs0,57,66,1|dh7czz,57,66,1|dh7d00,51,40,0|dphl3z,51,40,0|dphl40,57,66,1|dzxfnz,57,66,1|dzxfo0,51,40,0|e87nrz,51,40,0|e87ns0,57,66,1|einibz,57,66,1|einic0,51,40,0|eqxqfz,51,40,0|eqxqg0,57,66,1|f1dkzz,57,66,1|f1dl00,51,40,0|f9nt3z,51,40,0|f9nt40,57,66,1|fkgmbz,57,66,1|fkgmc0,51,40,0|fsdvrz,51,40,0|fsdvs0,57,66,1|g36ozz,57,66,1|g36p00,51,40,0|gb3yfz,51,40,0|gb3yg0,57,66,1|glwrnz,57,66,1|glwro0,51,40,0|gu6zrz,51,40,0|gu6zs0,57,66,1|h4mubz,57,66,1|h4muc0,51,40,0|hcx2fz,51,40,0|hcx2g0,57,66,1|hncwzz,57,66,1|hncx00,51,40,0|hvn53z,51,40,0|hvn540,57,66,1|i6fybz,57,66,1|i6fyc0,51,40,0|ied7rz,51,40,0|ied7s0,57,66,1|ip60zz,57,66,1|ip6100,51,40,0|ix3afz,51,40,0|ix3ag0,57,66,1|j7w3nz,57,66,1|j7w3o0,51,40,0|jeqh3z,51,40,0|jeqh40,57,66,1|jqz4zz,57,66,1|jqz500,51,40,0|jxgjrz,51,40,0|jxgjs0,57,66,1|k9p7nz,57,66,1|k9p7o0,51,40,0|kg6mfz,51,40,0|kg6mg0,57,66,1|ksfabz,57,66,1|ksfac0,51,40,0|kz9nrz,51,40,0|kz9ns0,57,66,1|lbibnz,57,66,1|lbibo0,51,40,0|lhzqfz,51,40,0|lhzqg0,57,66,1|lu8ebz,57,66,1|lu8ec0,51,40,0|m0pt3z,51,40,0|m0pt40,57,66,1|mcygzz,57,66,1|mcyh00,51,40,0|mjfvrz,51,40,0|mjfvs0,57,66,1|mvojnz,57,66,1|mvojo0,51,40,0|n25yfz,51,40,0|n25yg0,57,66,1|neembz,57,66,1|neemc0,51,40,0|nkw13z,51,40,0|nkw140,57,66,1|nx4ozz,57,66,1|nx4p00,51,40,0|o3z2fz,51,40,0|o3z2g0,57,66,1|og7qbz,57,66,1|og7qc0,51,40,0|omp53z,51,40,0|omp540,57,66,1|oyxszz,57,66,1|oyxt00,51,40,0|p5f7rz,51,40,0|p5f7s0,57,66,1|phnvnz,57,66,1|phnvo0,51,40,0|po5afz,51,40,0|po5ag0,57,66,1|q0dybz,57,66,1|q0dyc0,51,40,0|q6vd3z,51,40,0|q6vd40,57,66,1|qj40zz,57,66,1|qj4100,51,40,0|qpyefz,51,40,0|qpyeg0,57,66,1|r272bz,57,66,1|r272c0,51,40,0|r8oh3z,51,40,0|r8oh40,57,66,1|rkx4zz,57,66,1|rkx500,51,40,0|rrejrz,51,40,0|rrejs0,57,66,1|s3n7nz,57,66,1|s3n7o0,51,40,0|sa4mfz,51,40,0|sa4mg0,57,66,1|smdabz,57,66,1|smdac0,51,40,0|ssup3z,51,40,0|ssup40,57,66,1|t53czz,57,66,1|t53d00,51,40,0|tbkrrz,51,40,0|tbkrs0,57,66,1|tntfnz,57,66,1|tntfo0,51,40,0|tunt3z,51,40,0|tunt40,57,66,1|u6wgzz,57,66,1|u6wh00,51,40,0|uddvrz,51,40,0|uddvs0,57,66,1|upmjnz,57,66,1|upmjo0,51,40,0|uw3yfz,51,40,0|uw3yg0,57,66,1|v8cmbz,57,66,1|v8cmc0,51,40,0|veu13z,51,40,0|veu140,57,66,1|vr2ozz,57,66,1|vr2p00,51,40,0|vxk3rz,51,40,0|vxk3s0,57,66,1|w9srnz,57,66,1|w9sro0,51,40,0|wgn53z,51,40,0|wgn540,57,66,1|wsvszz,57,66,1|wsvt00,51,40,0|wzd7rz,51,40,0|wzd7s0,57,66,1|xblvnz,57,66,1|xblvo0,51,40,0|xi3afz,51,40,0|xi3ag0,57,66,1|xubybz,57,66,1|xubyc0,51,40,0|y0td3z,51,40,0|y0td40,57,66,1|yd20zz,57,66,1|yd2100,51,40,0|yjjfrz,51,40,0|yjjfs0,57,66,1|yvs3nz,57,66,1|yvs3o0,51,40,0|z29ifz,51,40,0|z29ig0,57,66,1|zei6bz,57,66,1|zei6c0,51,40,0\",\"America/Whitehorse|,0,187,0|-1079tno,36,37,0|-qzoms1,36,37,0|-qzoms0,64,40,1|-qplqw1,64,40,1|-qplqw0,36,37,0|-qess41,36,37,0|-qess40,64,40,1|-q6kps1,64,40,1|-q6kps0,36,37,0|-ek1tg1,36,37,0|-ek1tg0,65,40,1|-cq2tg1,65,40,1|-cq2tg0,66,40,1|-cnos81,66,40,1|-cnos80,36,37,0|-2g1oc1,36,37,0|-2g1oc0,67,66,1|-26boc1,67,66,1|-26boc0,36,37,0|-1cspo1,36,37,0|-1cspo0,51,40,0|5dwefz,51,40,0|5dweg0,57,66,1|5n9czz,57,66,1|5n9d00,51,40,0|5wmh3z,51,40,0|5wmh40,57,66,1|65zfnz,57,66,1|65zfo0,51,40,0|6fcjrz,51,40,0|6fcjs0,57,66,1|6p2gzz,57,66,1|6p2h00,51,40,0|6y2mfz,51,40,0|6y2mg0,57,66,1|77sjnz,57,66,1|77sjo0,51,40,0|7h5nrz,51,40,0|7h5ns0,57,66,1|7qimbz,57,66,1|7qimc0,51,40,0|7zvqfz,51,40,0|7zvqg0,57,66,1|898ozz,57,66,1|898p00,51,40,0|8ilt3z,51,40,0|8ilt40,57,66,1|8ryrnz,57,66,1|8ryro0,51,40,0|908zrz,51,40,0|908zs0,57,66,1|9aoubz,57,66,1|9aouc0,51,40,0|9iz2fz,51,40,0|9iz2g0,57,66,1|9trvnz,57,66,1|9trvo0,51,40,0|a1p53z,51,40,0|a1p540,57,66,1|achybz,57,66,1|achyc0,51,40,0|akf7rz,51,40,0|akf7s0,57,66,1|av80zz,57,66,1|av8100,51,40,0|b3i93z,51,40,0|b3i940,57,66,1|bdy3nz,57,66,1|bdy3o0,51,40,0|bm8brz,51,40,0|bm8bs0,57,66,1|bwo6bz,57,66,1|bwo6c0,51,40,0|c4yefz,51,40,0|c4yeg0,57,66,1|cfr7nz,57,66,1|cfr7o0,51,40,0|cnoh3z,51,40,0|cnoh40,57,66,1|cyhabz,57,66,1|cyhac0,51,40,0|d6ejrz,51,40,0|d6ejs0,57,66,1|dh7czz,57,66,1|dh7d00,51,40,0|dphl3z,51,40,0|dphl40,57,66,1|dzxfnz,57,66,1|dzxfo0,51,40,0|e87nrz,51,40,0|e87ns0,57,66,1|einibz,57,66,1|einic0,51,40,0|eqxqfz,51,40,0|eqxqg0,57,66,1|f1dkzz,57,66,1|f1dl00,51,40,0|f9nt3z,51,40,0|f9nt40,57,66,1|fkgmbz,57,66,1|fkgmc0,51,40,0|fsdvrz,51,40,0|fsdvs0,57,66,1|g36ozz,57,66,1|g36p00,51,40,0|gb3yfz,51,40,0|gb3yg0,57,66,1|glwrnz,57,66,1|glwro0,51,40,0|gu6zrz,51,40,0|gu6zs0,57,66,1|h4mubz,57,66,1|h4muc0,51,40,0|hcx2fz,51,40,0|hcx2g0,57,66,1|hncwzz,57,66,1|hncx00,51,40,0|hvn53z,51,40,0|hvn540,57,66,1|i6fybz,57,66,1|i6fyc0,51,40,0|ied7rz,51,40,0|ied7s0,57,66,1|ip60zz,57,66,1|ip6100,51,40,0|ix3afz,51,40,0|ix3ag0,57,66,1|j7w3nz,57,66,1|j7w3o0,51,40,0|jeqh3z,51,40,0|jeqh40,57,66,1|jqz4zz,57,66,1|jqz500,51,40,0|jxgjrz,51,40,0|jxgjs0,57,66,1|k9p7nz,57,66,1|k9p7o0,51,40,0|kg6mfz,51,40,0|kg6mg0,57,66,1|ksfabz,57,66,1|ksfac0,51,40,0|kz9nrz,51,40,0|kz9ns0,57,66,1|lbibnz,57,66,1|lbibo0,51,40,0|lhzqfz,51,40,0|lhzqg0,57,66,1|lu8ebz,57,66,1|lu8ec0,51,40,0|m0pt3z,51,40,0|m0pt40,57,66,1|mcygzz,57,66,1|mcyh00,51,40,0|mjfvrz,51,40,0|mjfvs0,57,66,1|mvojnz,57,66,1|mvojo0,51,40,0|n25yfz,51,40,0|n25yg0,57,66,1|neembz,57,66,1|neemc0,51,40,0|nkw13z,51,40,0|nkw140,57,66,1|nx4ozz,57,66,1|nx4p00,51,40,0|o3z2fz,51,40,0|o3z2g0,57,66,1|og7qbz,57,66,1|og7qc0,51,40,0|omp53z,51,40,0|omp540,57,66,1|oyxszz,57,66,1|oyxt00,51,40,0|p5f7rz,51,40,0|p5f7s0,57,66,1|phnvnz,57,66,1|phnvo0,51,40,0|po5afz,51,40,0|po5ag0,57,66,1|q0dybz,57,66,1|q0dyc0,51,40,0|q6vd3z,51,40,0|q6vd40,57,66,1|qj3vfz,57,66,1|qj3vg0,50,66,0\",\"America/Winnipeg|,0,188,0|-171bfcc,45,62,0|-s0s7c1,45,62,0|-s0s7c0,46,63,1|-rt8241,46,63,1|-rt8240,45,62,0|-qzov41,45,62,0|-qzov40,46,63,1|-qplz81,46,63,1|-qplz80,45,62,0|-h11r41,45,62,0|-h11r40,46,63,1|-gu7j81,46,63,1|-gu7j80,45,62,0|-ek21s1,45,62,0|-ek21s0,47,63,1|-cq2tg1,47,63,1|-cq2tg0,48,63,1|-cnp0k1,48,63,1|-cnp0k0,45,62,0|-cc64g1,45,62,0|-cc64g0,46,63,1|-c490k1,46,63,1|-c490k0,45,62,0|-bu5z41,45,62,0|-bu5z40,46,63,1|-bm8v81,46,63,1|-bm8v80,45,62,0|-bbfwg1,45,62,0|-bbfwg0,46,63,1|-b3isk1,46,63,1|-b3isk0,45,62,0|-aspts1,45,62,0|-aspts0,46,63,1|-akspw1,46,63,1|-akspw0,45,62,0|-a9kxs1,45,62,0|-a9kxs0,46,63,1|-a1rj81,46,63,1|-a1rj80,45,62,0|-9qwps1,45,62,0|-9qwps0,46,63,1|-9izlw1,46,63,1|-9izlw0,45,62,0|-986n41,45,62,0|-986n40,46,63,1|-909j81,46,63,1|-909j80,45,62,0|-8pgkg1,45,62,0|-8pgkg0,46,63,1|-8hjgk1,46,63,1|-8hjgk0,45,62,0|-86qhs1,45,62,0|-86qhs0,46,63,1|-7ytdw1,46,63,1|-7ytdw0,45,62,0|-7o0f41,45,62,0|-7o0f40,46,63,1|-7g3b81,46,63,1|-7g3b80,45,62,0|-74xds1,45,62,0|-74xds0,46,63,1|-6x09w1,46,63,1|-6x09w0,45,62,0|-6m7b41,45,62,0|-6m7b40,46,63,1|-6ea781,46,63,1|-6ea780,45,62,0|-63h8g1,45,62,0|-63h8g0,46,63,1|-5vk4k1,46,63,1|-5vk4k0,45,62,0|-5kr5s1,45,62,0|-5kr5s0,46,63,1|-5be781,46,63,1|-5be780,45,62,0|-521341,45,62,0|-521340,46,63,1|-4u3z81,46,63,1|-4u3z80,45,62,0|-3hhwg1,45,62,0|-3hhwg0,46,63,1|-39xr81,46,63,1|-39xr80,45,62,0|-1xbog1,45,62,0|-1xbog0,46,63,1|-1nlog1,46,63,1|-1nlog0,45,62,0|-1e8n41,45,62,0|-1e8n40,46,63,1|-14vls1,46,63,1|-14vls0,45,62,0|-vikg1,45,62,0|-vikg0,46,63,1|-m5j41,46,63,1|-m5j40,45,62,0|-cshs1,45,62,0|-cshs0,46,63,1|-3fgg1,46,63,1|-3fgg0,45,62,0|5xkvz,45,62,0|5xkw0,46,63,1|fam7z,46,63,1|fam80,45,62,0|onnjz,45,62,0|onnk0,46,63,1|ydnjz,46,63,1|ydnk0,45,62,0|17qovz,45,62,0|17qow0,46,63,1|1h3q7z,46,63,1|1h3q80,45,62,0|1qgrjz,45,62,0|1qgrk0,46,63,1|1ztsvz,46,63,1|1ztsw0,45,62,0|296u7z,45,62,0|296u80,46,63,1|2ijvjz,46,63,1|2ijvk0,45,62,0|2rwwvz,45,62,0|2rwww0,46,63,1|319y7z,46,63,1|319y80,45,62,0|3amzjz,45,62,0|3amzk0,46,63,1|3kczjz,46,63,1|3kczk0,45,62,0|3td27z,45,62,0|3td280,46,63,1|43327z,46,63,1|433280,45,62,0|4cg3jz,45,62,0|4cg3k0,46,63,1|4lt4vz,46,63,1|4lt4w0,45,62,0|4v667z,45,62,0|4v6680,46,63,1|54j7jz,46,63,1|54j7k0,45,62,0|5dw8vz,45,62,0|5dw8w0,46,63,1|5n9a7z,46,63,1|5n9a80,45,62,0|5wmbjz,45,62,0|5wmbk0,46,63,1|65zcvz,46,63,1|65zcw0,45,62,0|6fce7z,45,62,0|6fce80,46,63,1|6p2e7z,46,63,1|6p2e80,45,62,0|6y2gvz,45,62,0|6y2gw0,46,63,1|77sgvz,46,63,1|77sgw0,45,62,0|7h5i7z,45,62,0|7h5i80,46,63,1|7qijjz,46,63,1|7qijk0,45,62,0|7zvkvz,45,62,0|7zvkw0,46,63,1|898m7z,46,63,1|898m80,45,62,0|8ilnjz,45,62,0|8ilnk0,46,63,1|8ryovz,46,63,1|8ryow0,45,62,0|908u7z,45,62,0|908u80,46,63,1|9aorjz,46,63,1|9aork0,45,62,0|9iywvz,45,62,0|9iyww0,46,63,1|9trsvz,46,63,1|9trsw0,45,62,0|a1ozjz,45,62,0|a1ozk0,46,63,1|achvjz,46,63,1|achvk0,45,62,0|akf27z,45,62,0|akf280,46,63,1|av7y7z,46,63,1|av7y80,45,62,0|b3i3jz,45,62,0|b3i3k0,46,63,1|bdy0vz,46,63,1|bdy0w0,45,62,0|bm867z,45,62,0|bm8680,46,63,1|bwo3jz,46,63,1|bwo3k0,45,62,0|c4y8vz,45,62,0|c4y8w0,46,63,1|cfr4vz,46,63,1|cfr4w0,45,62,0|cnobjz,45,62,0|cnobk0,46,63,1|cyh7jz,46,63,1|cyh7k0,45,62,0|d6ee7z,45,62,0|d6ee80,46,63,1|dh7a7z,46,63,1|dh7a80,45,62,0|dphfjz,45,62,0|dphfk0,46,63,1|dzxcvz,46,63,1|dzxcw0,45,62,0|e87i7z,45,62,0|e87i80,46,63,1|einfjz,46,63,1|einfk0,45,62,0|eqxkvz,45,62,0|eqxkw0,46,63,1|f1di7z,46,63,1|f1di80,45,62,0|f9nnjz,45,62,0|f9nnk0,46,63,1|fkgjjz,46,63,1|fkgjk0,45,62,0|fsdq7z,45,62,0|fsdq80,46,63,1|g36m7z,46,63,1|g36m80,45,62,0|gb3svz,45,62,0|gb3sw0,46,63,1|glwovz,46,63,1|glwow0,45,62,0|gu6u7z,45,62,0|gu6u80,46,63,1|h4mrjz,46,63,1|h4mrk0,45,62,0|hcwwvz,45,62,0|hcwww0,46,63,1|hncu7z,46,63,1|hncu80,45,62,0|hvmzjz,45,62,0|hvmzk0,46,63,1|i6fvjz,46,63,1|i6fvk0,45,62,0|ied27z,45,62,0|ied280,46,63,1|ip5y7z,46,63,1|ip5y80,45,62,0|ix34vz,45,62,0|ix34w0,46,63,1|j7vy3z,46,63,1|j7vy40,45,62,0|jeqbjz,45,62,0|jeqbk0,46,63,1|jqyzfz,46,63,1|jqyzg0,45,62,0|jxge7z,45,62,0|jxge80,46,63,1|k9p23z,46,63,1|k9p240,45,62,0|kg6gvz,45,62,0|kg6gw0,46,63,1|ksf4rz,46,63,1|ksf4s0,45,62,0|kz9i7z,45,62,0|kz9i80,46,63,1|lbi63z,46,63,1|lbi640,45,62,0|lhzkvz,45,62,0|lhzkw0,46,63,1|lu88rz,46,63,1|lu88s0,45,62,0|m0pnjz,45,62,0|m0pnk0,46,63,1|mcybfz,46,63,1|mcybg0,45,62,0|mjfq7z,45,62,0|mjfq80,46,63,1|mvoe3z,46,63,1|mvoe40,45,62,0|n25svz,45,62,0|n25sw0,46,63,1|neegrz,46,63,1|neegs0,45,62,0|nkvvjz,45,62,0|nkvvk0,46,63,1|nx4jfz,46,63,1|nx4jg0,45,62,0|o3ywvz,45,62,0|o3yww0,46,63,1|og7krz,46,63,1|og7ks0,45,62,0|omozjz,45,62,0|omozk0,46,63,1|oyxnfz,46,63,1|oyxng0,45,62,0|p5f27z,45,62,0|p5f280,46,63,1|phnq3z,46,63,1|phnq40,45,62,0|po54vz,45,62,0|po54w0,46,63,1|q0dsrz,46,63,1|q0dss0,45,62,0|q6v7jz,45,62,0|q6v7k0,46,63,1|qj3vfz,46,63,1|qj3vg0,45,62,0|qpy8vz,45,62,0|qpy8w0,46,63,1|r26wrz,46,63,1|r26ws0,45,62,0|r8objz,45,62,0|r8obk0,46,63,1|rkwzfz,46,63,1|rkwzg0,45,62,0|rree7z,45,62,0|rree80,46,63,1|s3n23z,46,63,1|s3n240,45,62,0|sa4gvz,45,62,0|sa4gw0,46,63,1|smd4rz,46,63,1|smd4s0,45,62,0|ssujjz,45,62,0|ssujk0,46,63,1|t537fz,46,63,1|t537g0,45,62,0|tbkm7z,45,62,0|tbkm80,46,63,1|tnta3z,46,63,1|tnta40,45,62,0|tunnjz,45,62,0|tunnk0,46,63,1|u6wbfz,46,63,1|u6wbg0,45,62,0|uddq7z,45,62,0|uddq80,46,63,1|upme3z,46,63,1|upme40,45,62,0|uw3svz,45,62,0|uw3sw0,46,63,1|v8cgrz,46,63,1|v8cgs0,45,62,0|vetvjz,45,62,0|vetvk0,46,63,1|vr2jfz,46,63,1|vr2jg0,45,62,0|vxjy7z,45,62,0|vxjy80,46,63,1|w9sm3z,46,63,1|w9sm40,45,62,0|wgmzjz,45,62,0|wgmzk0,46,63,1|wsvnfz,46,63,1|wsvng0,45,62,0|wzd27z,45,62,0|wzd280,46,63,1|xblq3z,46,63,1|xblq40,45,62,0|xi34vz,45,62,0|xi34w0,46,63,1|xubsrz,46,63,1|xubss0,45,62,0|y0t7jz,45,62,0|y0t7k0,46,63,1|yd1vfz,46,63,1|yd1vg0,45,62,0|yjja7z,45,62,0|yjja80,46,63,1|yvry3z,46,63,1|yvry40,45,62,0|z29cvz,45,62,0|z29cw0,46,63,1|zei0rz,46,63,1|zei0s0,45,62,0\",\"America/Yakutat|,0,189,0|-1hc7qjz,0,190,0|-1078vgi,0,190,0|-1078vgh,36,37,0|-ek1tg1,36,37,0|-ek1tg0,65,40,1|-cq2tg1,65,40,1|-cq2tg0,66,40,1|-cnos81,66,40,1|-cnos80,36,37,0|-cs9g1,36,37,0|-cs9g0,64,40,1|-3faw1,64,40,1|-3faw0,36,37,0|5xt7z,36,37,0|5xt80,64,40,1|farrz,64,40,1|fars0,36,37,0|onvvz,36,37,0|onvw0,64,40,1|ydt3z,64,40,1|ydt40,36,37,0|17qx7z,36,37,0|17qx80,64,40,1|1h3vrz,64,40,1|1h3vs0,36,37,0|1qgzvz,36,37,0|1qgzw0,64,40,1|1ztyfz,64,40,1|1ztyg0,36,37,0|23fnvz,36,37,0|23fnw0,64,40,1|2ik13z,64,40,1|2ik140,36,37,0|2ooh7z,36,37,0|2ooh80,64,40,1|31a3rz,64,40,1|31a3s0,36,37,0|3an7vz,36,37,0|3an7w0,64,40,1|3kd53z,64,40,1|3kd540,36,37,0|3tdajz,36,37,0|3tdak0,64,40,1|4337rz,64,40,1|4337s0,36,37,0|4cgbvz,36,37,0|4cgbw0,64,40,1|4ltafz,64,40,1|4ltag0,36,37,0|4v6ejz,36,37,0|4v6ek0,64,40,1|54jd3z,64,40,1|54jd40,36,37,0|5dwh7z,36,37,0|5dwh80,64,40,1|5n9frz,64,40,1|5n9fs0,36,37,0|5wmjvz,36,37,0|5wmjw0,64,40,1|65zifz,64,40,1|65zig0,36,37,0|6fcmjz,36,37,0|6fcmk0,64,40,1|6p2jrz,64,40,1|6p2js0,36,37,0|6y2p7z,36,37,0|6y2p80,64,40,1|77smfz,64,40,1|77smg0,36,37,0|79dybz,36,37,0|79dyc0,37,37,0|7h5qjz,37,37,0|7h5qk0,38,40,1|7qip3z,38,40,1|7qip40,37,37,0|7zvt7z,37,37,0|7zvt80,38,40,1|898rrz,38,40,1|898rs0,37,37,0|8ilvvz,37,37,0|8ilvw0,38,40,1|8ryufz,38,40,1|8ryug0,37,37,0|9092jz,37,37,0|9092k0,38,40,1|9aox3z,38,40,1|9aox40,37,37,0|9iz57z,37,37,0|9iz580,38,40,1|9tryfz,38,40,1|9tryg0,37,37,0|a1p7vz,37,37,0|a1p7w0,38,40,1|aci13z,38,40,1|aci140,37,37,0|akfajz,37,37,0|akfak0,38,40,1|av83rz,38,40,1|av83s0,37,37,0|b3ibvz,37,37,0|b3ibw0,38,40,1|bdy6fz,38,40,1|bdy6g0,37,37,0|bm8ejz,37,37,0|bm8ek0,38,40,1|bwo93z,38,40,1|bwo940,37,37,0|c4yh7z,37,37,0|c4yh80,38,40,1|cfrafz,38,40,1|cfrag0,37,37,0|cnojvz,37,37,0|cnojw0,38,40,1|cyhd3z,38,40,1|cyhd40,37,37,0|d6emjz,37,37,0|d6emk0,38,40,1|dh7frz,38,40,1|dh7fs0,37,37,0|dphnvz,37,37,0|dphnw0,38,40,1|dzxifz,38,40,1|dzxig0,37,37,0|e87qjz,37,37,0|e87qk0,38,40,1|einl3z,38,40,1|einl40,37,37,0|eqxt7z,37,37,0|eqxt80,38,40,1|f1dnrz,38,40,1|f1dns0,37,37,0|f9nvvz,37,37,0|f9nvw0,38,40,1|fkgp3z,38,40,1|fkgp40,37,37,0|fsdyjz,37,37,0|fsdyk0,38,40,1|g36rrz,38,40,1|g36rs0,37,37,0|gb417z,37,37,0|gb4180,38,40,1|glwufz,38,40,1|glwug0,37,37,0|gu72jz,37,37,0|gu72k0,38,40,1|h4mx3z,38,40,1|h4mx40,37,37,0|hcx57z,37,37,0|hcx580,38,40,1|hnczrz,38,40,1|hnczs0,37,37,0|hvn7vz,37,37,0|hvn7w0,38,40,1|i6g13z,38,40,1|i6g140,37,37,0|iedajz,37,37,0|iedak0,38,40,1|ip63rz,38,40,1|ip63s0,37,37,0|ix3d7z,37,37,0|ix3d80,38,40,1|j7w6fz,38,40,1|j7w6g0,37,37,0|jeqjvz,37,37,0|jeqjw0,38,40,1|jqz7rz,38,40,1|jqz7s0,37,37,0|jxgmjz,37,37,0|jxgmk0,38,40,1|k9pafz,38,40,1|k9pag0,37,37,0|kg6p7z,37,37,0|kg6p80,38,40,1|ksfd3z,38,40,1|ksfd40,37,37,0|kz9qjz,37,37,0|kz9qk0,38,40,1|lbiefz,38,40,1|lbieg0,37,37,0|lhzt7z,37,37,0|lhzt80,38,40,1|lu8h3z,38,40,1|lu8h40,37,37,0|m0pvvz,37,37,0|m0pvw0,38,40,1|mcyjrz,38,40,1|mcyjs0,37,37,0|mjfyjz,37,37,0|mjfyk0,38,40,1|mvomfz,38,40,1|mvomg0,37,37,0|n2617z,37,37,0|n26180,38,40,1|neep3z,38,40,1|neep40,37,37,0|nkw3vz,37,37,0|nkw3w0,38,40,1|nx4rrz,38,40,1|nx4rs0,37,37,0|o3z57z,37,37,0|o3z580,38,40,1|og7t3z,38,40,1|og7t40,37,37,0|omp7vz,37,37,0|omp7w0,38,40,1|oyxvrz,38,40,1|oyxvs0,37,37,0|p5fajz,37,37,0|p5fak0,38,40,1|phnyfz,38,40,1|phnyg0,37,37,0|po5d7z,37,37,0|po5d80,38,40,1|q0e13z,38,40,1|q0e140,37,37,0|q6vfvz,37,37,0|q6vfw0,38,40,1|qj43rz,38,40,1|qj43s0,37,37,0|qpyh7z,37,37,0|qpyh80,38,40,1|r2753z,38,40,1|r27540,37,37,0|r8ojvz,37,37,0|r8ojw0,38,40,1|rkx7rz,38,40,1|rkx7s0,37,37,0|rremjz,37,37,0|rremk0,38,40,1|s3nafz,38,40,1|s3nag0,37,37,0|sa4p7z,37,37,0|sa4p80,38,40,1|smdd3z,38,40,1|smdd40,37,37,0|ssurvz,37,37,0|ssurw0,38,40,1|t53frz,38,40,1|t53fs0,37,37,0|tbkujz,37,37,0|tbkuk0,38,40,1|tntifz,38,40,1|tntig0,37,37,0|tunvvz,37,37,0|tunvw0,38,40,1|u6wjrz,38,40,1|u6wjs0,37,37,0|uddyjz,37,37,0|uddyk0,38,40,1|upmmfz,38,40,1|upmmg0,37,37,0|uw417z,37,37,0|uw4180,38,40,1|v8cp3z,38,40,1|v8cp40,37,37,0|veu3vz,37,37,0|veu3w0,38,40,1|vr2rrz,38,40,1|vr2rs0,37,37,0|vxk6jz,37,37,0|vxk6k0,38,40,1|w9sufz,38,40,1|w9sug0,37,37,0|wgn7vz,37,37,0|wgn7w0,38,40,1|wsvvrz,38,40,1|wsvvs0,37,37,0|wzdajz,37,37,0|wzdak0,38,40,1|xblyfz,38,40,1|xblyg0,37,37,0|xi3d7z,37,37,0|xi3d80,38,40,1|xuc13z,38,40,1|xuc140,37,37,0|y0tfvz,37,37,0|y0tfw0,38,40,1|yd23rz,38,40,1|yd23s0,37,37,0|yjjijz,37,37,0|yjjik0,38,40,1|yvs6fz,38,40,1|yvs6g0,37,37,0|z29l7z,37,37,0|z29l80,38,40,1|zei93z,38,40,1|zei940,37,37,0\",\"America/Yellowknife|,60,1,0|-i9m2o0,50,66,0|-ek1z01,50,66,0|-ek1z00,58,62,1|-cq2tg1,58,62,1|-cq2tg0,59,62,1|-cnoxs1,59,62,1|-cnoxs0,50,66,0|-2g1tw1,50,66,0|-2g1tw0,61,63,1|-26btw1,61,63,1|-26btw0,50,66,0|5dwbnz,50,66,0|5dwbo0,52,62,1|5n9a7z,52,62,1|5n9a80,50,66,0|5wmebz,50,66,0|5wmec0,52,62,1|65zcvz,52,62,1|65zcw0,50,66,0|6fcgzz,50,66,0|6fch00,52,62,1|6p2e7z,52,62,1|6p2e80,50,66,0|6y2jnz,50,66,0|6y2jo0,52,62,1|77sgvz,52,62,1|77sgw0,50,66,0|7h5kzz,50,66,0|7h5l00,52,62,1|7qijjz,52,62,1|7qijk0,50,66,0|7zvnnz,50,66,0|7zvno0,52,62,1|898m7z,52,62,1|898m80,50,66,0|8ilqbz,50,66,0|8ilqc0,52,62,1|8ryovz,52,62,1|8ryow0,50,66,0|908wzz,50,66,0|908x00,52,62,1|9aorjz,52,62,1|9aork0,50,66,0|9iyznz,50,66,0|9iyzo0,52,62,1|9trsvz,52,62,1|9trsw0,50,66,0|a1p2bz,50,66,0|a1p2c0,52,62,1|achvjz,52,62,1|achvk0,50,66,0|akf4zz,50,66,0|akf500,52,62,1|av7y7z,52,62,1|av7y80,50,66,0|b3i6bz,50,66,0|b3i6c0,52,62,1|bdy0vz,52,62,1|bdy0w0,50,66,0|bm88zz,50,66,0|bm8900,52,62,1|bwo3jz,52,62,1|bwo3k0,50,66,0|c4ybnz,50,66,0|c4ybo0,52,62,1|cfr4vz,52,62,1|cfr4w0,50,66,0|cnoebz,50,66,0|cnoec0,52,62,1|cyh7jz,52,62,1|cyh7k0,50,66,0|d6egzz,50,66,0|d6eh00,52,62,1|dh7a7z,52,62,1|dh7a80,50,66,0|dphibz,50,66,0|dphic0,52,62,1|dzxcvz,52,62,1|dzxcw0,50,66,0|e87kzz,50,66,0|e87l00,52,62,1|einfjz,52,62,1|einfk0,50,66,0|eqxnnz,50,66,0|eqxno0,52,62,1|f1di7z,52,62,1|f1di80,50,66,0|f9nqbz,50,66,0|f9nqc0,52,62,1|fkgjjz,52,62,1|fkgjk0,50,66,0|fsdszz,50,66,0|fsdt00,52,62,1|g36m7z,52,62,1|g36m80,50,66,0|gb3vnz,50,66,0|gb3vo0,52,62,1|glwovz,52,62,1|glwow0,50,66,0|gu6wzz,50,66,0|gu6x00,52,62,1|h4mrjz,52,62,1|h4mrk0,50,66,0|hcwznz,50,66,0|hcwzo0,52,62,1|hncu7z,52,62,1|hncu80,50,66,0|hvn2bz,50,66,0|hvn2c0,52,62,1|i6fvjz,52,62,1|i6fvk0,50,66,0|ied4zz,50,66,0|ied500,52,62,1|ip5y7z,52,62,1|ip5y80,50,66,0|ix37nz,50,66,0|ix37o0,52,62,1|j7w0vz,52,62,1|j7w0w0,50,66,0|jeqebz,50,66,0|jeqec0,52,62,1|jqz27z,52,62,1|jqz280,50,66,0|jxggzz,50,66,0|jxgh00,52,62,1|k9p4vz,52,62,1|k9p4w0,50,66,0|kg6jnz,50,66,0|kg6jo0,52,62,1|ksf7jz,52,62,1|ksf7k0,50,66,0|kz9kzz,50,66,0|kz9l00,52,62,1|lbi8vz,52,62,1|lbi8w0,50,66,0|lhznnz,50,66,0|lhzno0,52,62,1|lu8bjz,52,62,1|lu8bk0,50,66,0|m0pqbz,50,66,0|m0pqc0,52,62,1|mcye7z,52,62,1|mcye80,50,66,0|mjfszz,50,66,0|mjft00,52,62,1|mvogvz,52,62,1|mvogw0,50,66,0|n25vnz,50,66,0|n25vo0,52,62,1|neejjz,52,62,1|neejk0,50,66,0|nkvybz,50,66,0|nkvyc0,52,62,1|nx4m7z,52,62,1|nx4m80,50,66,0|o3yznz,50,66,0|o3yzo0,52,62,1|og7njz,52,62,1|og7nk0,50,66,0|omp2bz,50,66,0|omp2c0,52,62,1|oyxq7z,52,62,1|oyxq80,50,66,0|p5f4zz,50,66,0|p5f500,52,62,1|phnsvz,52,62,1|phnsw0,50,66,0|po57nz,50,66,0|po57o0,52,62,1|q0dvjz,52,62,1|q0dvk0,50,66,0|q6vabz,50,66,0|q6vac0,52,62,1|qj3y7z,52,62,1|qj3y80,50,66,0|qpybnz,50,66,0|qpybo0,52,62,1|r26zjz,52,62,1|r26zk0,50,66,0|r8oebz,50,66,0|r8oec0,52,62,1|rkx27z,52,62,1|rkx280,50,66,0|rregzz,50,66,0|rreh00,52,62,1|s3n4vz,52,62,1|s3n4w0,50,66,0|sa4jnz,50,66,0|sa4jo0,52,62,1|smd7jz,52,62,1|smd7k0,50,66,0|ssumbz,50,66,0|ssumc0,52,62,1|t53a7z,52,62,1|t53a80,50,66,0|tbkozz,50,66,0|tbkp00,52,62,1|tntcvz,52,62,1|tntcw0,50,66,0|tunqbz,50,66,0|tunqc0,52,62,1|u6we7z,52,62,1|u6we80,50,66,0|uddszz,50,66,0|uddt00,52,62,1|upmgvz,52,62,1|upmgw0,50,66,0|uw3vnz,50,66,0|uw3vo0,52,62,1|v8cjjz,52,62,1|v8cjk0,50,66,0|vetybz,50,66,0|vetyc0,52,62,1|vr2m7z,52,62,1|vr2m80,50,66,0|vxk0zz,50,66,0|vxk100,52,62,1|w9sovz,52,62,1|w9sow0,50,66,0|wgn2bz,50,66,0|wgn2c0,52,62,1|wsvq7z,52,62,1|wsvq80,50,66,0|wzd4zz,50,66,0|wzd500,52,62,1|xblsvz,52,62,1|xblsw0,50,66,0|xi37nz,50,66,0|xi37o0,52,62,1|xubvjz,52,62,1|xubvk0,50,66,0|y0tabz,50,66,0|y0tac0,52,62,1|yd1y7z,52,62,1|yd1y80,50,66,0|yjjczz,50,66,0|yjjd00,52,62,1|yvs0vz,52,62,1|yvs0w0,50,66,0|z29fnz,50,66,0|z29fo0,52,62,1|zei3jz,52,62,1|zei3k0,50,66,0\",\"Antarctica/Casey|,60,1,0|-irxc0,89,191,0|kro7bz,89,191,0|kro7c0,90,192,0|kyrizz,90,192,0|kyrj00,89,191,0|ltqknz,89,191,0|ltqko0,90,192,0|lzr5vz,90,192,0|lzr5w0,89,191,0|ofen3z,89,191,0|ofen40,90,192,0|p5dwjz,90,192,0|p5dwk0,89,191,0|pg70vz,89,191,0|pg70w0,90,192,0|pogv3z,90,192,0|pogv40,89,191,0|pytbfz,89,191,0|pytbg0,90,192,0|q6tz3z,90,192,0|q6tz40,89,191,0|qhmv5n,89,191,0|qhmv5o,90,192,0\",\"Antarctica/Davis|,60,1,0|-6rmdc0,91,193,0|-2p2zg1,91,193,0|-2p2zg0,60,1,0|-h6io1,60,1,0|-h6io0,91,193,0|kroa3z,91,193,0|kroa40,92,194,0|kz30vz,92,194,0|kz30w0,91,193,0|ltqnfz,91,193,0|ltqng0,92,194,0|lzre7z,92,194,0|lzre80,91,193,0\",\"Antarctica/DumontDUrville|,60,1,0|-c05eo0,93,195,0|-9dkmg1,93,195,0|-9dkmg0,60,1,0|-6vdk01,60,1,0|-6vdk00,93,195,0\",\"Antarctica/Macquarie|,60,1,0|-10mb9c0,94,195,0|-rsj4w1,94,195,0|-rsj4w0,95,192,1|-rjj281,95,192,1|-rjj280,94,195,0|-qhmeg1,94,195,0|-qhmeg0,60,1,0|-bd1xc1,60,1,0|-bd1xc0,94,195,0|-16cow1,94,195,0|-16cow0,95,192,1|-wznk1,95,192,1|-wznk0,94,195,0|-m6rk1,94,195,0|-m6rk0,95,192,1|-fcgw1,95,192,1|-fcgw0,94,195,0|-3gow1,94,195,0|-3gow0,95,192,1|3dlrz,95,192,1|3dls0,94,195,0|f9drz,94,195,0|f9ds0,95,192,1|mgn3z,95,192,1|mgn40,94,195,0|ycf3z,94,195,0|ycf40,95,192,1|14gsfz,95,192,1|14gsg0,94,195,0|1h2hrz,94,195,0|1h2hs0,95,192,1|1njtrz,95,192,1|1njts0,94,195,0|1zskfz,94,195,0|1zskg0,95,192,1|269wfz,95,192,1|269wg0,94,195,0|2iin3z,94,195,0|2iin40,95,192,1|2ozz3z,95,192,1|2ozz40,94,195,0|318prz,94,195,0|318ps0,95,192,1|3830fz,95,192,1|3830g0,94,195,0|3kbr3z,94,195,0|3kbr40,95,192,1|3qt33z,95,192,1|3qt340,94,195,0|431trz,94,195,0|431ts0,95,192,1|49j5rz,95,192,1|49j5s0,94,195,0|4lrwfz,94,195,0|4lrwg0,95,192,1|4s98fz,95,192,1|4s98g0,94,195,0|54hz3z,94,195,0|54hz40,95,192,1|5azb3z,95,192,1|5azb40,94,195,0|5n81rz,94,195,0|5n81s0,95,192,1|5tpdrz,95,192,1|5tpds0,94,195,0|65y4fz,94,195,0|65y4g0,95,192,1|6dvb3z,95,192,1|6dvb40,94,195,0|6p15rz,94,195,0|6p15s0,95,192,1|6wldrz,95,192,1|6wlds0,94,195,0|77r8fz,94,195,0|77r8g0,95,192,1|7e8kfz,95,192,1|7e8kg0,94,195,0|7qhb3z,94,195,0|7qhb40,95,192,1|7wyn3z,95,192,1|7wyn40,94,195,0|897drz,94,195,0|897ds0,95,192,1|8foprz,95,192,1|8fops0,94,195,0|8rkhrz,94,195,0|8rkhs0,95,192,1|8z4prz,95,192,1|8z4ps0,94,195,0|9anj3z,94,195,0|9anj40,95,192,1|9i7r3z,95,192,1|9i7r40,94,195,0|9tqkfz,94,195,0|9tqkg0,95,192,1|a0xtrz,95,192,1|a0xts0,94,195,0|acgn3z,94,195,0|acgn40,95,192,1|ajnwfz,95,192,1|ajnwg0,94,195,0|av6prz,94,195,0|av6ps0,95,192,1|b33wfz,95,192,1|b33wg0,94,195,0|bctwfz,94,195,0|bctwg0,95,192,1|bltz3z,95,192,1|bltz40,94,195,0|bvjz3z,94,195,0|bvjz40,95,192,1|c4k1rz,95,192,1|c4k1s0,94,195,0|cea1rz,94,195,0|cea1s0,95,192,1|cna4fz,95,192,1|cna4g0,94,195,0|cx04fz,94,195,0|cx04g0,95,192,1|d6073z,95,192,1|d60740,94,195,0|dfq73z,94,195,0|dfq740,95,192,1|dp38fz,95,192,1|dp38g0,94,195,0|dyt8fz,94,195,0|dyt8g0,95,192,1|e7tb3z,95,192,1|e7tb40,94,195,0|ehjb3z,94,195,0|ehjb40,95,192,1|eqjdrz,95,192,1|eqjds0,94,195,0|f09drz,94,195,0|f09ds0,95,192,1|f99gfz,95,192,1|f99gg0,94,195,0|fizgfz,94,195,0|fizgg0,95,192,1|frzj3z,95,192,1|frzj40,94,195,0|fzwprz,94,195,0|fzwps0,95,192,1|gaplrz,95,192,1|gapls0,94,195,0|gkskfz,94,195,0|gkskg0,95,192,1|gtsn3z,95,192,1|gtsn40,94,195,0|h3in3z,94,195,0|h3in40,95,192,1|hciprz,95,192,1|hcips0,94,195,0|hm8prz,94,195,0|hm8ps0,95,192,1|hv8sfz,95,192,1|hv8sg0,94,195,0|i4ysfz,94,195,0|i4ysg0,95,192,1|idyv3z,95,192,1|idyv40,94,195,0|inov3z,94,195,0|inov40,95,192,1|ix1wfz,95,192,1|ix1wg0,94,195,0|j6exrz,94,195,0|j6exs0,95,192,1|jff0fz,95,192,1|jff0g0,94,195,0|jphz3z,94,195,0|jphz40,95,192,1|jyv0fz,95,192,1|jyv0g0,94,195,0|k881rz,94,195,0|k881s0,95,192,1|khl33z,95,192,1|khl340,94,195,0|kqy4fz,94,195,0|kqy4g0,95,192,1|lj18fz,95,192,1|lj18g0,94,195,0|lse9rz,94,195,0|lse9s0,95,192,1|m1rb3z,95,192,1|m1rb40,94,195,0|mbhb3z,94,195,0|mbhb40,95,192,1|mkucfz,95,192,1|mkucg0,94,195,0|mu7drz,94,195,0|mu7ds0,95,192,1|n3kf3z,95,192,1|n3kf40,94,195,0|ncxgfz,94,195,0|ncxgg0,95,192,1|nmahrz,95,192,1|nmahs0,94,195,0|nvnj3z,94,195,0|nvnj40,95,192,1|o50kfz,95,192,1|o50kg0,94,195,0|oedlrz,94,195,0|oedls0,95,192,1|onqn3z,95,192,1|onqn40,94,195,0|ox3ofz,94,195,0|ox3og0,95,192,1|p6gprz,95,192,1|p6gps0,94,195,0|pg6prz,94,195,0|pg6ps0,95,192,1|ppjr3z,95,192,1|ppjr40,94,195,0|pywsfz,94,195,0|pywsg0,95,192,1|q89trz,95,192,1|q89ts0,94,195,0|qhmv3z,94,195,0|qhmv40,95,192,1|qqzwfz,95,192,1|qqzwg0,94,195,0|r0cxrz,94,195,0|r0cxs0,95,192,1|r9pz3z,95,192,1|r9pz40,94,195,0|rj30fz,94,195,0|rj30g0,95,192,1|rsg1rz,95,192,1|rsg1s0,94,195,0|s1t33z,94,195,0|s1t340,95,192,1|sbj33z,95,192,1|sbj340,94,195,0|skw4fz,94,195,0|skw4g0,95,192,1|su95rz,95,192,1|su95s0,94,195,0|t3m73z,94,195,0|t3m740,95,192,1|tcz8fz,95,192,1|tcz8g0,94,195,0|tmc9rz,94,195,0|tmc9s0,95,192,1|tvpb3z,95,192,1|tvpb40,94,195,0|u52cfz,94,195,0|u52cg0,95,192,1|uefdrz,95,192,1|uefds0,94,195,0|unsf3z,94,195,0|unsf40,95,192,1|ux5gfz,95,192,1|ux5gg0,94,195,0|v6vgfz,94,195,0|v6vgg0,95,192,1|vg8hrz,95,192,1|vg8hs0,94,195,0|vplj3z,94,195,0|vplj40,95,192,1|vyykfz,95,192,1|vyykg0,94,195,0|w8blrz,94,195,0|w8bls0,95,192,1|whon3z,95,192,1|whon40,94,195,0|wr1ofz,94,195,0|wr1og0,95,192,1|x0eprz,95,192,1|x0eps0,94,195,0|x9rr3z,94,195,0|x9rr40,95,192,1|xj4sfz,95,192,1|xj4sg0,94,195,0|xshtrz,94,195,0|xshts0,95,192,1|y1uv3z,95,192,1|y1uv40,94,195,0|ybkv3z,94,195,0|ybkv40,95,192,1|ykxwfz,95,192,1|ykxwg0,94,195,0|yuaxrz,94,195,0|yuaxs0,95,192,1|z3nz3z,95,192,1|z3nz40,94,195,0|zd10fz,94,195,0|zd10g0,95,192,1\",\"Antarctica/Mawson|,60,1,0|-8aelc0,96,196,0|krocvz,96,196,0|krocw0,92,194,0\",\"Antarctica/McMurdo|,0,197,0|-1gsoz14,97,198,0|-m01p21,97,198,0|-m01p20,98,199,1|-ltxei1,98,199,1|-ltxei0,97,198,0|-lieie1,97,198,0|-lieie0,98,200,1|-lahd41,98,200,1|-lahd40,97,198,0|-kzofq1,97,198,0|-kzofq0,98,200,1|-krrag1,98,200,1|-krrag0,97,198,0|-kgyd21,97,198,0|-kgyd20,98,200,1|-k917s1,98,200,1|-k917s0,97,198,0|-jy8ae1,97,198,0|-jy8ae0,98,200,1|-jpy6g1,98,200,1|-jpy6g0,97,198,0|-jfi7q1,97,198,0|-jfi7q0,98,200,1|-j783s1,98,200,1|-j783s0,97,198,0|-iws521,97,198,0|-iws520,98,200,1|-imc941,98,200,1|-imc940,97,198,0|-ief121,97,198,0|-ief120,98,200,1|-i3m6g1,98,200,1|-i3m6g0,97,198,0|-hvoye1,97,198,0|-hvoye0,98,200,1|-hkw3s1,98,200,1|-hkw3s0,97,198,0|-hcyvq1,97,198,0|-hcyvq0,98,200,1|-h26141,98,200,1|-h26140,97,198,0|-gu8t21,97,198,0|-gu8t20,98,200,1|-gjfyg1,98,200,1|-gjfyg0,97,198,0|-gbiqe1,97,198,0|-gbiqe0,98,200,1|-g0cx41,98,200,1|-g0cx40,97,198,0|-fssnq1,97,198,0|-fssnq0,98,200,1|-fhmug1,98,200,1|-fhmug0,97,198,0|-f9pme1,97,198,0|-f9pme0,98,200,1|-ciy9c1,98,200,1|-ciy9c0,98,200,0|2ivg7z,98,200,0|2ivg80,99,201,1|2omuvz,99,201,1|2omuw0,98,200,0|318k7z,98,200,0|318k80,99,201,1|382uvz,99,201,1|382uw0,98,200,0|3kbljz,98,200,0|3kblk0,99,201,1|3qsxjz,99,201,1|3qsxk0,98,200,0|431o7z,98,200,0|431o80,99,201,1|49j07z,99,201,1|49j080,98,200,0|4lrqvz,98,200,0|4lrqw0,99,201,1|4s92vz,99,201,1|4s92w0,98,200,0|54htjz,98,200,0|54htk0,99,201,1|5az5jz,99,201,1|5az5k0,98,200,0|5n7w7z,98,200,0|5n7w80,99,201,1|5tp87z,99,201,1|5tp880,98,200,0|65xyvz,98,200,0|65xyw0,99,201,1|6cs9jz,99,201,1|6cs9k0,98,200,0|6p107z,98,200,0|6p1080,99,201,1|6vic7z,99,201,1|6vic80,98,200,0|77r2vz,98,200,0|77r2w0,99,201,1|7e8evz,99,201,1|7e8ew0,98,200,0|7qh5jz,98,200,0|7qh5k0,99,201,1|7wyhjz,99,201,1|7wyhk0,98,200,0|89787z,98,200,0|897880,99,201,1|8fok7z,99,201,1|8fok80,98,200,0|8rxavz,98,200,0|8rxaw0,99,201,1|8yemvz,99,201,1|8yemw0,98,200,0|9andjz,98,200,0|9andk0,99,201,1|9hho7z,99,201,1|9hho80,98,200,0|9tqevz,98,200,0|9tqew0,99,201,1|a07qvz,99,201,1|a07qw0,98,200,0|abdljz,98,200,0|abdlk0,99,201,1|ajnqvz,99,201,1|ajnqw0,98,200,0|au3o7z,98,200,0|au3o80,99,201,1|b2dtjz,99,201,1|b2dtk0,98,200,0|bctqvz,98,200,0|bctqw0,99,201,1|bl3w7z,99,201,1|bl3w80,98,200,0|bvjtjz,98,200,0|bvjtk0,99,201,1|c46xjz,99,201,1|c46xk0,98,200,0|ce9w7z,98,200,0|ce9w80,99,201,1|cmx07z,99,201,1|cmx080,98,200,0|cwzyvz,98,200,0|cwzyw0,99,201,1|d5n2vz,99,201,1|d5n2w0,98,200,0|dfq1jz,98,200,0|dfq1k0,99,201,1|dod5jz,99,201,1|dod5k0,98,200,0|dyt2vz,98,200,0|dyt2w0,99,201,1|e7387z,99,201,1|e73880,98,200,0|ehj5jz,98,200,0|ehj5k0,99,201,1|eptavz,99,201,1|eptaw0,98,200,0|f0987z,98,200,0|f09880,99,201,1|f8wc7z,99,201,1|f8wc80,98,200,0|fizavz,98,200,0|fizaw0,99,201,1|frmevz,99,201,1|frmew0,98,200,0|g1pdjz,98,200,0|g1pdk0,99,201,1|gachjz,99,201,1|gachk0,98,200,0|gksevz,98,200,0|gksew0,99,201,1|gt2k7z,99,201,1|gt2k80,98,200,0|h3ihjz,98,200,0|h3ihk0,99,201,1|hbsmvz,99,201,1|hbsmw0,98,200,0|hm8k7z,98,200,0|hm8k80,99,201,1|huvo7z,99,201,1|huvo80,98,200,0|i4ymvz,98,200,0|i4ymw0,99,201,1|idlqvz,99,201,1|idlqw0,98,200,0|inopjz,98,200,0|inopk0,99,201,1|iwbtjz,99,201,1|iwbtk0,98,200,0|j6es7z,98,200,0|j6es80,99,201,1|jf1w7z,99,201,1|jf1w80,98,200,0|jp4uvz,98,200,0|jp4uw0,99,201,1|jyuuvz,99,201,1|jyuuw0,98,200,0|k7uxjz,98,200,0|k7uxk0,99,201,1|khkxjz,99,201,1|khkxk0,98,200,0|kql07z,98,200,0|kql080,99,201,1|l0b07z,99,201,1|l0b080,98,200,0|l9b2vz,98,200,0|l9b2w0,99,201,1|lj12vz,99,201,1|lj12w0,98,200,0|ls15jz,98,200,0|ls15k0,99,201,1|m1r5jz,99,201,1|m1r5k0,98,200,0|mb46vz,98,200,0|mb46w0,99,201,1|mku6vz,99,201,1|mku6w0,98,200,0|mtu9jz,98,200,0|mtu9k0,99,201,1|n3k9jz,99,201,1|n3k9k0,98,200,0|nckc7z,98,200,0|nckc80,99,201,1|nmac7z,99,201,1|nmac80,98,200,0|nvaevz,98,200,0|nvaew0,99,201,1|o50evz,99,201,1|o50ew0,98,200,0|oe0hjz,98,200,0|oe0hk0,99,201,1|onqhjz,99,201,1|onqhk0,98,200,0|owqk7z,98,200,0|owqk80,99,201,1|p6gk7z,99,201,1|p6gk80,98,200,0|pftljz,98,200,0|pftlk0,99,201,1|ppjljz,99,201,1|ppjlk0,98,200,0|pyjo7z,98,200,0|pyjo80,99,201,1|q89o7z,99,201,1|q89o80,98,200,0|qh9qvz,98,200,0|qh9qw0,99,201,1|qqzqvz,99,201,1|qqzqw0,98,200,0|qzztjz,98,200,0|qzztk0,99,201,1|r9ptjz,99,201,1|r9ptk0,98,200,0|ripw7z,98,200,0|ripw80,99,201,1|rsfw7z,99,201,1|rsfw80,98,200,0|s1fyvz,98,200,0|s1fyw0,99,201,1|sbixjz,99,201,1|sbixk0,98,200,0|skj07z,98,200,0|skj080,99,201,1|su907z,99,201,1|su9080,98,200,0|t392vz,98,200,0|t392w0,99,201,1|tcz2vz,99,201,1|tcz2w0,98,200,0|tlz5jz,98,200,0|tlz5k0,99,201,1|tvp5jz,99,201,1|tvp5k0,98,200,0|u4p87z,98,200,0|u4p880,99,201,1|uef87z,99,201,1|uef880,98,200,0|unfavz,98,200,0|unfaw0,99,201,1|ux5avz,99,201,1|ux5aw0,98,200,0|v6ic7z,98,200,0|v6ic80,99,201,1|vg8c7z,99,201,1|vg8c80,98,200,0|vp8evz,98,200,0|vp8ew0,99,201,1|vyyevz,99,201,1|vyyew0,98,200,0|w7yhjz,98,200,0|w7yhk0,99,201,1|whohjz,99,201,1|whohk0,98,200,0|wqok7z,98,200,0|wqok80,99,201,1|x0ek7z,99,201,1|x0ek80,98,200,0|x9emvz,98,200,0|x9emw0,99,201,1|xj4mvz,99,201,1|xj4mw0,98,200,0|xs4pjz,98,200,0|xs4pk0,99,201,1|y1upjz,99,201,1|y1upk0,98,200,0|yb7qvz,98,200,0|yb7qw0,99,201,1|ykxqvz,99,201,1|ykxqw0,98,200,0|ytxtjz,98,200,0|ytxtk0,99,201,1|z3ntjz,99,201,1|z3ntk0,98,200,0|zcnw7z,98,200,0|zcnw80,99,201,1\",\"Antarctica/Palmer|,60,1,0|-2lxhc0,39,44,1|-2ivzo1,39,44,1|-2ivzo0,42,42,0|-275ow1,42,42,0|-275ow0,39,44,1|-2042c1,39,44,1|-2042c0,42,42,0|-1odrk1,42,42,0|-1odrk0,39,44,1|-1fovo1,39,44,1|-1fovo0,42,42,0|-16brk1,42,42,0|-16brk0,39,44,1|-wluc1,39,44,1|-wluc0,42,42,0|-n8q81,42,42,0|-n8q80,39,44,1|-dvro1,39,44,1|-dvro0,42,42,0|-4ink1,42,42,0|-4ink0,39,44,0|24aizz,39,44,0|24aj00,40,45,1|29bxjz,40,45,1|29bxk0,39,44,0|6fn4bz,39,44,0|6fn4c0,42,42,0|6nz73z,42,42,0|6nz740,39,44,1|6vwazz,39,44,1|6vwb00,42,42,0|76p9rz,42,42,0|76p9s0,39,44,1|7emdnz,39,44,1|7emdo0,42,42,0|7psb3z,42,42,0|7psb40,39,44,1|7xcgbz,39,44,1|7xcgc0,42,42,0|88idrz,42,42,0|88ids0,39,44,1|8g2izz,39,44,1|8g2j00,42,42,0|8r8gfz,42,42,0|8r8gg0,39,44,1|90lezz,39,44,1|90lf00,42,42,0|99yj3z,42,42,0|99yj40,39,44,1|9hvmzz,39,44,1|9hvn00,42,42,0|9solrz,42,42,0|9sols0,39,44,1|a0lpnz,39,44,1|a0lpo0,42,42,0|abrn3z,42,42,0|abrn40,39,44,1|ajbsbz,39,44,1|ajbsc0,42,42,0|at1v3z,42,42,0|at1v40,39,44,1|b21uzz,39,44,1|b21v00,42,42,0|bd7sfz,42,42,0|bd7sg0,39,44,1|bl4wbz,39,44,1|bl4wc0,42,42,0|bvxv3z,42,42,0|bvxv40,39,44,1|c3uyzz,39,44,1|c3uz00,42,42,0|cenxrz,42,42,0|cenxs0,39,44,1|cml1nz,39,44,1|cml1o0,42,42,0|cxe0fz,42,42,0|cxe0g0,39,44,1|d5b4bz,39,44,1|d5b4c0,42,42,0|dgh1rz,42,42,0|dgh1s0,39,44,1|do16zz,39,44,1|do1700,42,42,0|dz74fz,42,42,0|dz74g0,39,44,1|e7u5nz,39,44,1|e7u5o0,42,42,0|ehx73z,42,42,0|ehx740,39,44,1|epuazz,39,44,1|epub00,42,42,0|ezxcfz,42,42,0|ezxcg0,39,44,1|f9n9nz,39,44,1|f9n9o0,42,42,0|fjdcfz,42,42,0|fjdcg0,39,44,1|fragbz,39,44,1|fragc0,42,42,0|g2gdrz,42,42,0|g2gds0,39,44,1|ga0izz,39,44,1|ga0j00,42,42,0|gl6gfz,42,42,0|gl6gg0,39,44,1|gsqlnz,39,44,1|gsqlo0,42,42,0|h3wj3z,42,42,0|h3wj40,39,44,1|hbgobz,39,44,1|hbgoc0,42,42,0|hmmlrz,42,42,0|hmmls0,39,44,1|hujpnz,39,44,1|hujpo0,42,42,0|i5cofz,42,42,0|i5cog0,39,44,1|id9sbz,39,44,1|id9sc0,42,42,0|io2r3z,42,42,0|io2r40,39,44,1|ivzuzz,39,44,1|ivzv00,42,42,0|j75sfz,42,42,0|j75sg0,39,44,1|jepxnz,39,44,1|jepxo0,42,42,0|jpvv3z,42,42,0|jpvv40,39,44,1|jyiwbz,39,44,1|jyiwc0,42,42,0|k8lxrz,42,42,0|k8lxs0,39,44,1|kgj1nz,39,44,1|kgj1o0,42,42,0|krc0fz,42,42,0|krc0g0,39,44,1|l0c0bz,39,44,1|l0c0c0,42,42,0|la233z,42,42,0|la2340,39,44,1|lkuwbz,39,44,1|lkuwc0,42,42,0|lq9f3z,42,42,0|lq9f40,39,44,1|m380bz,39,44,1|m380c0,42,42,0|m9pf3z,42,42,0|m9pf40,39,44,1|mly2zz,39,44,1|mly300,42,42,0|mssgfz,42,42,0|mssgg0,39,44,1|n4o5nz,39,44,1|n4o5o0,42,42,0|nbij3z,42,42,0|nbij40,39,44,1|o776zz,39,44,1|o77700,42,42,0|obvsfz,42,42,0|obvsg0,39,44,1|ohn4bz,39,44,1|ohn4c0,39,44,0\",\"Antarctica/Rothera|,60,1,0|3lxs00,39,44,0\",\"Antarctica/Syowa|,60,1,0|-6qsqo0,100,6,0\",\"Antarctica/Troll|,60,1,0|ibruo0,17,1,0|idzk3z,17,1,0|idzk40,101,11,1|ip5erz,101,11,1|ip5es0,17,1,0|iwpmrz,17,1,0|iwpms0,101,11,1|j7vhfz,101,11,1|j7vhg0,17,1,0|jffpfz,17,1,0|jffpg0,101,11,1|jqlk3z,101,11,1|jqlk40,17,1,0|jyiqrz,17,1,0|jyiqs0,101,11,1|k9bmrz,101,11,1|k9bms0,17,1,0|kh8tfz,17,1,0|kh8tg0,101,11,1|ks1pfz,101,11,1|ks1pg0,17,1,0|kzyw3z,17,1,0|kzyw40,101,11,1|lb4qrz,101,11,1|lb4qs0,17,1,0|lioyrz,17,1,0|lioys0,101,11,1|ltutfz,101,11,1|ltutg0,17,1,0|m1f1fz,17,1,0|m1f1g0,101,11,1|mckw3z,101,11,1|mckw40,17,1,0|mki2rz,17,1,0|mki2s0,101,11,1|mvayrz,101,11,1|mvays0,17,1,0|n385fz,17,1,0|n385g0,101,11,1|ne11fz,101,11,1|ne11g0,17,1,0|nly83z,17,1,0|nly840,101,11,1|nwr43z,101,11,1|nwr440,17,1,0|o4oarz,17,1,0|o4oas0,101,11,1|ofu5fz,101,11,1|ofu5g0,17,1,0|onedfz,17,1,0|onedg0,101,11,1|oyk83z,101,11,1|oyk840,17,1,0|p64g3z,17,1,0|p64g40,101,11,1|phaarz,101,11,1|phaas0,17,1,0|pp7hfz,17,1,0|pp7hg0,101,11,1|q00dfz,101,11,1|q00dg0,17,1,0|q7xk3z,17,1,0|q7xk40,101,11,1|qiqg3z,101,11,1|qiqg40,17,1,0|qqnmrz,17,1,0|qqnms0,101,11,1|r1thfz,101,11,1|r1thg0,17,1,0|r9dpfz,17,1,0|r9dpg0,101,11,1|rkjk3z,101,11,1|rkjk40,17,1,0|rs3s3z,17,1,0|rs3s40,101,11,1|s39mrz,101,11,1|s39ms0,17,1,0|sb6tfz,17,1,0|sb6tg0,101,11,1|slzpfz,101,11,1|slzpg0,17,1,0|stww3z,17,1,0|stww40,101,11,1|t4ps3z,101,11,1|t4ps40,17,1,0|tcmyrz,17,1,0|tcmys0,101,11,1|tnfurz,101,11,1|tnfus0,17,1,0|tvd1fz,17,1,0|tvd1g0,101,11,1|u6iw3z,101,11,1|u6iw40,17,1,0|ue343z,17,1,0|ue3440,101,11,1|up8yrz,101,11,1|up8ys0,17,1,0|uwt6rz,17,1,0|uwt6s0,101,11,1|v7z1fz,101,11,1|v7z1g0,17,1,0|vfw83z,17,1,0|vfw840,101,11,1|vqp43z,101,11,1|vqp440,17,1,0|vymarz,17,1,0|vymas0,101,11,1|w9f6rz,101,11,1|w9f6s0,17,1,0|whcdfz,17,1,0|whcdg0,101,11,1|wsi83z,101,11,1|wsi840,17,1,0|x02g3z,17,1,0|x02g40,101,11,1|xb8arz,101,11,1|xb8as0,17,1,0|xisirz,17,1,0|xisis0,101,11,1|xtydfz,101,11,1|xtydg0,17,1,0|y1ilfz,17,1,0|y1ilg0,101,11,1|ycog3z,101,11,1|ycog40,17,1,0|yklmrz,17,1,0|yklms0,101,11,1|yveirz,101,11,1|yveis0,17,1,0|z3bpfz,17,1,0|z3bpg0,101,11,1|ze4lfz,101,11,1|ze4lg0,17,1,0\",\"Antarctica/Vostok|,60,1,0|-6aaao0,96,196,0\",\"Arctic/Longyearbyen|,0,202,0|-1353tzo,10,10,0|-rzayo1,10,10,0|-rzayo0,11,11,1|-rskiw1,11,11,1|-rskiw0,10,10,0|-fc7s81,10,10,0|-fc7s80,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cnnmk1,11,11,1|-cnnmk0,10,10,0|-5mxh81,10,10,0|-5mxh80,11,11,1|-5d7h81,11,11,1|-5d7h80,10,10,0|-53ufw1,10,10,0|-53ufw0,11,11,1|-4uhek1,11,11,1|-4uhek0,10,10,0|-4l4d81,10,10,0|-4l4d80,11,11,1|-4brbw1,11,11,1|-4brbw0,10,10,0|-42eak1,10,10,0|-42eak0,11,11,1|-3t1981,11,11,1|-3t1980,10,10,0|-3jo7w1,10,10,0|-3jo7w0,11,11,1|-3ab6k1,11,11,1|-3ab6k0,10,10,0|-30y581,10,10,0|-30y580,11,11,1|-2r8581,11,11,1|-2r8580,10,10,0|-2g2ak1,10,10,0|-2g2ak0,11,11,1|-28i2k1,11,11,1|-28i2k0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Asia/Aden|,0,203,0|-bwgbbg,100,6,0\",\"Asia/Almaty|,0,204,0|-nu1a90,92,194,0|-kmr9w1,92,194,0|-kmr9w0,96,196,0|5vaxzz,96,196,0|5vay00,91,193,1|64pr7z,91,193,1|64pr80,96,196,0|6e2vbz,96,196,0|6e2vc0,91,193,1|6nhojz,91,193,1|6nhok0,96,196,0|6wusnz,96,196,0|6wuso0,91,193,1|769lvz,91,193,1|769lw0,96,196,0|7foknz,96,196,0|7foko0,91,193,1|7p1rjz,91,193,1|7p1rk0,96,196,0|7yesvz,96,196,0|7yesw0,91,193,1|87ru7z,91,193,1|87ru80,96,196,0|8h4vjz,96,196,0|8h4vk0,91,193,1|8qhwvz,91,193,1|8qhww0,96,196,0|8zuy7z,96,196,0|8zuy80,91,193,1|997zjz,91,193,1|997zk0,96,196,0|9il0vz,96,196,0|9il0w0,91,193,1|9ry27z,91,193,1|9ry280,96,196,0|a1b3jz,96,196,0|a1b3k0,91,193,1|aao4vz,91,193,1|aao4w0,96,196,0|ak167z,96,196,0|ak1680,91,193,1|atr67z,91,193,1|atr680,96,196,0|b347jz,96,196,0|b347k0,96,196,1|bchbnz,96,196,1|bchbo0,92,194,0|bi8qbz,92,194,0|bi8qc0,96,196,0|blua7z,96,196,0|blua80,91,193,1|bv7bjz,91,193,1|bv7bk0,96,196,0|c4kcvz,96,196,0|c4kcw0,91,193,1|cdxe7z,91,193,1|cdxe80,96,196,0|cnafjz,96,196,0|cnafk0,91,193,1|cwngvz,91,193,1|cwngw0,96,196,0|d60i7z,96,196,0|d60i80,91,193,1|dfdjjz,91,193,1|dfdjk0,96,196,0|dp3jjz,96,196,0|dp3jk0,91,193,1|dzwfjz,91,193,1|dzwfk0,96,196,0|e7tm7z,96,196,0|e7tm80,91,193,1|eimi7z,91,193,1|eimi80,96,196,0|eqjovz,96,196,0|eqjow0,91,193,1|f1ckvz,91,193,1|f1ckw0,96,196,0|f99rjz,96,196,0|f99rk0,91,193,1|fkfm7z,91,193,1|fkfm80,96,196,0|frzu7z,96,196,0|frzu80,91,193,1|g35ovz,91,193,1|g35ow0,96,196,0|gapwvz,96,196,0|gapww0,91,193,1|glvrjz,91,193,1|glvrk0,96,196,0|gtsy7z,96,196,0|gtsy80,91,193,1|h4lu7z,91,193,1|h4lu80,96,196,0|hcj0vz,96,196,0|hcj0w0,91,193,1|hnbwvz,91,193,1|hnbww0,96,196,0|hv93jz,96,196,0|hv93k0,91,193,1|i6ey7z,91,193,1|i6ey80,96,196,0\",\"Asia/Amman|,0,205,0|-kcrtbk,15,11,0|1sed3z,15,11,0|1sed40,16,6,1|1yeybz,16,6,1|1yeyc0,15,11,0|29bmfz,15,11,0|29bmg0,16,6,1|2h6vnz,16,6,1|2h6vo0,15,11,0|2s3jrz,15,11,0|2s3js0,16,6,1|2zyszz,16,6,1|2zyt00,15,11,0|3axbrz,15,11,0|3axbs0,16,6,1|3kdznz,16,6,1|3kdzo0,15,11,0|3tp93z,15,11,0|3tp940,16,6,1|41kibz,16,6,1|41kic0,15,11,0|4cfbrz,15,11,0|4cfbs0,16,6,1|4kakzz,16,6,1|4kal00,15,11,0|7ygt3z,15,11,0|7ygt40,16,6,1|87vmbz,16,6,1|87vmc0,15,11,0|8heafz,15,11,0|8heag0,16,6,1|8qr8zz,16,6,1|8qr900,15,11,0|904d3z,15,11,0|904d40,16,6,1|99hbnz,16,6,1|99hbo0,15,11,0|9iufrz,15,11,0|9iufs0,16,6,1|9skczz,16,6,1|9skd00,15,11,0|a3ivrz,15,11,0|a3ivs0,16,6,1|abafnz,16,6,1|abafo0,15,11,0|alqfrz,15,11,0|alqfs0,16,6,1|au0ibz,16,6,1|au0ic0,15,11,0|b3zufz,15,11,0|b3zug0,16,6,1|bcdmbz,16,6,1|bcdmc0,15,11,0|bmgnrz,15,11,0|bmgns0,16,6,1|bvgnnz,16,6,1|bvgno0,15,11,0|c4trrz,15,11,0|c4trs0,16,6,1|ce6qbz,16,6,1|ce6qc0,15,11,0|cnjufz,15,11,0|cnjug0,16,6,1|cw6vnz,16,6,1|cw6vo0,15,11,0|d6mvrz,15,11,0|d6mvs0,16,6,1|dex13z,16,6,1|dex140,15,11,0|dpcyfz,15,11,0|dpcyg0,16,6,1|dy02fz,16,6,1|dy02g0,15,11,0|e8313z,15,11,0|e83140,16,6,1|egq53z,16,6,1|egq540,15,11,0|eqt3rz,15,11,0|eqt3s0,16,6,1|ezg7rz,16,6,1|ezg7s0,15,11,0|fe5ufz,15,11,0|fe5ug0,16,6,1|fij93z,16,6,1|fij940,15,11,0|fs7efz,15,11,0|fs7eg0,16,6,1|g1mafz,16,6,1|g1mag0,15,11,0|gaxh3z,15,11,0|gaxh40,16,6,1|gkcd3z,16,6,1|gkcd40,15,11,0|gtpefz,15,11,0|gtpeg0,16,6,1|h32frz,16,6,1|h32fs0,15,11,0|hcfh3z,15,11,0|hcfh40,16,6,1|hn8d3z,16,6,1|hn8d40,15,11,0|hv5jrz,15,11,0|hv5js0,16,6,1|i5lh3z,16,6,1|i5lh40,15,11,0|ie8l3z,15,11,0|ie8l40,16,6,1|inlmfz,16,6,1|inlmg0,15,11,0|iwynrz,15,11,0|iwyns0,16,6,1|j7rjrz,16,6,1|j7rjs0,15,11,0|jfoqfz,15,11,0|jfoqg0,16,6,1|jqhmfz,16,6,1|jqhmg0,15,11,0|jyet3z,15,11,0|jyet40,16,6,1|k9knrz,16,6,1|k9kns0,15,11,0|kh4vrz,15,11,0|kh4vs0,16,6,1|ksaqfz,16,6,1|ksaqg0,15,11,0|kzuyfz,15,11,0|kzuyg0,16,6,1|lb0t3z,16,6,1|lb0t40,15,11,0|lixzrz,15,11,0|lixzs0,16,6,1|ltqvrz,16,6,1|ltqvs0,15,11,0|m1o2fz,15,11,0|m1o2g0,16,6,1|my2nnz,16,6,1|my2no0,15,11,0|n347rz,15,11,0|n347s0,16,6,1|nea2fz,16,6,1|nea2g0,15,11,0|nluafz,15,11,0|nluag0,16,6,1|nx053z,16,6,1|nx0540,15,11,0|o4xbrz,15,11,0|o4xbs0,16,6,1|ofq7rz,16,6,1|ofq7s0,15,11,0|onnefz,15,11,0|onneg0,16,6,1|oygafz,16,6,1|oygag0,15,11,0|p6dh3z,15,11,0|p6dh40,16,6,1|ph6d3z,16,6,1|ph6d40,15,11,0|pp3jrz,15,11,0|pp3js0,16,6,1|pzwfrz,16,6,1|pzwfs0,15,11,0|q7tmfz,15,11,0|q7tmg0,16,6,1|qizh3z,16,6,1|qizh40,15,11,0|qqjp3z,15,11,0|qqjp40,16,6,1|r1pjrz,16,6,1|r1pjs0,15,11,0|r9mqfz,15,11,0|r9mqg0,16,6,1|rkfmfz,16,6,1|rkfmg0,15,11,0|rsct3z,15,11,0|rsct40,16,6,1|s35p3z,16,6,1|s35p40,15,11,0|sb2vrz,15,11,0|sb2vs0,16,6,1|slvrrz,16,6,1|slvrs0,15,11,0|stsyfz,15,11,0|stsyg0,16,6,1|t4yt3z,16,6,1|t4yt40,15,11,0|tcj13z,15,11,0|tcj140,16,6,1|tnovrz,16,6,1|tnovs0,15,11,0|tv93rz,15,11,0|tv93s0,16,6,1|u6eyfz,16,6,1|u6eyg0,15,11,0|uec53z,15,11,0|uec540,16,6,1|up513z,16,6,1|up5140,15,11,0|ux27rz,15,11,0|ux27s0,16,6,1|v7v3rz,16,6,1|v7v3s0,15,11,0|vfsafz,15,11,0|vfsag0,16,6,1|vql6fz,16,6,1|vql6g0,15,11,0|vyid3z,15,11,0|vyid40,16,6,1|w9o7rz,16,6,1|w9o7s0,15,11,0|wh8frz,15,11,0|wh8fs0,16,6,1|wseafz,16,6,1|wseag0,15,11,0|x0bh3z,15,11,0|x0bh40,16,6,1|xb4d3z,16,6,1|xb4d40,15,11,0|xj1jrz,15,11,0|xj1js0,16,6,1|xtufrz,16,6,1|xtufs0,15,11,0|y1rmfz,15,11,0|y1rmg0,16,6,1|yckifz,16,6,1|yckig0,15,11,0|ykhp3z,15,11,0|ykhp40,16,6,1|yvnjrz,16,6,1|yvnjs0,15,11,0|z37rrz,15,11,0|z37rs0,16,6,1|zedmfz,16,6,1|zedmg0,15,11,0\",\"Asia/Anadyr|,0,206,0|-nu1sv8,102,200,0|-kmrtc1,102,200,0|-kmrtc0,103,201,0|5vaejz,103,201,0|5vaek0,104,207,1|64p7rz,104,207,1|64p7s0,103,201,0|6e2bvz,103,201,0|6e2bw0,103,201,1|6nh7vz,103,201,1|6nh7w0,102,200,0|6wubzz,102,200,0|6wuc00,103,201,1|76957z,103,201,1|769580,102,200,0|7fo3zz,102,200,0|7fo400,103,201,1|7p1avz,103,201,1|7p1aw0,102,200,0|7yec7z,102,200,0|7yec80,103,201,1|87rdjz,103,201,1|87rdk0,102,200,0|8h4evz,102,200,0|8h4ew0,103,201,1|8qhg7z,103,201,1|8qhg80,102,200,0|8zuhjz,102,200,0|8zuhk0,103,201,1|997ivz,103,201,1|997iw0,102,200,0|9ikk7z,102,200,0|9ikk80,103,201,1|9rxljz,103,201,1|9rxlk0,102,200,0|a1amvz,102,200,0|a1amw0,103,201,1|aano7z,103,201,1|aano80,102,200,0|ak0pjz,102,200,0|ak0pk0,103,201,1|atqpjz,103,201,1|atqpk0,102,200,0|b33qvz,102,200,0|b33qw0,102,200,1|bcguzz,102,200,1|bcgv00,90,192,0|bi89nz,90,192,0|bi89o0,102,200,0|blttjz,102,200,0|blttk0,103,201,1|bv6uvz,103,201,1|bv6uw0,102,200,0|c4jw7z,102,200,0|c4jw80,103,201,1|cdwxjz,103,201,1|cdwxk0,102,200,0|cn9yvz,102,200,0|cn9yw0,103,201,1|cwn07z,103,201,1|cwn080,102,200,0|d601jz,102,200,0|d601k0,103,201,1|dfd2vz,103,201,1|dfd2w0,102,200,0|dp32vz,102,200,0|dp32w0,103,201,1|dzvyvz,103,201,1|dzvyw0,102,200,0|e7t5jz,102,200,0|e7t5k0,103,201,1|eim1jz,103,201,1|eim1k0,102,200,0|eqj87z,102,200,0|eqj880,103,201,1|f1c47z,103,201,1|f1c480,102,200,0|f99avz,102,200,0|f99aw0,103,201,1|fkf5jz,103,201,1|fkf5k0,102,200,0|frzdjz,102,200,0|frzdk0,103,201,1|g3587z,103,201,1|g35880,102,200,0|gapg7z,102,200,0|gapg80,103,201,1|glvavz,103,201,1|glvaw0,102,200,0|gtshjz,102,200,0|gtshk0,103,201,1|h4ldjz,103,201,1|h4ldk0,102,200,0|hcik7z,102,200,0|hcik80,103,201,1|hnbg7z,103,201,1|hnbg80,102,200,0|hv8mvz,102,200,0|hv8mw0,103,201,1|i6ehjz,103,201,1|i6ehk0,102,200,0|idypjz,102,200,0|idypk0,103,201,1|ip4k7z,103,201,1|ip4k80,102,200,0|iwos7z,102,200,0|iwos80,103,201,1|j7umvz,103,201,1|j7umw0,102,200,0|jfeuvz,102,200,0|jfeuw0,103,201,1|jqkpjz,103,201,1|jqkpk0,102,200,0|jyhw7z,102,200,0|jyhw80,103,201,1|k9as7z,103,201,1|k9as80,102,200,0|kh7yvz,102,200,0|kh7yw0,103,201,1|ks0uvz,103,201,1|ks0uw0,102,200,0|kzy1jz,102,200,0|kzy1k0,102,200,1|lb3yzz,102,200,1|lb3z00,90,192,0|lio6zz,90,192,0|lio700,102,200,0\",\"Asia/Aqtau|,0,208,0|-nu15b4,105,209,0|-kmr741,105,209,0|-kmr740,92,194,0|64pwrz,92,194,0|64pws0,96,196,0|6e2vbz,96,196,0|6e2vc0,96,196,1|6nhrbz,96,196,1|6nhrc0,92,194,0|6wuvfz,92,194,0|6wuvg0,96,196,1|769onz,96,196,1|769oo0,92,194,0|7fonfz,92,194,0|7fong0,96,196,1|7p1ubz,96,196,1|7p1uc0,92,194,0|7yevnz,92,194,0|7yevo0,96,196,1|87rwzz,96,196,1|87rx00,92,194,0|8h4ybz,92,194,0|8h4yc0,96,196,1|8qhznz,96,196,1|8qhzo0,92,194,0|8zv0zz,92,194,0|8zv100,96,196,1|9982bz,96,196,1|9982c0,92,194,0|9il3nz,92,194,0|9il3o0,96,196,1|9ry4zz,96,196,1|9ry500,92,194,0|a1b6bz,92,194,0|a1b6c0,96,196,1|aao7nz,96,196,1|aao7o0,92,194,0|ak18zz,92,194,0|ak1900,96,196,1|atr8zz,96,196,1|atr900,92,194,0|b34abz,92,194,0|b34ac0,92,194,1|bchefz,92,194,1|bcheg0,105,209,0|bi8t3z,105,209,0|bi8t40,92,194,0|bluczz,92,194,0|blud00,96,196,1|bv7ebz,96,196,1|bv7ec0,92,194,0|c4kfnz,92,194,0|c4kfo0,96,196,1|cdxgzz,96,196,1|cdxh00,92,194,0|cnaibz,92,194,0|cnaic0,96,196,1|cwnjnz,96,196,1|cwnjo0,105,209,0|d60nrz,105,209,0|d60ns0,92,194,1|dfdp3z,92,194,1|dfdp40,105,209,0|dp3p3z,105,209,0|dp3p40,92,194,1|dzwl3z,92,194,1|dzwl40,105,209,0|e7trrz,105,209,0|e7trs0,92,194,1|eimnrz,92,194,1|eimns0,105,209,0|eqjufz,105,209,0|eqjug0,92,194,1|f1cqfz,92,194,1|f1cqg0,105,209,0|f99x3z,105,209,0|f99x40,92,194,1|fkfrrz,92,194,1|fkfrs0,105,209,0|frzzrz,105,209,0|frzzs0,92,194,1|g35ufz,92,194,1|g35ug0,105,209,0|gaq2fz,105,209,0|gaq2g0,92,194,1|glvx3z,92,194,1|glvx40,105,209,0|gtt3rz,105,209,0|gtt3s0,92,194,1|h4lzrz,92,194,1|h4lzs0,105,209,0|hcj6fz,105,209,0|hcj6g0,92,194,1|hnc2fz,92,194,1|hnc2g0,105,209,0|hv993z,105,209,0|hv9940,92,194,1|i6f3rz,92,194,1|i6f3s0,92,194,0\",\"Asia/Aqtobe|,0,210,0|-nu16l4,105,209,0|-kmr741,105,209,0|-kmr740,92,194,0|5vb0rz,92,194,0|5vb0s0,96,196,1|64ptzz,96,196,1|64pu00,96,196,0|6e2vbz,96,196,0|6e2vc0,96,196,1|6nhrbz,96,196,1|6nhrc0,92,194,0|6wuvfz,92,194,0|6wuvg0,96,196,1|769onz,96,196,1|769oo0,92,194,0|7fonfz,92,194,0|7fong0,96,196,1|7p1ubz,96,196,1|7p1uc0,92,194,0|7yevnz,92,194,0|7yevo0,96,196,1|87rwzz,96,196,1|87rx00,92,194,0|8h4ybz,92,194,0|8h4yc0,96,196,1|8qhznz,96,196,1|8qhzo0,92,194,0|8zv0zz,92,194,0|8zv100,96,196,1|9982bz,96,196,1|9982c0,92,194,0|9il3nz,92,194,0|9il3o0,96,196,1|9ry4zz,96,196,1|9ry500,92,194,0|a1b6bz,92,194,0|a1b6c0,96,196,1|aao7nz,96,196,1|aao7o0,92,194,0|ak18zz,92,194,0|ak1900,96,196,1|atr8zz,96,196,1|atr900,92,194,0|b34abz,92,194,0|b34ac0,92,194,1|bchefz,92,194,1|bcheg0,105,209,0|bi8t3z,105,209,0|bi8t40,92,194,0|bluczz,92,194,0|blud00,96,196,1|bv7ebz,96,196,1|bv7ec0,92,194,0|c4kfnz,92,194,0|c4kfo0,96,196,1|cdxgzz,96,196,1|cdxh00,92,194,0|cnaibz,92,194,0|cnaic0,96,196,1|cwnjnz,96,196,1|cwnjo0,92,194,0|d60kzz,92,194,0|d60l00,96,196,1|dfdmbz,96,196,1|dfdmc0,92,194,0|dp3mbz,92,194,0|dp3mc0,96,196,1|dzwibz,96,196,1|dzwic0,92,194,0|e7tozz,92,194,0|e7tp00,96,196,1|eimkzz,96,196,1|eiml00,92,194,0|eqjrnz,92,194,0|eqjro0,96,196,1|f1cnnz,96,196,1|f1cno0,92,194,0|f99ubz,92,194,0|f99uc0,96,196,1|fkfozz,96,196,1|fkfp00,92,194,0|frzwzz,92,194,0|frzx00,96,196,1|g35rnz,96,196,1|g35ro0,92,194,0|gapznz,92,194,0|gapzo0,96,196,1|glvubz,96,196,1|glvuc0,92,194,0|gtt0zz,92,194,0|gtt100,96,196,1|h4lwzz,96,196,1|h4lx00,92,194,0|hcj3nz,92,194,0|hcj3o0,96,196,1|hnbznz,96,196,1|hnbzo0,92,194,0|hv96bz,92,194,0|hv96c0,96,196,1|i6f0zz,96,196,1|i6f100,92,194,0\",\"Asia/Ashgabat|,0,211,0|-nu16t8,105,209,0|-kmr741,105,209,0|-kmr740,92,194,0|5vb0rz,92,194,0|5vb0s0,96,196,1|64ptzz,96,196,1|64pu00,92,194,0|6e2y3z,92,194,0|6e2y40,96,196,1|6nhrbz,96,196,1|6nhrc0,92,194,0|6wuvfz,92,194,0|6wuvg0,96,196,1|769onz,96,196,1|769oo0,92,194,0|7fonfz,92,194,0|7fong0,96,196,1|7p1ubz,96,196,1|7p1uc0,92,194,0|7yevnz,92,194,0|7yevo0,96,196,1|87rwzz,96,196,1|87rx00,92,194,0|8h4ybz,92,194,0|8h4yc0,96,196,1|8qhznz,96,196,1|8qhzo0,92,194,0|8zv0zz,92,194,0|8zv100,96,196,1|9982bz,96,196,1|9982c0,92,194,0|9il3nz,92,194,0|9il3o0,96,196,1|9ry4zz,96,196,1|9ry500,92,194,0|a1b6bz,92,194,0|a1b6c0,96,196,1|aao7nz,96,196,1|aao7o0,92,194,0|ak18zz,92,194,0|ak1900,96,196,1|atr8zz,96,196,1|atr900,92,194,0|b34abz,92,194,0|b34ac0,92,194,1|bchefz,92,194,1|bcheg0,105,209,0|bi8t3z,105,209,0|bi8t40,92,194,0\",\"Asia/Atyrau|,0,212,0|-nu15m8,100,6,0|-kmr4c1,100,6,0|-kmr4c0,92,194,0|64pwrz,92,194,0|64pws0,96,196,0|6e2vbz,96,196,0|6e2vc0,96,196,1|6nhrbz,96,196,1|6nhrc0,92,194,0|6wuvfz,92,194,0|6wuvg0,96,196,1|769onz,96,196,1|769oo0,92,194,0|7fonfz,92,194,0|7fong0,96,196,1|7p1ubz,96,196,1|7p1uc0,92,194,0|7yevnz,92,194,0|7yevo0,96,196,1|87rwzz,96,196,1|87rx00,92,194,0|8h4ybz,92,194,0|8h4yc0,96,196,1|8qhznz,96,196,1|8qhzo0,92,194,0|8zv0zz,92,194,0|8zv100,96,196,1|9982bz,96,196,1|9982c0,92,194,0|9il3nz,92,194,0|9il3o0,96,196,1|9ry4zz,96,196,1|9ry500,92,194,0|a1b6bz,92,194,0|a1b6c0,96,196,1|aao7nz,96,196,1|aao7o0,92,194,0|ak18zz,92,194,0|ak1900,96,196,1|atr8zz,96,196,1|atr900,92,194,0|b34abz,92,194,0|b34ac0,92,194,1|bchefz,92,194,1|bcheg0,105,209,0|bi8t3z,105,209,0|bi8t40,92,194,0|bluczz,92,194,0|blud00,96,196,1|bv7ebz,96,196,1|bv7ec0,92,194,0|c4kfnz,92,194,0|c4kfo0,96,196,1|cdxgzz,96,196,1|cdxh00,92,194,0|cnaibz,92,194,0|cnaic0,96,196,1|cwnjnz,96,196,1|cwnjo0,92,194,0|d60kzz,92,194,0|d60l00,96,196,1|dfdmbz,96,196,1|dfdmc0,92,194,0|dp3mbz,92,194,0|dp3mc0,96,196,1|dzwibz,96,196,1|dzwic0,92,194,0|e7tozz,92,194,0|e7tp00,96,196,1|eimkzz,96,196,1|eiml00,92,194,0|eqjrnz,92,194,0|eqjro0,96,196,1|f1cnnz,96,196,1|f1cno0,92,194,0|f99ubz,92,194,0|f99uc0,92,194,1|fkfrrz,92,194,1|fkfrs0,105,209,0|frzzrz,105,209,0|frzzs0,92,194,1|g35ufz,92,194,1|g35ug0,105,209,0|gaq2fz,105,209,0|gaq2g0,92,194,1|glvx3z,92,194,1|glvx40,105,209,0|gtt3rz,105,209,0|gtt3s0,92,194,1|h4lzrz,92,194,1|h4lzs0,105,209,0|hcj6fz,105,209,0|hcj6g0,92,194,1|hnc2fz,92,194,1|hnc2g0,105,209,0|hv993z,105,209,0|hv9940,92,194,1|i6f3rz,92,194,1|i6f3s0,92,194,0\",\"Asia/Baghdad|,0,213,0|-15r1hk4,53,214,0|-r50g81,53,214,0|-r50g80,100,6,0|6fmnnz,100,6,0|6fmno0,105,209,1|6nhwvz,105,209,1|6nhww0,100,6,0|6wt6bz,100,6,0|6wt6c0,105,209,1|769u7z,105,209,1|769u80,100,6,0|7foszz,100,6,0|7fot00,105,209,1|7p3m7z,105,209,1|7p3m80,100,6,0|7ygqbz,100,6,0|7ygqc0,105,209,1|87rzrz,105,209,1|87rzs0,100,6,0|8h513z,100,6,0|8h5140,105,209,1|8qi2fz,105,209,1|8qi2g0,100,6,0|8zv3rz,100,6,0|8zv3s0,105,209,1|99853z,105,209,1|998540,100,6,0|9il6fz,100,6,0|9il6g0,105,209,1|9ry7rz,105,209,1|9ry7s0,100,6,0|a1b93z,100,6,0|a1b940,105,209,1|aaoafz,105,209,1|aaoag0,100,6,0|ak1brz,100,6,0|ak1bs0,105,209,1|atrbrz,105,209,1|atrbs0,100,6,0|b36dbz,100,6,0|b36dc0,105,209,1|bcl9bz,105,209,1|bcl9c0,100,6,0|bm05bz,100,6,0|bm05c0,105,209,1|bvf1bz,105,209,1|bvf1c0,100,6,0|c4s2nz,100,6,0|c4s2o0,105,209,1|ce6ynz,105,209,1|ce6yo0,100,6,0|cnjzzz,100,6,0|cnk000,105,209,1|cwyvzz,105,209,1|cwyw00,100,6,0|d6bxbz,100,6,0|d6bxc0,105,209,1|dfqtbz,105,209,1|dfqtc0,100,6,0|dp5pbz,100,6,0|dp5pc0,105,209,1|dyklbz,105,209,1|dyklc0,100,6,0|e7xmnz,100,6,0|e7xmo0,105,209,1|ehcinz,105,209,1|ehcio0,100,6,0|eqpjzz,100,6,0|eqpk00,105,209,1|f04fzz,105,209,1|f04g00,100,6,0|f9hhbz,100,6,0|f9hhc0,105,209,1|fiwdbz,105,209,1|fiwdc0,100,6,0|fsb9bz,100,6,0|fsb9c0,105,209,1|g1q5bz,105,209,1|g1q5c0,100,6,0|gb36nz,100,6,0|gb36o0,105,209,1|gki2nz,105,209,1|gki2o0,100,6,0|gtv3zz,100,6,0|gtv400,105,209,1|h39zzz,105,209,1|h3a000,100,6,0|hcn1bz,100,6,0|hcn1c0,105,209,1|hm1xbz,105,209,1|hm1xc0,100,6,0|hvgtbz,100,6,0|hvgtc0,105,209,1|i4vpbz,105,209,1|i4vpc0,100,6,0|ie8qnz,100,6,0|ie8qo0,105,209,1|innmnz,105,209,1|innmo0,100,6,0|ix0nzz,100,6,0|ix0o00,105,209,1|j6fjzz,105,209,1|j6fk00,100,6,0|jfslbz,100,6,0|jfslc0,105,209,1|jp7hbz,105,209,1|jp7hc0,100,6,0\",\"Asia/Bahrain|,0,215,0|-q3gmvk,105,209,0|19d0vz,105,209,0|19d0w0,100,6,0\",\"Asia/Baku|,0,216,0|-nu158c,100,6,0|-6p7kc1,100,6,0|-6p7kc0,105,209,0|5vb3jz,105,209,0|5vb3k0,92,194,1|64pwrz,92,194,1|64pws0,105,209,0|6e30vz,105,209,0|6e30w0,92,194,1|6nhu3z,92,194,1|6nhu40,105,209,0|6wuy7z,105,209,0|6wuy80,92,194,1|769rfz,92,194,1|769rg0,105,209,0|7foq7z,105,209,0|7foq80,92,194,1|7p1x3z,92,194,1|7p1x40,105,209,0|7yeyfz,105,209,0|7yeyg0,92,194,1|87rzrz,92,194,1|87rzs0,105,209,0|8h513z,105,209,0|8h5140,92,194,1|8qi2fz,92,194,1|8qi2g0,105,209,0|8zv3rz,105,209,0|8zv3s0,92,194,1|99853z,92,194,1|998540,105,209,0|9il6fz,105,209,0|9il6g0,92,194,1|9ry7rz,92,194,1|9ry7s0,105,209,0|a1b93z,105,209,0|a1b940,92,194,1|aaoafz,92,194,1|aaoag0,105,209,0|ak1brz,105,209,0|ak1bs0,92,194,1|atrbrz,92,194,1|atrbs0,105,209,0|b34d3z,105,209,0|b34d40,105,209,1|bchh7z,105,209,1|bchh80,100,6,0|bluijz,100,6,0|bluik0,105,209,1|bv7jvz,105,209,1|bv7jw0,105,209,0|dp3xfz,105,209,0|dp3xg0,92,194,1|dzwtfz,92,194,1|dzwtg0,105,209,0|e7txbz,105,209,0|e7txc0,92,194,1|eimtbz,92,194,1|eimtc0,105,209,0|eqjzzz,105,209,0|eqk000,92,194,1|f1cvzz,92,194,1|f1cw00,105,209,0|f9a2nz,105,209,0|f9a2o0,92,194,1|fkfxbz,92,194,1|fkfxc0,105,209,0|fs05bz,105,209,0|fs05c0,92,194,1|g35zzz,92,194,1|g36000,105,209,0|gaq7zz,105,209,0|gaq800,92,194,1|glw2nz,92,194,1|glw2o0,105,209,0|gtt9bz,105,209,0|gtt9c0,92,194,1|h4m5bz,92,194,1|h4m5c0,105,209,0|hcjbzz,105,209,0|hcjc00,92,194,1|hnc7zz,92,194,1|hnc800,105,209,0|hv9enz,105,209,0|hv9eo0,92,194,1|i6f9bz,92,194,1|i6f9c0,105,209,0|idzhbz,105,209,0|idzhc0,92,194,1|ip5bzz,92,194,1|ip5c00,105,209,0|iwpjzz,105,209,0|iwpk00,92,194,1|j7venz,92,194,1|j7veo0,105,209,0|jffmnz,105,209,0|jffmo0,92,194,1|jqlhbz,92,194,1|jqlhc0,105,209,0|jyinzz,105,209,0|jyio00,92,194,1|k9bjzz,92,194,1|k9bk00,105,209,0|kh8qnz,105,209,0|kh8qo0,92,194,1|ks1mnz,92,194,1|ks1mo0,105,209,0|kzytbz,105,209,0|kzytc0,92,194,1|lb4nzz,92,194,1|lb4o00,105,209,0|liovzz,105,209,0|liow00,92,194,1|ltuqnz,92,194,1|ltuqo0,105,209,0|m1eynz,105,209,0|m1eyo0,92,194,1|mcktbz,92,194,1|mcktc0,105,209,0|mkhzzz,105,209,0|mki000,92,194,1|mvavzz,92,194,1|mvaw00,105,209,0|n382nz,105,209,0|n382o0,92,194,1|ne0ynz,92,194,1|ne0yo0,105,209,0|nly5bz,105,209,0|nly5c0,92,194,1|nwr1bz,92,194,1|nwr1c0,105,209,0\",\"Asia/Bangkok|,0,217,0|-1ayyla4,53,217,0|-pysda5,53,217,0|-pysda4,91,193,0\",\"Asia/Barnaul|,0,218,0|-q4ljic,96,196,0|-kmrco1,96,196,0|-kmrco0,91,193,0|5vav7z,91,193,0|5vav80,89,191,1|64pofz,89,191,1|64pog0,91,193,0|6e2sjz,91,193,0|6e2sk0,89,191,1|6nhlrz,89,191,1|6nhls0,91,193,0|6wupvz,91,193,0|6wupw0,89,191,1|769j3z,89,191,1|769j40,91,193,0|7fohvz,91,193,0|7fohw0,89,191,1|7p1orz,89,191,1|7p1os0,91,193,0|7yeq3z,91,193,0|7yeq40,89,191,1|87rrfz,89,191,1|87rrg0,91,193,0|8h4srz,91,193,0|8h4ss0,89,191,1|8qhu3z,89,191,1|8qhu40,91,193,0|8zuvfz,91,193,0|8zuvg0,89,191,1|997wrz,89,191,1|997ws0,91,193,0|9iky3z,91,193,0|9iky40,89,191,1|9rxzfz,89,191,1|9rxzg0,91,193,0|a1b0rz,91,193,0|a1b0s0,89,191,1|aao23z,89,191,1|aao240,91,193,0|ak13fz,91,193,0|ak13g0,89,191,1|atr3fz,89,191,1|atr3g0,91,193,0|b344rz,91,193,0|b344s0,91,193,1|bch8vz,91,193,1|bch8w0,96,196,0|bi8njz,96,196,0|bi8nk0,91,193,0|blu7fz,91,193,0|blu7g0,89,191,1|bv78rz,89,191,1|bv78s0,91,193,0|c4ka3z,91,193,0|c4ka40,89,191,1|cdxbfz,89,191,1|cdxbg0,91,193,0|cnacrz,91,193,0|cnacs0,89,191,1|cwne3z,89,191,1|cwne40,91,193,0|d60ffz,91,193,0|d60fg0,89,191,1|d98v3z,89,191,1|d98v40,91,193,1|dfdjjz,91,193,1|dfdjk0,96,196,0|dp3jjz,96,196,0|dp3jk0,91,193,1|dzwfjz,91,193,1|dzwfk0,96,196,0|e7tm7z,96,196,0|e7tm80,91,193,1|eimi7z,91,193,1|eimi80,96,196,0|eqjovz,96,196,0|eqjow0,91,193,1|f1ckvz,91,193,1|f1ckw0,96,196,0|f99rjz,96,196,0|f99rk0,91,193,1|fkfm7z,91,193,1|fkfm80,96,196,0|frzu7z,96,196,0|frzu80,91,193,1|g35ovz,91,193,1|g35ow0,96,196,0|gapwvz,96,196,0|gapww0,91,193,1|glvrjz,91,193,1|glvrk0,96,196,0|gtsy7z,96,196,0|gtsy80,91,193,1|h4lu7z,91,193,1|h4lu80,96,196,0|hcj0vz,96,196,0|hcj0w0,91,193,1|hnbwvz,91,193,1|hnbww0,96,196,0|hv93jz,96,196,0|hv93k0,91,193,1|i6ey7z,91,193,1|i6ey80,96,196,0|idz67z,96,196,0|idz680,91,193,1|ip50vz,91,193,1|ip50w0,96,196,0|iwp8vz,96,196,0|iwp8w0,91,193,1|j7v3jz,91,193,1|j7v3k0,96,196,0|jffbjz,96,196,0|jffbk0,91,193,1|jql67z,91,193,1|jql680,96,196,0|jyicvz,96,196,0|jyicw0,91,193,1|k9b8vz,91,193,1|k9b8w0,96,196,0|kh8fjz,96,196,0|kh8fk0,91,193,1|ks1bjz,91,193,1|ks1bk0,96,196,0|kzyi7z,96,196,0|kzyi80,91,193,1|lb4cvz,91,193,1|lb4cw0,96,196,0|liokvz,96,196,0|liokw0,91,193,0|ne0krz,91,193,0|ne0ks0,96,196,0|o4nwvz,96,196,0|o4nww0,91,193,0\",\"Asia/Beirut|,0,219,0|-1ayy98o,15,11,0|-pyzew1,15,11,0|-pyzew0,16,6,1|-po4r01,16,6,1|-po4r00,15,11,0|-pfwdk1,15,11,0|-pfwdk0,16,6,1|-p6hkc1,16,6,1|-p6hkc0,15,11,0|-oxj9k1,15,11,0|-oxj9k0,16,6,1|-ongdo1,16,6,1|-ongdo0,15,11,0|-oddc81,15,11,0|-oddc80,16,6,1|-o5t701,16,6,1|-o5t700,15,11,0|-6m2iw1,15,11,0|-6m2iw0,16,6,1|-6e79o1,16,6,1|-6e79o0,15,11,0|-63alk1,15,11,0|-63alk0,16,6,1|-5vfcc1,16,6,1|-5vfcc0,15,11,0|-5kio81,15,11,0|-5kio80,16,6,1|-5cnf01,16,6,1|-5cnf00,15,11,0|-51ow81,15,11,0|-51ow80,16,6,1|-4ttn01,16,6,1|-4ttn00,15,11,0|-4iwyw1,15,11,0|-4iwyw0,16,6,1|-4b1po1,16,6,1|-4b1po0,15,11,0|1ag2fz,15,11,0|1ag2g0,16,6,1|1fn0zz,16,6,1|1fn100,15,11,0|1qjp3z,15,11,0|1qjp40,16,6,1|1yeybz,16,6,1|1yeyc0,15,11,0|29bmfz,15,11,0|29bmg0,16,6,1|2h6vnz,16,6,1|2h6vo0,15,11,0|2s3jrz,15,11,0|2s3js0,16,6,1|2zyszz,16,6,1|2zyt00,15,11,0|3axbrz,15,11,0|3axbs0,16,6,1|3iskzz,16,6,1|3isl00,15,11,0|3tp93z,15,11,0|3tp940,16,6,1|41kibz,16,6,1|41kic0,15,11,0|4cfbrz,15,11,0|4cfbs0,16,6,1|4kakzz,16,6,1|4kal00,15,11,0|7h8frz,15,11,0|7h8fs0,16,6,1|7pvgzz,16,6,1|7pvh00,15,11,0|800d3z,15,11,0|800d40,16,6,1|88nebz,16,6,1|88nec0,15,11,0|8isafz,15,11,0|8isag0,16,6,1|8rfbnz,16,6,1|8rfbo0,15,11,0|91k7rz,15,11,0|91k7s0,16,6,1|9a78zz,16,6,1|9a7900,15,11,0|9lzefz,15,11,0|9lzeg0,16,6,1|9t10zz,16,6,1|9t1100,15,11,0|a3ml3z,15,11,0|a3ml40,16,6,1|absybz,16,6,1|absyc0,15,11,0|alxufz,15,11,0|alxug0,16,6,1|aukvnz,16,6,1|aukvo0,15,11,0|b4prrz,15,11,0|b4prs0,16,6,1|bdcszz,16,6,1|bdct00,15,11,0|bnjjrz,15,11,0|bnjjs0,16,6,1|bvkczz,16,6,1|bvkd00,15,11,0|c4kifz,15,11,0|c4kig0,16,6,1|cdxgzz,16,6,1|cdxh00,15,11,0|cnal3z,15,11,0|cnal40,16,6,1|cwnjnz,16,6,1|cwnjo0,15,11,0|d60nrz,15,11,0|d60ns0,16,6,1|dfdmbz,16,6,1|dfdmc0,15,11,0|dp3p3z,15,11,0|dp3p40,16,6,1|dygnnz,16,6,1|dygno0,15,11,0|e7trrz,15,11,0|e7trs0,16,6,1|eh6qbz,16,6,1|eh6qc0,15,11,0|eqjufz,15,11,0|eqjug0,16,6,1|ezwszz,16,6,1|ezwt00,15,11,0|f99x3z,15,11,0|f99x40,16,6,1|fkfozz,16,6,1|fkfp00,15,11,0|frzzrz,15,11,0|frzzs0,16,6,1|g35rnz,16,6,1|g35ro0,15,11,0|gaq2fz,15,11,0|gaq2g0,16,6,1|glvubz,16,6,1|glvuc0,15,11,0|gtt3rz,15,11,0|gtt3s0,16,6,1|h4lwzz,16,6,1|h4lx00,15,11,0|hcj6fz,15,11,0|hcj6g0,16,6,1|hnbznz,16,6,1|hnbzo0,15,11,0|hv993z,15,11,0|hv9940,16,6,1|i6f0zz,16,6,1|i6f100,15,11,0|idzbrz,15,11,0|idzbs0,16,6,1|ip53nz,16,6,1|ip53o0,15,11,0|iwpefz,15,11,0|iwpeg0,16,6,1|j7v6bz,16,6,1|j7v6c0,15,11,0|jffh3z,15,11,0|jffh40,16,6,1|jql8zz,16,6,1|jql900,15,11,0|jyiifz,15,11,0|jyiig0,16,6,1|k9bbnz,16,6,1|k9bbo0,15,11,0|kh8l3z,15,11,0|kh8l40,16,6,1|ks1ebz,16,6,1|ks1ec0,15,11,0|kzynrz,15,11,0|kzyns0,16,6,1|lb4fnz,16,6,1|lb4fo0,15,11,0|lioqfz,15,11,0|lioqg0,16,6,1|ltuibz,16,6,1|ltuic0,15,11,0|m1et3z,15,11,0|m1et40,16,6,1|mckkzz,16,6,1|mckl00,15,11,0|mkhufz,15,11,0|mkhug0,16,6,1|mvannz,16,6,1|mvano0,15,11,0|n37x3z,15,11,0|n37x40,16,6,1|ne0qbz,16,6,1|ne0qc0,15,11,0|nlxzrz,15,11,0|nlxzs0,16,6,1|nwqszz,16,6,1|nwqt00,15,11,0|o4o2fz,15,11,0|o4o2g0,16,6,1|oftubz,16,6,1|oftuc0,15,11,0|one53z,15,11,0|one540,16,6,1|oyjwzz,16,6,1|oyjx00,15,11,0|p647rz,15,11,0|p647s0,16,6,1|ph9znz,16,6,1|ph9zo0,15,11,0|pp793z,15,11,0|pp7940,16,6,1|q002bz,16,6,1|q002c0,15,11,0|q7xbrz,15,11,0|q7xbs0,16,6,1|qiq4zz,16,6,1|qiq500,15,11,0|qqnefz,15,11,0|qqneg0,16,6,1|r1t6bz,16,6,1|r1t6c0,15,11,0|r9dh3z,15,11,0|r9dh40,16,6,1|rkj8zz,16,6,1|rkj900,15,11,0|rs3jrz,15,11,0|rs3js0,16,6,1|s39bnz,16,6,1|s39bo0,15,11,0|sb6l3z,15,11,0|sb6l40,16,6,1|slzebz,16,6,1|slzec0,15,11,0|stwnrz,15,11,0|stwns0,16,6,1|t4pgzz,16,6,1|t4ph00,15,11,0|tcmqfz,15,11,0|tcmqg0,16,6,1|tnfjnz,16,6,1|tnfjo0,15,11,0|tvct3z,15,11,0|tvct40,16,6,1|u6ikzz,16,6,1|u6il00,15,11,0|ue2vrz,15,11,0|ue2vs0,16,6,1|up8nnz,16,6,1|up8no0,15,11,0|uwsyfz,15,11,0|uwsyg0,16,6,1|v7yqbz,16,6,1|v7yqc0,15,11,0|vfvzrz,15,11,0|vfvzs0,16,6,1|vqoszz,16,6,1|vqot00,15,11,0|vym2fz,15,11,0|vym2g0,16,6,1|w9evnz,16,6,1|w9evo0,15,11,0|whc53z,15,11,0|whc540,16,6,1|wshwzz,16,6,1|wshx00,15,11,0|x027rz,15,11,0|x027s0,16,6,1|xb7znz,16,6,1|xb7zo0,15,11,0|xisafz,15,11,0|xisag0,16,6,1|xty2bz,16,6,1|xty2c0,15,11,0|y1id3z,15,11,0|y1id40,16,6,1|yco4zz,16,6,1|yco500,15,11,0|yklefz,15,11,0|ykleg0,16,6,1|yve7nz,16,6,1|yve7o0,15,11,0|z3bh3z,15,11,0|z3bh40,16,6,1|ze4abz,16,6,1|ze4ac0,15,11,0\",\"Asia/Bishkek|,0,220,0|-nu19tc,92,194,0|-kmr9w1,92,194,0|-kmr9w0,96,196,0|5vaxzz,96,196,0|5vay00,91,193,1|64pr7z,91,193,1|64pr80,96,196,0|6e2vbz,96,196,0|6e2vc0,91,193,1|6nhojz,91,193,1|6nhok0,96,196,0|6wusnz,96,196,0|6wuso0,91,193,1|769lvz,91,193,1|769lw0,96,196,0|7foknz,96,196,0|7foko0,91,193,1|7p1rjz,91,193,1|7p1rk0,96,196,0|7yesvz,96,196,0|7yesw0,91,193,1|87ru7z,91,193,1|87ru80,96,196,0|8h4vjz,96,196,0|8h4vk0,91,193,1|8qhwvz,91,193,1|8qhww0,96,196,0|8zuy7z,96,196,0|8zuy80,91,193,1|997zjz,91,193,1|997zk0,96,196,0|9il0vz,96,196,0|9il0w0,91,193,1|9ry27z,91,193,1|9ry280,96,196,0|a1b3jz,96,196,0|a1b3k0,91,193,1|aao4vz,91,193,1|aao4w0,96,196,0|ak167z,96,196,0|ak1680,91,193,1|atr67z,91,193,1|atr680,96,196,0|b347jz,96,196,0|b347k0,96,196,1|bazjjz,96,196,1|bazjk0,92,194,0|bmk4rz,92,194,0|bmk4s0,96,196,1|bv75zz,96,196,1|bv7600,92,194,0|c5a7fz,92,194,0|c5a7g0,96,196,1|cdx8nz,96,196,1|cdx8o0,92,194,0|co0a3z,92,194,0|co0a40,96,196,1|cwnbbz,96,196,1|cwnbc0,92,194,0|d6qcrz,92,194,0|d6qcs0,96,196,1|dfddzz,96,196,1|dfde00,92,194,0|dpgffz,92,194,0|dpgfg0,96,196,1|dygfbz,96,196,1|dygfc0,92,194,0|e7tqdz,92,194,0|e7tqe0,96,196,1|eimjlz,96,196,1|eimjm0,92,194,0|eqjt1z,92,194,0|eqjt20,96,196,1|f1cm9z,96,196,1|f1cma0,92,194,0|f99vpz,92,194,0|f99vq0,96,196,1|fkfnlz,96,196,1|fkfnm0,92,194,0|frzydz,92,194,0|frzye0,96,196,1|g35q9z,96,196,1|g35qa0,92,194,0|gaq11z,92,194,0|gaq120,96,196,1|glvsxz,96,196,1|glvsy0,92,194,0|gtt2dz,92,194,0|gtt2e0,96,196,1|h4lvlz,96,196,1|h4lvm0,92,194,0|hcj51z,92,194,0|hcj520,96,196,1|hnby9z,96,196,1|hnbya0,92,194,0|hv97pz,92,194,0|hv97q0,96,196,1|i6ezlz,96,196,1|i6ezm0,92,194,0|idzadz,92,194,0|idzae0,96,196,1|il2knz,96,196,1|il2ko0,96,196,0\",\"Asia/Brunei|,0,221,0|-mvofy4,106,222,0|-jb6i61,106,222,0|-jb6i60,89,191,0\",\"Asia/Chita|,0,223,0|-q4cfog,89,191,0|-kmri81,89,191,0|-kmri80,107,224,0|5vapnz,107,224,0|5vapo0,93,195,1|64pivz,93,195,1|64piw0,107,224,0|6e2mzz,107,224,0|6e2n00,93,195,1|6nhg7z,93,195,1|6nhg80,107,224,0|6wukbz,107,224,0|6wukc0,93,195,1|769djz,93,195,1|769dk0,107,224,0|7focbz,107,224,0|7focc0,93,195,1|7p1j7z,93,195,1|7p1j80,107,224,0|7yekjz,107,224,0|7yekk0,93,195,1|87rlvz,93,195,1|87rlw0,107,224,0|8h4n7z,107,224,0|8h4n80,93,195,1|8qhojz,93,195,1|8qhok0,107,224,0|8zupvz,107,224,0|8zupw0,93,195,1|997r7z,93,195,1|997r80,107,224,0|9iksjz,107,224,0|9iksk0,93,195,1|9rxtvz,93,195,1|9rxtw0,107,224,0|a1av7z,107,224,0|a1av80,93,195,1|aanwjz,93,195,1|aanwk0,107,224,0|ak0xvz,107,224,0|ak0xw0,93,195,1|atqxvz,93,195,1|atqxw0,107,224,0|b33z7z,107,224,0|b33z80,107,224,1|bch3bz,107,224,1|bch3c0,89,191,0|bi8hzz,89,191,0|bi8i00,107,224,0|blu1vz,107,224,0|blu1w0,93,195,1|bv737z,93,195,1|bv7380,107,224,0|c4k4jz,107,224,0|c4k4k0,93,195,1|cdx5vz,93,195,1|cdx5w0,107,224,0|cna77z,107,224,0|cna780,93,195,1|cwn8jz,93,195,1|cwn8k0,107,224,0|d609vz,107,224,0|d609w0,93,195,1|dfdb7z,93,195,1|dfdb80,107,224,0|dp3b7z,107,224,0|dp3b80,93,195,1|dzw77z,93,195,1|dzw780,107,224,0|e7tdvz,107,224,0|e7tdw0,93,195,1|eim9vz,93,195,1|eim9w0,107,224,0|eqjgjz,107,224,0|eqjgk0,93,195,1|f1ccjz,93,195,1|f1cck0,107,224,0|f99j7z,107,224,0|f99j80,93,195,1|fkfdvz,93,195,1|fkfdw0,107,224,0|frzlvz,107,224,0|frzlw0,93,195,1|g35gjz,93,195,1|g35gk0,107,224,0|gapojz,107,224,0|gapok0,93,195,1|glvj7z,93,195,1|glvj80,107,224,0|gtspvz,107,224,0|gtspw0,93,195,1|h4llvz,93,195,1|h4llw0,107,224,0|hcisjz,107,224,0|hcisk0,93,195,1|hnbojz,93,195,1|hnbok0,107,224,0|hv8v7z,107,224,0|hv8v80,93,195,1|i6epvz,93,195,1|i6epw0,107,224,0|idyxvz,107,224,0|idyxw0,93,195,1|ip4sjz,93,195,1|ip4sk0,107,224,0|iwp0jz,107,224,0|iwp0k0,93,195,1|j7uv7z,93,195,1|j7uv80,107,224,0|jff37z,107,224,0|jff380,93,195,1|jqkxvz,93,195,1|jqkxw0,107,224,0|jyi4jz,107,224,0|jyi4k0,93,195,1|k9b0jz,93,195,1|k9b0k0,107,224,0|kh877z,107,224,0|kh8780,93,195,1|ks137z,93,195,1|ks1380,107,224,0|kzy9vz,107,224,0|kzy9w0,93,195,1|lb44jz,93,195,1|lb44k0,107,224,0|liocjz,107,224,0|liock0,93,195,0|ne0cfz,93,195,0|ne0cg0,89,191,0|o4nrbz,89,191,0|o4nrc0,107,224,0\",\"Asia/Choibalsan|,0,225,0|-xmct7c,91,193,0|46akjz,91,193,0|46akk0,89,191,0|6wun3z,89,191,0|6wun40,93,195,1|769djz,93,195,1|769dk0,107,224,0|7focbz,107,224,0|7focc0,93,195,1|7p1avz,93,195,1|7p1aw0,107,224,0|7yeezz,107,224,0|7yef00,93,195,1|87rdjz,93,195,1|87rdk0,107,224,0|8h4hnz,107,224,0|8h4ho0,93,195,1|8qhg7z,93,195,1|8qhg80,107,224,0|8zukbz,107,224,0|8zukc0,93,195,1|997ivz,93,195,1|997iw0,107,224,0|9ikmzz,107,224,0|9ikn00,93,195,1|9rxljz,93,195,1|9rxlk0,107,224,0|a1apnz,107,224,0|a1apo0,93,195,1|aano7z,93,195,1|aano80,107,224,0|ak0sbz,107,224,0|ak0sc0,93,195,1|atqpjz,93,195,1|atqpk0,107,224,0|b33tnz,107,224,0|b33to0,93,195,1|bcgs7z,93,195,1|bcgs80,107,224,0|bltwbz,107,224,0|bltwc0,93,195,1|bv6uvz,93,195,1|bv6uw0,107,224,0|c4jyzz,107,224,0|c4jz00,93,195,1|cdwxjz,93,195,1|cdwxk0,107,224,0|cna1nz,107,224,0|cna1o0,93,195,1|cwn07z,93,195,1|cwn080,107,224,0|d604bz,107,224,0|d604c0,93,195,1|dfd2vz,93,195,1|dfd2w0,107,224,0|dp35nz,107,224,0|dp35o0,93,195,1|dyg47z,93,195,1|dyg480,107,224,0|e7t8bz,107,224,0|e7t8c0,93,195,1|eh66vz,93,195,1|eh66w0,107,224,0|eqjazz,107,224,0|eqjb00,93,195,1|ezw9jz,93,195,1|ezw9k0,107,224,0|gcgn7z,107,224,0|gcgn80,93,195,1|gkdr3z,93,195,1|gkdr40,107,224,0|gtqv7z,107,224,0|gtqv80,93,195,1|h33trz,93,195,1|h33ts0,107,224,0|hcgxvz,107,224,0|hcgxw0,93,195,1|hltwfz,93,195,1|hltwg0,107,224,0|hv70jz,107,224,0|hv70k0,93,195,1|i4jz3z,93,195,1|i4jz40,107,224,0|idx37z,107,224,0|idx380,93,195,1|ina1rz,93,195,1|ina1s0,107,224,0|iwn5vz,107,224,0|iwn5w0,93,195,1|j6d33z,93,195,1|j6d340,107,224,0|jyjtnz,107,224,0|jyjto0,89,191,0|nlvtzz,89,191,0|nlvu00,107,224,1|nv8mzz,107,224,1|nv8n00,89,191,0|o4lwnz,89,191,0|o4lwo0,107,224,1|odypnz,107,224,1|odypo0,89,191,0\",\"Asia/Colombo|,0,226,0|-1ayyhgc,21,227,0|-xehasl,21,227,0|-xehask,108,228,0|-elvwm1,108,228,0|-elvwm0,96,196,1|-e9lco1,96,196,1|-e9lco0,109,229,1|-cmw9u1,109,229,1|-cmw9u0,108,228,0|drxa1z,108,228,0|drxa20,109,229,0|dzufbz,109,229,0|dzufc0,96,196,0|ixq61z,96,196,0|ixq620,108,228,0\",\"Asia/Damascus|,0,230,0|-q3gk20,15,11,0|-pxwdc1,15,11,0|-pxwdc0,16,6,1|-pp9c41,16,6,1|-pp9c40,15,11,0|-pf6ao1,15,11,0|-pf6ao0,16,6,1|-p6j9g1,16,6,1|-p6j9g0,15,11,0|-owg801,15,11,0|-owg800,16,6,1|-ont6s1,16,6,1|-ont6s0,15,11,0|-odq5c1,15,11,0|-odq5c0,16,6,1|-o4q5g1,16,6,1|-o4q5g0,15,11,0|-408lc1,15,11,0|-408lc0,16,6,1|-3s9ms1,16,6,1|-3s9ms0,15,11,0|-3hcyo1,15,11,0|-3hcyo0,16,6,1|-39jk41,16,6,1|-39jk40,15,11,0|-2yj6o1,15,11,0|-2yj6o0,16,6,1|-2qnxg1,16,6,1|-2qnxg0,15,11,0|-2fr9c1,15,11,0|-2fr9c0,16,6,1|-27xus1,16,6,1|-27xus0,15,11,0|-1xcao1,15,11,0|-1xcao0,16,6,1|-1p42s1,16,6,1|-1p42s0,15,11,0|-1e7eo1,15,11,0|-1e7eo0,16,6,1|-16c5g1,16,6,1|-16c5g0,15,11,0|-vdmo1,15,11,0|-vdmo0,16,6,1|-nidg1,16,6,1|-nidg0,15,11,0|-clpc1,15,11,0|-clpc0,16,6,1|-4qg41,16,6,1|-4qg40,15,11,0|667zz,15,11,0|66800,16,6,1|e1h7z,16,6,1|e1h80,15,11,0|oy5bz,15,11,0|oy5c0,16,6,1|wtejz,16,6,1|wtek0,15,11,0|17rxbz,15,11,0|17rxc0,16,6,1|1fn6jz,16,6,1|1fn6k0,15,11,0|1qjunz,15,11,0|1qjuo0,16,6,1|1yf3vz,16,6,1|1yf3w0,15,11,0|29brzz,15,11,0|29bs00,16,6,1|2h717z,16,6,1|2h7180,15,11,0|2s3pbz,15,11,0|2s3pc0,16,6,1|2zyyjz,16,6,1|2zyyk0,15,11,0|3axhbz,15,11,0|3axhc0,16,6,1|3isqjz,16,6,1|3isqk0,15,11,0|3tpenz,15,11,0|3tpeo0,16,6,1|4013vz,16,6,1|4013w0,15,11,0|4chbzz,15,11,0|4chc00,16,6,1|4it17z,16,6,1|4it180,15,11,0|6xa2nz,15,11,0|6xa2o0,16,6,1|76a2jz,16,6,1|76a2k0,15,11,0|7g3unz,15,11,0|7g3uo0,16,6,1|7p3ujz,16,6,1|7p3uk0,15,11,0|8ezenz,15,11,0|8ezeo0,16,6,1|8r2ijz,16,6,1|8r2ik0,15,11,0|8yfenz,15,11,0|8yfeo0,16,6,1|9az6jz,16,6,1|9az6k0,15,11,0|9hz3zz,15,11,0|9hz400,16,6,1|9tsyjz,16,6,1|9tsyk0,15,11,0|a1knzz,15,11,0|a1ko00,16,6,1|ab1bvz,16,6,1|ab1bw0,15,11,0|akefzz,15,11,0|akeg00,16,6,1|atrejz,16,6,1|atrek0,15,11,0|b367rz,15,11,0|b367s0,16,6,1|bcl0zz,16,6,1|bcl100,15,11,0|bmcyfz,15,11,0|bmcyg0,16,6,1|bveszz,16,6,1|bvet00,15,11,0|c4gt3z,15,11,0|c4gt40,16,6,1|cdvmbz,16,6,1|cdvmc0,15,11,0|cnjufz,15,11,0|cnjug0,16,6,1|cwynnz,16,6,1|cwyno0,15,11,0|d6brrz,15,11,0|d6brs0,16,6,1|dfqkzz,16,6,1|dfql00,15,11,0|dp5jrz,15,11,0|dp5js0,16,6,1|dykczz,16,6,1|dykd00,15,11,0|e7vmfz,15,11,0|e7vmg0,16,6,1|ehcabz,16,6,1|ehcac0,15,11,0|eqlp3z,15,11,0|eqlp40,16,6,1|f047nz,16,6,1|f047o0,15,11,0|f9hbrz,15,11,0|f9hbs0,16,6,1|fiw4zz,16,6,1|fiw500,15,11,0|fsb3rz,15,11,0|fsb3s0,16,6,1|g1pwzz,16,6,1|g1px00,15,11,0|gb313z,15,11,0|gb3140,16,6,1|gkhubz,16,6,1|gkhuc0,15,11,0|gtuyfz,15,11,0|gtuyg0,16,6,1|h39rnz,16,6,1|h39ro0,15,11,0|hcmvrz,15,11,0|hcmvs0,16,6,1|hm1ozz,16,6,1|hm1p00,15,11,0|hvgnrz,15,11,0|hvgns0,16,6,1|i4vgzz,16,6,1|i4vh00,15,11,0|ie8l3z,15,11,0|ie8l40,16,6,1|innebz,16,6,1|innec0,15,11,0|ix0ifz,15,11,0|ix0ig0,16,6,1|j5ynnz,16,6,1|j5yno0,15,11,0|jfoqfz,15,11,0|jfoqg0,16,6,1|jquibz,16,6,1|jquic0,15,11,0|jyrrrz,15,11,0|jyrrs0,16,6,1|k9mfnz,16,6,1|k9mfo0,15,11,0|kh4vrz,15,11,0|kh4vs0,16,6,1|ksannz,16,6,1|ksano0,15,11,0|l07x3z,15,11,0|l07x40,16,6,1|lb0qbz,16,6,1|lb0qc0,15,11,0|lixzrz,15,11,0|lixzs0,16,6,1|ltqszz,16,6,1|ltqt00,15,11,0|m1o2fz,15,11,0|m1o2g0,16,6,1|mcgvnz,16,6,1|mcgvo0,15,11,0|mke53z,15,11,0|mke540,16,6,1|mv6ybz,16,6,1|mv6yc0,15,11,0|n347rz,15,11,0|n347s0,16,6,1|ne9znz,16,6,1|ne9zo0,15,11,0|nluafz,15,11,0|nluag0,16,6,1|nx02bz,16,6,1|nx02c0,15,11,0|o4kd3z,15,11,0|o4kd40,16,6,1|ofq4zz,16,6,1|ofq500,15,11,0|onnefz,15,11,0|onneg0,16,6,1|oyg7nz,16,6,1|oyg7o0,15,11,0|p6dh3z,15,11,0|p6dh40,16,6,1|ph6abz,16,6,1|ph6ac0,15,11,0|pp3jrz,15,11,0|pp3js0,16,6,1|pzwczz,16,6,1|pzwd00,15,11,0|q7tmfz,15,11,0|q7tmg0,16,6,1|qizebz,16,6,1|qizec0,15,11,0|qqjp3z,15,11,0|qqjp40,16,6,1|r1pgzz,16,6,1|r1ph00,15,11,0|r99rrz,15,11,0|r99rs0,16,6,1|rkfjnz,16,6,1|rkfjo0,15,11,0|rsct3z,15,11,0|rsct40,16,6,1|s35mbz,16,6,1|s35mc0,15,11,0|sb2vrz,15,11,0|sb2vs0,16,6,1|slvozz,16,6,1|slvp00,15,11,0|stsyfz,15,11,0|stsyg0,16,6,1|t4yqbz,16,6,1|t4yqc0,15,11,0|tcj13z,15,11,0|tcj140,16,6,1|tnoszz,16,6,1|tnot00,15,11,0|tv93rz,15,11,0|tv93s0,16,6,1|u6evnz,16,6,1|u6evo0,15,11,0|uec53z,15,11,0|uec540,16,6,1|up4ybz,16,6,1|up4yc0,15,11,0|ux27rz,15,11,0|ux27s0,16,6,1|v7v0zz,16,6,1|v7v100,15,11,0|vfsafz,15,11,0|vfsag0,16,6,1|vql3nz,16,6,1|vql3o0,15,11,0|vyid3z,15,11,0|vyid40,16,6,1|w9o4zz,16,6,1|w9o500,15,11,0|wh8frz,15,11,0|wh8fs0,16,6,1|wse7nz,16,6,1|wse7o0,15,11,0|wzyifz,15,11,0|wzyig0,16,6,1|xb4abz,16,6,1|xb4ac0,15,11,0|xj1jrz,15,11,0|xj1js0,16,6,1|xtuczz,16,6,1|xtud00,15,11,0|y1rmfz,15,11,0|y1rmg0,16,6,1|yckfnz,16,6,1|yckfo0,15,11,0|ykhp3z,15,11,0|ykhp40,16,6,1|yvngzz,16,6,1|yvnh00,15,11,0|z37rrz,15,11,0|z37rs0,16,6,1|zedjnz,16,6,1|zedjo0,15,11,0\",\"Asia/Dhaka|,0,231,0|-15r1q2s,77,232,0|-eqtpox,77,232,0|-eqtpow,109,229,0|-ef78q1,109,229,0|-ef78q0,108,228,0|-e9lba1,108,228,0|-e9lba0,109,229,0|-9j0ne1,109,229,0|-9j0ne0,96,196,0|klhwjz,96,196,0|klhwk0,91,193,1|kvj0jz,91,193,1|kvj0k0,96,196,0\",\"Asia/Dili|,0,233,0|-u9s4l8,89,191,0|-ejfac1,89,191,0|-ejfac0,107,224,0|3b0hnz,107,224,0|3b0ho0,89,191,0|g0zlrz,89,191,0|g0zls0,107,224,0\",\"Asia/Dubai|,0,234,0|-q3gnko,105,209,0\",\"Asia/Dushanbe|,0,235,0|-nu18qo,92,194,0|-kmr9w1,92,194,0|-kmr9w0,96,196,0|5vaxzz,96,196,0|5vay00,91,193,1|64pr7z,91,193,1|64pr80,96,196,0|6e2vbz,96,196,0|6e2vc0,91,193,1|6nhojz,91,193,1|6nhok0,96,196,0|6wusnz,96,196,0|6wuso0,91,193,1|769lvz,91,193,1|769lw0,96,196,0|7foknz,96,196,0|7foko0,91,193,1|7p1rjz,91,193,1|7p1rk0,96,196,0|7yesvz,96,196,0|7yesw0,91,193,1|87ru7z,91,193,1|87ru80,96,196,0|8h4vjz,96,196,0|8h4vk0,91,193,1|8qhwvz,91,193,1|8qhww0,96,196,0|8zuy7z,96,196,0|8zuy80,91,193,1|997zjz,91,193,1|997zk0,96,196,0|9il0vz,96,196,0|9il0w0,91,193,1|9ry27z,91,193,1|9ry280,96,196,0|a1b3jz,96,196,0|a1b3k0,91,193,1|aao4vz,91,193,1|aao4w0,96,196,0|ak167z,96,196,0|ak1680,91,193,1|atr67z,91,193,1|atr680,96,196,0|b347jz,96,196,0|b347k0,96,196,1|bbgabz,96,196,1|bbgac0,92,194,0\",\"Asia/Famagusta|,0,236,0|-p4bqac,15,11,0|2r67rz,15,11,0|2r67s0,16,6,1|30j6bz,16,6,1|30j6c0,15,11,0|3bn93z,15,11,0|3bn940,16,6,1|3jb3nz,16,6,1|3jb3o0,15,11,0|3s9efz,15,11,0|3s9eg0,16,6,1|419ebz,16,6,1|419ec0,15,11,0|4azh3z,15,11,0|4azh40,16,6,1|4keabz,16,6,1|4keac0,15,11,0|4tpjrz,15,11,0|4tpjs0,16,6,1|532ibz,16,6,1|532ic0,15,11,0|5csl3z,15,11,0|5csl40,16,6,1|5lskzz,16,6,1|5lsl00,15,11,0|5v5p3z,15,11,0|5v5p40,16,6,1|64innz,16,6,1|64ino0,15,11,0|6dvrrz,15,11,0|6dvrs0,16,6,1|6n8qbz,16,6,1|6n8qc0,15,11,0|6wlufz,15,11,0|6wlug0,16,6,1|75yszz,16,6,1|75yt00,15,11,0|7fbx3z,15,11,0|7fbx40,16,6,1|7p1ubz,16,6,1|7p1uc0,15,11,0|7yeyfz,15,11,0|7yeyg0,16,6,1|87rwzz,16,6,1|87rx00,15,11,0|8h513z,15,11,0|8h5140,16,6,1|8qhznz,16,6,1|8qhzo0,15,11,0|8zv3rz,15,11,0|8zv3s0,16,6,1|9982bz,16,6,1|9982c0,15,11,0|9il6fz,15,11,0|9il6g0,16,6,1|9ry4zz,16,6,1|9ry500,15,11,0|a1b93z,15,11,0|a1b940,16,6,1|aao7nz,16,6,1|aao7o0,15,11,0|ak1brz,15,11,0|ak1bs0,16,6,1|atr8zz,16,6,1|atr900,15,11,0|b34d3z,15,11,0|b34d40,16,6,1|bchbnz,16,6,1|bchbo0,15,11,0|blufrz,15,11,0|blufs0,16,6,1|bv7ebz,16,6,1|bv7ec0,15,11,0|c4kifz,15,11,0|c4kig0,16,6,1|cdxgzz,16,6,1|cdxh00,15,11,0|cnal3z,15,11,0|cnal40,16,6,1|cwnjnz,16,6,1|cwnjo0,15,11,0|d60nrz,15,11,0|d60ns0,16,6,1|dfdmbz,16,6,1|dfdmc0,15,11,0|dp3p3z,15,11,0|dp3p40,16,6,1|dygnnz,16,6,1|dygno0,15,11,0|e7trrz,15,11,0|e7trs0,16,6,1|eh6qbz,16,6,1|eh6qc0,15,11,0|eqjufz,15,11,0|eqjug0,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|fs083z,15,11,0|fs0840,16,6,1|g362rz,16,6,1|g362s0,15,11,0|gaqarz,15,11,0|gaqas0,16,6,1|glw5fz,16,6,1|glw5g0,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|od5jnz,16,6,1|od5jo0,100,6,0|oyk83z,100,6,0|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Asia/Gaza|,0,237,0|-1054x1s,15,11,0|-ffv401,15,11,0|-ffv400,16,6,1|-f9l6o1,16,6,1|-f9l6o0,15,11,0|-f765c1,15,11,0|-f765c0,16,6,1|-e6fxc1,16,6,1|-e6fxc0,15,11,0|-dyoao1,15,11,0|-dyoao0,16,6,1|-dno001,16,6,1|-dno000,15,11,0|-dfuio1,15,11,0|-dfuio0,16,6,1|-d4u801,16,6,1|-d4u800,15,11,0|-cwatc1,15,11,0|-cwatc0,16,6,1|-cm2ao1,16,6,1|-cm2ao0,15,11,0|-cdiw01,15,11,0|-cdiw00,16,6,1|-c3adc1,16,6,1|-c3adc0,15,11,0|-6lluw1,15,11,0|-6lluw0,16,6,1|-6e79o1,16,6,1|-6e79o0,15,11,0|-63alk1,15,11,0|-63alk0,16,6,1|-5vfcc1,16,6,1|-5vfcc0,15,11,0|-5kilg1,15,11,0|-5kilg0,16,6,1|-5cp1c1,16,6,1|-5cp1c0,15,11,0|-51otg1,15,11,0|-51otg0,16,6,1|-4tv9c1,16,6,1|-4tv9c0,15,11,0|-4iww41,15,11,0|-4iww40,16,6,1|-4b3c01,16,6,1|-4b3c00,15,11,0|-404ys1,15,11,0|-404ys0,16,6,1|-3sbeo1,16,6,1|-3sbeo0,15,11,0|-3hd1g1,15,11,0|-3hd1g0,16,6,1|-39jhc1,16,6,1|-39jhc0,15,11,0|-2yj9g1,15,11,0|-2yj9g0,16,6,1|-2qppc1,16,6,1|-2qppc0,15,11,0|-2frc41,15,11,0|-2frc40,16,6,1|-27xs01,16,6,1|-27xs00,15,11,0|-1wzes1,15,11,0|-1wzes0,16,6,1|-1p4001,16,6,1|-1p4000,15,11,0|-1e7hg1,15,11,0|-1e7hg0,16,6,1|-1ceto1,16,6,1|-1ceto0,110,11,0|2crp3z,110,11,0|2crp40,111,6,1|2ht3nz,111,6,1|2ht3o0,110,11,0|2rj6fz,110,11,0|2rj6g0,111,6,1|2ydebz,111,6,1|2ydec0,110,11,0|5iwyfz,110,11,0|5iwyg0,111,6,1|5l2qfz,111,6,1|5l2qg0,110,11,0|7hhp3z,110,11,0|7hhp40,111,6,1|7n93rz,111,6,1|7n93s0,110,11,0|7z4vrz,110,11,0|7z4vs0,111,6,1|86c2bz,111,6,1|86c2c0,110,11,0|8jnrrz,110,11,0|8jnrs0,111,6,1|8pf3nz,111,6,1|8pf3o0,110,11,0|90ql3z,110,11,0|90ql40,111,6,1|98i4zz,111,6,1|98i500,110,11,0|9jb3rz,110,11,0|9jb3s0,111,6,1|9qv8zz,111,6,1|9qv900,110,11,0|a342fz,110,11,0|a342g0,111,6,1|a9lbnz,111,6,1|a9lbo0,110,11,0|ak1brz,110,11,0|ak1bs0,111,6,1|aryfnz,111,6,1|aryfo0,110,11,0|b2refz,110,11,0|b2reg0,111,6,1|bb1gzz,111,6,1|bb1h00,110,11,0|blufrz,110,11,0|blufs0,111,6,1|bu4ibz,111,6,1|bu4ic0,110,11,0|c4trrz,110,11,0|c4trs0,111,6,1|ccukzz,111,6,1|ccul00,110,11,0|cnjufz,110,11,0|cnjug0,111,6,1|cv7ozz,111,6,1|cv7p00,110,11,0|d69x3z,110,11,0|d69x40,111,6,1|deaqbz,111,6,1|deaqc0,110,11,0|dkh13z,110,11,0|dkh140,15,11,0|dpcyfz,15,11,0|dpcyg0,16,6,1|dy02fz,16,6,1|dy02g0,15,11,0|e8313z,15,11,0|e83140,16,6,1|egq53z,16,6,1|egq540,15,11,0|eqt3rz,15,11,0|eqt3s0,16,6,1|ezg7rz,16,6,1|ezg7s0,15,11,0|fa93rz,15,11,0|fa93s0,16,6,1|fjm2bz,16,6,1|fjm2c0,15,11,0|ftc53z,15,11,0|ftc540,16,6,1|g2p3nz,16,6,1|g2p3o0,15,11,0|gc27rz,15,11,0|gc27s0,16,6,1|glf6bz,16,6,1|glf6c0,15,11,0|gusafz,15,11,0|gusag0,16,6,1|h458zz,16,6,1|h45900,15,11,0|hdid3z,15,11,0|hdid40,16,6,1|hmvbnz,16,6,1|hmvbo0,15,11,0|hw8frz,15,11,0|hw8fs0,16,6,1|i4vjrz,16,6,1|i4vjs0,15,11,0|ieyifz,15,11,0|ieyig0,16,6,1|int3vz,16,6,1|int3w0,15,11,0|ix0ifz,15,11,0|ix0ig0,16,6,1|j5ynnz,16,6,1|j5yno0,15,11,0|jfsfrz,15,11,0|jfsfs0,16,6,1|joa2jz,16,6,1|joa2k0,15,11,0|jyet3z,15,11,0|jyet40,16,6,1|k6bwzz,16,6,1|k6bx00,15,11,0|kh4vrz,15,11,0|kh4vs0,16,6,1|kpf13z,16,6,1|kpf140,15,11,0|kzwt5n,15,11,0|kzwt5o,16,6,1|l6yfnz,16,6,1|l6yfo0,15,11,0|lixztn,15,11,0|lixzto,16,6,1|lp7ubz,16,6,1|lp7uc0,15,11,0|m1o2fz,15,11,0|m1o2g0,16,6,1|mao53z,16,6,1|mao540,15,11,0|mke53z,15,11,0|mke540,16,6,1|mtr3nz,16,6,1|mtr3o0,15,11,0|n347rz,15,11,0|n347s0,16,6,1|ndx0zz,16,6,1|ndx100,15,11,0|nlw53z,15,11,0|nlw540,16,6,1|nwn6fz,16,6,1|nwn6g0,15,11,0|o4majz,15,11,0|o4mak0,16,6,1|ofs2fz,16,6,1|ofs2g0,15,11,0|oncd7z,15,11,0|oncd80,16,6,1|oyi53z,16,6,1|oyi540,15,11,0|p62fvz,15,11,0|p62fw0,16,6,1|ph87rz,16,6,1|ph87s0,15,11,0|pp3jrz,15,11,0|pp3js0,16,6,1|pzy7nz,16,6,1|pzy7o0,15,11,0|q7vh3z,15,11,0|q7vh40,16,6,1|qiod3z,16,6,1|qiod40,15,11,0|qqljrz,15,11,0|qqljs0,16,6,1|r1refz,16,6,1|r1reg0,15,11,0|r9bmfz,15,11,0|r9bmg0,16,6,1|rkhh3z,16,6,1|rkhh40,15,11,0|rs1p3z,15,11,0|rs1p40,16,6,1|s37jrz,16,6,1|s37js0,15,11,0|sb4qfz,15,11,0|sb4qg0,16,6,1|slxmfz,16,6,1|slxmg0,15,11,0|stut3z,15,11,0|stut40,16,6,1|t4np3z,16,6,1|t4np40,15,11,0|tckvrz,15,11,0|tckvs0,16,6,1|tndrrz,16,6,1|tndrs0,15,11,0|tvayfz,15,11,0|tvayg0,16,6,1|u6gt3z,16,6,1|u6gt40,15,11,0|ue113z,15,11,0|ue1140,16,6,1|up6vrz,16,6,1|up6vs0,15,11,0|uwr3rz,15,11,0|uwr3s0,16,6,1|v7wyfz,16,6,1|v7wyg0,15,11,0|vfu53z,15,11,0|vfu540,16,6,1|vqn13z,16,6,1|vqn140,15,11,0|vyk7rz,15,11,0|vyk7s0,16,6,1|w9d3rz,16,6,1|w9d3s0,15,11,0|whaafz,15,11,0|whaag0,16,6,1|wsg53z,16,6,1|wsg540,15,11,0|x00d3z,15,11,0|x00d40,16,6,1|xb67rz,16,6,1|xb67s0,15,11,0|xiqfrz,15,11,0|xiqfs0,16,6,1|xtwafz,16,6,1|xtwag0,15,11,0|y1gifz,15,11,0|y1gig0,16,6,1|ycmd3z,16,6,1|ycmd40,15,11,0|ykjjrz,15,11,0|ykjjs0,16,6,1|yvcfrz,16,6,1|yvcfs0,15,11,0|z39mfz,15,11,0|z39mg0,16,6,1|ze2ifz,16,6,1|ze2ig0,15,11,0\",\"Asia/Hebron|,0,238,0|-1054x5z,15,11,0|-ffv401,15,11,0|-ffv400,16,6,1|-f9l6o1,16,6,1|-f9l6o0,15,11,0|-f765c1,15,11,0|-f765c0,16,6,1|-e6fxc1,16,6,1|-e6fxc0,15,11,0|-dyoao1,15,11,0|-dyoao0,16,6,1|-dno001,16,6,1|-dno000,15,11,0|-dfuio1,15,11,0|-dfuio0,16,6,1|-d4u801,16,6,1|-d4u800,15,11,0|-cwatc1,15,11,0|-cwatc0,16,6,1|-cm2ao1,16,6,1|-cm2ao0,15,11,0|-cdiw01,15,11,0|-cdiw00,16,6,1|-c3adc1,16,6,1|-c3adc0,15,11,0|-6lluw1,15,11,0|-6lluw0,16,6,1|-6e79o1,16,6,1|-6e79o0,15,11,0|-63alk1,15,11,0|-63alk0,16,6,1|-5vfcc1,16,6,1|-5vfcc0,15,11,0|-5kilg1,15,11,0|-5kilg0,16,6,1|-5cp1c1,16,6,1|-5cp1c0,15,11,0|-51otg1,15,11,0|-51otg0,16,6,1|-4tv9c1,16,6,1|-4tv9c0,15,11,0|-4iww41,15,11,0|-4iww40,16,6,1|-4b3c01,16,6,1|-4b3c00,15,11,0|-404ys1,15,11,0|-404ys0,16,6,1|-3sbeo1,16,6,1|-3sbeo0,15,11,0|-3hd1g1,15,11,0|-3hd1g0,16,6,1|-39jhc1,16,6,1|-39jhc0,15,11,0|-2yj9g1,15,11,0|-2yj9g0,16,6,1|-2qppc1,16,6,1|-2qppc0,15,11,0|-2frc41,15,11,0|-2frc40,16,6,1|-27xs01,16,6,1|-27xs00,15,11,0|-1wzes1,15,11,0|-1wzes0,16,6,1|-1p4001,16,6,1|-1p4000,15,11,0|-1e7hg1,15,11,0|-1e7hg0,16,6,1|-1ceto1,16,6,1|-1ceto0,110,11,0|2crp3z,110,11,0|2crp40,111,6,1|2ht3nz,111,6,1|2ht3o0,110,11,0|2rj6fz,110,11,0|2rj6g0,111,6,1|2ydebz,111,6,1|2ydec0,110,11,0|5iwyfz,110,11,0|5iwyg0,111,6,1|5l2qfz,111,6,1|5l2qg0,110,11,0|7hhp3z,110,11,0|7hhp40,111,6,1|7n93rz,111,6,1|7n93s0,110,11,0|7z4vrz,110,11,0|7z4vs0,111,6,1|86c2bz,111,6,1|86c2c0,110,11,0|8jnrrz,110,11,0|8jnrs0,111,6,1|8pf3nz,111,6,1|8pf3o0,110,11,0|90ql3z,110,11,0|90ql40,111,6,1|98i4zz,111,6,1|98i500,110,11,0|9jb3rz,110,11,0|9jb3s0,111,6,1|9qv8zz,111,6,1|9qv900,110,11,0|a342fz,110,11,0|a342g0,111,6,1|a9lbnz,111,6,1|a9lbo0,110,11,0|ak1brz,110,11,0|ak1bs0,111,6,1|aryfnz,111,6,1|aryfo0,110,11,0|b2refz,110,11,0|b2reg0,111,6,1|bb1gzz,111,6,1|bb1h00,110,11,0|blufrz,110,11,0|blufs0,111,6,1|bu4ibz,111,6,1|bu4ic0,110,11,0|c4trrz,110,11,0|c4trs0,111,6,1|ccukzz,111,6,1|ccul00,110,11,0|cnjufz,110,11,0|cnjug0,111,6,1|cv7ozz,111,6,1|cv7p00,110,11,0|d69x3z,110,11,0|d69x40,111,6,1|deaqbz,111,6,1|deaqc0,110,11,0|dkh13z,110,11,0|dkh140,15,11,0|dpcyfz,15,11,0|dpcyg0,16,6,1|dy02fz,16,6,1|dy02g0,15,11,0|e8313z,15,11,0|e83140,16,6,1|egq53z,16,6,1|egq540,15,11,0|eqt3rz,15,11,0|eqt3s0,16,6,1|ezg7rz,16,6,1|ezg7s0,15,11,0|fa93rz,15,11,0|fa93s0,16,6,1|fjm2bz,16,6,1|fjm2c0,15,11,0|ftc53z,15,11,0|ftc540,16,6,1|g2p3nz,16,6,1|g2p3o0,15,11,0|gc27rz,15,11,0|gc27s0,16,6,1|glf6bz,16,6,1|glf6c0,15,11,0|gusafz,15,11,0|gusag0,16,6,1|h458zz,16,6,1|h45900,15,11,0|hdid3z,15,11,0|hdid40,16,6,1|hmvbnz,16,6,1|hmvbo0,15,11,0|hw8frz,15,11,0|hw8fs0,16,6,1|i4vjrz,16,6,1|i4vjs0,15,11,0|ieyifz,15,11,0|ieyig0,16,6,1|int3vz,16,6,1|int3w0,15,11,0|ix0ifz,15,11,0|ix0ig0,16,6,1|j5ynnz,16,6,1|j5yno0,15,11,0|jfsfrz,15,11,0|jfsfs0,16,6,1|joa2jz,16,6,1|joa2k0,15,11,0|jyet3z,15,11,0|jyet40,16,6,1|k6hgzz,16,6,1|k6hh00,15,11,0|kh4vrz,15,11,0|kh4vs0,16,6,1|kpf13z,16,6,1|kpf140,15,11,0|kzuyfz,15,11,0|kzuyg0,16,6,1|l6yfnz,16,6,1|l6yfo0,15,11,0|lixztn,15,11,0|lixzto,16,6,1|lp7ubz,16,6,1|lp7uc0,15,11,0|lqpmfz,15,11,0|lqpmg0,16,6,1|lsaybz,16,6,1|lsayc0,15,11,0|m1o2fz,15,11,0|m1o2g0,16,6,1|mao53z,16,6,1|mao540,15,11,0|mke53z,15,11,0|mke540,16,6,1|mtr3nz,16,6,1|mtr3o0,15,11,0|n347rz,15,11,0|n347s0,16,6,1|ndx0zz,16,6,1|ndx100,15,11,0|nlw53z,15,11,0|nlw540,16,6,1|nwn6fz,16,6,1|nwn6g0,15,11,0|o4majz,15,11,0|o4mak0,16,6,1|ofs2fz,16,6,1|ofs2g0,15,11,0|oncd7z,15,11,0|oncd80,16,6,1|oyi53z,16,6,1|oyi540,15,11,0|p62fvz,15,11,0|p62fw0,16,6,1|ph87rz,16,6,1|ph87s0,15,11,0|pp3jrz,15,11,0|pp3js0,16,6,1|pzy7nz,16,6,1|pzy7o0,15,11,0|q7vh3z,15,11,0|q7vh40,16,6,1|qiod3z,16,6,1|qiod40,15,11,0|qqljrz,15,11,0|qqljs0,16,6,1|r1refz,16,6,1|r1reg0,15,11,0|r9bmfz,15,11,0|r9bmg0,16,6,1|rkhh3z,16,6,1|rkhh40,15,11,0|rs1p3z,15,11,0|rs1p40,16,6,1|s37jrz,16,6,1|s37js0,15,11,0|sb4qfz,15,11,0|sb4qg0,16,6,1|slxmfz,16,6,1|slxmg0,15,11,0|stut3z,15,11,0|stut40,16,6,1|t4np3z,16,6,1|t4np40,15,11,0|tckvrz,15,11,0|tckvs0,16,6,1|tndrrz,16,6,1|tndrs0,15,11,0|tvayfz,15,11,0|tvayg0,16,6,1|u6gt3z,16,6,1|u6gt40,15,11,0|ue113z,15,11,0|ue1140,16,6,1|up6vrz,16,6,1|up6vs0,15,11,0|uwr3rz,15,11,0|uwr3s0,16,6,1|v7wyfz,16,6,1|v7wyg0,15,11,0|vfu53z,15,11,0|vfu540,16,6,1|vqn13z,16,6,1|vqn140,15,11,0|vyk7rz,15,11,0|vyk7s0,16,6,1|w9d3rz,16,6,1|w9d3s0,15,11,0|whaafz,15,11,0|whaag0,16,6,1|wsg53z,16,6,1|wsg540,15,11,0|x00d3z,15,11,0|x00d40,16,6,1|xb67rz,16,6,1|xb67s0,15,11,0|xiqfrz,15,11,0|xiqfs0,16,6,1|xtwafz,16,6,1|xtwag0,15,11,0|y1gifz,15,11,0|y1gig0,16,6,1|ycmd3z,16,6,1|ycmd40,15,11,0|ykjjrz,15,11,0|ykjjs0,16,6,1|yvcfrz,16,6,1|yvcfs0,15,11,0|z39mfz,15,11,0|z39mg0,16,6,1|ze2ifz,16,6,1|ze2ig0,15,11,0\",\"Asia/Ho_Chi_Minh|,0,239,0|-x56934,112,240,0|-umdqev,112,240,0|-umdqeu,91,193,0|-e3bkw1,91,193,0|-e3bkw0,89,191,0|-cxyro1,89,191,0|-cxyro0,107,224,0|-cp63o1,107,224,0|-cp63o0,91,193,0|-bvja41,91,193,0|-bvja40,89,191,0|-7kjq81,89,191,0|-7kjq80,91,193,0|-57xfk1,91,193,0|-57xfk0,89,191,0|2uaprz,89,191,0|2uaps0,91,193,0\",\"Asia/Hong_Kong|,0,241,0|-y0i0s0,113,191,0|-ewdn81,113,191,0|-ewdn80,114,224,1|-eqtn81,114,224,1|-eqtn80,115,242,1|-emgia1,115,242,1|-emgia0,116,224,0|-cl7cs1,116,224,0|-cl7cs0,113,191,0|-cda8w1,113,191,0|-cda8w0,114,224,1|-c1r5u1,114,224,1|-c1r5u0,113,191,0|-buwv61,113,191,0|-buwv60,114,224,1|-bj1361,114,224,1|-bj1360,113,191,0|-bb3wi1,113,191,0|-bb3wi0,114,224,1|-b1qv61,114,224,1|-b1qv60,113,191,0|-attoi1,113,191,0|-attoi0,114,224,1|-aj0si1,114,224,1|-aj0si0,113,191,0|-ab3lu1,113,191,0|-ab3lu0,114,224,1|-a0apu1,114,224,1|-a0apu0,113,191,0|-9sdj61,113,191,0|-9sdj60,114,224,1|-9hkn61,114,224,1|-9hkn60,113,191,0|-99ahu1,113,191,0|-99ahu0,114,224,1|-8yhlu1,114,224,1|-8yhlu0,113,191,0|-8qkf61,113,191,0|-8qkf60,114,224,1|-8frly1,114,224,1|-8frly0,113,191,0|-88k9u1,113,191,0|-88k9u0,114,224,1|-7x1ja1,114,224,1|-7x1ja0,113,191,0|-7pu761,113,191,0|-7pu760,114,224,1|-7dyhy1,114,224,1|-7dyhy0,113,191,0|-7744i1,113,191,0|-7744i0,114,224,1|-6v8fa1,114,224,1|-6v8fa0,113,191,0|-6o1361,113,191,0|-6o1360,114,224,1|-6cicm1,114,224,1|-6cicm0,113,191,0|-65b0i1,113,191,0|-65b0i0,114,224,1|-5ts9y1,114,224,1|-5ts9y0,113,191,0|-5mkxu1,113,191,0|-5mkxu0,114,224,1|-5b27a1,114,224,1|-5b27a0,113,191,0|-53uv61,113,191,0|-53uv60,114,224,1|-4rz5y1,114,224,1|-4rz5y0,113,191,0|-4l4si1,113,191,0|-4l4si0,114,224,1|-4993a1,114,224,1|-4993a0,113,191,0|-42epu1,113,191,0|-42epu0,114,224,1|-3qj0m1,114,224,1|-3qj0m0,113,191,0|-3jboi1,113,191,0|-3jboi0,114,224,1|-37sxy1,114,224,1|-37sxy0,113,191,0|-30llu1,113,191,0|-30llu0,114,224,1|-2p2va1,114,224,1|-2p2va0,113,191,0|-2gfoi1,113,191,0|-2gfoi0,114,224,1|-272py1,114,224,1|-272py0,113,191,0|-1xplu1,113,191,0|-1xplu0,114,224,1|-1ocna1,114,224,1|-1ocna0,113,191,0|-1ezj61,113,191,0|-1ezj60,114,224,1|-159ly1,114,224,1|-159ly0,113,191,0|-vwhu1,113,191,0|-vwhu0,114,224,1|-mjja1,114,224,1|-mjja0,113,191,0|-d6f61,113,191,0|-d6f60,114,224,1|-3tgm1,114,224,1|-3tgm0,113,191,0|5jnhz,113,191,0|5jni0,114,224,1|ewm1z,114,224,1|ewm20,113,191,0|o9q5z,113,191,0|o9q60,114,224,1|xmopz,114,224,1|xmoq0,113,191,0|16zstz,113,191,0|16zsu0,114,224,1|1gpq1z,114,224,1|1gpq20,113,191,0|1q2u5z,113,191,0|1q2u60,114,224,1|1zfspz,114,224,1|1zfsq0,113,191,0|231i5z,113,191,0|231i60,114,224,1|2i5vdz,114,224,1|2i5ve0,113,191,0|2rizhz,113,191,0|2rizi0,114,224,1|30vy1z,114,224,1|30vy20,113,191,0|3a925z,113,191,0|3a9260,114,224,1|3jm0pz,114,224,1|3jm0q0,113,191,0|4vv4tz,113,191,0|4vv4u0,114,224,1|5457dz,114,224,1|5457e0,113,191,0\",\"Asia/Hovd|,0,243,0|-xmcoz0,96,196,0|46anbz,96,196,0|46anc0,91,193,0|6wupvz,91,193,0|6wupw0,89,191,1|769j3z,89,191,1|769j40,91,193,0|7fohvz,91,193,0|7fohw0,89,191,1|7p1gfz,89,191,1|7p1gg0,91,193,0|7yekjz,91,193,0|7yekk0,89,191,1|87rj3z,89,191,1|87rj40,91,193,0|8h4n7z,91,193,0|8h4n80,89,191,1|8qhlrz,89,191,1|8qhls0,91,193,0|8zupvz,91,193,0|8zupw0,89,191,1|997ofz,89,191,1|997og0,91,193,0|9iksjz,91,193,0|9iksk0,89,191,1|9rxr3z,89,191,1|9rxr40,91,193,0|a1av7z,91,193,0|a1av80,89,191,1|aantrz,89,191,1|aants0,91,193,0|ak0xvz,91,193,0|ak0xw0,89,191,1|atqv3z,89,191,1|atqv40,91,193,0|b33z7z,91,193,0|b33z80,89,191,1|bcgxrz,89,191,1|bcgxs0,91,193,0|blu1vz,91,193,0|blu1w0,89,191,1|bv70fz,89,191,1|bv70g0,91,193,0|c4k4jz,91,193,0|c4k4k0,89,191,1|cdx33z,89,191,1|cdx340,91,193,0|cna77z,91,193,0|cna780,89,191,1|cwn5rz,89,191,1|cwn5s0,91,193,0|d609vz,91,193,0|d609w0,89,191,1|dfd8fz,89,191,1|dfd8g0,91,193,0|dp3b7z,91,193,0|dp3b80,89,191,1|dyg9rz,89,191,1|dyg9s0,91,193,0|e7tdvz,91,193,0|e7tdw0,89,191,1|eh6cfz,89,191,1|eh6cg0,91,193,0|eqjgjz,91,193,0|eqjgk0,89,191,1|ezwf3z,89,191,1|ezwf40,91,193,0|gcgsrz,91,193,0|gcgss0,89,191,1|gkdwnz,89,191,1|gkdwo0,91,193,0|gtr0rz,91,193,0|gtr0s0,89,191,1|h33zbz,89,191,1|h33zc0,91,193,0|hch3fz,91,193,0|hch3g0,89,191,1|hlu1zz,89,191,1|hlu200,91,193,0|hv763z,91,193,0|hv7640,89,191,1|i4k4nz,89,191,1|i4k4o0,91,193,0|idx8rz,91,193,0|idx8s0,89,191,1|ina7bz,89,191,1|ina7c0,91,193,0|iwnbfz,91,193,0|iwnbg0,89,191,1|j6d8nz,89,191,1|j6d8o0,91,193,0|nlvwrz,91,193,0|nlvws0,89,191,1|nv8prz,89,191,1|nv8ps0,91,193,0|o4lzfz,91,193,0|o4lzg0,89,191,1|odysfz,89,191,1|odysg0,91,193,0\",\"Asia/Irkutsk|,0,244,0|-1ayylz5,117,244,0|-q28gn6,117,244,0|-q28gn5,91,193,0|-kmrfg1,91,193,0|-kmrfg0,89,191,0|5vasfz,89,191,0|5vasg0,107,224,1|64plnz,107,224,1|64plo0,89,191,0|6e2prz,89,191,0|6e2ps0,107,224,1|6nhizz,107,224,1|6nhj00,89,191,0|6wun3z,89,191,0|6wun40,107,224,1|769gbz,107,224,1|769gc0,89,191,0|7fof3z,89,191,0|7fof40,107,224,1|7p1lzz,107,224,1|7p1m00,89,191,0|7yenbz,89,191,0|7yenc0,107,224,1|87ronz,107,224,1|87roo0,89,191,0|8h4pzz,89,191,0|8h4q00,107,224,1|8qhrbz,107,224,1|8qhrc0,89,191,0|8zusnz,89,191,0|8zuso0,107,224,1|997tzz,107,224,1|997u00,89,191,0|9ikvbz,89,191,0|9ikvc0,107,224,1|9rxwnz,107,224,1|9rxwo0,89,191,0|a1axzz,89,191,0|a1ay00,107,224,1|aanzbz,107,224,1|aanzc0,89,191,0|ak10nz,89,191,0|ak10o0,107,224,1|atr0nz,107,224,1|atr0o0,89,191,0|b341zz,89,191,0|b34200,89,191,1|bch63z,89,191,1|bch640,91,193,0|bi8krz,91,193,0|bi8ks0,89,191,0|blu4nz,89,191,0|blu4o0,107,224,1|bv75zz,107,224,1|bv7600,89,191,0|c4k7bz,89,191,0|c4k7c0,107,224,1|cdx8nz,107,224,1|cdx8o0,89,191,0|cna9zz,89,191,0|cnaa00,107,224,1|cwnbbz,107,224,1|cwnbc0,89,191,0|d60cnz,89,191,0|d60co0,107,224,1|dfddzz,107,224,1|dfde00,89,191,0|dp3dzz,89,191,0|dp3e00,107,224,1|dzw9zz,107,224,1|dzwa00,89,191,0|e7tgnz,89,191,0|e7tgo0,107,224,1|eimcnz,107,224,1|eimco0,89,191,0|eqjjbz,89,191,0|eqjjc0,107,224,1|f1cfbz,107,224,1|f1cfc0,89,191,0|f99lzz,89,191,0|f99m00,107,224,1|fkfgnz,107,224,1|fkfgo0,89,191,0|frzonz,89,191,0|frzoo0,107,224,1|g35jbz,107,224,1|g35jc0,89,191,0|gaprbz,89,191,0|gaprc0,107,224,1|glvlzz,107,224,1|glvm00,89,191,0|gtssnz,89,191,0|gtsso0,107,224,1|h4lonz,107,224,1|h4loo0,89,191,0|hcivbz,89,191,0|hcivc0,107,224,1|hnbrbz,107,224,1|hnbrc0,89,191,0|hv8xzz,89,191,0|hv8y00,107,224,1|i6esnz,107,224,1|i6eso0,89,191,0|idz0nz,89,191,0|idz0o0,107,224,1|ip4vbz,107,224,1|ip4vc0,89,191,0|iwp3bz,89,191,0|iwp3c0,107,224,1|j7uxzz,107,224,1|j7uy00,89,191,0|jff5zz,89,191,0|jff600,107,224,1|jql0nz,107,224,1|jql0o0,89,191,0|jyi7bz,89,191,0|jyi7c0,107,224,1|k9b3bz,107,224,1|k9b3c0,89,191,0|kh89zz,89,191,0|kh8a00,107,224,1|ks15zz,107,224,1|ks1600,89,191,0|kzycnz,89,191,0|kzyco0,107,224,1|lb47bz,107,224,1|lb47c0,89,191,0|liofbz,89,191,0|liofc0,107,224,0|ne0f7z,107,224,0|ne0f80,89,191,0\",\"Asia/Jakarta|,0,245,0|-1hftyg0,53,245,0|-o0bdpd,53,245,0|-o0bdpc,118,246,0|-jebgdd,118,246,0|-jebgdc,106,222,0|-ehxgu1,106,222,0|-ehxgu0,107,224,0|-co37o1,107,224,0|-co37o0,106,222,0|-bb5zi1,106,222,0|-bb5zi0,89,191,0|-a9m681,89,191,0|-a9m680,106,222,0|-34ru61,106,222,0|-34ru60,119,193,0\",\"Asia/Jayapura|,0,247,0|-jebm20,107,224,0|-d7zvo1,107,224,0|-d7zvo0,120,248,0|-34rzq1,120,248,0|-34rzq0,121,224,0\",\"Asia/Jerusalem|,0,249,0|-1ayy96u,122,250,0|-r50eih,122,250,0|-r50eig,110,11,0|-ffv401,110,11,0|-ffv400,111,6,1|-f9l6o1,111,6,1|-f9l6o0,110,11,0|-f765c1,110,11,0|-f765c0,111,6,1|-e6fxc1,111,6,1|-e6fxc0,110,11,0|-dyoao1,110,11,0|-dyoao0,111,6,1|-dno001,111,6,1|-dno000,110,11,0|-dfuio1,110,11,0|-dfuio0,111,6,1|-d4u801,111,6,1|-d4u800,110,11,0|-cwatc1,110,11,0|-cwatc0,111,6,1|-cm2ao1,111,6,1|-cm2ao0,110,11,0|-cdiw01,110,11,0|-cdiw00,111,6,1|-c3adc1,111,6,1|-c3adc0,110,11,0|-ba0o01,110,11,0|-ba0o00,123,209,1|-b4tmo1,123,209,1|-b4tmo0,111,6,1|-b1oo01,111,6,1|-b1oo00,110,11,0|-asdhc1,110,11,0|-asdhc0,111,6,1|-aiwqo1,111,6,1|-aiwqo0,110,11,0|-aadc01,110,11,0|-aadc00,111,6,1|-a2juo1,111,6,1|-a2juo0,110,11,0|-9sd6o1,110,11,0|-9sd6o0,111,6,1|-9gudc1,111,6,1|-9gudc0,110,11,0|-98k801,110,11,0|-98k800,111,6,1|-8z76o1,111,6,1|-8z76o0,110,11,0|-8q7401,110,11,0|-8q7400,111,6,1|-8i9xc1,111,6,1|-8i9xc0,110,11,0|-848dc1,110,11,0|-848dc0,111,6,1|-7zjuo1,111,6,1|-7zjuo0,110,11,0|-7liao1,110,11,0|-7liao0,111,6,1|-7gts01,111,6,1|-7gts00,110,11,0|-7356o1,110,11,0|-7356o0,111,6,1|-6x0tc1,111,6,1|-6x0tc0,110,11,0|-6m7xc1,110,11,0|-6m7xc0,111,6,1|-6enpc1,111,6,1|-6enpc0,110,11,0|2crp3z,110,11,0|2crp40,111,6,1|2ht3nz,111,6,1|2ht3o0,110,11,0|2rj6fz,110,11,0|2rj6g0,111,6,1|2ydebz,111,6,1|2ydec0,110,11,0|5iwyfz,110,11,0|5iwyg0,111,6,1|5l2qfz,111,6,1|5l2qg0,110,11,0|7hhp3z,110,11,0|7hhp40,111,6,1|7n93rz,111,6,1|7n93s0,110,11,0|7z4vrz,110,11,0|7z4vs0,111,6,1|86c2bz,111,6,1|86c2c0,110,11,0|8jnrrz,110,11,0|8jnrs0,111,6,1|8pf3nz,111,6,1|8pf3o0,110,11,0|90ql3z,110,11,0|90ql40,111,6,1|98i4zz,111,6,1|98i500,110,11,0|9jb3rz,110,11,0|9jb3s0,111,6,1|9qv8zz,111,6,1|9qv900,110,11,0|a342fz,110,11,0|a342g0,111,6,1|a9lbnz,111,6,1|a9lbo0,110,11,0|ak1brz,110,11,0|ak1bs0,111,6,1|aryfnz,111,6,1|aryfo0,110,11,0|b2refz,110,11,0|b2reg0,111,6,1|bb1gzz,111,6,1|bb1h00,110,11,0|blufrz,110,11,0|blufs0,111,6,1|bu4ibz,111,6,1|bu4ic0,110,11,0|c4trrz,110,11,0|c4trs0,111,6,1|ccukzz,111,6,1|ccul00,110,11,0|cnjufz,110,11,0|cnjug0,111,6,1|cv7ozz,111,6,1|cv7p00,110,11,0|d69x3z,110,11,0|d69x40,111,6,1|deaqbz,111,6,1|deaqc0,110,11,0|doa2fz,110,11,0|doa2g0,111,6,1|dxskzz,111,6,1|dxsl00,110,11,0|e7d3rz,110,11,0|e7d3s0,111,6,1|eggszz,111,6,1|eggt00,110,11,0|eq36fz,110,11,0|eq36g0,111,6,1|eytwzz,111,6,1|eytx00,110,11,0|f9jbzz,110,11,0|f9jc00,111,6,1|fhgfvz,111,6,1|fhgfw0,110,11,0|fszbzz,110,11,0|fszc00,111,6,1|g1z93z,111,6,1|g1z940,110,11,0|gbhx7z,110,11,0|gbhx80,111,6,1|gk4yfz,111,6,1|gk4yg0,110,11,0|gtph7z,110,11,0|gtph80,111,6,1|h3kyfz,111,6,1|h3kyg0,110,11,0|hcfjvz,110,11,0|hcfjw0,111,6,1|hm5h3z,111,6,1|hm5h40,110,11,0|hvrujz,110,11,0|hvruk0,111,6,1|i4evrz,111,6,1|i4evs0,110,11,0|ie8qnz,110,11,0|ie8qo0,111,6,1|io2d7z,111,6,1|io2d80,110,11,0|iwytbz,110,11,0|iwytc0,111,6,1|j6fh7z,111,6,1|j6fh80,110,11,0|jfovzz,110,11,0|jfow00,111,6,1|jofmjz,111,6,1|jofmk0,110,11,0|jyeynz,110,11,0|jyeyo0,111,6,1|k88l7z,111,6,1|k88l80,110,11,0|kh51bz,110,11,0|kh51c0,111,6,1|kqlp7z,111,6,1|kqlp80,110,11,0|kzv3zz,110,11,0|kzv400,111,6,1|l8lujz,111,6,1|l8luk0,110,11,0|liy5bz,110,11,0|liy5c0,111,6,1|lset7z,111,6,1|lset80,110,11,0|m1o7zz,110,11,0|m1o800,111,6,1|marx7z,111,6,1|marx80,110,11,0|mkeanz,110,11,0|mkeao0,111,6,1|mvat7z,111,6,1|mvat80,110,11,0|n34dbz,110,11,0|n34dc0,111,6,1|ne0vvz,111,6,1|ne0vw0,110,11,0|nlufzz,110,11,0|nlug00,111,6,1|nwqyjz,111,6,1|nwqyk0,110,11,0|o4kinz,110,11,0|o4kio0,111,6,1|oftzvz,111,6,1|oftzw0,110,11,0|onalbz,110,11,0|onalc0,111,6,1|oyk2jz,111,6,1|oyk2k0,110,11,0|p60nzz,110,11,0|p60o00,111,6,1|pha57z,111,6,1|pha580,110,11,0|pp3pbz,110,11,0|pp3pc0,111,6,1|q007vz,111,6,1|q007w0,110,11,0|q7trzz,110,11,0|q7ts00,111,6,1|qiqajz,111,6,1|qiqak0,110,11,0|qqjunz,110,11,0|qqjuo0,111,6,1|r1tbvz,111,6,1|r1tbw0,110,11,0|r99xbz,110,11,0|r99xc0,111,6,1|rkjejz,111,6,1|rkjek0,110,11,0|rrzzzz,110,11,0|rs0000,111,6,1|s39h7z,111,6,1|s39h80,110,11,0|sb31bz,110,11,0|sb31c0,111,6,1|slzjvz,111,6,1|slzjw0,110,11,0|stt3zz,110,11,0|stt400,111,6,1|t4pmjz,111,6,1|t4pmk0,110,11,0|tcj6nz,110,11,0|tcj6o0,111,6,1|tnfp7z,111,6,1|tnfp80,110,11,0|tv99bz,110,11,0|tv99c0,111,6,1|u6iqjz,111,6,1|u6iqk0,110,11,0|udzbzz,110,11,0|udzc00,111,6,1|up8t7z,111,6,1|up8t80,110,11,0|uwpenz,110,11,0|uwpeo0,111,6,1|v7yvvz,111,6,1|v7yvw0,110,11,0|vfsfzz,110,11,0|vfsg00,111,6,1|vqoyjz,111,6,1|vqoyk0,110,11,0|vyiinz,110,11,0|vyiio0,111,6,1|w9f17z,111,6,1|w9f180,110,11,0|wh8lbz,110,11,0|wh8lc0,111,6,1|wsi2jz,111,6,1|wsi2k0,110,11,0|wzynzz,110,11,0|wzyo00,111,6,1|xb857z,111,6,1|xb8580,110,11,0|xioqnz,110,11,0|xioqo0,111,6,1|xty7vz,111,6,1|xty7w0,110,11,0|y1etbz,110,11,0|y1etc0,111,6,1|ycoajz,111,6,1|ycoak0,110,11,0|ykhunz,110,11,0|ykhuo0,111,6,1|yved7z,111,6,1|yved80,110,11,0|z37xbz,110,11,0|z37xc0,111,6,1|ze4fvz,111,6,1|ze4fw0,110,11,0\",\"Asia/Kabul|,0,251,0|-15r1m5c,105,209,0|-d1pkg1,105,209,0|-d1pkg0,124,252,0\",\"Asia/Kamchatka|,0,253,0|-olrupo,90,192,0|-kmrqk1,90,192,0|-kmrqk0,102,200,0|5vahbz,102,200,0|5vahc0,103,201,1|64pajz,103,201,1|64pak0,102,200,0|6e2enz,102,200,0|6e2eo0,103,201,1|6nh7vz,103,201,1|6nh7w0,102,200,0|6wubzz,102,200,0|6wuc00,103,201,1|76957z,103,201,1|769580,102,200,0|7fo3zz,102,200,0|7fo400,103,201,1|7p1avz,103,201,1|7p1aw0,102,200,0|7yec7z,102,200,0|7yec80,103,201,1|87rdjz,103,201,1|87rdk0,102,200,0|8h4evz,102,200,0|8h4ew0,103,201,1|8qhg7z,103,201,1|8qhg80,102,200,0|8zuhjz,102,200,0|8zuhk0,103,201,1|997ivz,103,201,1|997iw0,102,200,0|9ikk7z,102,200,0|9ikk80,103,201,1|9rxljz,103,201,1|9rxlk0,102,200,0|a1amvz,102,200,0|a1amw0,103,201,1|aano7z,103,201,1|aano80,102,200,0|ak0pjz,102,200,0|ak0pk0,103,201,1|atqpjz,103,201,1|atqpk0,102,200,0|b33qvz,102,200,0|b33qw0,102,200,1|bcguzz,102,200,1|bcgv00,90,192,0|bi89nz,90,192,0|bi89o0,102,200,0|blttjz,102,200,0|blttk0,103,201,1|bv6uvz,103,201,1|bv6uw0,102,200,0|c4jw7z,102,200,0|c4jw80,103,201,1|cdwxjz,103,201,1|cdwxk0,102,200,0|cn9yvz,102,200,0|cn9yw0,103,201,1|cwn07z,103,201,1|cwn080,102,200,0|d601jz,102,200,0|d601k0,103,201,1|dfd2vz,103,201,1|dfd2w0,102,200,0|dp32vz,102,200,0|dp32w0,103,201,1|dzvyvz,103,201,1|dzvyw0,102,200,0|e7t5jz,102,200,0|e7t5k0,103,201,1|eim1jz,103,201,1|eim1k0,102,200,0|eqj87z,102,200,0|eqj880,103,201,1|f1c47z,103,201,1|f1c480,102,200,0|f99avz,102,200,0|f99aw0,103,201,1|fkf5jz,103,201,1|fkf5k0,102,200,0|frzdjz,102,200,0|frzdk0,103,201,1|g3587z,103,201,1|g35880,102,200,0|gapg7z,102,200,0|gapg80,103,201,1|glvavz,103,201,1|glvaw0,102,200,0|gtshjz,102,200,0|gtshk0,103,201,1|h4ldjz,103,201,1|h4ldk0,102,200,0|hcik7z,102,200,0|hcik80,103,201,1|hnbg7z,103,201,1|hnbg80,102,200,0|hv8mvz,102,200,0|hv8mw0,103,201,1|i6ehjz,103,201,1|i6ehk0,102,200,0|idypjz,102,200,0|idypk0,103,201,1|ip4k7z,103,201,1|ip4k80,102,200,0|iwos7z,102,200,0|iwos80,103,201,1|j7umvz,103,201,1|j7umw0,102,200,0|jfeuvz,102,200,0|jfeuw0,103,201,1|jqkpjz,103,201,1|jqkpk0,102,200,0|jyhw7z,102,200,0|jyhw80,103,201,1|k9as7z,103,201,1|k9as80,102,200,0|kh7yvz,102,200,0|kh7yw0,103,201,1|ks0uvz,103,201,1|ks0uw0,102,200,0|kzy1jz,102,200,0|kzy1k0,102,200,1|lb3yzz,102,200,1|lb3z00,90,192,0|lio6zz,90,192,0|lio700,102,200,0\",\"Asia/Karachi|,0,254,0|-wvpb30,108,228,0|-e9lba1,108,228,0|-e9lba0,109,229,1|-cmya21,109,229,1|-cmya20,108,228,0|-9j0km1,108,228,0|-9j0km0,92,194,0|n33fz,92,194,0|n33g0,125,194,0|gu5u3z,125,194,0|gu5u40,126,196,1|h3isnz,126,196,1|h3iso0,125,194,0|k1qy3z,125,194,0|k1qy40,126,196,1|k9m7bz,126,196,1|k9m7c0,125,194,0|ki3u3z,125,194,0|ki3u40,126,196,1|kse4nz,126,196,1|kse4o0,125,194,0\",\"Asia/Kathmandu|,0,255,0|-q3gt4s,108,228,0|8clspz,108,228,0|8clsq0,127,256,0\",\"Asia/Khandyga|,0,257,0|-q4cjrp,89,191,0|-kmri81,89,191,0|-kmri80,107,224,0|5vapnz,107,224,0|5vapo0,93,195,1|64pivz,93,195,1|64piw0,107,224,0|6e2mzz,107,224,0|6e2n00,93,195,1|6nhg7z,93,195,1|6nhg80,107,224,0|6wukbz,107,224,0|6wukc0,93,195,1|769djz,93,195,1|769dk0,107,224,0|7focbz,107,224,0|7focc0,93,195,1|7p1j7z,93,195,1|7p1j80,107,224,0|7yekjz,107,224,0|7yekk0,93,195,1|87rlvz,93,195,1|87rlw0,107,224,0|8h4n7z,107,224,0|8h4n80,93,195,1|8qhojz,93,195,1|8qhok0,107,224,0|8zupvz,107,224,0|8zupw0,93,195,1|997r7z,93,195,1|997r80,107,224,0|9iksjz,107,224,0|9iksk0,93,195,1|9rxtvz,93,195,1|9rxtw0,107,224,0|a1av7z,107,224,0|a1av80,93,195,1|aanwjz,93,195,1|aanwk0,107,224,0|ak0xvz,107,224,0|ak0xw0,93,195,1|atqxvz,93,195,1|atqxw0,107,224,0|b33z7z,107,224,0|b33z80,107,224,1|bch3bz,107,224,1|bch3c0,89,191,0|bi8hzz,89,191,0|bi8i00,107,224,0|blu1vz,107,224,0|blu1w0,93,195,1|bv737z,93,195,1|bv7380,107,224,0|c4k4jz,107,224,0|c4k4k0,93,195,1|cdx5vz,93,195,1|cdx5w0,107,224,0|cna77z,107,224,0|cna780,93,195,1|cwn8jz,93,195,1|cwn8k0,107,224,0|d609vz,107,224,0|d609w0,93,195,1|dfdb7z,93,195,1|dfdb80,107,224,0|dp3b7z,107,224,0|dp3b80,93,195,1|dzw77z,93,195,1|dzw780,107,224,0|e7tdvz,107,224,0|e7tdw0,93,195,1|eim9vz,93,195,1|eim9w0,107,224,0|eqjgjz,107,224,0|eqjgk0,93,195,1|f1ccjz,93,195,1|f1cck0,107,224,0|f99j7z,107,224,0|f99j80,93,195,1|fkfdvz,93,195,1|fkfdw0,107,224,0|frzlvz,107,224,0|frzlw0,93,195,1|g35gjz,93,195,1|g35gk0,107,224,0|gapojz,107,224,0|gapok0,93,195,1|glvj7z,93,195,1|glvj80,107,224,0|gtspvz,107,224,0|gtspw0,93,195,1|h4llvz,93,195,1|h4llw0,107,224,0|hcisjz,107,224,0|hcisk0,93,195,1|hnbojz,93,195,1|hnbok0,107,224,0|hqrlnz,107,224,0|hqrlo0,93,195,0|hv8sfz,93,195,0|hv8sg0,90,192,1|i6en3z,90,192,1|i6en40,93,195,0|idyv3z,93,195,0|idyv40,90,192,1|ip4prz,90,192,1|ip4ps0,93,195,0|iwoxrz,93,195,0|iwoxs0,90,192,1|j7usfz,90,192,1|j7usg0,93,195,0|jff0fz,93,195,0|jff0g0,90,192,1|jqkv3z,90,192,1|jqkv40,93,195,0|jyi1rz,93,195,0|jyi1s0,90,192,1|k9axrz,90,192,1|k9axs0,93,195,0|kh84fz,93,195,0|kh84g0,90,192,1|ks10fz,90,192,1|ks10g0,93,195,0|kzy73z,93,195,0|kzy740,90,192,1|lb41rz,90,192,1|lb41s0,93,195,0|lio9rz,93,195,0|lio9s0,90,192,0|lreurz,90,192,0|lreus0,93,195,0|ne0cfz,93,195,0|ne0cg0,107,224,0\",\"Asia/Kolkata|,0,258,0|-1oaa314,77,232,0|-1g6thox,77,232,0|-1g6thow,21,259,0|-xehavb,21,259,0|-xehava,110,228,0|-eqtom1,110,228,0|-eqtom0,109,229,1|-ef78q1,109,229,1|-ef78q0,110,228,0|-e9lba1,110,228,0|-e9lba0,109,229,1|-cmya21,109,229,1|-cmya20,110,228,0\",\"Asia/Krasnoyarsk|,0,260,0|-q37l72,96,196,0|-kmrco1,96,196,0|-kmrco0,91,193,0|5vav7z,91,193,0|5vav80,89,191,1|64pofz,89,191,1|64pog0,91,193,0|6e2sjz,91,193,0|6e2sk0,89,191,1|6nhlrz,89,191,1|6nhls0,91,193,0|6wupvz,91,193,0|6wupw0,89,191,1|769j3z,89,191,1|769j40,91,193,0|7fohvz,91,193,0|7fohw0,89,191,1|7p1orz,89,191,1|7p1os0,91,193,0|7yeq3z,91,193,0|7yeq40,89,191,1|87rrfz,89,191,1|87rrg0,91,193,0|8h4srz,91,193,0|8h4ss0,89,191,1|8qhu3z,89,191,1|8qhu40,91,193,0|8zuvfz,91,193,0|8zuvg0,89,191,1|997wrz,89,191,1|997ws0,91,193,0|9iky3z,91,193,0|9iky40,89,191,1|9rxzfz,89,191,1|9rxzg0,91,193,0|a1b0rz,91,193,0|a1b0s0,89,191,1|aao23z,89,191,1|aao240,91,193,0|ak13fz,91,193,0|ak13g0,89,191,1|atr3fz,89,191,1|atr3g0,91,193,0|b344rz,91,193,0|b344s0,91,193,1|bch8vz,91,193,1|bch8w0,96,196,0|bi8njz,96,196,0|bi8nk0,91,193,0|blu7fz,91,193,0|blu7g0,89,191,1|bv78rz,89,191,1|bv78s0,91,193,0|c4ka3z,91,193,0|c4ka40,89,191,1|cdxbfz,89,191,1|cdxbg0,91,193,0|cnacrz,91,193,0|cnacs0,89,191,1|cwne3z,89,191,1|cwne40,91,193,0|d60ffz,91,193,0|d60fg0,89,191,1|dfdgrz,89,191,1|dfdgs0,91,193,0|dp3grz,91,193,0|dp3gs0,89,191,1|dzwcrz,89,191,1|dzwcs0,91,193,0|e7tjfz,91,193,0|e7tjg0,89,191,1|eimffz,89,191,1|eimfg0,91,193,0|eqjm3z,91,193,0|eqjm40,89,191,1|f1ci3z,89,191,1|f1ci40,91,193,0|f99orz,91,193,0|f99os0,89,191,1|fkfjfz,89,191,1|fkfjg0,91,193,0|frzrfz,91,193,0|frzrg0,89,191,1|g35m3z,89,191,1|g35m40,91,193,0|gapu3z,91,193,0|gapu40,89,191,1|glvorz,89,191,1|glvos0,91,193,0|gtsvfz,91,193,0|gtsvg0,89,191,1|h4lrfz,89,191,1|h4lrg0,91,193,0|hciy3z,91,193,0|hciy40,89,191,1|hnbu3z,89,191,1|hnbu40,91,193,0|hv90rz,91,193,0|hv90s0,89,191,1|i6evfz,89,191,1|i6evg0,91,193,0|idz3fz,91,193,0|idz3g0,89,191,1|ip4y3z,89,191,1|ip4y40,91,193,0|iwp63z,91,193,0|iwp640,89,191,1|j7v0rz,89,191,1|j7v0s0,91,193,0|jff8rz,91,193,0|jff8s0,89,191,1|jql3fz,89,191,1|jql3g0,91,193,0|jyia3z,91,193,0|jyia40,89,191,1|k9b63z,89,191,1|k9b640,91,193,0|kh8crz,91,193,0|kh8cs0,89,191,1|ks18rz,89,191,1|ks18s0,91,193,0|kzyffz,91,193,0|kzyfg0,89,191,1|lb4a3z,89,191,1|lb4a40,91,193,0|lioi3z,91,193,0|lioi40,89,191,0|ne0hzz,89,191,0|ne0i00,91,193,0\",\"Asia/Kuala_Lumpur|,0,261,0|-100ew5y,85,262,0|-xphpwe,85,262,0|-xphpwd,91,193,0|-jb6gs1,91,193,0|-jb6gs0,118,246,1|-hquppd,118,246,1|-hquppc,118,246,0|-esddpd,118,246,0|-esddpc,106,222,0|-ejqa61,106,222,0|-ejqa60,107,224,0|-conl01,107,224,0|-conl00,106,222,0|69g35z,106,222,0|69g360,89,191,0\",\"Asia/Kuching|,0,263,0|-mvof3k,106,222,0|-jb6i61,106,222,0|-jb6i60,89,191,0|-hwgm81,89,191,0|-hwgm80,128,264,1|-hrs4hd,128,264,1|-hrs4hc,89,191,0|-hdmu81,89,191,0|-hdmu80,128,264,1|-h8ychd,128,264,1|-h8ychc,89,191,0|-guuww1,89,191,0|-guuww0,128,264,1|-gq6f5d,128,264,1|-gq6f5c,89,191,0|-gc2zk1,89,191,0|-gc2zk0,128,264,1|-g7ehtd,128,264,1|-g7ehtc,89,191,0|-ftb281,89,191,0|-ftb280,128,264,1|-fomkhd,128,264,1|-fomkhc,89,191,0|-faha81,89,191,0|-faha80,128,264,1|-f5sshd,128,264,1|-f5sshc,89,191,0|-erpcw1,89,191,0|-erpcw0,128,264,1|-en0v5d,128,264,1|-en0v5c,89,191,0|-ejqbk1,89,191,0|-ejqbk0,107,224,0|-conl01,107,224,0|-conl00,89,191,0\",\"Asia/Kuwait|,0,203,0|-bwgbbg,100,6,0\",\"Asia/Macau|,0,265,0|-y0i2cy,45,191,0|-emm3o1,45,191,0|-emm3o0,107,224,0|-efxfs1,107,224,0|-efxfs0,93,195,1|-e5lak1,93,195,1|-e5lak0,107,224,0|-dx5ig1,107,224,0|-dx5ig0,93,195,1|-dpa981,93,195,1|-dpa980,107,224,0|-cnoec1,107,224,0|-cnoec0,45,191,0|-ccrt01,45,191,0|-ccrt00,46,224,1|-c4wh01,46,224,1|-c4wh00,45,191,0|-buk901,45,191,0|-buk900,46,224,1|-bizl01,46,224,1|-bizl00,45,191,0|-bb2ec1,45,191,0|-bb2ec0,46,224,1|-b1pd01,46,224,1|-b1pd00,45,191,0|-atu101,45,191,0|-atu100,46,224,1|-aj1501,46,224,1|-aj1500,45,191,0|-ab3yc1,45,191,0|-ab3yc0,46,224,1|-a0b2c1,46,224,1|-a0b2c0,45,191,0|-9sdvo1,45,191,0|-9sdvo0,46,224,1|-9hj501,46,224,1|-9hj500,45,191,0|-99auc1,45,191,0|-99auc0,46,224,1|-8yhyc1,46,224,1|-8yhyc0,45,191,0|-8qkro1,45,191,0|-8qkro0,46,224,1|-8frvo1,46,224,1|-8frvo0,45,191,0|-88kmc1,45,191,0|-88kmc0,46,224,1|-7x1t01,46,224,1|-7x1t00,45,191,0|-7pujo1,45,191,0|-7pujo0,46,224,1|-7dyro1,46,224,1|-7dyro0,45,191,0|-774h01,45,191,0|-774h00,46,224,1|-6v8fa1,46,224,1|-6v8fa0,45,191,0|-6o1361,45,191,0|-6o1360,46,224,1|-6cicm1,46,224,1|-6cicm0,45,191,0|-65b0i1,45,191,0|-65b0i0,46,224,1|-5ts9y1,46,224,1|-5ts9y0,45,191,0|-5mkxu1,45,191,0|-5mkxu0,46,224,1|-5b27a1,46,224,1|-5b27a0,45,191,0|-53uv61,45,191,0|-53uv60,46,224,1|-4rz5y1,46,224,1|-4rz5y0,45,191,0|-4l4si1,45,191,0|-4l4si0,46,224,1|-4993a1,46,224,1|-4993a0,45,191,0|-42epu1,45,191,0|-42epu0,46,224,1|-3qj0m1,46,224,1|-3qj0m0,45,191,0|-3jboi1,45,191,0|-3jboi0,46,224,1|-37sxy1,46,224,1|-37sxy0,45,191,0|-30llu1,45,191,0|-30llu0,46,224,1|-2p2va1,46,224,1|-2p2va0,45,191,0|-2gfoi1,45,191,0|-2gfoi0,46,224,1|-272sq1,46,224,1|-272sq0,45,191,0|-1xplu1,45,191,0|-1xplu0,46,224,1|-1ocq21,46,224,1|-1ocq20,45,191,0|-1ezj61,45,191,0|-1ezj60,46,224,1|-159ly1,46,224,1|-159ly0,45,191,0|-vwhu1,45,191,0|-vwhu0,46,224,1|-mjja1,46,224,1|-mjja0,45,191,0|-d6f61,45,191,0|-d6f60,46,224,1|-3tgm1,46,224,1|-3tgm0,45,191,0|5jnhz,45,191,0|5jni0,46,224,1|ewm1z,46,224,1|ewm20,45,191,0|o9q5z,45,191,0|o9q60,46,224,1|xmopz,46,224,1|xmoq0,45,191,0|16zstz,45,191,0|16zsu0,46,224,1|1gpq1z,46,224,1|1gpq20,45,191,0|1q2u5z,45,191,0|1q2u60,46,224,1|1zfspz,46,224,1|1zfsq0,45,191,0|231i5z,45,191,0|231i60,46,224,1|2i5vdz,46,224,1|2i5ve0,45,191,0|2rizhz,45,191,0|2rizi0,46,224,1|30vy1z,46,224,1|30vy20,45,191,0|3a925z,45,191,0|3a9260,46,224,1|3jm0pz,46,224,1|3jm0q0,45,191,0|4vv4tz,45,191,0|4vv4u0,46,224,1|5457dz,46,224,1|5457e0,45,191,0\",\"Asia/Magadan|,0,266,0|-nu1nxc,93,195,0|-kmrns1,93,195,0|-kmrns0,90,192,0|5vak3z,90,192,0|5vak40,102,200,1|64pdbz,102,200,1|64pdc0,90,192,0|6e2hfz,90,192,0|6e2hg0,102,200,1|6nhanz,102,200,1|6nhao0,90,192,0|6wuerz,90,192,0|6wues0,102,200,1|7697zz,102,200,1|769800,90,192,0|7fo6rz,90,192,0|7fo6s0,102,200,1|7p1dnz,102,200,1|7p1do0,90,192,0|7yeezz,90,192,0|7yef00,102,200,1|87rgbz,102,200,1|87rgc0,90,192,0|8h4hnz,90,192,0|8h4ho0,102,200,1|8qhizz,102,200,1|8qhj00,90,192,0|8zukbz,90,192,0|8zukc0,102,200,1|997lnz,102,200,1|997lo0,90,192,0|9ikmzz,90,192,0|9ikn00,102,200,1|9rxobz,102,200,1|9rxoc0,90,192,0|a1apnz,90,192,0|a1apo0,102,200,1|aanqzz,102,200,1|aanr00,90,192,0|ak0sbz,90,192,0|ak0sc0,102,200,1|atqsbz,102,200,1|atqsc0,90,192,0|b33tnz,90,192,0|b33to0,90,192,1|bcgxrz,90,192,1|bcgxs0,93,195,0|bi8cfz,93,195,0|bi8cg0,90,192,0|bltwbz,90,192,0|bltwc0,102,200,1|bv6xnz,102,200,1|bv6xo0,90,192,0|c4jyzz,90,192,0|c4jz00,102,200,1|cdx0bz,102,200,1|cdx0c0,90,192,0|cna1nz,90,192,0|cna1o0,102,200,1|cwn2zz,102,200,1|cwn300,90,192,0|d604bz,90,192,0|d604c0,102,200,1|dfd5nz,102,200,1|dfd5o0,90,192,0|dp35nz,90,192,0|dp35o0,102,200,1|dzw1nz,102,200,1|dzw1o0,90,192,0|e7t8bz,90,192,0|e7t8c0,102,200,1|eim4bz,102,200,1|eim4c0,90,192,0|eqjazz,90,192,0|eqjb00,102,200,1|f1c6zz,102,200,1|f1c700,90,192,0|f99dnz,90,192,0|f99do0,102,200,1|fkf8bz,102,200,1|fkf8c0,90,192,0|frzgbz,90,192,0|frzgc0,102,200,1|g35azz,102,200,1|g35b00,90,192,0|gapizz,90,192,0|gapj00,102,200,1|glvdnz,102,200,1|glvdo0,90,192,0|gtskbz,90,192,0|gtskc0,102,200,1|h4lgbz,102,200,1|h4lgc0,90,192,0|hcimzz,90,192,0|hcin00,102,200,1|hnbizz,102,200,1|hnbj00,90,192,0|hv8pnz,90,192,0|hv8po0,102,200,1|i6ekbz,102,200,1|i6ekc0,90,192,0|idysbz,90,192,0|idysc0,102,200,1|ip4mzz,102,200,1|ip4n00,90,192,0|iwouzz,90,192,0|iwov00,102,200,1|j7upnz,102,200,1|j7upo0,90,192,0|jfexnz,90,192,0|jfexo0,102,200,1|jqksbz,102,200,1|jqksc0,90,192,0|jyhyzz,90,192,0|jyhz00,102,200,1|k9auzz,102,200,1|k9av00,90,192,0|kh81nz,90,192,0|kh81o0,102,200,1|ks0xnz,102,200,1|ks0xo0,90,192,0|kzy4bz,90,192,0|kzy4c0,102,200,1|lb3yzz,102,200,1|lb3z00,90,192,0|lio6zz,90,192,0|lio700,102,200,0|ne06vz,102,200,0|ne06w0,93,195,0|o63gfz,93,195,0|o63gg0,90,192,0\",\"Asia/Makassar|,0,267,0|-q3gzg0,21,267,0|-jebi41,21,267,0|-jebi40,89,191,0|-ek3a81,89,191,0|-ek3a80,107,224,0|-co37o1,107,224,0|-co37o0,129,191,0\",\"Asia/Manila|,0,268,0|-1t8ix2o,0,269,0|-10va3qp,0,269,0|-10va3qo,51,191,0|-hb5y81,51,191,0|-hb5y80,57,224,1|-h6fno1,57,224,1|-h6fno0,51,191,0|-efxa81,51,191,0|-efxa80,116,224,0|-d4ux01,116,224,0|-d4ux00,51,191,0|-87fsw1,51,191,0|-87fsw0,57,224,1|-83bqc1,57,224,1|-83bqc0,51,191,0|4aen3z,51,191,0|4aen40,57,224,1|4jtgbz,57,224,1|4jtgc0,51,191,0\",\"Asia/Muscat|,0,234,0|-q3gnko,105,209,0\",\"Asia/Nicosia|,0,270,0|-p4bq6g,15,11,0|2r67rz,15,11,0|2r67s0,16,6,1|30j6bz,16,6,1|30j6c0,15,11,0|3bn93z,15,11,0|3bn940,16,6,1|3jb3nz,16,6,1|3jb3o0,15,11,0|3s9efz,15,11,0|3s9eg0,16,6,1|419ebz,16,6,1|419ec0,15,11,0|4azh3z,15,11,0|4azh40,16,6,1|4keabz,16,6,1|4keac0,15,11,0|4tpjrz,15,11,0|4tpjs0,16,6,1|532ibz,16,6,1|532ic0,15,11,0|5csl3z,15,11,0|5csl40,16,6,1|5lskzz,16,6,1|5lsl00,15,11,0|5v5p3z,15,11,0|5v5p40,16,6,1|64innz,16,6,1|64ino0,15,11,0|6dvrrz,15,11,0|6dvrs0,16,6,1|6n8qbz,16,6,1|6n8qc0,15,11,0|6wlufz,15,11,0|6wlug0,16,6,1|75yszz,16,6,1|75yt00,15,11,0|7fbx3z,15,11,0|7fbx40,16,6,1|7p1ubz,16,6,1|7p1uc0,15,11,0|7yeyfz,15,11,0|7yeyg0,16,6,1|87rwzz,16,6,1|87rx00,15,11,0|8h513z,15,11,0|8h5140,16,6,1|8qhznz,16,6,1|8qhzo0,15,11,0|8zv3rz,15,11,0|8zv3s0,16,6,1|9982bz,16,6,1|9982c0,15,11,0|9il6fz,15,11,0|9il6g0,16,6,1|9ry4zz,16,6,1|9ry500,15,11,0|a1b93z,15,11,0|a1b940,16,6,1|aao7nz,16,6,1|aao7o0,15,11,0|ak1brz,15,11,0|ak1bs0,16,6,1|atr8zz,16,6,1|atr900,15,11,0|b34d3z,15,11,0|b34d40,16,6,1|bchbnz,16,6,1|bchbo0,15,11,0|blufrz,15,11,0|blufs0,16,6,1|bv7ebz,16,6,1|bv7ec0,15,11,0|c4kifz,15,11,0|c4kig0,16,6,1|cdxgzz,16,6,1|cdxh00,15,11,0|cnal3z,15,11,0|cnal40,16,6,1|cwnjnz,16,6,1|cwnjo0,15,11,0|d60nrz,15,11,0|d60ns0,16,6,1|dfdmbz,16,6,1|dfdmc0,15,11,0|dp3p3z,15,11,0|dp3p40,16,6,1|dygnnz,16,6,1|dygno0,15,11,0|e7trrz,15,11,0|e7trs0,16,6,1|eh6qbz,16,6,1|eh6qc0,15,11,0|eqjufz,15,11,0|eqjug0,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|fs083z,15,11,0|fs0840,16,6,1|g362rz,16,6,1|g362s0,15,11,0|gaqarz,15,11,0|gaqas0,16,6,1|glw5fz,16,6,1|glw5g0,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|ofu5fz,16,6,1|ofu5g0,15,11,0|onedfz,15,11,0|onedg0,16,6,1|oyk83z,16,6,1|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Asia/Novokuznetsk|,0,271,0|-nu36tc,96,196,0|-kmrco1,96,196,0|-kmrco0,91,193,0|5vav7z,91,193,0|5vav80,89,191,1|64pofz,89,191,1|64pog0,91,193,0|6e2sjz,91,193,0|6e2sk0,89,191,1|6nhlrz,89,191,1|6nhls0,91,193,0|6wupvz,91,193,0|6wupw0,89,191,1|769j3z,89,191,1|769j40,91,193,0|7fohvz,91,193,0|7fohw0,89,191,1|7p1orz,89,191,1|7p1os0,91,193,0|7yeq3z,91,193,0|7yeq40,89,191,1|87rrfz,89,191,1|87rrg0,91,193,0|8h4srz,91,193,0|8h4ss0,89,191,1|8qhu3z,89,191,1|8qhu40,91,193,0|8zuvfz,91,193,0|8zuvg0,89,191,1|997wrz,89,191,1|997ws0,91,193,0|9iky3z,91,193,0|9iky40,89,191,1|9rxzfz,89,191,1|9rxzg0,91,193,0|a1b0rz,91,193,0|a1b0s0,89,191,1|aao23z,89,191,1|aao240,91,193,0|ak13fz,91,193,0|ak13g0,89,191,1|atr3fz,89,191,1|atr3g0,91,193,0|b344rz,91,193,0|b344s0,91,193,1|bch8vz,91,193,1|bch8w0,96,196,0|bi8njz,96,196,0|bi8nk0,91,193,0|blu7fz,91,193,0|blu7g0,89,191,1|bv78rz,89,191,1|bv78s0,91,193,0|c4ka3z,91,193,0|c4ka40,89,191,1|cdxbfz,89,191,1|cdxbg0,91,193,0|cnacrz,91,193,0|cnacs0,89,191,1|cwne3z,89,191,1|cwne40,91,193,0|d60ffz,91,193,0|d60fg0,89,191,1|dfdgrz,89,191,1|dfdgs0,91,193,0|dp3grz,91,193,0|dp3gs0,89,191,1|dzwcrz,89,191,1|dzwcs0,91,193,0|e7tjfz,91,193,0|e7tjg0,89,191,1|eimffz,89,191,1|eimfg0,91,193,0|eqjm3z,91,193,0|eqjm40,89,191,1|f1ci3z,89,191,1|f1ci40,91,193,0|f99orz,91,193,0|f99os0,89,191,1|fkfjfz,89,191,1|fkfjg0,91,193,0|frzrfz,91,193,0|frzrg0,89,191,1|g35m3z,89,191,1|g35m40,91,193,0|gapu3z,91,193,0|gapu40,89,191,1|glvorz,89,191,1|glvos0,91,193,0|gtsvfz,91,193,0|gtsvg0,89,191,1|h4lrfz,89,191,1|h4lrg0,91,193,0|hciy3z,91,193,0|hciy40,89,191,1|hnbu3z,89,191,1|hnbu40,91,193,0|hv90rz,91,193,0|hv90s0,89,191,1|i6evfz,89,191,1|i6evg0,91,193,0|idz3fz,91,193,0|idz3g0,89,191,1|ip4y3z,89,191,1|ip4y40,91,193,0|iwp63z,91,193,0|iwp640,89,191,1|j7v0rz,89,191,1|j7v0s0,91,193,0|jff8rz,91,193,0|jff8s0,89,191,1|jql3fz,89,191,1|jql3g0,91,193,0|jyia3z,91,193,0|jyia40,89,191,1|k9b63z,89,191,1|k9b640,91,193,0|kh8crz,91,193,0|kh8cs0,89,191,1|ks18rz,89,191,1|ks18s0,91,193,0|kzyffz,91,193,0|kzyfg0,91,193,1|lb4cvz,91,193,1|lb4cw0,96,196,0|liokvz,96,196,0|liokw0,91,193,0\",\"Asia/Novosibirsk|,0,272,0|-q4do0s,96,196,0|-kmrco1,96,196,0|-kmrco0,91,193,0|5vav7z,91,193,0|5vav80,89,191,1|64pofz,89,191,1|64pog0,91,193,0|6e2sjz,91,193,0|6e2sk0,89,191,1|6nhlrz,89,191,1|6nhls0,91,193,0|6wupvz,91,193,0|6wupw0,89,191,1|769j3z,89,191,1|769j40,91,193,0|7fohvz,91,193,0|7fohw0,89,191,1|7p1orz,89,191,1|7p1os0,91,193,0|7yeq3z,91,193,0|7yeq40,89,191,1|87rrfz,89,191,1|87rrg0,91,193,0|8h4srz,91,193,0|8h4ss0,89,191,1|8qhu3z,89,191,1|8qhu40,91,193,0|8zuvfz,91,193,0|8zuvg0,89,191,1|997wrz,89,191,1|997ws0,91,193,0|9iky3z,91,193,0|9iky40,89,191,1|9rxzfz,89,191,1|9rxzg0,91,193,0|a1b0rz,91,193,0|a1b0s0,89,191,1|aao23z,89,191,1|aao240,91,193,0|ak13fz,91,193,0|ak13g0,89,191,1|atr3fz,89,191,1|atr3g0,91,193,0|b344rz,91,193,0|b344s0,91,193,1|bch8vz,91,193,1|bch8w0,96,196,0|bi8njz,96,196,0|bi8nk0,91,193,0|blu7fz,91,193,0|blu7g0,89,191,1|bv78rz,89,191,1|bv78s0,91,193,0|c4ka3z,91,193,0|c4ka40,89,191,1|c7fr3z,89,191,1|c7fr40,91,193,1|cdxe7z,91,193,1|cdxe80,96,196,0|cnafjz,96,196,0|cnafk0,91,193,1|cwngvz,91,193,1|cwngw0,96,196,0|d60i7z,96,196,0|d60i80,91,193,1|dfdjjz,91,193,1|dfdjk0,96,196,0|dp3jjz,96,196,0|dp3jk0,91,193,1|dzwfjz,91,193,1|dzwfk0,96,196,0|e7tm7z,96,196,0|e7tm80,91,193,1|eimi7z,91,193,1|eimi80,96,196,0|eqjovz,96,196,0|eqjow0,91,193,1|f1ckvz,91,193,1|f1ckw0,96,196,0|f99rjz,96,196,0|f99rk0,91,193,1|fkfm7z,91,193,1|fkfm80,96,196,0|frzu7z,96,196,0|frzu80,91,193,1|g35ovz,91,193,1|g35ow0,96,196,0|gapwvz,96,196,0|gapww0,91,193,1|glvrjz,91,193,1|glvrk0,96,196,0|gtsy7z,96,196,0|gtsy80,91,193,1|h4lu7z,91,193,1|h4lu80,96,196,0|hcj0vz,96,196,0|hcj0w0,91,193,1|hnbwvz,91,193,1|hnbww0,96,196,0|hv93jz,96,196,0|hv93k0,91,193,1|i6ey7z,91,193,1|i6ey80,96,196,0|idz67z,96,196,0|idz680,91,193,1|ip50vz,91,193,1|ip50w0,96,196,0|iwp8vz,96,196,0|iwp8w0,91,193,1|j7v3jz,91,193,1|j7v3k0,96,196,0|jffbjz,96,196,0|jffbk0,91,193,1|jql67z,91,193,1|jql680,96,196,0|jyicvz,96,196,0|jyicw0,91,193,1|k9b8vz,91,193,1|k9b8w0,96,196,0|kh8fjz,96,196,0|kh8fk0,91,193,1|ks1bjz,91,193,1|ks1bk0,96,196,0|kzyi7z,96,196,0|kzyi80,91,193,1|lb4cvz,91,193,1|lb4cw0,96,196,0|liokvz,96,196,0|liokw0,91,193,0|ne0krz,91,193,0|ne0ks0,96,196,0|oasa7z,96,196,0|oasa80,91,193,0\",\"Asia/Omsk|,0,273,0|-q5xmx6,92,194,0|-kmr9w1,92,194,0|-kmr9w0,96,196,0|5vaxzz,96,196,0|5vay00,91,193,1|64pr7z,91,193,1|64pr80,96,196,0|6e2vbz,96,196,0|6e2vc0,91,193,1|6nhojz,91,193,1|6nhok0,96,196,0|6wusnz,96,196,0|6wuso0,91,193,1|769lvz,91,193,1|769lw0,96,196,0|7foknz,96,196,0|7foko0,91,193,1|7p1rjz,91,193,1|7p1rk0,96,196,0|7yesvz,96,196,0|7yesw0,91,193,1|87ru7z,91,193,1|87ru80,96,196,0|8h4vjz,96,196,0|8h4vk0,91,193,1|8qhwvz,91,193,1|8qhww0,96,196,0|8zuy7z,96,196,0|8zuy80,91,193,1|997zjz,91,193,1|997zk0,96,196,0|9il0vz,96,196,0|9il0w0,91,193,1|9ry27z,91,193,1|9ry280,96,196,0|a1b3jz,96,196,0|a1b3k0,91,193,1|aao4vz,91,193,1|aao4w0,96,196,0|ak167z,96,196,0|ak1680,91,193,1|atr67z,91,193,1|atr680,96,196,0|b347jz,96,196,0|b347k0,96,196,1|bchbnz,96,196,1|bchbo0,92,194,0|bi8qbz,92,194,0|bi8qc0,96,196,0|blua7z,96,196,0|blua80,91,193,1|bv7bjz,91,193,1|bv7bk0,96,196,0|c4kcvz,96,196,0|c4kcw0,91,193,1|cdxe7z,91,193,1|cdxe80,96,196,0|cnafjz,96,196,0|cnafk0,91,193,1|cwngvz,91,193,1|cwngw0,96,196,0|d60i7z,96,196,0|d60i80,91,193,1|dfdjjz,91,193,1|dfdjk0,96,196,0|dp3jjz,96,196,0|dp3jk0,91,193,1|dzwfjz,91,193,1|dzwfk0,96,196,0|e7tm7z,96,196,0|e7tm80,91,193,1|eimi7z,91,193,1|eimi80,96,196,0|eqjovz,96,196,0|eqjow0,91,193,1|f1ckvz,91,193,1|f1ckw0,96,196,0|f99rjz,96,196,0|f99rk0,91,193,1|fkfm7z,91,193,1|fkfm80,96,196,0|frzu7z,96,196,0|frzu80,91,193,1|g35ovz,91,193,1|g35ow0,96,196,0|gapwvz,96,196,0|gapww0,91,193,1|glvrjz,91,193,1|glvrk0,96,196,0|gtsy7z,96,196,0|gtsy80,91,193,1|h4lu7z,91,193,1|h4lu80,96,196,0|hcj0vz,96,196,0|hcj0w0,91,193,1|hnbwvz,91,193,1|hnbww0,96,196,0|hv93jz,96,196,0|hv93k0,91,193,1|i6ey7z,91,193,1|i6ey80,96,196,0|idz67z,96,196,0|idz680,91,193,1|ip50vz,91,193,1|ip50w0,96,196,0|iwp8vz,96,196,0|iwp8w0,91,193,1|j7v3jz,91,193,1|j7v3k0,96,196,0|jffbjz,96,196,0|jffbk0,91,193,1|jql67z,91,193,1|jql680,96,196,0|jyicvz,96,196,0|jyicw0,91,193,1|k9b8vz,91,193,1|k9b8w0,96,196,0|kh8fjz,96,196,0|kh8fk0,91,193,1|ks1bjz,91,193,1|ks1bk0,96,196,0|kzyi7z,96,196,0|kzyi80,91,193,1|lb4cvz,91,193,1|lb4cw0,96,196,0|liokvz,96,196,0|liokw0,91,193,0|ne0krz,91,193,0|ne0ks0,96,196,0\",\"Asia/Oral|,0,274,0|-nu15ic,100,6,0|-kmr4c1,100,6,0|-kmr4c0,92,194,0|5vb0rz,92,194,0|5vb0s0,96,196,1|64ptzz,96,196,1|64pu00,96,196,0|6e2vbz,96,196,0|6e2vc0,96,196,1|6nhrbz,96,196,1|6nhrc0,92,194,0|6wuvfz,92,194,0|6wuvg0,96,196,1|769onz,96,196,1|769oo0,92,194,0|7fonfz,92,194,0|7fong0,96,196,1|7p1ubz,96,196,1|7p1uc0,92,194,0|7yevnz,92,194,0|7yevo0,96,196,1|87rwzz,96,196,1|87rx00,92,194,0|8h4ybz,92,194,0|8h4yc0,96,196,1|8qhznz,96,196,1|8qhzo0,92,194,0|8zv0zz,92,194,0|8zv100,96,196,1|9982bz,96,196,1|9982c0,92,194,0|9il3nz,92,194,0|9il3o0,96,196,1|9ry4zz,96,196,1|9ry500,92,194,0|a1b6bz,92,194,0|a1b6c0,92,194,1|aaoafz,92,194,1|aaoag0,105,209,0|ak1brz,105,209,0|ak1bs0,92,194,1|atrbrz,92,194,1|atrbs0,105,209,0|b34d3z,105,209,0|b34d40,92,194,1|bchefz,92,194,1|bcheg0,105,209,0|bi8t3z,105,209,0|bi8t40,92,194,0|bluczz,92,194,0|blud00,92,194,1|bv7h3z,92,194,1|bv7h40,105,209,0|c4kifz,105,209,0|c4kig0,92,194,1|cdxjrz,92,194,1|cdxjs0,105,209,0|cnal3z,105,209,0|cnal40,92,194,1|cwnmfz,92,194,1|cwnmg0,105,209,0|d60nrz,105,209,0|d60ns0,92,194,1|dfdp3z,92,194,1|dfdp40,105,209,0|dp3p3z,105,209,0|dp3p40,92,194,1|dzwl3z,92,194,1|dzwl40,105,209,0|e7trrz,105,209,0|e7trs0,92,194,1|eimnrz,92,194,1|eimns0,105,209,0|eqjufz,105,209,0|eqjug0,92,194,1|f1cqfz,92,194,1|f1cqg0,105,209,0|f99x3z,105,209,0|f99x40,92,194,1|fkfrrz,92,194,1|fkfrs0,105,209,0|frzzrz,105,209,0|frzzs0,92,194,1|g35ufz,92,194,1|g35ug0,105,209,0|gaq2fz,105,209,0|gaq2g0,92,194,1|glvx3z,92,194,1|glvx40,105,209,0|gtt3rz,105,209,0|gtt3s0,92,194,1|h4lzrz,92,194,1|h4lzs0,105,209,0|hcj6fz,105,209,0|hcj6g0,92,194,1|hnc2fz,92,194,1|hnc2g0,105,209,0|hv993z,105,209,0|hv9940,92,194,1|i6f3rz,92,194,1|i6f3s0,92,194,0\",\"Asia/Phnom_Penh|,0,217,0|-1ayyla4,53,217,0|-pysda5,53,217,0|-pysda4,91,193,0\",\"Asia/Pontianak|,0,275,0|-w6piww,7,275,0|-jebg8x,7,275,0|-jebg8w,106,222,0|-eknm61,106,222,0|-eknm60,107,224,0|-co37o1,107,224,0|-co37o0,106,222,0|-bb5zi1,106,222,0|-bb5zi0,89,191,0|-a9m681,89,191,0|-a9m680,106,222,0|-34ru61,106,222,0|-34ru60,129,191,0|9e5gfz,129,191,0|9e5gg0,119,193,0\",\"Asia/Pyongyang|,0,276,0|-w895yc,130,242,0|-u9s4y1,130,242,0|-u9s4y0,116,224,0|-cpmro1,116,224,0|-cpmro0,130,224,0|nt2uzz,130,224,0|nt2v00,130,242,0|p87lnz,130,242,0|p87lo0,130,224,0\",\"Asia/Qatar|,0,215,0|-q3gmvk,105,209,0|19d0vz,105,209,0|19d0w0,100,6,0\",\"Asia/Qostanay|,0,277,0|-nu17s4,105,209,0|-kmr741,105,209,0|-kmr740,92,194,0|5vb0rz,92,194,0|5vb0s0,96,196,1|64ptzz,96,196,1|64pu00,96,196,0|6e2vbz,96,196,0|6e2vc0,96,196,1|6nhrbz,96,196,1|6nhrc0,92,194,0|6wuvfz,92,194,0|6wuvg0,96,196,1|769onz,96,196,1|769oo0,92,194,0|7fonfz,92,194,0|7fong0,96,196,1|7p1ubz,96,196,1|7p1uc0,92,194,0|7yevnz,92,194,0|7yevo0,96,196,1|87rwzz,96,196,1|87rx00,92,194,0|8h4ybz,92,194,0|8h4yc0,96,196,1|8qhznz,96,196,1|8qhzo0,92,194,0|8zv0zz,92,194,0|8zv100,96,196,1|9982bz,96,196,1|9982c0,92,194,0|9il3nz,92,194,0|9il3o0,96,196,1|9ry4zz,96,196,1|9ry500,92,194,0|a1b6bz,92,194,0|a1b6c0,96,196,1|aao7nz,96,196,1|aao7o0,92,194,0|ak18zz,92,194,0|ak1900,96,196,1|atr8zz,96,196,1|atr900,92,194,0|b34abz,92,194,0|b34ac0,92,194,1|bchefz,92,194,1|bcheg0,105,209,0|bi8t3z,105,209,0|bi8t40,92,194,0|bluczz,92,194,0|blud00,96,196,1|bv7ebz,96,196,1|bv7ec0,92,194,0|c4kfnz,92,194,0|c4kfo0,96,196,1|cdxgzz,96,196,1|cdxh00,92,194,0|cnaibz,92,194,0|cnaic0,96,196,1|cwnjnz,96,196,1|cwnjo0,92,194,0|d60kzz,92,194,0|d60l00,96,196,1|dfdmbz,96,196,1|dfdmc0,92,194,0|dp3mbz,92,194,0|dp3mc0,96,196,1|dzwibz,96,196,1|dzwic0,92,194,0|e7tozz,92,194,0|e7tp00,96,196,1|eimkzz,96,196,1|eiml00,92,194,0|eqjrnz,92,194,0|eqjro0,96,196,1|f1cnnz,96,196,1|f1cno0,92,194,0|f99ubz,92,194,0|f99uc0,96,196,1|fkfozz,96,196,1|fkfp00,92,194,0|frzwzz,92,194,0|frzx00,96,196,1|g35rnz,96,196,1|g35ro0,92,194,0|gapznz,92,194,0|gapzo0,96,196,1|glvubz,96,196,1|glvuc0,92,194,0|gtt0zz,92,194,0|gtt100,96,196,1|h4lwzz,96,196,1|h4lx00,92,194,0|hcj3nz,92,194,0|hcj3o0,96,196,1|hnbznz,96,196,1|hnbzo0,92,194,0|hv96bz,92,194,0|hv96c0,96,196,1|i6f0zz,96,196,1|i6f100,96,196,0\",\"Asia/Qyzylorda|,0,278,0|-nu184g,105,209,0|-kmr741,105,209,0|-kmr740,92,194,0|5vb0rz,92,194,0|5vb0s0,96,196,1|64ptzz,96,196,1|64pu00,96,196,0|6e2vbz,96,196,0|6e2vc0,96,196,1|6nhrbz,96,196,1|6nhrc0,92,194,0|6wuvfz,92,194,0|6wuvg0,96,196,1|769onz,96,196,1|769oo0,92,194,0|7fonfz,92,194,0|7fong0,96,196,1|7p1ubz,96,196,1|7p1uc0,92,194,0|7yevnz,92,194,0|7yevo0,96,196,1|87rwzz,96,196,1|87rx00,92,194,0|8h4ybz,92,194,0|8h4yc0,96,196,1|8qhznz,96,196,1|8qhzo0,92,194,0|8zv0zz,92,194,0|8zv100,96,196,1|9982bz,96,196,1|9982c0,92,194,0|9il3nz,92,194,0|9il3o0,96,196,1|9ry4zz,96,196,1|9ry500,92,194,0|a1b6bz,92,194,0|a1b6c0,96,196,1|aao7nz,96,196,1|aao7o0,92,194,0|ak18zz,92,194,0|ak1900,96,196,1|atr8zz,96,196,1|atr900,92,194,0|b34abz,92,194,0|b34ac0,92,194,1|bchefz,92,194,1|bcheg0,92,194,0|bi8qbz,92,194,0|bi8qc0,96,196,0|blua7z,96,196,0|blua80,96,196,1|bv7ebz,96,196,1|bv7ec0,92,194,0|c4kfnz,92,194,0|c4kfo0,96,196,1|cdxgzz,96,196,1|cdxh00,92,194,0|cnaibz,92,194,0|cnaic0,96,196,1|cwnjnz,96,196,1|cwnjo0,92,194,0|d60kzz,92,194,0|d60l00,96,196,1|dfdmbz,96,196,1|dfdmc0,92,194,0|dp3mbz,92,194,0|dp3mc0,96,196,1|dzwibz,96,196,1|dzwic0,92,194,0|e7tozz,92,194,0|e7tp00,96,196,1|eimkzz,96,196,1|eiml00,92,194,0|eqjrnz,92,194,0|eqjro0,96,196,1|f1cnnz,96,196,1|f1cno0,92,194,0|f99ubz,92,194,0|f99uc0,96,196,1|fkfozz,96,196,1|fkfp00,92,194,0|frzwzz,92,194,0|frzx00,96,196,1|g35rnz,96,196,1|g35ro0,92,194,0|gapznz,92,194,0|gapzo0,96,196,1|glvubz,96,196,1|glvuc0,92,194,0|gtt0zz,92,194,0|gtt100,96,196,1|h4lwzz,96,196,1|h4lx00,92,194,0|hcj3nz,92,194,0|hcj3o0,96,196,1|hnbznz,96,196,1|hnbzo0,92,194,0|hv96bz,92,194,0|hv96c0,96,196,1|i6f0zz,96,196,1|i6f100,96,196,0|pk1rbz,96,196,0|pk1rc0,92,194,0\",\"Asia/Riyadh|,0,203,0|-bwgbbg,100,6,0\",\"Asia/Sakhalin|,0,279,0|-xl87rc,107,224,0|-cpkx01,107,224,0|-cpkx00,90,192,0|5vak3z,90,192,0|5vak40,102,200,1|64pdbz,102,200,1|64pdc0,90,192,0|6e2hfz,90,192,0|6e2hg0,102,200,1|6nhanz,102,200,1|6nhao0,90,192,0|6wuerz,90,192,0|6wues0,102,200,1|7697zz,102,200,1|769800,90,192,0|7fo6rz,90,192,0|7fo6s0,102,200,1|7p1dnz,102,200,1|7p1do0,90,192,0|7yeezz,90,192,0|7yef00,102,200,1|87rgbz,102,200,1|87rgc0,90,192,0|8h4hnz,90,192,0|8h4ho0,102,200,1|8qhizz,102,200,1|8qhj00,90,192,0|8zukbz,90,192,0|8zukc0,102,200,1|997lnz,102,200,1|997lo0,90,192,0|9ikmzz,90,192,0|9ikn00,102,200,1|9rxobz,102,200,1|9rxoc0,90,192,0|a1apnz,90,192,0|a1apo0,102,200,1|aanqzz,102,200,1|aanr00,90,192,0|ak0sbz,90,192,0|ak0sc0,102,200,1|atqsbz,102,200,1|atqsc0,90,192,0|b33tnz,90,192,0|b33to0,90,192,1|bcgxrz,90,192,1|bcgxs0,93,195,0|bi8cfz,93,195,0|bi8cg0,90,192,0|bltwbz,90,192,0|bltwc0,102,200,1|bv6xnz,102,200,1|bv6xo0,90,192,0|c4jyzz,90,192,0|c4jz00,102,200,1|cdx0bz,102,200,1|cdx0c0,90,192,0|cna1nz,90,192,0|cna1o0,102,200,1|cwn2zz,102,200,1|cwn300,90,192,0|d604bz,90,192,0|d604c0,102,200,1|dfd5nz,102,200,1|dfd5o0,90,192,0|dp35nz,90,192,0|dp35o0,102,200,1|dzw1nz,102,200,1|dzw1o0,90,192,0|e7t8bz,90,192,0|e7t8c0,90,192,1|eim73z,90,192,1|eim740,93,195,0|eqjdrz,93,195,0|eqjds0,90,192,1|f1c9rz,90,192,1|f1c9s0,93,195,0|f99gfz,93,195,0|f99gg0,90,192,1|fkfb3z,90,192,1|fkfb40,93,195,0|frzj3z,93,195,0|frzj40,90,192,1|g35drz,90,192,1|g35ds0,93,195,0|gaplrz,93,195,0|gapls0,90,192,1|glvgfz,90,192,1|glvgg0,93,195,0|gtsn3z,93,195,0|gtsn40,90,192,1|h4lj3z,90,192,1|h4lj40,93,195,0|hciprz,93,195,0|hcips0,90,192,1|hnblrz,90,192,1|hnbls0,93,195,0|hv8sfz,93,195,0|hv8sg0,90,192,1|i6en3z,90,192,1|i6en40,93,195,0|idyv3z,93,195,0|idyv40,90,192,1|ip4prz,90,192,1|ip4ps0,93,195,0|iwoxrz,93,195,0|iwoxs0,90,192,1|j7usfz,90,192,1|j7usg0,93,195,0|jff0fz,93,195,0|jff0g0,90,192,1|jqkv3z,90,192,1|jqkv40,93,195,0|jyi1rz,93,195,0|jyi1s0,90,192,1|k9axrz,90,192,1|k9axs0,93,195,0|kh84fz,93,195,0|kh84g0,90,192,1|ks10fz,90,192,1|ks10g0,93,195,0|kzy73z,93,195,0|kzy740,90,192,1|lb41rz,90,192,1|lb41s0,93,195,0|lio9rz,93,195,0|lio9s0,90,192,0|ne09nz,90,192,0|ne09o0,93,195,0|o4nlrz,93,195,0|o4nls0,90,192,0\",\"Asia/Samarkand|,0,280,0|-nu18eh,105,209,0|-kmr741,105,209,0|-kmr740,92,194,0|5vb0rz,92,194,0|5vb0s0,96,196,1|64ptzz,96,196,1|64pu00,96,196,0|6e2vbz,96,196,0|6e2vc0,96,196,1|6nhrbz,96,196,1|6nhrc0,92,194,0|6wuvfz,92,194,0|6wuvg0,96,196,1|769onz,96,196,1|769oo0,92,194,0|7fonfz,92,194,0|7fong0,96,196,1|7p1ubz,96,196,1|7p1uc0,92,194,0|7yevnz,92,194,0|7yevo0,96,196,1|87rwzz,96,196,1|87rx00,92,194,0|8h4ybz,92,194,0|8h4yc0,96,196,1|8qhznz,96,196,1|8qhzo0,92,194,0|8zv0zz,92,194,0|8zv100,96,196,1|9982bz,96,196,1|9982c0,92,194,0|9il3nz,92,194,0|9il3o0,96,196,1|9ry4zz,96,196,1|9ry500,92,194,0|a1b6bz,92,194,0|a1b6c0,96,196,1|aao7nz,96,196,1|aao7o0,92,194,0|ak18zz,92,194,0|ak1900,96,196,1|atr8zz,96,196,1|atr900,92,194,0|b34abz,92,194,0|b34ac0,96,196,1|bchbnz,96,196,1|bchbo0,92,194,0\",\"Asia/Seoul|,0,281,0|-w8966g,130,242,0|-u9s4y1,130,242,0|-u9s4y0,116,224,0|-couzo1,116,224,0|-couzo0,130,224,0|-b9kp01,130,224,0|-b9kp00,131,195,1|-b486g1,131,195,1|-b486g0,130,224,0|-atu101,130,224,0|-atu100,131,195,1|-aljyg1,131,195,1|-aljyg0,130,224,0|-ab5t01,130,224,0|-ab5t00,131,195,1|-a2tvs1,131,195,1|-a2tvs0,130,224,0|-9ql2c1,130,224,0|-9ql2c0,131,195,1|-9k3t41,131,195,1|-9k3t40,130,224,0|-88kmc1,130,224,0|-88kmc0,130,242,0|-7nhbm1,130,242,0|-7nhbm0,131,248,1|-7gy7q1,131,248,1|-7gy7q0,130,242,0|-73vrm1,130,242,0|-73vrm0,131,248,1|-6x1jq1,131,248,1|-6x1jq0,130,242,0|-6lvma1,130,242,0|-6lvma0,131,248,1|-6eofq1,131,248,1|-6eofq0,130,242,0|-635jm1,130,242,0|-635jm0,131,248,1|-5vyd21,131,248,1|-5vyd20,130,242,0|-5kfgy1,130,242,0|-5kfgy0,131,248,1|-5d8ae1,131,248,1|-5d8ae0,130,242,0|-51pea1,130,242,0|-51pea0,131,248,1|-4ui7q1,131,248,1|-4ui7q0,130,242,0|-4dqfm1,130,242,0|-4dqfm0,130,224,0|920hvz,130,224,0|920hw0,131,195,1|99xojz,131,195,1|99xok0,130,224,0|9kqkjz,130,224,0|9kqkk0,131,195,1|9snr7z,131,195,1|9snr80,130,224,0\",\"Asia/Shanghai|,0,282,0|-100eztj,45,191,0|-qh00w1,45,191,0|-qh00w0,46,224,1|-q87fo1,46,224,1|-q87fo0,45,191,0|-ffvq81,45,191,0|-ffvq80,46,224,1|-f8zno1,46,224,1|-f8zno0,45,191,0|-f148w1,45,191,0|-f148w0,46,224,1|-ep6p01,46,224,1|-ep6p00,45,191,0|-ekjy81,45,191,0|-ekjy80,46,224,1|-cp63o1,46,224,1|-cp63o0,45,191,0|-cc1sw1,45,191,0|-cc1sw0,46,224,1|-c4wh01,46,224,1|-c4wh00,45,191,0|-butfk1,45,191,0|-butfk0,46,224,1|-bkj501,46,224,1|-bkj500,45,191,0|-bb60w1,45,191,0|-bb60w0,46,224,1|-b3aro1,46,224,1|-b3aro0,45,191,0|-ase3k1,45,191,0|-ase3k0,46,224,1|-ar06c1,46,224,1|-ar06c0,45,191,0|8ixjbz,45,191,0|8ixjc0,46,224,1|8prr7z,46,224,1|8prr80,45,191,0|90kpzz,45,191,0|90kq00,46,224,1|98htvz,46,224,1|98htw0,45,191,0|9jnrbz,45,191,0|9jnrc0,46,224,1|9r7wjz,46,224,1|9r7wk0,45,191,0|a2dtzz,45,191,0|a2du00,46,224,1|aaaxvz,46,224,1|aaaxw0,45,191,0|al3wnz,45,191,0|al3wo0,46,224,1|at10jz,46,224,1|at10k0,45,191,0|b3tzbz,45,191,0|b3tzc0,46,224,1|bbr37z,46,224,1|bbr380,45,191,0\",\"Asia/Singapore|,0,262,0|-100ewkd,85,262,0|-xphpwe,85,262,0|-xphpwd,91,193,0|-jb6gs1,91,193,0|-jb6gs0,118,246,1|-hquppd,118,246,1|-hquppc,118,246,0|-esddpd,118,246,0|-esddpc,106,222,0|-ejqa61,106,222,0|-ejqa60,107,224,0|-conl01,107,224,0|-conl00,106,222,0|69g35z,106,222,0|69g360,89,191,0\",\"Asia/Srednekolymsk|,0,283,0|-nu1ogs,93,195,0|-kmrns1,93,195,0|-kmrns0,90,192,0|5vak3z,90,192,0|5vak40,102,200,1|64pdbz,102,200,1|64pdc0,90,192,0|6e2hfz,90,192,0|6e2hg0,102,200,1|6nhanz,102,200,1|6nhao0,90,192,0|6wuerz,90,192,0|6wues0,102,200,1|7697zz,102,200,1|769800,90,192,0|7fo6rz,90,192,0|7fo6s0,102,200,1|7p1dnz,102,200,1|7p1do0,90,192,0|7yeezz,90,192,0|7yef00,102,200,1|87rgbz,102,200,1|87rgc0,90,192,0|8h4hnz,90,192,0|8h4ho0,102,200,1|8qhizz,102,200,1|8qhj00,90,192,0|8zukbz,90,192,0|8zukc0,102,200,1|997lnz,102,200,1|997lo0,90,192,0|9ikmzz,90,192,0|9ikn00,102,200,1|9rxobz,102,200,1|9rxoc0,90,192,0|a1apnz,90,192,0|a1apo0,102,200,1|aanqzz,102,200,1|aanr00,90,192,0|ak0sbz,90,192,0|ak0sc0,102,200,1|atqsbz,102,200,1|atqsc0,90,192,0|b33tnz,90,192,0|b33to0,90,192,1|bcgxrz,90,192,1|bcgxs0,93,195,0|bi8cfz,93,195,0|bi8cg0,90,192,0|bltwbz,90,192,0|bltwc0,102,200,1|bv6xnz,102,200,1|bv6xo0,90,192,0|c4jyzz,90,192,0|c4jz00,102,200,1|cdx0bz,102,200,1|cdx0c0,90,192,0|cna1nz,90,192,0|cna1o0,102,200,1|cwn2zz,102,200,1|cwn300,90,192,0|d604bz,90,192,0|d604c0,102,200,1|dfd5nz,102,200,1|dfd5o0,90,192,0|dp35nz,90,192,0|dp35o0,102,200,1|dzw1nz,102,200,1|dzw1o0,90,192,0|e7t8bz,90,192,0|e7t8c0,102,200,1|eim4bz,102,200,1|eim4c0,90,192,0|eqjazz,90,192,0|eqjb00,102,200,1|f1c6zz,102,200,1|f1c700,90,192,0|f99dnz,90,192,0|f99do0,102,200,1|fkf8bz,102,200,1|fkf8c0,90,192,0|frzgbz,90,192,0|frzgc0,102,200,1|g35azz,102,200,1|g35b00,90,192,0|gapizz,90,192,0|gapj00,102,200,1|glvdnz,102,200,1|glvdo0,90,192,0|gtskbz,90,192,0|gtskc0,102,200,1|h4lgbz,102,200,1|h4lgc0,90,192,0|hcimzz,90,192,0|hcin00,102,200,1|hnbizz,102,200,1|hnbj00,90,192,0|hv8pnz,90,192,0|hv8po0,102,200,1|i6ekbz,102,200,1|i6ekc0,90,192,0|idysbz,90,192,0|idysc0,102,200,1|ip4mzz,102,200,1|ip4n00,90,192,0|iwouzz,90,192,0|iwov00,102,200,1|j7upnz,102,200,1|j7upo0,90,192,0|jfexnz,90,192,0|jfexo0,102,200,1|jqksbz,102,200,1|jqksc0,90,192,0|jyhyzz,90,192,0|jyhz00,102,200,1|k9auzz,102,200,1|k9av00,90,192,0|kh81nz,90,192,0|kh81o0,102,200,1|ks0xnz,102,200,1|ks0xo0,90,192,0|kzy4bz,90,192,0|kzy4c0,102,200,1|lb3yzz,102,200,1|lb3z00,90,192,0|lio6zz,90,192,0|lio700,102,200,0|ne06vz,102,200,0|ne06w0,90,192,0\",\"Asia/Taipei|,0,284,0|-12mch60,45,191,0|-gtzfk1,45,191,0|-gtzfk0,116,224,0|-co6u81,116,224,0|-co6u80,45,191,0|-cc1sw1,45,191,0|-cc1sw0,46,224,1|-c4wh01,46,224,1|-c4wh00,45,191,0|-butfk1,45,191,0|-butfk0,46,224,1|-bkj501,46,224,1|-bkj500,45,191,0|-bb60w1,45,191,0|-bb60w0,46,224,1|-b3aro1,46,224,1|-b3aro0,45,191,0|-ase3k1,45,191,0|-ase3k0,46,224,1|-akiuc1,46,224,1|-akiuc0,45,191,0|-a9m681,45,191,0|-a9m680,46,224,1|-a1qx01,46,224,1|-a1qx00,45,191,0|-9qu8w1,45,191,0|-9qu8w0,46,224,1|-9iyzo1,46,224,1|-9iyzo0,45,191,0|-9b5fk1,45,191,0|-9b5fk0,46,224,1|-8yjt01,46,224,1|-8yjt00,45,191,0|-8qs3k1,45,191,0|-8qs3k0,46,224,1|-8frvo1,46,224,1|-8frvo0,45,191,0|-880681,45,191,0|-880680,46,224,1|-7wzyc1,46,224,1|-7wzyc0,45,191,0|-7p88w1,45,191,0|-7p88w0,46,224,1|-7ftfo1,46,224,1|-7ftfo0,45,191,0|-76egw1,45,191,0|-76egw0,46,224,1|-6wzno1,46,224,1|-6wzno0,45,191,0|-6nmjk1,45,191,0|-6nmjk0,46,224,1|-6e7qc1,46,224,1|-6e7qc0,45,191,0|-64um81,45,191,0|-64um80,46,224,1|-5vft01,46,224,1|-5vft00,45,191,0|-5m2ow1,45,191,0|-5m2ow0,46,224,1|-5cnvo1,46,224,1|-5cnvo0,45,191,0|-503y81,45,191,0|-503y80,46,224,1|-4tu3o1,46,224,1|-4tu3o0,45,191,0|-4hc0w1,45,191,0|-4hc0w0,46,224,1|-4b26c1,46,224,1|-4b26c0,45,191,0|27rlrz,45,191,0|27rls0,46,224,1|2h6ezz,46,224,1|2h6f00,45,191,0|2qjj3z,45,191,0|2qjj40,46,224,1|2zycbz,46,224,1|2zycc0,45,191,0|4ydlrz,45,191,0|4ydls0,46,224,1|533wbz,46,224,1|533wc0,45,191,0\",\"Asia/Tashkent|,0,285,0|-nu18tz,92,194,0|-kmr9w1,92,194,0|-kmr9w0,96,196,0|5vaxzz,96,196,0|5vay00,91,193,1|64pr7z,91,193,1|64pr80,96,196,0|6e2vbz,96,196,0|6e2vc0,91,193,1|6nhojz,91,193,1|6nhok0,96,196,0|6wusnz,96,196,0|6wuso0,91,193,1|769lvz,91,193,1|769lw0,96,196,0|7foknz,96,196,0|7foko0,91,193,1|7p1rjz,91,193,1|7p1rk0,96,196,0|7yesvz,96,196,0|7yesw0,91,193,1|87ru7z,91,193,1|87ru80,96,196,0|8h4vjz,96,196,0|8h4vk0,91,193,1|8qhwvz,91,193,1|8qhww0,96,196,0|8zuy7z,96,196,0|8zuy80,91,193,1|997zjz,91,193,1|997zk0,96,196,0|9il0vz,96,196,0|9il0w0,91,193,1|9ry27z,91,193,1|9ry280,96,196,0|a1b3jz,96,196,0|a1b3k0,91,193,1|aao4vz,91,193,1|aao4w0,96,196,0|ak167z,96,196,0|ak1680,91,193,1|atr67z,91,193,1|atr680,96,196,0|b347jz,96,196,0|b347k0,96,196,1|bchbnz,96,196,1|bchbo0,92,194,0\",\"Asia/Tbilisi|,0,286,0|-1ayyayn,132,286,0|-nu14ao,132,286,0|-nu14an,100,6,0|-6p7kc1,100,6,0|-6p7kc0,105,209,0|5vb3jz,105,209,0|5vb3k0,92,194,1|64pwrz,92,194,1|64pws0,105,209,0|6e30vz,105,209,0|6e30w0,92,194,1|6nhu3z,92,194,1|6nhu40,105,209,0|6wuy7z,105,209,0|6wuy80,92,194,1|769rfz,92,194,1|769rg0,105,209,0|7foq7z,105,209,0|7foq80,92,194,1|7p1x3z,92,194,1|7p1x40,105,209,0|7yeyfz,105,209,0|7yeyg0,92,194,1|87rzrz,92,194,1|87rzs0,105,209,0|8h513z,105,209,0|8h5140,92,194,1|8qi2fz,92,194,1|8qi2g0,105,209,0|8zv3rz,105,209,0|8zv3s0,92,194,1|99853z,92,194,1|998540,105,209,0|9il6fz,105,209,0|9il6g0,92,194,1|9ry7rz,92,194,1|9ry7s0,105,209,0|a1b93z,105,209,0|a1b940,92,194,1|aaoafz,92,194,1|aaoag0,105,209,0|ak1brz,105,209,0|ak1bs0,92,194,1|atrbrz,92,194,1|atrbs0,105,209,0|b34d3z,105,209,0|b34d40,105,209,1|bchh7z,105,209,1|bchh80,100,6,0|bluczz,100,6,0|blud00,105,209,1|bv7bjz,105,209,1|bv7bk0,100,6,0|c4kfnz,100,6,0|c4kfo0,105,209,1|cdxe7z,105,209,1|cdxe80,100,6,0|cnaibz,100,6,0|cnaic0,105,209,1|cwngvz,105,209,1|cwngw0,105,209,0|d60i7z,105,209,0|d60i80,92,194,1|dfdgrz,92,194,1|dfdgs0,105,209,0|dp3jjz,105,209,0|dp3jk0,92,194,1|eimffz,92,194,1|eimfg0,105,209,0|eqjovz,105,209,0|eqjow0,92,194,1|f1ci3z,92,194,1|f1ci40,105,209,0|f99rjz,105,209,0|f99rk0,92,194,1|fkfjfz,92,194,1|fkfjg0,105,209,0|frzu7z,105,209,0|frzu80,92,194,1|g35m3z,92,194,1|g35m40,105,209,0|gapwvz,105,209,0|gapww0,92,194,1|glvorz,92,194,1|glvos0,105,209,0|gtsy7z,105,209,0|gtsy80,92,194,1|h4lrfz,92,194,1|h4lrg0,105,209,0|hcj0vz,105,209,0|hcj0w0,92,194,1|hnbu3z,92,194,1|hnbu40,105,209,0|hv93jz,105,209,0|hv93k0,92,194,1|hzxjfz,92,194,1|hzxjg0,105,209,1|i6f6jz,105,209,1|i6f6k0,100,6,0|idzejz,100,6,0|idzek0,105,209,0\",\"Asia/Tehran|,0,287,0|-s6m6uw,133,287,0|-cixlix,133,287,0|-cixliw,134,288,0|435vlz,134,288,0|435vm0,105,209,0|4ad3jz,105,209,0|4ad3k0,92,194,1|4ldbfz,92,194,1|4ldbg0,105,209,0|4p2q7z,105,209,0|4p2q80,134,288,0|4t529z,134,288,0|4t52a0,124,252,1|52i0tz,124,252,1|52i0u0,134,288,0|5byu9z,134,288,0|5byua0,124,252,1|5lj7hz,124,252,1|5lj7i0,134,288,0|b4tcxz,134,288,0|b4tcy0,124,252,1|bc48tz,124,252,1|bc48u0,134,288,0|blhcxz,134,288,0|blhcy0,124,252,1|buy0tz,124,252,1|buy0u0,134,288,0|c49a9z,134,288,0|c49aa0,124,252,1|cdpy5z,124,252,1|cdpy60,134,288,0|cn17lz,134,288,0|cn17m0,124,252,1|cwhvhz,124,252,1|cwhvi0,134,288,0|d5t4xz,134,288,0|d5t4y0,124,252,1|df9stz,124,252,1|df9su0,134,288,0|dol29z,134,288,0|dol2a0,124,252,1|dy1q5z,124,252,1|dy1q60,134,288,0|e7eu9z,134,288,0|e7eua0,124,252,1|egvi5z,124,252,1|egvi60,134,288,0|eq6rlz,134,288,0|eq6rm0,124,252,1|eznfhz,124,252,1|eznfi0,134,288,0|f8yoxz,134,288,0|f8yoy0,124,252,1|fifctz,124,252,1|fifcu0,134,288,0|frqm9z,134,288,0|frqma0,124,252,1|g17a5z,124,252,1|g17a60,134,288,0|gake9z,134,288,0|gakea0,124,252,1|gk125z,124,252,1|gk1260,134,288,0|gtcblz,134,288,0|gtcbm0,124,252,1|h2szhz,124,252,1|h2szi0,134,288,0|hc48xz,134,288,0|hc48y0,124,252,1|hlkwtz,124,252,1|hlkwu0,134,288,0|huw69z,134,288,0|huw6a0,124,252,1|i4cu5z,124,252,1|i4cu60,134,288,0|idpy9z,134,288,0|idpya0,124,252,1|in6m5z,124,252,1|in6m60,134,288,0|jy1q9z,134,288,0|jy1qa0,124,252,1|k7ie5z,124,252,1|k7ie60,134,288,0|kgvi9z,134,288,0|kgvia0,124,252,1|kqc65z,124,252,1|kqc660,134,288,0|kznflz,134,288,0|kznfm0,124,252,1|l943hz,124,252,1|l943i0,134,288,0|lifcxz,134,288,0|lifcy0,124,252,1|lrw0tz,124,252,1|lrw0u0,134,288,0|m17a9z,134,288,0|m17aa0,124,252,1|many5z,124,252,1|many60,134,288,0|mk129z,134,288,0|mk12a0,124,252,1|mthq5z,124,252,1|mthq60,134,288,0|n2szlz,134,288,0|n2szm0,124,252,1|nc9nhz,124,252,1|nc9ni0,134,288,0|nlkwxz,134,288,0|nlkwy0,124,252,1|nv1ktz,124,252,1|nv1ku0,134,288,0|o4cu9z,134,288,0|o4cua0,124,252,1|odti5z,124,252,1|odti60,134,288,0|on6m9z,134,288,0|on6ma0,124,252,1|owna5z,124,252,1|owna60,134,288,0|p5yjlz,134,288,0|p5yjm0,124,252,1|pff7hz,124,252,1|pff7i0,134,288,0|poqgxz,134,288,0|poqgy0,124,252,1|py74tz,124,252,1|py74u0,134,288,0|q7ie9z,134,288,0|q7iea0,124,252,1|qgz25z,124,252,1|qgz260,134,288,0|qqc69z,134,288,0|qqc6a0,124,252,1|qzsu5z,124,252,1|qzsu60,134,288,0|r943lz,134,288,0|r943m0,124,252,1|rikrhz,124,252,1|rikri0,134,288,0|rrw0xz,134,288,0|rrw0y0,124,252,1|s1cotz,124,252,1|s1cou0,134,288,0|sany9z,134,288,0|sanya0,124,252,1|sk4m5z,124,252,1|sk4m60,134,288,0|sthq9z,134,288,0|sthqa0,124,252,1|t2ye5z,124,252,1|t2ye60,134,288,0|tc9nlz,134,288,0|tc9nm0,124,252,1|tlqbhz,124,252,1|tlqbi0,134,288,0|tv1kxz,134,288,0|tv1ky0,124,252,1|u4i8tz,124,252,1|u4i8u0,134,288,0|udti9z,134,288,0|udtia0,124,252,1|una65z,124,252,1|una660,134,288,0|uwlflz,134,288,0|uwlfm0,124,252,1|v623hz,124,252,1|v623i0,134,288,0|vff7lz,134,288,0|vff7m0,124,252,1|vovvhz,124,252,1|vovvi0,134,288,0|vy74xz,134,288,0|vy74y0,124,252,1|w7nstz,124,252,1|w7nsu0,134,288,0|wgz29z,134,288,0|wgz2a0,124,252,1|wqfq5z,124,252,1|wqfq60,134,288,0|wzqzlz,134,288,0|wzqzm0,124,252,1|x97nhz,124,252,1|x97ni0,134,288,0|xikrlz,134,288,0|xikrm0,124,252,1|xs1fhz,124,252,1|xs1fi0,134,288,0|y1coxz,134,288,0|y1coy0,124,252,1|yatctz,124,252,1|yatcu0,134,288,0|yk4m9z,134,288,0|yk4ma0,124,252,1|ytla5z,124,252,1|ytla60,134,288,0|z2wjlz,134,288,0|z2wjm0,124,252,1|zcd7hz,124,252,1|zcd7i0,134,288,0\",\"Asia/Thimphu|,0,289,0|-bojclo,108,228,0|99fa1z,108,228,0|99fa20,96,196,0\",\"Asia/Tokyo|,0,290,0|-16snno0,116,224,0|-bb4901,116,224,0|-bb4900,135,195,1|-b49yc1,135,195,1|-b49yc0,116,224,0|-atu101,116,224,0|-atu100,135,195,1|-aljvo1,135,195,1|-aljvo0,116,224,0|-a9b501,116,224,0|-a9b500,135,195,1|-a2tt01,135,195,1|-a2tt00,116,224,0|-9ql2c1,116,224,0|-9ql2c0,135,195,1|-9k3qc1,135,195,1|-9k3qc0,116,224,0\",\"Asia/Tomsk|,0,291,0|-q3zbqf,96,196,0|-kmrco1,96,196,0|-kmrco0,91,193,0|5vav7z,91,193,0|5vav80,89,191,1|64pofz,89,191,1|64pog0,91,193,0|6e2sjz,91,193,0|6e2sk0,89,191,1|6nhlrz,89,191,1|6nhls0,91,193,0|6wupvz,91,193,0|6wupw0,89,191,1|769j3z,89,191,1|769j40,91,193,0|7fohvz,91,193,0|7fohw0,89,191,1|7p1orz,89,191,1|7p1os0,91,193,0|7yeq3z,91,193,0|7yeq40,89,191,1|87rrfz,89,191,1|87rrg0,91,193,0|8h4srz,91,193,0|8h4ss0,89,191,1|8qhu3z,89,191,1|8qhu40,91,193,0|8zuvfz,91,193,0|8zuvg0,89,191,1|997wrz,89,191,1|997ws0,91,193,0|9iky3z,91,193,0|9iky40,89,191,1|9rxzfz,89,191,1|9rxzg0,91,193,0|a1b0rz,91,193,0|a1b0s0,89,191,1|aao23z,89,191,1|aao240,91,193,0|ak13fz,91,193,0|ak13g0,89,191,1|atr3fz,89,191,1|atr3g0,91,193,0|b344rz,91,193,0|b344s0,91,193,1|bch8vz,91,193,1|bch8w0,96,196,0|bi8njz,96,196,0|bi8nk0,91,193,0|blu7fz,91,193,0|blu7g0,89,191,1|bv78rz,89,191,1|bv78s0,91,193,0|c4ka3z,91,193,0|c4ka40,89,191,1|cdxbfz,89,191,1|cdxbg0,91,193,0|cnacrz,91,193,0|cnacs0,89,191,1|cwne3z,89,191,1|cwne40,91,193,0|d60ffz,91,193,0|d60fg0,89,191,1|dfdgrz,89,191,1|dfdgs0,91,193,0|dp3grz,91,193,0|dp3gs0,89,191,1|dzwcrz,89,191,1|dzwcs0,91,193,0|e7tjfz,91,193,0|e7tjg0,89,191,1|eimffz,89,191,1|eimfg0,91,193,0|eqjm3z,91,193,0|eqjm40,89,191,1|f1ci3z,89,191,1|f1ci40,91,193,0|f99orz,91,193,0|f99os0,89,191,1|fkfjfz,89,191,1|fkfjg0,91,193,0|frzrfz,91,193,0|frzrg0,89,191,1|g35m3z,89,191,1|g35m40,91,193,0|gapu3z,91,193,0|gapu40,89,191,1|glvorz,89,191,1|glvos0,91,193,0|gtsvfz,91,193,0|gtsvg0,89,191,1|gvea3z,89,191,1|gvea40,91,193,1|h4lu7z,91,193,1|h4lu80,96,196,0|hcj0vz,96,196,0|hcj0w0,91,193,1|hnbwvz,91,193,1|hnbww0,96,196,0|hv93jz,96,196,0|hv93k0,91,193,1|i6ey7z,91,193,1|i6ey80,96,196,0|idz67z,96,196,0|idz680,91,193,1|ip50vz,91,193,1|ip50w0,96,196,0|iwp8vz,96,196,0|iwp8w0,91,193,1|j7v3jz,91,193,1|j7v3k0,96,196,0|jffbjz,96,196,0|jffbk0,91,193,1|jql67z,91,193,1|jql680,96,196,0|jyicvz,96,196,0|jyicw0,91,193,1|k9b8vz,91,193,1|k9b8w0,96,196,0|kh8fjz,96,196,0|kh8fk0,91,193,1|ks1bjz,91,193,1|ks1bk0,96,196,0|kzyi7z,96,196,0|kzyi80,91,193,1|lb4cvz,91,193,1|lb4cw0,96,196,0|liokvz,96,196,0|liokw0,91,193,0|ne0krz,91,193,0|ne0ks0,96,196,0|o7wkvz,96,196,0|o7wkw0,91,193,0\",\"Asia/Ulaanbaatar|,0,292,0|-xmcrsk,91,193,0|46akjz,91,193,0|46akk0,89,191,0|6wun3z,89,191,0|6wun40,107,224,1|769gbz,107,224,1|769gc0,89,191,0|7fof3z,89,191,0|7fof40,107,224,1|7p1dnz,107,224,1|7p1do0,89,191,0|7yehrz,89,191,0|7yehs0,107,224,1|87rgbz,107,224,1|87rgc0,89,191,0|8h4kfz,89,191,0|8h4kg0,107,224,1|8qhizz,107,224,1|8qhj00,89,191,0|8zun3z,89,191,0|8zun40,107,224,1|997lnz,107,224,1|997lo0,89,191,0|9ikprz,89,191,0|9ikps0,107,224,1|9rxobz,107,224,1|9rxoc0,89,191,0|a1asfz,89,191,0|a1asg0,107,224,1|aanqzz,107,224,1|aanr00,89,191,0|ak0v3z,89,191,0|ak0v40,107,224,1|atqsbz,107,224,1|atqsc0,89,191,0|b33wfz,89,191,0|b33wg0,107,224,1|bcguzz,107,224,1|bcgv00,89,191,0|bltz3z,89,191,0|bltz40,107,224,1|bv6xnz,107,224,1|bv6xo0,89,191,0|c4k1rz,89,191,0|c4k1s0,107,224,1|cdx0bz,107,224,1|cdx0c0,89,191,0|cna4fz,89,191,0|cna4g0,107,224,1|cwn2zz,107,224,1|cwn300,89,191,0|d6073z,89,191,0|d60740,107,224,1|dfd5nz,107,224,1|dfd5o0,89,191,0|dp38fz,89,191,0|dp38g0,107,224,1|dyg6zz,107,224,1|dyg700,89,191,0|e7tb3z,89,191,0|e7tb40,107,224,1|eh69nz,107,224,1|eh69o0,89,191,0|eqjdrz,89,191,0|eqjds0,107,224,1|ezwcbz,107,224,1|ezwcc0,89,191,0|gcgpzz,89,191,0|gcgq00,107,224,1|gkdtvz,107,224,1|gkdtw0,89,191,0|gtqxzz,89,191,0|gtqy00,107,224,1|h33wjz,107,224,1|h33wk0,89,191,0|hch0nz,89,191,0|hch0o0,107,224,1|hltz7z,107,224,1|hltz80,89,191,0|hv73bz,89,191,0|hv73c0,107,224,1|i4k1vz,107,224,1|i4k1w0,89,191,0|idx5zz,89,191,0|idx600,107,224,1|ina4jz,107,224,1|ina4k0,89,191,0|iwn8nz,89,191,0|iwn8o0,107,224,1|j6d5vz,107,224,1|j6d5w0,89,191,0|nlvtzz,89,191,0|nlvu00,107,224,1|nv8mzz,107,224,1|nv8n00,89,191,0|o4lwnz,89,191,0|o4lwo0,107,224,1|odypnz,107,224,1|odypo0,89,191,0\",\"Asia/Urumqi|,0,293,0|-lx5pjw,96,196,0\",\"Asia/Ust-Nera|,0,294,0|-q4cl6u,89,191,0|-kmri81,89,191,0|-kmri80,107,224,0|5vapnz,107,224,0|5vapo0,102,200,1|64pdbz,102,200,1|64pdc0,90,192,0|6e2hfz,90,192,0|6e2hg0,102,200,1|6nhanz,102,200,1|6nhao0,90,192,0|6wuerz,90,192,0|6wues0,102,200,1|7697zz,102,200,1|769800,90,192,0|7fo6rz,90,192,0|7fo6s0,102,200,1|7p1dnz,102,200,1|7p1do0,90,192,0|7yeezz,90,192,0|7yef00,102,200,1|87rgbz,102,200,1|87rgc0,90,192,0|8h4hnz,90,192,0|8h4ho0,102,200,1|8qhizz,102,200,1|8qhj00,90,192,0|8zukbz,90,192,0|8zukc0,102,200,1|997lnz,102,200,1|997lo0,90,192,0|9ikmzz,90,192,0|9ikn00,102,200,1|9rxobz,102,200,1|9rxoc0,90,192,0|a1apnz,90,192,0|a1apo0,102,200,1|aanqzz,102,200,1|aanr00,90,192,0|ak0sbz,90,192,0|ak0sc0,102,200,1|atqsbz,102,200,1|atqsc0,90,192,0|b33tnz,90,192,0|b33to0,90,192,1|bcgxrz,90,192,1|bcgxs0,93,195,0|bi8cfz,93,195,0|bi8cg0,90,192,0|bltwbz,90,192,0|bltwc0,102,200,1|bv6xnz,102,200,1|bv6xo0,90,192,0|c4jyzz,90,192,0|c4jz00,102,200,1|cdx0bz,102,200,1|cdx0c0,90,192,0|cna1nz,90,192,0|cna1o0,102,200,1|cwn2zz,102,200,1|cwn300,90,192,0|d604bz,90,192,0|d604c0,102,200,1|dfd5nz,102,200,1|dfd5o0,90,192,0|dp35nz,90,192,0|dp35o0,102,200,1|dzw1nz,102,200,1|dzw1o0,90,192,0|e7t8bz,90,192,0|e7t8c0,102,200,1|eim4bz,102,200,1|eim4c0,90,192,0|eqjazz,90,192,0|eqjb00,102,200,1|f1c6zz,102,200,1|f1c700,90,192,0|f99dnz,90,192,0|f99do0,102,200,1|fkf8bz,102,200,1|fkf8c0,90,192,0|frzgbz,90,192,0|frzgc0,102,200,1|g35azz,102,200,1|g35b00,90,192,0|gapizz,90,192,0|gapj00,102,200,1|glvdnz,102,200,1|glvdo0,90,192,0|gtskbz,90,192,0|gtskc0,102,200,1|h4lgbz,102,200,1|h4lgc0,90,192,0|hcimzz,90,192,0|hcin00,102,200,1|hnbizz,102,200,1|hnbj00,90,192,0|hv8pnz,90,192,0|hv8po0,102,200,1|i6ekbz,102,200,1|i6ekc0,90,192,0|idysbz,90,192,0|idysc0,102,200,1|ip4mzz,102,200,1|ip4n00,90,192,0|iwouzz,90,192,0|iwov00,102,200,1|j7upnz,102,200,1|j7upo0,90,192,0|jfexnz,90,192,0|jfexo0,102,200,1|jqksbz,102,200,1|jqksc0,90,192,0|jyhyzz,90,192,0|jyhz00,102,200,1|k9auzz,102,200,1|k9av00,90,192,0|kh81nz,90,192,0|kh81o0,102,200,1|ks0xnz,102,200,1|ks0xo0,90,192,0|kzy4bz,90,192,0|kzy4c0,102,200,1|lb3yzz,102,200,1|lb3z00,90,192,0|lio6zz,90,192,0|lio700,102,200,0|lrerzz,102,200,0|lres00,90,192,0|ne09nz,90,192,0|ne09o0,93,195,0\",\"Asia/Vientiane|,0,217,0|-1ayyla4,53,217,0|-pysda5,53,217,0|-pysda4,91,193,0\",\"Asia/Vladivostok|,0,295,0|-oligf7,107,224,0|-kmrl01,107,224,0|-kmrl00,93,195,0|5vamvz,93,195,0|5vamw0,90,192,1|64pg3z,90,192,1|64pg40,93,195,0|6e2k7z,93,195,0|6e2k80,90,192,1|6nhdfz,90,192,1|6nhdg0,93,195,0|6wuhjz,93,195,0|6wuhk0,90,192,1|769arz,90,192,1|769as0,93,195,0|7fo9jz,93,195,0|7fo9k0,90,192,1|7p1gfz,90,192,1|7p1gg0,93,195,0|7yehrz,93,195,0|7yehs0,90,192,1|87rj3z,90,192,1|87rj40,93,195,0|8h4kfz,93,195,0|8h4kg0,90,192,1|8qhlrz,90,192,1|8qhls0,93,195,0|8zun3z,93,195,0|8zun40,90,192,1|997ofz,90,192,1|997og0,93,195,0|9ikprz,93,195,0|9ikps0,90,192,1|9rxr3z,90,192,1|9rxr40,93,195,0|a1asfz,93,195,0|a1asg0,90,192,1|aantrz,90,192,1|aants0,93,195,0|ak0v3z,93,195,0|ak0v40,90,192,1|atqv3z,90,192,1|atqv40,93,195,0|b33wfz,93,195,0|b33wg0,93,195,1|bch0jz,93,195,1|bch0k0,107,224,0|bi8f7z,107,224,0|bi8f80,93,195,0|bltz3z,93,195,0|bltz40,90,192,1|bv70fz,90,192,1|bv70g0,93,195,0|c4k1rz,93,195,0|c4k1s0,90,192,1|cdx33z,90,192,1|cdx340,93,195,0|cna4fz,93,195,0|cna4g0,90,192,1|cwn5rz,90,192,1|cwn5s0,93,195,0|d6073z,93,195,0|d60740,90,192,1|dfd8fz,90,192,1|dfd8g0,93,195,0|dp38fz,93,195,0|dp38g0,90,192,1|dzw4fz,90,192,1|dzw4g0,93,195,0|e7tb3z,93,195,0|e7tb40,90,192,1|eim73z,90,192,1|eim740,93,195,0|eqjdrz,93,195,0|eqjds0,90,192,1|f1c9rz,90,192,1|f1c9s0,93,195,0|f99gfz,93,195,0|f99gg0,90,192,1|fkfb3z,90,192,1|fkfb40,93,195,0|frzj3z,93,195,0|frzj40,90,192,1|g35drz,90,192,1|g35ds0,93,195,0|gaplrz,93,195,0|gapls0,90,192,1|glvgfz,90,192,1|glvgg0,93,195,0|gtsn3z,93,195,0|gtsn40,90,192,1|h4lj3z,90,192,1|h4lj40,93,195,0|hciprz,93,195,0|hcips0,90,192,1|hnblrz,90,192,1|hnbls0,93,195,0|hv8sfz,93,195,0|hv8sg0,90,192,1|i6en3z,90,192,1|i6en40,93,195,0|idyv3z,93,195,0|idyv40,90,192,1|ip4prz,90,192,1|ip4ps0,93,195,0|iwoxrz,93,195,0|iwoxs0,90,192,1|j7usfz,90,192,1|j7usg0,93,195,0|jff0fz,93,195,0|jff0g0,90,192,1|jqkv3z,90,192,1|jqkv40,93,195,0|jyi1rz,93,195,0|jyi1s0,90,192,1|k9axrz,90,192,1|k9axs0,93,195,0|kh84fz,93,195,0|kh84g0,90,192,1|ks10fz,90,192,1|ks10g0,93,195,0|kzy73z,93,195,0|kzy740,90,192,1|lb41rz,90,192,1|lb41s0,93,195,0|lio9rz,93,195,0|lio9s0,90,192,0|ne09nz,90,192,0|ne09o0,93,195,0\",\"Asia/Yakutsk|,0,296,0|-q4cioy,89,191,0|-kmri81,89,191,0|-kmri80,107,224,0|5vapnz,107,224,0|5vapo0,93,195,1|64pivz,93,195,1|64piw0,107,224,0|6e2mzz,107,224,0|6e2n00,93,195,1|6nhg7z,93,195,1|6nhg80,107,224,0|6wukbz,107,224,0|6wukc0,93,195,1|769djz,93,195,1|769dk0,107,224,0|7focbz,107,224,0|7focc0,93,195,1|7p1j7z,93,195,1|7p1j80,107,224,0|7yekjz,107,224,0|7yekk0,93,195,1|87rlvz,93,195,1|87rlw0,107,224,0|8h4n7z,107,224,0|8h4n80,93,195,1|8qhojz,93,195,1|8qhok0,107,224,0|8zupvz,107,224,0|8zupw0,93,195,1|997r7z,93,195,1|997r80,107,224,0|9iksjz,107,224,0|9iksk0,93,195,1|9rxtvz,93,195,1|9rxtw0,107,224,0|a1av7z,107,224,0|a1av80,93,195,1|aanwjz,93,195,1|aanwk0,107,224,0|ak0xvz,107,224,0|ak0xw0,93,195,1|atqxvz,93,195,1|atqxw0,107,224,0|b33z7z,107,224,0|b33z80,107,224,1|bch3bz,107,224,1|bch3c0,89,191,0|bi8hzz,89,191,0|bi8i00,107,224,0|blu1vz,107,224,0|blu1w0,93,195,1|bv737z,93,195,1|bv7380,107,224,0|c4k4jz,107,224,0|c4k4k0,93,195,1|cdx5vz,93,195,1|cdx5w0,107,224,0|cna77z,107,224,0|cna780,93,195,1|cwn8jz,93,195,1|cwn8k0,107,224,0|d609vz,107,224,0|d609w0,93,195,1|dfdb7z,93,195,1|dfdb80,107,224,0|dp3b7z,107,224,0|dp3b80,93,195,1|dzw77z,93,195,1|dzw780,107,224,0|e7tdvz,107,224,0|e7tdw0,93,195,1|eim9vz,93,195,1|eim9w0,107,224,0|eqjgjz,107,224,0|eqjgk0,93,195,1|f1ccjz,93,195,1|f1cck0,107,224,0|f99j7z,107,224,0|f99j80,93,195,1|fkfdvz,93,195,1|fkfdw0,107,224,0|frzlvz,107,224,0|frzlw0,93,195,1|g35gjz,93,195,1|g35gk0,107,224,0|gapojz,107,224,0|gapok0,93,195,1|glvj7z,93,195,1|glvj80,107,224,0|gtspvz,107,224,0|gtspw0,93,195,1|h4llvz,93,195,1|h4llw0,107,224,0|hcisjz,107,224,0|hcisk0,93,195,1|hnbojz,93,195,1|hnbok0,107,224,0|hv8v7z,107,224,0|hv8v80,93,195,1|i6epvz,93,195,1|i6epw0,107,224,0|idyxvz,107,224,0|idyxw0,93,195,1|ip4sjz,93,195,1|ip4sk0,107,224,0|iwp0jz,107,224,0|iwp0k0,93,195,1|j7uv7z,93,195,1|j7uv80,107,224,0|jff37z,107,224,0|jff380,93,195,1|jqkxvz,93,195,1|jqkxw0,107,224,0|jyi4jz,107,224,0|jyi4k0,93,195,1|k9b0jz,93,195,1|k9b0k0,107,224,0|kh877z,107,224,0|kh8780,93,195,1|ks137z,93,195,1|ks1380,107,224,0|kzy9vz,107,224,0|kzy9w0,93,195,1|lb44jz,93,195,1|lb44k0,107,224,0|liocjz,107,224,0|liock0,93,195,0|ne0cfz,93,195,0|ne0cg0,107,224,0\",\"Asia/Yangon|,0,297,0|-1ayykhb,136,297,0|-q3gv5c,136,297,0|-q3gv5b,109,229,0|-efx621,109,229,0|-efx620,107,224,0|-cvg101,107,224,0|-cvg100,109,229,0\",\"Asia/Yekaterinburg|,0,298,0|-rx5hw9,7,299,0|-qc75z6,7,299,0|-qc75z5,105,209,0|-kmr741,105,209,0|-kmr740,92,194,0|5vb0rz,92,194,0|5vb0s0,96,196,1|64ptzz,96,196,1|64pu00,92,194,0|6e2y3z,92,194,0|6e2y40,96,196,1|6nhrbz,96,196,1|6nhrc0,92,194,0|6wuvfz,92,194,0|6wuvg0,96,196,1|769onz,96,196,1|769oo0,92,194,0|7fonfz,92,194,0|7fong0,96,196,1|7p1ubz,96,196,1|7p1uc0,92,194,0|7yevnz,92,194,0|7yevo0,96,196,1|87rwzz,96,196,1|87rx00,92,194,0|8h4ybz,92,194,0|8h4yc0,96,196,1|8qhznz,96,196,1|8qhzo0,92,194,0|8zv0zz,92,194,0|8zv100,96,196,1|9982bz,96,196,1|9982c0,92,194,0|9il3nz,92,194,0|9il3o0,96,196,1|9ry4zz,96,196,1|9ry500,92,194,0|a1b6bz,92,194,0|a1b6c0,96,196,1|aao7nz,96,196,1|aao7o0,92,194,0|ak18zz,92,194,0|ak1900,96,196,1|atr8zz,96,196,1|atr900,92,194,0|b34abz,92,194,0|b34ac0,92,194,1|bchefz,92,194,1|bcheg0,105,209,0|bi8t3z,105,209,0|bi8t40,92,194,0|bluczz,92,194,0|blud00,96,196,1|bv7ebz,96,196,1|bv7ec0,92,194,0|c4kfnz,92,194,0|c4kfo0,96,196,1|cdxgzz,96,196,1|cdxh00,92,194,0|cnaibz,92,194,0|cnaic0,96,196,1|cwnjnz,96,196,1|cwnjo0,92,194,0|d60kzz,92,194,0|d60l00,96,196,1|dfdmbz,96,196,1|dfdmc0,92,194,0|dp3mbz,92,194,0|dp3mc0,96,196,1|dzwibz,96,196,1|dzwic0,92,194,0|e7tozz,92,194,0|e7tp00,96,196,1|eimkzz,96,196,1|eiml00,92,194,0|eqjrnz,92,194,0|eqjro0,96,196,1|f1cnnz,96,196,1|f1cno0,92,194,0|f99ubz,92,194,0|f99uc0,96,196,1|fkfozz,96,196,1|fkfp00,92,194,0|frzwzz,92,194,0|frzx00,96,196,1|g35rnz,96,196,1|g35ro0,92,194,0|gapznz,92,194,0|gapzo0,96,196,1|glvubz,96,196,1|glvuc0,92,194,0|gtt0zz,92,194,0|gtt100,96,196,1|h4lwzz,96,196,1|h4lx00,92,194,0|hcj3nz,92,194,0|hcj3o0,96,196,1|hnbznz,96,196,1|hnbzo0,92,194,0|hv96bz,92,194,0|hv96c0,96,196,1|i6f0zz,96,196,1|i6f100,92,194,0|idz8zz,92,194,0|idz900,96,196,1|ip53nz,96,196,1|ip53o0,92,194,0|iwpbnz,92,194,0|iwpbo0,96,196,1|j7v6bz,96,196,1|j7v6c0,92,194,0|jffebz,92,194,0|jffec0,96,196,1|jql8zz,96,196,1|jql900,92,194,0|jyifnz,92,194,0|jyifo0,96,196,1|k9bbnz,96,196,1|k9bbo0,92,194,0|kh8ibz,92,194,0|kh8ic0,96,196,1|ks1ebz,96,196,1|ks1ec0,92,194,0|kzykzz,92,194,0|kzyl00,96,196,1|lb4fnz,96,196,1|lb4fo0,92,194,0|lionnz,92,194,0|liono0,96,196,0|ne0njz,96,196,0|ne0nk0,92,194,0\",\"Asia/Yerevan|,0,300,0|-nu148o,100,6,0|-6p7kc1,100,6,0|-6p7kc0,105,209,0|5vb3jz,105,209,0|5vb3k0,92,194,1|64pwrz,92,194,1|64pws0,105,209,0|6e30vz,105,209,0|6e30w0,92,194,1|6nhu3z,92,194,1|6nhu40,105,209,0|6wuy7z,105,209,0|6wuy80,92,194,1|769rfz,92,194,1|769rg0,105,209,0|7foq7z,105,209,0|7foq80,92,194,1|7p1x3z,92,194,1|7p1x40,105,209,0|7yeyfz,105,209,0|7yeyg0,92,194,1|87rzrz,92,194,1|87rzs0,105,209,0|8h513z,105,209,0|8h5140,92,194,1|8qi2fz,92,194,1|8qi2g0,105,209,0|8zv3rz,105,209,0|8zv3s0,92,194,1|99853z,92,194,1|998540,105,209,0|9il6fz,105,209,0|9il6g0,92,194,1|9ry7rz,92,194,1|9ry7s0,105,209,0|a1b93z,105,209,0|a1b940,92,194,1|aaoafz,92,194,1|aaoag0,105,209,0|ak1brz,105,209,0|ak1bs0,92,194,1|atrbrz,92,194,1|atrbs0,105,209,0|b34d3z,105,209,0|b34d40,105,209,1|bchh7z,105,209,1|bchh80,100,6,0|bluijz,100,6,0|bluik0,105,209,1|bv7jvz,105,209,1|bv7jw0,100,6,0|c4kl7z,100,6,0|c4kl80,105,209,1|cdxmjz,105,209,1|cdxmk0,100,6,0|cnanvz,100,6,0|cnanw0,105,209,1|cwnp7z,105,209,1|cwnp80,100,6,0|d60qjz,100,6,0|d60qk0,105,209,1|dfdrvz,105,209,1|dfdrw0,105,209,0|e7trrz,105,209,0|e7trs0,92,194,1|eimnrz,92,194,1|eimns0,105,209,0|eqjufz,105,209,0|eqjug0,92,194,1|f1cqfz,92,194,1|f1cqg0,105,209,0|f99x3z,105,209,0|f99x40,92,194,1|fkfrrz,92,194,1|fkfrs0,105,209,0|frzzrz,105,209,0|frzzs0,92,194,1|g35ufz,92,194,1|g35ug0,105,209,0|gaq2fz,105,209,0|gaq2g0,92,194,1|glvx3z,92,194,1|glvx40,105,209,0|gtt3rz,105,209,0|gtt3s0,92,194,1|h4lzrz,92,194,1|h4lzs0,105,209,0|hcj6fz,105,209,0|hcj6g0,92,194,1|hnc2fz,92,194,1|hnc2g0,105,209,0|hv993z,105,209,0|hv9940,92,194,1|i6f3rz,92,194,1|i6f3s0,105,209,0|idzbrz,105,209,0|idzbs0,92,194,1|ip56fz,92,194,1|ip56g0,105,209,0|iwpefz,105,209,0|iwpeg0,92,194,1|j7v93z,92,194,1|j7v940,105,209,0|jffh3z,105,209,0|jffh40,92,194,1|jqlbrz,92,194,1|jqlbs0,105,209,0|jyiifz,105,209,0|jyiig0,92,194,1|k9befz,92,194,1|k9beg0,105,209,0|kh8l3z,105,209,0|kh8l40,92,194,1|ks1h3z,92,194,1|ks1h40,105,209,0|kzynrz,105,209,0|kzyns0,92,194,1|lb4ifz,92,194,1|lb4ig0,105,209,0|lioqfz,105,209,0|lioqg0,92,194,1|ltul3z,92,194,1|ltul40,105,209,0\",\"Atlantic/Azores|,0,301,0|-18vsdww,77,302,0|-u9rbs1,77,302,0|-u9rbs0,40,45,0|-rxwvw1,40,45,0|-rxwvw0,13,15,1|-rqwyg1,13,15,1|-rqwyg0,40,45,0|-rkqt81,40,45,0|-rkqt80,13,15,1|-r90l81,13,15,1|-r90l80,40,45,0|-r1x181,40,45,0|-r1x180,13,15,1|-qq8nw1,13,15,1|-qq8nw0,40,45,0|-qj6yk1,40,45,0|-qj6yk0,13,15,1|-q7gqk1,13,15,1|-q7gqk0,40,45,0|-q0d6k1,40,45,0|-q0d6k0,13,15,1|-pomyk1,13,15,1|-pomyk0,40,45,0|-phl981,40,45,0|-phl980,13,15,1|-p5v181,13,15,1|-p5v180,40,45,0|-nusl81,40,45,0|-nusl80,13,15,1|-nlhek1,13,15,1|-nlhek0,40,45,0|-mt6vw1,40,45,0|-mt6vw0,13,15,1|-mkjrw1,13,15,1|-mkjrw0,40,45,0|-matrw1,40,45,0|-matrw0,13,15,1|-m1tp81,13,15,1|-m1tp80,40,45,0|-lrqqk1,40,45,0|-lrqqk0,13,15,1|-liqnw1,13,15,1|-liqnw0,40,45,0|-l8np81,40,45,0|-l8np80,13,15,1|-l00l81,13,15,1|-l00l80,40,45,0|-k77jw1,40,45,0|-k77jw0,13,15,1|-jykfw1,13,15,1|-jykfw0,40,45,0|-jp7ek1,40,45,0|-jp7ek0,13,15,1|-jfud81,13,15,1|-jfud80,40,45,0|-ineak1,40,45,0|-ineak0,13,15,1|-ie1981,13,15,1|-ie1980,40,45,0|-i516k1,40,45,0|-i516k0,13,15,1|-hvb6k1,13,15,1|-hvb6k0,40,45,0|-hl87w1,40,45,0|-hl87w0,13,15,1|-hcl3w1,13,15,1|-hcl3w0,40,45,0|-h382k1,40,45,0|-h382k0,13,15,1|-gtv181,13,15,1|-gtv180,40,45,0|-gkuyk1,40,45,0|-gkuyk0,13,15,1|-gb4yk1,13,15,1|-gb4yk0,40,45,0|-g11zw1,40,45,0|-g11zw0,13,15,1|-fpw581,13,15,1|-fpw580,40,45,0|-fkunw1,40,45,0|-fkunw0,13,15,1|-f9buk1,13,15,1|-f9buk0,40,45,0|-ezyt81,40,45,0|-ezyt80,13,15,1|-eqjx81,13,15,1|-eqjx80,40,45,0|-eibmk1,40,45,0|-eibmk0,13,15,1|-eg5xc1,13,15,1|-eg5xc0,17,1,1|-eaeio1,17,1,1|-eaeio0,13,15,1|-e6st81,13,15,1|-e6st80,40,45,0|-dzljw1,40,45,0|-dzljw0,13,15,1|-dxstc1,13,15,1|-dxstc0,17,1,1|-dqyio1,17,1,1|-dqyio0,13,15,1|-dnprw1,13,15,1|-dnprw0,40,45,0|-dgvh81,40,45,0|-dgvh80,13,15,1|-deps01,13,15,1|-deps00,17,1,1|-d88g01,17,1,1|-d88g00,13,15,1|-d4zp81,13,15,1|-d4zp80,40,45,0|-cy5ek1,40,45,0|-cy5ek0,13,15,1|-cvzpc1,13,15,1|-cvzpc0,17,1,1|-cpidc1,17,1,1|-cpidc0,13,15,1|-cm9mk1,13,15,1|-cm9mk0,40,45,0|-cdzh81,40,45,0|-cdzh80,13,15,1|-c4mfw1,13,15,1|-c4mfw0,40,45,0|-bv9681,40,45,0|-bv9680,13,15,1|-blw4w1,13,15,1|-blw4w0,40,45,0|-bcj3k1,40,45,0|-bcj3k0,13,15,1|-b36281,13,15,1|-b36280,40,45,0|-att0w1,40,45,0|-att0w0,13,15,1|-akfzk1,13,15,1|-akfzk0,40,45,0|-9scvk1,40,45,0|-9scvk0,13,15,1|-9imvk1,13,15,1|-9imvk0,40,45,0|-999u81,40,45,0|-999u80,13,15,1|-8zwsw1,13,15,1|-8zwsw0,40,45,0|-8qjrk1,40,45,0|-8qjrk0,13,15,1|-8h6q81,13,15,1|-8h6q80,40,45,0|-87tow1,40,45,0|-87tow0,13,15,1|-7ygnk1,13,15,1|-7ygnk0,40,45,0|-7p3m81,40,45,0|-7p3m80,13,15,1|-7fqkw1,13,15,1|-7fqkw0,40,45,0|-76djk1,40,45,0|-76djk0,13,15,1|-6wnjk1,13,15,1|-6wnjk0,40,45,0|-6nai81,40,45,0|-6nai80,13,15,1|-6dxgw1,13,15,1|-6dxgw0,40,45,0|-64kfk1,40,45,0|-64kfk0,13,15,1|-5v7e81,13,15,1|-5v7e80,40,45,0|-5lucw1,40,45,0|-5lucw0,13,15,1|-5chbk1,13,15,1|-5chbk0,40,45,0|-534a81,40,45,0|-534a80,13,15,1|-4tr8w1,13,15,1|-4tr8w0,40,45,0|-4ke7k1,40,45,0|-4ke7k0,13,15,1|-4b1681,13,15,1|-4b1680,40,45,0|-41o4w1,40,45,0|-41o4w0,13,15,1|-3ry4w1,13,15,1|-3ry4w0,40,45,0|-3il3k1,40,45,0|-3il3k0,13,15,1|-398281,13,15,1|-398280,40,45,0|-2zv0w1,40,45,0|-2zv0w0,13,15,1|-2qhzk1,13,15,1|-2qhzk0,40,45,0|-2h4y81,40,45,0|-2h4y80,13,15,1|-27rww1,13,15,1|-27rww0,40,45,0|-1yevk1,40,45,0|-1yevk0,13,15,0|3rwo3z,13,15,0|3rwo40,17,1,1|419pfz,17,1,1|419pg0,13,15,0|4azpfz,13,15,0|4azpg0,17,1,1|4kcqrz,17,1,1|4kcqs0,13,15,0|4tps3z,13,15,0|4tps40,17,1,1|532w7z,17,1,1|532w80,13,15,0|5cfurz,13,15,0|5cfus0,17,1,1|5lsyvz,17,1,1|5lsyw0,13,15,0|5v607z,13,15,0|5v6080,17,1,1|64j1jz,17,1,1|64j1k0,13,15,0|6dw2vz,13,15,0|6dw2w0,17,1,1|6n947z,17,1,1|6n9480,13,15,0|6wm8bz,13,15,0|6wm8c0,17,1,1|75z6vz,17,1,1|75z6w0,13,15,0|7fc87z,13,15,0|7fc880,17,1,1|7p287z,17,1,1|7p2880,13,15,0|7yf9jz,13,15,0|7yf9k0,17,1,1|87savz,17,1,1|87saw0,13,15,0|8h5c7z,13,15,0|8h5c80,17,1,1|8qidjz,17,1,1|8qidk0,13,15,0|8zvevz,13,15,0|8zvew0,17,1,1|998g7z,17,1,1|998g80,13,15,0|9ilhjz,13,15,0|9ilhk0,17,1,1|9ryivz,17,1,1|9ryiw0,13,15,0|a1bk7z,13,15,0|a1bk80,17,1,1|aaoljz,17,1,1|aaolk0,13,15,0|ak1mvz,13,15,0|ak1mw0,17,1,1|atrmvz,17,1,1|atrmw0,13,15,0|b34o7z,13,15,0|b34o80,17,1,1|bchpjz,17,1,1|bchpk0,13,15,0|bluqvz,13,15,0|bluqw0,17,1,1|bv7s7z,17,1,1|bv7s80,8,1,0|c4kqrz,8,1,0|c4kqs0,17,1,1|cdxs3z,17,1,1|cdxs40,13,15,0|cnatfz,13,15,0|cnatg0,17,1,1|cwnurz,17,1,1|cwnus0,13,15,0|d60w3z,13,15,0|d60w40,17,1,1|dfdxfz,17,1,1|dfdxg0,13,15,0|dp3xfz,13,15,0|dp3xg0,17,1,1|dzwtfz,17,1,1|dzwtg0,13,15,0|e7u03z,13,15,0|e7u040,17,1,1|eimw3z,17,1,1|eimw40,13,15,0|eqk2rz,13,15,0|eqk2s0,17,1,1|f1cyrz,17,1,1|f1cys0,13,15,0|f9a5fz,13,15,0|f9a5g0,17,1,1|fkg03z,17,1,1|fkg040,13,15,0|fs083z,13,15,0|fs0840,17,1,1|g362rz,17,1,1|g362s0,13,15,0|gaqarz,13,15,0|gaqas0,17,1,1|glw5fz,17,1,1|glw5g0,13,15,0|gttc3z,13,15,0|gttc40,17,1,1|h4m83z,17,1,1|h4m840,13,15,0|hcjerz,13,15,0|hcjes0,17,1,1|hncarz,17,1,1|hncas0,13,15,0|hv9hfz,13,15,0|hv9hg0,17,1,1|i6fc3z,17,1,1|i6fc40,13,15,0|idzk3z,13,15,0|idzk40,17,1,1|ip5erz,17,1,1|ip5es0,13,15,0|iwpmrz,13,15,0|iwpms0,17,1,1|j7vhfz,17,1,1|j7vhg0,13,15,0|jffpfz,13,15,0|jffpg0,17,1,1|jqlk3z,17,1,1|jqlk40,13,15,0|jyiqrz,13,15,0|jyiqs0,17,1,1|k9bmrz,17,1,1|k9bms0,13,15,0|kh8tfz,13,15,0|kh8tg0,17,1,1|ks1pfz,17,1,1|ks1pg0,13,15,0|kzyw3z,13,15,0|kzyw40,17,1,1|lb4qrz,17,1,1|lb4qs0,13,15,0|lioyrz,13,15,0|lioys0,17,1,1|ltutfz,17,1,1|ltutg0,13,15,0|m1f1fz,13,15,0|m1f1g0,17,1,1|mckw3z,17,1,1|mckw40,13,15,0|mki2rz,13,15,0|mki2s0,17,1,1|mvayrz,17,1,1|mvays0,13,15,0|n385fz,13,15,0|n385g0,17,1,1|ne11fz,17,1,1|ne11g0,13,15,0|nly83z,13,15,0|nly840,17,1,1|nwr43z,17,1,1|nwr440,13,15,0|o4oarz,13,15,0|o4oas0,17,1,1|ofu5fz,17,1,1|ofu5g0,13,15,0|onedfz,13,15,0|onedg0,17,1,1|oyk83z,17,1,1|oyk840,13,15,0|p64g3z,13,15,0|p64g40,17,1,1|phaarz,17,1,1|phaas0,13,15,0|pp7hfz,13,15,0|pp7hg0,17,1,1|q00dfz,17,1,1|q00dg0,13,15,0|q7xk3z,13,15,0|q7xk40,17,1,1|qiqg3z,17,1,1|qiqg40,13,15,0|qqnmrz,13,15,0|qqnms0,17,1,1|r1thfz,17,1,1|r1thg0,13,15,0|r9dpfz,13,15,0|r9dpg0,17,1,1|rkjk3z,17,1,1|rkjk40,13,15,0|rs3s3z,13,15,0|rs3s40,17,1,1|s39mrz,17,1,1|s39ms0,13,15,0|sb6tfz,13,15,0|sb6tg0,17,1,1|slzpfz,17,1,1|slzpg0,13,15,0|stww3z,13,15,0|stww40,17,1,1|t4ps3z,17,1,1|t4ps40,13,15,0|tcmyrz,13,15,0|tcmys0,17,1,1|tnfurz,17,1,1|tnfus0,13,15,0|tvd1fz,13,15,0|tvd1g0,17,1,1|u6iw3z,17,1,1|u6iw40,13,15,0|ue343z,13,15,0|ue3440,17,1,1|up8yrz,17,1,1|up8ys0,13,15,0|uwt6rz,13,15,0|uwt6s0,17,1,1|v7z1fz,17,1,1|v7z1g0,13,15,0|vfw83z,13,15,0|vfw840,17,1,1|vqp43z,17,1,1|vqp440,13,15,0|vymarz,13,15,0|vymas0,17,1,1|w9f6rz,17,1,1|w9f6s0,13,15,0|whcdfz,13,15,0|whcdg0,17,1,1|wsi83z,17,1,1|wsi840,13,15,0|x02g3z,13,15,0|x02g40,17,1,1|xb8arz,17,1,1|xb8as0,13,15,0|xisirz,13,15,0|xisis0,17,1,1|xtydfz,17,1,1|xtydg0,13,15,0|y1ilfz,13,15,0|y1ilg0,17,1,1|ycog3z,17,1,1|ycog40,13,15,0|yklmrz,13,15,0|yklms0,17,1,1|yveirz,17,1,1|yveis0,13,15,0|z3bpfz,13,15,0|z3bpg0,17,1,1|ze4lfz,17,1,1|ze4lg0,13,15,0\",\"Atlantic/Bermuda|,0,303,0|-15r0xbu,53,303,0|-rivvzv,53,303,0|-rivvzu,27,304,1|-r9qc3v,27,304,1|-r9qc3u,53,303,0|-qzp5bv,53,303,0|-qzp5bu,27,304,1|-qrq6rv,27,304,1|-qrq6ru,53,303,0|-kvj2fv,53,303,0|-kvj2fu,32,42,0|-eljwo1,32,42,0|-eljwo0,54,44,1|-e75gs1,54,44,1|-e75gs0,32,42,0|-dz87c1,32,42,0|-dz87c0,54,44,1|-dnpgs1,54,44,1|-dnpgs0,32,42,0|-dgv3c1,32,42,0|-dgv3c0,54,44,1|-d4mfg1,54,44,1|-d4mfg0,32,42,0|-cy50o1,32,42,0|-cy50o0,54,44,1|-clwcs1,54,44,1|-clwcs0,32,42,0|-bt38o1,32,42,0|-bt38o0,54,44,1|-bmyy41,54,44,1|-bmyy40,32,42,0|-ba07c1,32,42,0|-ba07c0,54,44,1|-b4lu41,54,44,1|-b4lu40,32,42,0|-ara4o1,32,42,0|-ara4o0,54,44,1|-alvrg1,54,44,1|-alvrg0,32,42,0|-a873c1,32,42,0|-a873c0,54,44,1|-a35os1,54,44,1|-a35os0,32,42,0|-9ph0o1,32,42,0|-9ph0o0,54,44,1|-9kfm41,54,44,1|-9kfm40,32,42,0|-96qy01,32,42,0|-96qy00,54,44,1|-91cks1,54,44,1|-91cks0,32,42,0|-73hoo1,32,42,0|-73hoo0,54,44,1|-6vkks1,54,44,1|-6vkks0,32,42,0|296onz,32,42,0|296oo0,54,44,1|2ijn7z,54,44,1|2ijn80,32,42,0|2rwrbz,32,42,0|2rwrc0,54,44,1|319pvz,54,44,1|319pw0,32,42,0|3amtzz,32,42,0|3amu00,54,44,1|3kcr7z,54,44,1|3kcr80,32,42,0|3tcwnz,32,42,0|3tcwo0,54,44,1|432tvz,54,44,1|432tw0,32,42,0|4cfxzz,32,42,0|4cfy00,54,44,1|4lswjz,54,44,1|4lswk0,32,42,0|4v60nz,32,42,0|4v60o0,54,44,1|54iz7z,54,44,1|54iz80,32,42,0|5dw3bz,32,42,0|5dw3c0,54,44,1|5n91vz,54,44,1|5n91w0,32,42,0|5wm5zz,32,42,0|5wm600,54,44,1|65z4jz,54,44,1|65z4k0,32,42,0|6fc8nz,32,42,0|6fc8o0,54,44,1|6p25vz,54,44,1|6p25w0,32,42,0|6y2bbz,32,42,0|6y2bc0,54,44,1|77s8jz,54,44,1|77s8k0,32,42,0|7h5cnz,32,42,0|7h5co0,54,44,1|7qib7z,54,44,1|7qib80,32,42,0|7zvfbz,32,42,0|7zvfc0,54,44,1|898dvz,54,44,1|898dw0,32,42,0|8ilhzz,32,42,0|8ili00,54,44,1|8rygjz,54,44,1|8rygk0,32,42,0|908onz,32,42,0|908oo0,54,44,1|9aoj7z,54,44,1|9aoj80,32,42,0|9iyrbz,32,42,0|9iyrc0,54,44,1|9trkjz,54,44,1|9trkk0,32,42,0|a1otzz,32,42,0|a1ou00,54,44,1|achn7z,54,44,1|achn80,32,42,0|akewnz,32,42,0|akewo0,54,44,1|av7pvz,54,44,1|av7pw0,32,42,0|b3hxzz,32,42,0|b3hy00,54,44,1|bdxsjz,54,44,1|bdxsk0,32,42,0|bm80nz,32,42,0|bm80o0,54,44,1|bwnv7z,54,44,1|bwnv80,32,42,0|c4y3bz,32,42,0|c4y3c0,54,44,1|cfqwjz,54,44,1|cfqwk0,32,42,0|cno5zz,32,42,0|cno600,54,44,1|cygz7z,54,44,1|cygz80,32,42,0|d6e8nz,32,42,0|d6e8o0,54,44,1|dh71vz,54,44,1|dh71w0,32,42,0|dph9zz,32,42,0|dpha00,54,44,1|dzx4jz,54,44,1|dzx4k0,32,42,0|e87cnz,32,42,0|e87co0,54,44,1|ein77z,54,44,1|ein780,32,42,0|eqxfbz,32,42,0|eqxfc0,54,44,1|f1d9vz,54,44,1|f1d9w0,32,42,0|f9nhzz,32,42,0|f9ni00,54,44,1|fkgb7z,54,44,1|fkgb80,32,42,0|fsdknz,32,42,0|fsdko0,54,44,1|g36dvz,54,44,1|g36dw0,32,42,0|gb3nbz,32,42,0|gb3nc0,54,44,1|glwgjz,54,44,1|glwgk0,32,42,0|gu6onz,32,42,0|gu6oo0,54,44,1|h4mj7z,54,44,1|h4mj80,32,42,0|hcwrbz,32,42,0|hcwrc0,54,44,1|hnclvz,54,44,1|hnclw0,32,42,0|hvmtzz,32,42,0|hvmu00,54,44,1|i6fn7z,54,44,1|i6fn80,32,42,0|iecwnz,32,42,0|iecwo0,54,44,1|ip5pvz,54,44,1|ip5pw0,32,42,0|ix2zbz,32,42,0|ix2zc0,54,44,1|j7vsjz,54,44,1|j7vsk0,32,42,0|jeq5zz,32,42,0|jeq600,54,44,1|jqytvz,54,44,1|jqytw0,32,42,0|jxg8nz,32,42,0|jxg8o0,54,44,1|k9owjz,54,44,1|k9owk0,32,42,0|kg6bbz,32,42,0|kg6bc0,54,44,1|ksez7z,54,44,1|ksez80,32,42,0|kz9cnz,32,42,0|kz9co0,54,44,1|lbi0jz,54,44,1|lbi0k0,32,42,0|lhzfbz,32,42,0|lhzfc0,54,44,1|lu837z,54,44,1|lu8380,32,42,0|m0phzz,32,42,0|m0pi00,54,44,1|mcy5vz,54,44,1|mcy5w0,32,42,0|mjfknz,32,42,0|mjfko0,54,44,1|mvo8jz,54,44,1|mvo8k0,32,42,0|n25nbz,32,42,0|n25nc0,54,44,1|neeb7z,54,44,1|neeb80,32,42,0|nkvpzz,32,42,0|nkvq00,54,44,1|nx4dvz,54,44,1|nx4dw0,32,42,0|o3yrbz,32,42,0|o3yrc0,54,44,1|og7f7z,54,44,1|og7f80,32,42,0|omotzz,32,42,0|omou00,54,44,1|oyxhvz,54,44,1|oyxhw0,32,42,0|p5ewnz,32,42,0|p5ewo0,54,44,1|phnkjz,54,44,1|phnkk0,32,42,0|po4zbz,32,42,0|po4zc0,54,44,1|q0dn7z,54,44,1|q0dn80,32,42,0|q6v1zz,32,42,0|q6v200,54,44,1|qj3pvz,54,44,1|qj3pw0,32,42,0|qpy3bz,32,42,0|qpy3c0,54,44,1|r26r7z,54,44,1|r26r80,32,42,0|r8o5zz,32,42,0|r8o600,54,44,1|rkwtvz,54,44,1|rkwtw0,32,42,0|rre8nz,32,42,0|rre8o0,54,44,1|s3mwjz,54,44,1|s3mwk0,32,42,0|sa4bbz,32,42,0|sa4bc0,54,44,1|smcz7z,54,44,1|smcz80,32,42,0|ssudzz,32,42,0|ssue00,54,44,1|t531vz,54,44,1|t531w0,32,42,0|tbkgnz,32,42,0|tbkgo0,54,44,1|tnt4jz,54,44,1|tnt4k0,32,42,0|tunhzz,32,42,0|tuni00,54,44,1|u6w5vz,54,44,1|u6w5w0,32,42,0|uddknz,32,42,0|uddko0,54,44,1|upm8jz,54,44,1|upm8k0,32,42,0|uw3nbz,32,42,0|uw3nc0,54,44,1|v8cb7z,54,44,1|v8cb80,32,42,0|vetpzz,32,42,0|vetq00,54,44,1|vr2dvz,54,44,1|vr2dw0,32,42,0|vxjsnz,32,42,0|vxjso0,54,44,1|w9sgjz,54,44,1|w9sgk0,32,42,0|wgmtzz,32,42,0|wgmu00,54,44,1|wsvhvz,54,44,1|wsvhw0,32,42,0|wzcwnz,32,42,0|wzcwo0,54,44,1|xblkjz,54,44,1|xblkk0,32,42,0|xi2zbz,32,42,0|xi2zc0,54,44,1|xubn7z,54,44,1|xubn80,32,42,0|y0t1zz,32,42,0|y0t200,54,44,1|yd1pvz,54,44,1|yd1pw0,32,42,0|yjj4nz,32,42,0|yjj4o0,54,44,1|yvrsjz,54,44,1|yvrsk0,32,42,0|z297bz,32,42,0|z297c0,54,44,1|zehv7z,54,44,1|zehv80,32,42,0\",\"Atlantic/Canary|,0,305,0|-oytbtc,13,15,0|-c4xh41,13,15,0|-c4xh40,8,1,0|5csqnz,8,1,0|5csqo0,9,10,1|5lsw3z,9,10,1|5lsw40,8,1,0|5v5xfz,8,1,0|5v5xg0,9,10,1|64iyrz,9,10,1|64iys0,8,1,0|6dw03z,8,1,0|6dw040,9,10,1|6n91fz,9,10,1|6n91g0,8,1,0|6wm2rz,8,1,0|6wm2s0,9,10,1|75z43z,9,10,1|75z440,8,1,0|7fc5fz,8,1,0|7fc5g0,9,10,1|7p25fz,9,10,1|7p25g0,8,1,0|7yf6rz,8,1,0|7yf6s0,9,10,1|87s83z,9,10,1|87s840,8,1,0|8h59fz,8,1,0|8h59g0,9,10,1|8qiarz,9,10,1|8qias0,8,1,0|8zvc3z,8,1,0|8zvc40,9,10,1|998dfz,9,10,1|998dg0,8,1,0|9ilerz,8,1,0|9iles0,9,10,1|9ryg3z,9,10,1|9ryg40,8,1,0|a1bhfz,8,1,0|a1bhg0,9,10,1|aaoirz,9,10,1|aaois0,8,1,0|ak1k3z,8,1,0|ak1k40,9,10,1|atrk3z,9,10,1|atrk40,8,1,0|b34lfz,8,1,0|b34lg0,9,10,1|bchmrz,9,10,1|bchms0,8,1,0|bluo3z,8,1,0|bluo40,9,10,1|bv7pfz,9,10,1|bv7pg0,8,1,0|c4kqrz,8,1,0|c4kqs0,9,10,1|cdxs3z,9,10,1|cdxs40,8,1,0|cnatfz,8,1,0|cnatg0,9,10,1|cwnurz,9,10,1|cwnus0,8,1,0|d60w3z,8,1,0|d60w40,9,10,1|dfdxfz,9,10,1|dfdxg0,8,1,0|dp3xfz,8,1,0|dp3xg0,9,10,1|dzwtfz,9,10,1|dzwtg0,8,1,0|e7u03z,8,1,0|e7u040,9,10,1|eimw3z,9,10,1|eimw40,8,1,0|eqk2rz,8,1,0|eqk2s0,9,10,1|f1cyrz,9,10,1|f1cys0,8,1,0|f9a5fz,8,1,0|f9a5g0,9,10,1|fkg03z,9,10,1|fkg040,8,1,0|fs083z,8,1,0|fs0840,9,10,1|g362rz,9,10,1|g362s0,8,1,0|gaqarz,8,1,0|gaqas0,9,10,1|glw5fz,9,10,1|glw5g0,8,1,0|gttc3z,8,1,0|gttc40,9,10,1|h4m83z,9,10,1|h4m840,8,1,0|hcjerz,8,1,0|hcjes0,9,10,1|hncarz,9,10,1|hncas0,8,1,0|hv9hfz,8,1,0|hv9hg0,9,10,1|i6fc3z,9,10,1|i6fc40,8,1,0|idzk3z,8,1,0|idzk40,9,10,1|ip5erz,9,10,1|ip5es0,8,1,0|iwpmrz,8,1,0|iwpms0,9,10,1|j7vhfz,9,10,1|j7vhg0,8,1,0|jffpfz,8,1,0|jffpg0,9,10,1|jqlk3z,9,10,1|jqlk40,8,1,0|jyiqrz,8,1,0|jyiqs0,9,10,1|k9bmrz,9,10,1|k9bms0,8,1,0|kh8tfz,8,1,0|kh8tg0,9,10,1|ks1pfz,9,10,1|ks1pg0,8,1,0|kzyw3z,8,1,0|kzyw40,9,10,1|lb4qrz,9,10,1|lb4qs0,8,1,0|lioyrz,8,1,0|lioys0,9,10,1|ltutfz,9,10,1|ltutg0,8,1,0|m1f1fz,8,1,0|m1f1g0,9,10,1|mckw3z,9,10,1|mckw40,8,1,0|mki2rz,8,1,0|mki2s0,9,10,1|mvayrz,9,10,1|mvays0,8,1,0|n385fz,8,1,0|n385g0,9,10,1|ne11fz,9,10,1|ne11g0,8,1,0|nly83z,8,1,0|nly840,9,10,1|nwr43z,9,10,1|nwr440,8,1,0|o4oarz,8,1,0|o4oas0,9,10,1|ofu5fz,9,10,1|ofu5g0,8,1,0|onedfz,8,1,0|onedg0,9,10,1|oyk83z,9,10,1|oyk840,8,1,0|p64g3z,8,1,0|p64g40,9,10,1|phaarz,9,10,1|phaas0,8,1,0|pp7hfz,8,1,0|pp7hg0,9,10,1|q00dfz,9,10,1|q00dg0,8,1,0|q7xk3z,8,1,0|q7xk40,9,10,1|qiqg3z,9,10,1|qiqg40,8,1,0|qqnmrz,8,1,0|qqnms0,9,10,1|r1thfz,9,10,1|r1thg0,8,1,0|r9dpfz,8,1,0|r9dpg0,9,10,1|rkjk3z,9,10,1|rkjk40,8,1,0|rs3s3z,8,1,0|rs3s40,9,10,1|s39mrz,9,10,1|s39ms0,8,1,0|sb6tfz,8,1,0|sb6tg0,9,10,1|slzpfz,9,10,1|slzpg0,8,1,0|stww3z,8,1,0|stww40,9,10,1|t4ps3z,9,10,1|t4ps40,8,1,0|tcmyrz,8,1,0|tcmys0,9,10,1|tnfurz,9,10,1|tnfus0,8,1,0|tvd1fz,8,1,0|tvd1g0,9,10,1|u6iw3z,9,10,1|u6iw40,8,1,0|ue343z,8,1,0|ue3440,9,10,1|up8yrz,9,10,1|up8ys0,8,1,0|uwt6rz,8,1,0|uwt6s0,9,10,1|v7z1fz,9,10,1|v7z1g0,8,1,0|vfw83z,8,1,0|vfw840,9,10,1|vqp43z,9,10,1|vqp440,8,1,0|vymarz,8,1,0|vymas0,9,10,1|w9f6rz,9,10,1|w9f6s0,8,1,0|whcdfz,8,1,0|whcdg0,9,10,1|wsi83z,9,10,1|wsi840,8,1,0|x02g3z,8,1,0|x02g40,9,10,1|xb8arz,9,10,1|xb8as0,8,1,0|xisirz,8,1,0|xisis0,9,10,1|xtydfz,9,10,1|xtydg0,8,1,0|y1ilfz,8,1,0|y1ilg0,9,10,1|ycog3z,9,10,1|ycog40,8,1,0|yklmrz,8,1,0|yklms0,9,10,1|yveirz,9,10,1|yveis0,8,1,0|z3bpfz,8,1,0|z3bpg0,9,10,1|ze4lfz,9,10,1|ze4lg0,8,1,0\",\"Atlantic/Cape_Verde|,0,306,0|-u9rbs0,40,45,0|-e9kqg1,40,45,0|-e9kqg0,13,15,1|-cmxp81,13,15,1|-cmxp80,40,45,0|32t73z,40,45,0|32t740,13,15,0\",\"Atlantic/Faroe|,0,307,0|-wcehew,8,1,0|5v5xfz,8,1,0|5v5xg0,9,10,1|64iyrz,9,10,1|64iys0,8,1,0|6dw03z,8,1,0|6dw040,9,10,1|6n91fz,9,10,1|6n91g0,8,1,0|6wm2rz,8,1,0|6wm2s0,9,10,1|75z43z,9,10,1|75z440,8,1,0|7fc5fz,8,1,0|7fc5g0,9,10,1|7p25fz,9,10,1|7p25g0,8,1,0|7yf6rz,8,1,0|7yf6s0,9,10,1|87s83z,9,10,1|87s840,8,1,0|8h59fz,8,1,0|8h59g0,9,10,1|8qiarz,9,10,1|8qias0,8,1,0|8zvc3z,8,1,0|8zvc40,9,10,1|998dfz,9,10,1|998dg0,8,1,0|9ilerz,8,1,0|9iles0,9,10,1|9ryg3z,9,10,1|9ryg40,8,1,0|a1bhfz,8,1,0|a1bhg0,9,10,1|aaoirz,9,10,1|aaois0,8,1,0|ak1k3z,8,1,0|ak1k40,9,10,1|atrk3z,9,10,1|atrk40,8,1,0|b34lfz,8,1,0|b34lg0,9,10,1|bchmrz,9,10,1|bchms0,8,1,0|bluo3z,8,1,0|bluo40,9,10,1|bv7pfz,9,10,1|bv7pg0,8,1,0|c4kqrz,8,1,0|c4kqs0,9,10,1|cdxs3z,9,10,1|cdxs40,8,1,0|cnatfz,8,1,0|cnatg0,9,10,1|cwnurz,9,10,1|cwnus0,8,1,0|d60w3z,8,1,0|d60w40,9,10,1|dfdxfz,9,10,1|dfdxg0,8,1,0|dp3xfz,8,1,0|dp3xg0,9,10,1|dzwtfz,9,10,1|dzwtg0,8,1,0|e7u03z,8,1,0|e7u040,9,10,1|eimw3z,9,10,1|eimw40,8,1,0|eqk2rz,8,1,0|eqk2s0,9,10,1|f1cyrz,9,10,1|f1cys0,8,1,0|f9a5fz,8,1,0|f9a5g0,9,10,1|fkg03z,9,10,1|fkg040,8,1,0|fs083z,8,1,0|fs0840,9,10,1|g362rz,9,10,1|g362s0,8,1,0|gaqarz,8,1,0|gaqas0,9,10,1|glw5fz,9,10,1|glw5g0,8,1,0|gttc3z,8,1,0|gttc40,9,10,1|h4m83z,9,10,1|h4m840,8,1,0|hcjerz,8,1,0|hcjes0,9,10,1|hncarz,9,10,1|hncas0,8,1,0|hv9hfz,8,1,0|hv9hg0,9,10,1|i6fc3z,9,10,1|i6fc40,8,1,0|idzk3z,8,1,0|idzk40,9,10,1|ip5erz,9,10,1|ip5es0,8,1,0|iwpmrz,8,1,0|iwpms0,9,10,1|j7vhfz,9,10,1|j7vhg0,8,1,0|jffpfz,8,1,0|jffpg0,9,10,1|jqlk3z,9,10,1|jqlk40,8,1,0|jyiqrz,8,1,0|jyiqs0,9,10,1|k9bmrz,9,10,1|k9bms0,8,1,0|kh8tfz,8,1,0|kh8tg0,9,10,1|ks1pfz,9,10,1|ks1pg0,8,1,0|kzyw3z,8,1,0|kzyw40,9,10,1|lb4qrz,9,10,1|lb4qs0,8,1,0|lioyrz,8,1,0|lioys0,9,10,1|ltutfz,9,10,1|ltutg0,8,1,0|m1f1fz,8,1,0|m1f1g0,9,10,1|mckw3z,9,10,1|mckw40,8,1,0|mki2rz,8,1,0|mki2s0,9,10,1|mvayrz,9,10,1|mvays0,8,1,0|n385fz,8,1,0|n385g0,9,10,1|ne11fz,9,10,1|ne11g0,8,1,0|nly83z,8,1,0|nly840,9,10,1|nwr43z,9,10,1|nwr440,8,1,0|o4oarz,8,1,0|o4oas0,9,10,1|ofu5fz,9,10,1|ofu5g0,8,1,0|onedfz,8,1,0|onedg0,9,10,1|oyk83z,9,10,1|oyk840,8,1,0|p64g3z,8,1,0|p64g40,9,10,1|phaarz,9,10,1|phaas0,8,1,0|pp7hfz,8,1,0|pp7hg0,9,10,1|q00dfz,9,10,1|q00dg0,8,1,0|q7xk3z,8,1,0|q7xk40,9,10,1|qiqg3z,9,10,1|qiqg40,8,1,0|qqnmrz,8,1,0|qqnms0,9,10,1|r1thfz,9,10,1|r1thg0,8,1,0|r9dpfz,8,1,0|r9dpg0,9,10,1|rkjk3z,9,10,1|rkjk40,8,1,0|rs3s3z,8,1,0|rs3s40,9,10,1|s39mrz,9,10,1|s39ms0,8,1,0|sb6tfz,8,1,0|sb6tg0,9,10,1|slzpfz,9,10,1|slzpg0,8,1,0|stww3z,8,1,0|stww40,9,10,1|t4ps3z,9,10,1|t4ps40,8,1,0|tcmyrz,8,1,0|tcmys0,9,10,1|tnfurz,9,10,1|tnfus0,8,1,0|tvd1fz,8,1,0|tvd1g0,9,10,1|u6iw3z,9,10,1|u6iw40,8,1,0|ue343z,8,1,0|ue3440,9,10,1|up8yrz,9,10,1|up8ys0,8,1,0|uwt6rz,8,1,0|uwt6s0,9,10,1|v7z1fz,9,10,1|v7z1g0,8,1,0|vfw83z,8,1,0|vfw840,9,10,1|vqp43z,9,10,1|vqp440,8,1,0|vymarz,8,1,0|vymas0,9,10,1|w9f6rz,9,10,1|w9f6s0,8,1,0|whcdfz,8,1,0|whcdg0,9,10,1|wsi83z,9,10,1|wsi840,8,1,0|x02g3z,8,1,0|x02g40,9,10,1|xb8arz,9,10,1|xb8as0,8,1,0|xisirz,8,1,0|xisis0,9,10,1|xtydfz,9,10,1|xtydg0,8,1,0|y1ilfz,8,1,0|y1ilg0,9,10,1|ycog3z,9,10,1|ycog40,8,1,0|yklmrz,8,1,0|yklms0,9,10,1|yveirz,9,10,1|yveis0,8,1,0|z3bpfz,8,1,0|z3bpg0,9,10,1|ze4lfz,9,10,1|ze4lg0,8,1,0\",\"Atlantic/Madeira|,0,308,0|-18vsfjc,137,308,0|-u9rek1,137,308,0|-u9rek0,13,15,0|-rxwyo1,13,15,0|-rxwyo0,17,1,1|-rqx181,17,1,1|-rqx180,13,15,0|-rkqw01,13,15,0|-rkqw00,17,1,1|-r90o01,17,1,1|-r90o00,13,15,0|-r1x401,13,15,0|-r1x400,17,1,1|-qq8qo1,17,1,1|-qq8qo0,13,15,0|-qj71c1,13,15,0|-qj71c0,17,1,1|-q7gtc1,17,1,1|-q7gtc0,13,15,0|-q0d9c1,13,15,0|-q0d9c0,17,1,1|-pon1c1,17,1,1|-pon1c0,13,15,0|-phlc01,13,15,0|-phlc00,17,1,1|-p5v401,17,1,1|-p5v400,13,15,0|-nuso01,13,15,0|-nuso00,17,1,1|-nlhhc1,17,1,1|-nlhhc0,13,15,0|-mt6yo1,13,15,0|-mt6yo0,17,1,1|-mkjuo1,17,1,1|-mkjuo0,13,15,0|-matuo1,13,15,0|-matuo0,17,1,1|-m1ts01,17,1,1|-m1ts00,13,15,0|-lrqtc1,13,15,0|-lrqtc0,17,1,1|-liqqo1,17,1,1|-liqqo0,13,15,0|-l8ns01,13,15,0|-l8ns00,17,1,1|-l00o01,17,1,1|-l00o00,13,15,0|-k77mo1,13,15,0|-k77mo0,17,1,1|-jykio1,17,1,1|-jykio0,13,15,0|-jp7hc1,13,15,0|-jp7hc0,17,1,1|-jfug01,17,1,1|-jfug00,13,15,0|-inedc1,13,15,0|-inedc0,17,1,1|-ie1c01,17,1,1|-ie1c00,13,15,0|-i519c1,13,15,0|-i519c0,17,1,1|-hvb9c1,17,1,1|-hvb9c0,13,15,0|-hl8ao1,13,15,0|-hl8ao0,17,1,1|-hcl6o1,17,1,1|-hcl6o0,13,15,0|-h385c1,13,15,0|-h385c0,17,1,1|-gtv401,17,1,1|-gtv400,13,15,0|-gkv1c1,13,15,0|-gkv1c0,17,1,1|-gb51c1,17,1,1|-gb51c0,13,15,0|-g122o1,13,15,0|-g122o0,17,1,1|-fpw801,17,1,1|-fpw800,13,15,0|-fkuqo1,13,15,0|-fkuqo0,17,1,1|-f9bxc1,17,1,1|-f9bxc0,13,15,0|-ezyw01,13,15,0|-ezyw00,17,1,1|-eqk001,17,1,1|-eqk000,13,15,0|-eibpc1,13,15,0|-eibpc0,17,1,1|-eg6041,17,1,1|-eg6040,18,10,1|-eaelg1,18,10,1|-eaelg0,17,1,1|-e6sw01,17,1,1|-e6sw00,13,15,0|-dzlmo1,13,15,0|-dzlmo0,17,1,1|-dxsw41,17,1,1|-dxsw40,18,10,1|-dqylg1,18,10,1|-dqylg0,17,1,1|-dnpuo1,17,1,1|-dnpuo0,13,15,0|-dgvk01,13,15,0|-dgvk00,17,1,1|-depus1,17,1,1|-depus0,18,10,1|-d88is1,18,10,1|-d88is0,17,1,1|-d4zs01,17,1,1|-d4zs00,13,15,0|-cy5hc1,13,15,0|-cy5hc0,17,1,1|-cvzs41,17,1,1|-cvzs40,18,10,1|-cpig41,18,10,1|-cpig40,17,1,1|-cm9pc1,17,1,1|-cm9pc0,13,15,0|-cdzk01,13,15,0|-cdzk00,17,1,1|-c4mio1,17,1,1|-c4mio0,13,15,0|-bv9901,13,15,0|-bv9900,17,1,1|-blw7o1,17,1,1|-blw7o0,13,15,0|-bcj6c1,13,15,0|-bcj6c0,17,1,1|-b36501,17,1,1|-b36500,13,15,0|-att3o1,13,15,0|-att3o0,17,1,1|-akg2c1,17,1,1|-akg2c0,13,15,0|-9scyc1,13,15,0|-9scyc0,17,1,1|-9imyc1,17,1,1|-9imyc0,13,15,0|-999x01,13,15,0|-999x00,17,1,1|-8zwvo1,17,1,1|-8zwvo0,13,15,0|-8qjuc1,13,15,0|-8qjuc0,17,1,1|-8h6t01,17,1,1|-8h6t00,13,15,0|-87tro1,13,15,0|-87tro0,17,1,1|-7ygqc1,17,1,1|-7ygqc0,13,15,0|-7p3p01,13,15,0|-7p3p00,17,1,1|-7fqno1,17,1,1|-7fqno0,13,15,0|-76dmc1,13,15,0|-76dmc0,17,1,1|-6wnmc1,17,1,1|-6wnmc0,13,15,0|-6nal01,13,15,0|-6nal00,17,1,1|-6dxjo1,17,1,1|-6dxjo0,13,15,0|-64kic1,13,15,0|-64kic0,17,1,1|-5v7h01,17,1,1|-5v7h00,13,15,0|-5lufo1,13,15,0|-5lufo0,17,1,1|-5chec1,17,1,1|-5chec0,13,15,0|-534d01,13,15,0|-534d00,17,1,1|-4trbo1,17,1,1|-4trbo0,13,15,0|-4keac1,13,15,0|-4keac0,17,1,1|-4b1901,17,1,1|-4b1900,13,15,0|-41o7o1,13,15,0|-41o7o0,17,1,1|-3ry7o1,17,1,1|-3ry7o0,13,15,0|-3il6c1,13,15,0|-3il6c0,17,1,1|-398501,17,1,1|-398500,13,15,0|-2zv3o1,13,15,0|-2zv3o0,17,1,1|-2qi2c1,17,1,1|-2qi2c0,13,15,0|-2h5101,13,15,0|-2h5100,17,1,1|-27rzo1,17,1,1|-27rzo0,13,15,0|-1yeyc1,13,15,0|-1yeyc0,8,1,0|3rwlbz,8,1,0|3rwlc0,9,10,1|419mnz,9,10,1|419mo0,8,1,0|4azmnz,8,1,0|4azmo0,9,10,1|4kcnzz,9,10,1|4kco00,8,1,0|4tppbz,8,1,0|4tppc0,9,10,1|532tfz,9,10,1|532tg0,8,1,0|5cfrzz,8,1,0|5cfs00,9,10,1|5lsw3z,9,10,1|5lsw40,8,1,0|5v5xfz,8,1,0|5v5xg0,9,10,1|64iyrz,9,10,1|64iys0,8,1,0|6dw03z,8,1,0|6dw040,9,10,1|6n91fz,9,10,1|6n91g0,8,1,0|6wm5jz,8,1,0|6wm5k0,9,10,1|75z43z,9,10,1|75z440,8,1,0|7fc5fz,8,1,0|7fc5g0,9,10,1|7p25fz,9,10,1|7p25g0,8,1,0|7yf6rz,8,1,0|7yf6s0,9,10,1|87s83z,9,10,1|87s840,8,1,0|8h59fz,8,1,0|8h59g0,9,10,1|8qiarz,9,10,1|8qias0,8,1,0|8zvc3z,8,1,0|8zvc40,9,10,1|998dfz,9,10,1|998dg0,8,1,0|9ilerz,8,1,0|9iles0,9,10,1|9ryg3z,9,10,1|9ryg40,8,1,0|a1bhfz,8,1,0|a1bhg0,9,10,1|aaoirz,9,10,1|aaois0,8,1,0|ak1k3z,8,1,0|ak1k40,9,10,1|atrk3z,9,10,1|atrk40,8,1,0|b34lfz,8,1,0|b34lg0,9,10,1|bchmrz,9,10,1|bchms0,8,1,0|bluo3z,8,1,0|bluo40,9,10,1|bv7pfz,9,10,1|bv7pg0,8,1,0|c4kqrz,8,1,0|c4kqs0,9,10,1|cdxs3z,9,10,1|cdxs40,8,1,0|cnatfz,8,1,0|cnatg0,9,10,1|cwnurz,9,10,1|cwnus0,8,1,0|d60w3z,8,1,0|d60w40,9,10,1|dfdxfz,9,10,1|dfdxg0,8,1,0|dp3xfz,8,1,0|dp3xg0,9,10,1|dzwtfz,9,10,1|dzwtg0,8,1,0|e7u03z,8,1,0|e7u040,9,10,1|eimw3z,9,10,1|eimw40,8,1,0|eqk2rz,8,1,0|eqk2s0,9,10,1|f1cyrz,9,10,1|f1cys0,8,1,0|f9a5fz,8,1,0|f9a5g0,9,10,1|fkg03z,9,10,1|fkg040,8,1,0|fs083z,8,1,0|fs0840,9,10,1|g362rz,9,10,1|g362s0,8,1,0|gaqarz,8,1,0|gaqas0,9,10,1|glw5fz,9,10,1|glw5g0,8,1,0|gttc3z,8,1,0|gttc40,9,10,1|h4m83z,9,10,1|h4m840,8,1,0|hcjerz,8,1,0|hcjes0,9,10,1|hncarz,9,10,1|hncas0,8,1,0|hv9hfz,8,1,0|hv9hg0,9,10,1|i6fc3z,9,10,1|i6fc40,8,1,0|idzk3z,8,1,0|idzk40,9,10,1|ip5erz,9,10,1|ip5es0,8,1,0|iwpmrz,8,1,0|iwpms0,9,10,1|j7vhfz,9,10,1|j7vhg0,8,1,0|jffpfz,8,1,0|jffpg0,9,10,1|jqlk3z,9,10,1|jqlk40,8,1,0|jyiqrz,8,1,0|jyiqs0,9,10,1|k9bmrz,9,10,1|k9bms0,8,1,0|kh8tfz,8,1,0|kh8tg0,9,10,1|ks1pfz,9,10,1|ks1pg0,8,1,0|kzyw3z,8,1,0|kzyw40,9,10,1|lb4qrz,9,10,1|lb4qs0,8,1,0|lioyrz,8,1,0|lioys0,9,10,1|ltutfz,9,10,1|ltutg0,8,1,0|m1f1fz,8,1,0|m1f1g0,9,10,1|mckw3z,9,10,1|mckw40,8,1,0|mki2rz,8,1,0|mki2s0,9,10,1|mvayrz,9,10,1|mvays0,8,1,0|n385fz,8,1,0|n385g0,9,10,1|ne11fz,9,10,1|ne11g0,8,1,0|nly83z,8,1,0|nly840,9,10,1|nwr43z,9,10,1|nwr440,8,1,0|o4oarz,8,1,0|o4oas0,9,10,1|ofu5fz,9,10,1|ofu5g0,8,1,0|onedfz,8,1,0|onedg0,9,10,1|oyk83z,9,10,1|oyk840,8,1,0|p64g3z,8,1,0|p64g40,9,10,1|phaarz,9,10,1|phaas0,8,1,0|pp7hfz,8,1,0|pp7hg0,9,10,1|q00dfz,9,10,1|q00dg0,8,1,0|q7xk3z,8,1,0|q7xk40,9,10,1|qiqg3z,9,10,1|qiqg40,8,1,0|qqnmrz,8,1,0|qqnms0,9,10,1|r1thfz,9,10,1|r1thg0,8,1,0|r9dpfz,8,1,0|r9dpg0,9,10,1|rkjk3z,9,10,1|rkjk40,8,1,0|rs3s3z,8,1,0|rs3s40,9,10,1|s39mrz,9,10,1|s39ms0,8,1,0|sb6tfz,8,1,0|sb6tg0,9,10,1|slzpfz,9,10,1|slzpg0,8,1,0|stww3z,8,1,0|stww40,9,10,1|t4ps3z,9,10,1|t4ps40,8,1,0|tcmyrz,8,1,0|tcmys0,9,10,1|tnfurz,9,10,1|tnfus0,8,1,0|tvd1fz,8,1,0|tvd1g0,9,10,1|u6iw3z,9,10,1|u6iw40,8,1,0|ue343z,8,1,0|ue3440,9,10,1|up8yrz,9,10,1|up8ys0,8,1,0|uwt6rz,8,1,0|uwt6s0,9,10,1|v7z1fz,9,10,1|v7z1g0,8,1,0|vfw83z,8,1,0|vfw840,9,10,1|vqp43z,9,10,1|vqp440,8,1,0|vymarz,8,1,0|vymas0,9,10,1|w9f6rz,9,10,1|w9f6s0,8,1,0|whcdfz,8,1,0|whcdg0,9,10,1|wsi83z,9,10,1|wsi840,8,1,0|x02g3z,8,1,0|x02g40,9,10,1|xb8arz,9,10,1|xb8as0,8,1,0|xisirz,8,1,0|xisis0,9,10,1|xtydfz,9,10,1|xtydg0,8,1,0|y1ilfz,8,1,0|y1ilg0,9,10,1|ycog3z,9,10,1|ycog40,8,1,0|yklmrz,8,1,0|yklms0,9,10,1|yveirz,9,10,1|yveis0,8,1,0|z3bpfz,8,1,0|z3bpg0,9,10,1|ze4lfz,9,10,1|ze4lg0,8,1,0\",\"Atlantic/Reykjavik|,0,309,0|-wcwx9c,13,15,0|-rl7k01,13,15,0|-rl7k00,17,1,1|-r8ph81,17,1,1|-r8ph80,13,15,0|-r2fmo1,13,15,0|-r2fmo0,17,1,1|-qolek1,17,1,1|-qolek0,13,15,0|-qjnpc1,13,15,0|-qjnpc0,17,1,1|-q5th81,17,1,1|-q5th80,13,15,0|-pgm5c1,13,15,0|-pgm5c0,17,1,1|-pbq581,17,1,1|-pbq580,13,15,0|-g0c5c1,13,15,0|-g0c5c0,17,1,1|-fqyyg1,17,1,1|-fqyyg0,13,15,0|-fkuic1,13,15,0|-fkuic0,17,1,1|-f7vx41,17,1,1|-f7vx40,13,15,0|-f1rjs1,13,15,0|-f1rjs0,17,1,1|-ep5ug1,17,1,1|-ep5ug0,13,15,0|-eioig1,13,15,0|-eioig0,17,1,1|-e6sqg1,17,1,1|-e6sqg0,13,15,0|-dzyfs1,13,15,0|-dzyfs0,17,1,1|-do2ns1,17,1,1|-do2ns0,13,15,0|-dh8d41,13,15,0|-dh8d40,17,1,1|-d5cl41,17,1,1|-d5cl40,13,15,0|-cyiag1,13,15,0|-cyiag0,17,1,1|-cm9js1,17,1,1|-cm9js0,13,15,0|-cfs7s1,13,15,0|-cfs7s0,17,1,1|-c3jh41,17,1,1|-c3jh40,13,15,0|-bv9bs1,13,15,0|-bv9bs0,17,1,1|-bkteg1,17,1,1|-bkteg0,13,15,0|-bcj941,13,15,0|-bcj940,17,1,1|-b23bs1,17,1,1|-b23bs0,13,15,0|-att6g1,13,15,0|-att6g0,17,1,1|-aj0ag1,17,1,1|-aj0ag0,13,15,0|-ab33s1,13,15,0|-ab33s0,17,1,1|-a0n6g1,17,1,1|-a0n6g0,13,15,0|-9sd141,13,15,0|-9sd140,17,1,1|-9hk541,17,1,1|-9hk540,13,15,0|-999zs1,13,15,0|-999zs0,17,1,1|-8yu2g1,17,1,1|-8yu2g0,13,15,0|-8qjx41,13,15,0|-8qjx40,17,1,1|-8g3zs1,17,1,1|-8g3zs0,13,15,0|-87tug1,13,15,0|-87tug0,17,1,1|-7xdx41,17,1,1|-7xdx40,13,15,0|-7p3rs1,13,15,0|-7p3rs0,17,1,1|-7enug1,17,1,1|-7enug0,13,15,0|-76dp41,13,15,0|-76dp40,17,1,1|-6vkt41,17,1,1|-6vkt40,13,15,0|-6nans1,13,15,0|-6nans0,17,1,1|-6cuqg1,17,1,1|-6cuqg0,13,15,0|-64kl41,13,15,0|-64kl40,17,1,1|-5u4ns1,17,1,1|-5u4ns0,13,15,0|-5luig1,13,15,0|-5luig0,17,1,1|-5bel41,17,1,1|-5bel40,13,15,0|-534fs1,13,15,0|-534fs0,17,1,1|-4soig1,17,1,1|-4soig0,13,15,0|-4ked41,13,15,0|-4ked40,17,1,1|-49yfs1,17,1,1|-49yfs0,13,15,0|-41oag1,13,15,0|-41oag0,17,1,1|-3qveg1,17,1,1|-3qveg0,13,15,0|-3il941,13,15,0|-3il940,17,1,1|-385bs1,17,1,1|-385bs0,13,15,0|-2zv6g1,13,15,0|-2zv6g0,17,1,1|-2pf941,17,1,1|-2pf940,13,15,0|-2h53s1,13,15,0|-2h53s0,17,1,1|-26p6g1,17,1,1|-26p6g0,13,15,0|-1yf141,13,15,0|-1yf140,17,1,1|-1nz3s1,17,1,1|-1nz3s0,13,15,0|-1foyg1,13,15,0|-1foyg0,17,1,1|-14w2g1,17,1,1|-14w2g0,13,15,0|-wlx41,13,15,0|-wlx40,1,1,0\",\"Atlantic/South_Georgia|,0,310,0|-15r12kg,40,45,0\",\"Atlantic/St_Helena|,0,12,0|-u9rgl4,1,1,0\",\"Atlantic/Stanley|,0,311,0|-15r0ymc,85,311,0|-u63pad,85,311,0|-u63pac,42,42,0|-gu7rk1,42,42,0|-gu7rk0,39,44,1|-gl7ro1,39,44,1|-gl7ro0,42,42,0|-gbhow1,42,42,0|-gbhow0,39,44,1|-g2hp01,39,44,1|-g2hp00,42,42,0|-fsenk1,42,42,0|-fsenk0,39,44,1|-fjeno1,39,44,1|-fjeno0,42,42,0|-f9okw1,42,42,0|-f9okw0,39,44,1|-f0ol01,39,44,1|-f0ol00,42,42,0|-eqyi81,42,42,0|-eqyi80,39,44,1|-ehyic1,39,44,1|-ehyic0,42,42,0|-e88fk1,42,42,0|-e88fk0,39,44,1|-e3aqc1,39,44,1|-e3aqc0,42,42,0|6yf4fz,42,42,0|6yf4g0,39,44,0|75z9nz,39,44,0|75z9o0,40,45,1|7h51jz,40,45,1|7h51k0,39,44,0|7ocdnz,39,44,0|7ocdo0,40,45,1|7zv47z,40,45,1|7zv480,39,44,0|872gbz,39,44,0|872gc0,39,44,1|8i8azz,39,44,1|8i8b00,42,42,0|8pslrz,42,42,0|8psls0,39,44,1|90ydnz,39,44,1|90ydo0,42,42,0|98iofz,42,42,0|98iog0,39,44,1|9jogbz,39,44,1|9jogc0,42,42,0|9r8r3z,42,42,0|9r8r40,39,44,1|a2eizz,39,44,1|a2ej00,42,42,0|a9ytrz,42,42,0|a9yts0,39,44,1|alhkbz,39,44,1|alhkc0,42,42,0|asowfz,42,42,0|asowg0,39,44,1|b47mzz,39,44,1|b47n00,42,42,0|bbrxrz,42,42,0|bbrxs0,39,44,1|bmxpnz,39,44,1|bmxpo0,42,42,0|bui0fz,42,42,0|bui0g0,39,44,1|c5nsbz,39,44,1|c5nsc0,42,42,0|cd833z,42,42,0|cd8340,39,44,1|coduzz,39,44,1|codv00,42,42,0|cvy5rz,42,42,0|cvy5s0,39,44,1|d73xnz,39,44,1|d73xo0,42,42,0|deo8fz,42,42,0|deo8g0,39,44,1|dq6yzz,39,44,1|dq6z00,42,42,0|dxr9rz,42,42,0|dxr9s0,39,44,1|e8x1nz,39,44,1|e8x1o0,42,42,0|eghcfz,42,42,0|eghcg0,39,44,1|ern4bz,39,44,1|ern4c0,42,42,0|ez7f3z,42,42,0|ez7f40,39,44,1|fad6zz,39,44,1|fad700,42,42,0|fhxhrz,42,42,0|fhxhs0,39,44,1|ft39nz,39,44,1|ft39o0,42,42,0|g0nkfz,42,42,0|g0nkg0,39,44,1|gbthvz,39,44,1|gbthw0,42,42,0|gj0tzz,42,42,0|gj0u00,39,44,1|guwj7z,39,44,1|guwj80,42,42,0|h1qwnz,42,42,0|h1qwo0,39,44,1|hdmlvz,39,44,1|hdmlw0,42,42,0|hktxzz,42,42,0|hkty00,39,44,1|hwcojz,39,44,1|hwcok0,42,42,0|i3k0nz,42,42,0|i3k0o0,39,44,1|if2r7z,39,44,1|if2r80,42,42,0|ima3bz,42,42,0|ima3c0,39,44,1|ixstvz,39,44,1|ixstw0,42,42,0|j505zz,42,42,0|j50600,39,44,1|jgiwjz,39,44,1|jgiwk0,42,42,0|jnq8nz,42,42,0|jnq8o0,39,44,1|jzlxvz,39,44,1|jzlxw0,42,42,0|k6t9zz,42,42,0|k6ta00,39,44,1|kic0jz,39,44,1|kic0k0,42,42,0|kpjcnz,42,42,0|kpjco0,39,44,1|l1237z,39,44,1|l12380,42,42,0|l89fbz,42,42,0|l89fc0,39,44,0\",\"Australia/Adelaide|,0,312,0|-133j2zw,138,224,0|-10vsp01,138,224,0|-10vsp00,138,248,0|-rnsq61,138,248,0|-rnsq60,139,313,1|-rjj0u1,139,313,1|-rjj0u0,138,248,0|-em3gu1,138,248,0|-em3gu0,139,313,1|-ehmcu1,139,313,1|-ehmcu0,138,248,0|-e89bi1,138,248,0|-e89bi0,139,313,1|-dywa61,139,313,1|-dywa60,138,248,0|-dp6a61,138,248,0|-dp6a60,139,313,1|-dg67i1,139,313,1|-dg67i0,138,248,0|ycghz,138,248,0|ycgi0,139,313,1|14gttz,139,313,1|14gtu0,138,248,0|1h2j5z,138,248,0|1h2j60,139,313,1|1njv5z,139,313,1|1njv60,138,248,0|1zsltz,138,248,0|1zslu0,139,313,1|269xtz,139,313,1|269xu0,138,248,0|2iiohz,138,248,0|2iioi0,139,313,1|2p00hz,139,313,1|2p00i0,138,248,0|318r5z,138,248,0|318r60,139,313,1|3831tz,139,313,1|3831u0,138,248,0|3kbshz,138,248,0|3kbsi0,139,313,1|3qt4hz,139,313,1|3qt4i0,138,248,0|431v5z,138,248,0|431v60,139,313,1|49j75z,139,313,1|49j760,138,248,0|4lrxtz,138,248,0|4lrxu0,139,313,1|4s99tz,139,313,1|4s99u0,138,248,0|54i0hz,138,248,0|54i0i0,139,313,1|5azchz,139,313,1|5azci0,138,248,0|5n835z,138,248,0|5n8360,139,313,1|5tpf5z,139,313,1|5tpf60,138,248,0|65y5tz,138,248,0|65y5u0,139,313,1|6csghz,139,313,1|6csgi0,138,248,0|6p175z,138,248,0|6p1760,139,313,1|6vij5z,139,313,1|6vij60,138,248,0|77r9tz,138,248,0|77r9u0,139,313,1|7e8ltz,139,313,1|7e8lu0,138,248,0|7qhchz,138,248,0|7qhci0,139,313,1|7wyohz,139,313,1|7wyoi0,138,248,0|897f5z,138,248,0|897f60,139,313,1|8geohz,139,313,1|8geoi0,138,248,0|8rkj5z,138,248,0|8rkj60,139,313,1|8z4r5z,139,313,1|8z4r60,138,248,0|9ankhz,138,248,0|9anki0,139,313,1|9i7shz,139,313,1|9i7si0,138,248,0|9tqltz,138,248,0|9tqlu0,139,313,1|a0xv5z,139,313,1|a0xv60,138,248,0|acgohz,138,248,0|acgoi0,139,313,1|ajnxtz,139,313,1|ajnxu0,138,248,0|av6r5z,138,248,0|av6r60,139,313,1|b1o35z,139,313,1|b1o360,138,248,0|bdwttz,138,248,0|bdwtu0,139,313,1|blh1tz,139,313,1|blh1u0,138,248,0|bwmwhz,138,248,0|bwmwi0,139,313,1|c3h75z,139,313,1|c3h760,138,248,0|cfpxtz,138,248,0|cfpxu0,139,313,1|cmx75z,139,313,1|cmx760,138,248,0|cyg0hz,138,248,0|cyg0i0,139,313,1|d608hz,139,313,1|d608i0,138,248,0|dh635z,138,248,0|dh6360,139,313,1|dp39tz,139,313,1|dp39u0,138,248,0|dzw5tz,138,248,0|dzw5u0,139,313,1|e7tchz,139,313,1|e7tci0,138,248,0|eim8hz,138,248,0|eim8i0,139,313,1|eqjf5z,139,313,1|eqjf60,138,248,0|f1cb5z,138,248,0|f1cb60,139,313,1|f99htz,139,313,1|f99hu0,138,248,0|fkfchz,138,248,0|fkfci0,139,313,1|frzkhz,139,313,1|frzki0,138,248,0|g35f5z,138,248,0|g35f60,139,313,1|gapn5z,139,313,1|gapn60,138,248,0|glvhtz,138,248,0|glvhu0,139,313,1|gtsohz,139,313,1|gtsoi0,138,248,0|h4lkhz,138,248,0|h4lki0,139,313,1|hcir5z,139,313,1|hcir60,138,248,0|hnbn5z,138,248,0|hnbn60,139,313,1|hv8ttz,139,313,1|hv8tu0,138,248,0|i6eohz,138,248,0|i6eoi0,139,313,1|idywhz,139,313,1|idywi0,138,248,0|ip4r5z,138,248,0|ip4r60,139,313,1|ix1xtz,139,313,1|ix1xu0,138,248,0|j7uttz,138,248,0|j7utu0,139,313,1|jff1tz,139,313,1|jff1u0,138,248,0|jqkwhz,138,248,0|jqkwi0,139,313,1|jyv1tz,139,313,1|jyv1u0,138,248,0|k8835z,138,248,0|k88360,139,313,1|khl4hz,139,313,1|khl4i0,138,248,0|kqy5tz,138,248,0|kqy5u0,139,313,1|l0b75z,139,313,1|l0b760,138,248,0|l9o8hz,138,248,0|l9o8i0,139,313,1|lj19tz,139,313,1|lj19u0,138,248,0|lseb5z,138,248,0|lseb60,139,313,1|m1rchz,139,313,1|m1rci0,138,248,0|mbhchz,138,248,0|mbhci0,139,313,1|mkudtz,139,313,1|mkudu0,138,248,0|mu7f5z,138,248,0|mu7f60,139,313,1|n3kghz,139,313,1|n3kgi0,138,248,0|ncxhtz,138,248,0|ncxhu0,139,313,1|nmaj5z,139,313,1|nmaj60,138,248,0|nvnkhz,138,248,0|nvnki0,139,313,1|o50ltz,139,313,1|o50lu0,138,248,0|oedn5z,138,248,0|oedn60,139,313,1|onqohz,139,313,1|onqoi0,138,248,0|ox3ptz,138,248,0|ox3pu0,139,313,1|p6gr5z,139,313,1|p6gr60,138,248,0|pg6r5z,138,248,0|pg6r60,139,313,1|ppjshz,139,313,1|ppjsi0,138,248,0|pywttz,138,248,0|pywtu0,139,313,1|q89v5z,139,313,1|q89v60,138,248,0|qhmwhz,138,248,0|qhmwi0,139,313,1|qqzxtz,139,313,1|qqzxu0,138,248,0|r0cz5z,138,248,0|r0cz60,139,313,1|r9q0hz,139,313,1|r9q0i0,138,248,0|rj31tz,138,248,0|rj31u0,139,313,1|rsg35z,139,313,1|rsg360,138,248,0|s1t4hz,138,248,0|s1t4i0,139,313,1|sbj4hz,139,313,1|sbj4i0,138,248,0|skw5tz,138,248,0|skw5u0,139,313,1|su975z,139,313,1|su9760,138,248,0|t3m8hz,138,248,0|t3m8i0,139,313,1|tcz9tz,139,313,1|tcz9u0,138,248,0|tmcb5z,138,248,0|tmcb60,139,313,1|tvpchz,139,313,1|tvpci0,138,248,0|u52dtz,138,248,0|u52du0,139,313,1|ueff5z,139,313,1|ueff60,138,248,0|unsghz,138,248,0|unsgi0,139,313,1|ux5htz,139,313,1|ux5hu0,138,248,0|v6vhtz,138,248,0|v6vhu0,139,313,1|vg8j5z,139,313,1|vg8j60,138,248,0|vplkhz,138,248,0|vplki0,139,313,1|vyyltz,139,313,1|vyylu0,138,248,0|w8bn5z,138,248,0|w8bn60,139,313,1|whoohz,139,313,1|whooi0,138,248,0|wr1ptz,138,248,0|wr1pu0,139,313,1|x0er5z,139,313,1|x0er60,138,248,0|x9rshz,138,248,0|x9rsi0,139,313,1|xj4ttz,139,313,1|xj4tu0,138,248,0|xshv5z,138,248,0|xshv60,139,313,1|y1uwhz,139,313,1|y1uwi0,138,248,0|ybkwhz,138,248,0|ybkwi0,139,313,1|ykxxtz,139,313,1|ykxxu0,138,248,0|yuaz5z,138,248,0|yuaz60,139,313,1|z3o0hz,139,313,1|z3o0i0,138,248,0|zd11tz,138,248,0|zd11u0,139,313,1\",\"Australia/Brisbane|,0,314,0|-1354kc8,94,195,0|-rnsrk1,94,195,0|-rnsrk0,95,192,1|-rjj281,95,192,1|-rjj280,94,195,0|-em3i81,94,195,0|-em3i80,95,192,1|-ehme81,95,192,1|-ehme80,94,195,0|-e89cw1,94,195,0|-e89cw0,95,192,1|-dywbk1,95,192,1|-dywbk0,94,195,0|-dp6bk1,94,195,0|-dp6bk0,95,192,1|-dg68w1,95,192,1|-dg68w0,94,195,0|ycf3z,94,195,0|ycf40,95,192,1|14gsfz,95,192,1|14gsg0,94,195,0|acgn3z,94,195,0|acgn40,95,192,1|aixz3z,95,192,1|aixz40,94,195,0|av6prz,94,195,0|av6ps0,95,192,1|b1o1rz,95,192,1|b1o1s0,94,195,0|bdwsfz,94,195,0|bdwsg0,95,192,1|bke4fz,95,192,1|bke4g0,94,195,0\",\"Australia/Broken_Hill|,0,315,0|-133j3j0,94,195,0|-12a9fs1,94,195,0|-12a9fs0,138,224,0|-10vsp01,138,224,0|-10vsp00,138,248,0|-rnsq61,138,248,0|-rnsq60,139,313,1|-rjj0u1,139,313,1|-rjj0u0,138,248,0|-em3gu1,138,248,0|-em3gu0,139,313,1|-ehmcu1,139,313,1|-ehmcu0,138,248,0|-e89bi1,138,248,0|-e89bi0,139,313,1|-dywa61,139,313,1|-dywa60,138,248,0|-dp6a61,138,248,0|-dp6a60,139,313,1|-dg67i1,139,313,1|-dg67i0,138,248,0|ycghz,138,248,0|ycgi0,139,313,1|14gttz,139,313,1|14gtu0,138,248,0|1h2j5z,138,248,0|1h2j60,139,313,1|1njv5z,139,313,1|1njv60,138,248,0|1zsltz,138,248,0|1zslu0,139,313,1|269xtz,139,313,1|269xu0,138,248,0|2iiohz,138,248,0|2iioi0,139,313,1|2p00hz,139,313,1|2p00i0,138,248,0|318r5z,138,248,0|318r60,139,313,1|3831tz,139,313,1|3831u0,138,248,0|3kbshz,138,248,0|3kbsi0,139,313,1|3qt4hz,139,313,1|3qt4i0,138,248,0|431v5z,138,248,0|431v60,139,313,1|49j75z,139,313,1|49j760,138,248,0|4lrxtz,138,248,0|4lrxu0,139,313,1|4s99tz,139,313,1|4s99u0,138,248,0|54i0hz,138,248,0|54i0i0,139,313,1|5azchz,139,313,1|5azci0,138,248,0|5n835z,138,248,0|5n8360,139,313,1|5tpf5z,139,313,1|5tpf60,138,248,0|65y5tz,138,248,0|65y5u0,139,313,1|6e8b5z,139,313,1|6e8b60,138,248,0|6p175z,138,248,0|6p1760,139,313,1|6vij5z,139,313,1|6vij60,138,248,0|77r9tz,138,248,0|77r9u0,139,313,1|7e8ltz,139,313,1|7e8lu0,138,248,0|7qhchz,138,248,0|7qhci0,139,313,1|7wyohz,139,313,1|7wyoi0,138,248,0|897f5z,138,248,0|897f60,139,313,1|8geohz,139,313,1|8geoi0,138,248,0|8rkj5z,138,248,0|8rkj60,139,313,1|8z4r5z,139,313,1|8z4r60,138,248,0|9ankhz,138,248,0|9anki0,139,313,1|9i7shz,139,313,1|9i7si0,138,248,0|9tqltz,138,248,0|9tqlu0,139,313,1|a0xv5z,139,313,1|a0xv60,138,248,0|acgohz,138,248,0|acgoi0,139,313,1|aiy0hz,139,313,1|aiy0i0,138,248,0|av6r5z,138,248,0|av6r60,139,313,1|b1o35z,139,313,1|b1o360,138,248,0|bdwttz,138,248,0|bdwtu0,139,313,1|bke5tz,139,313,1|bke5u0,138,248,0|bwmwhz,138,248,0|bwmwi0,139,313,1|c3h75z,139,313,1|c3h760,138,248,0|cfpxtz,138,248,0|cfpxu0,139,313,1|cm79tz,139,313,1|cm79u0,138,248,0|cyg0hz,138,248,0|cyg0i0,139,313,1|d4xchz,139,313,1|d4xci0,138,248,0|dh635z,138,248,0|dh6360,139,313,1|dp39tz,139,313,1|dp39u0,138,248,0|dzw5tz,138,248,0|dzw5u0,139,313,1|e7tchz,139,313,1|e7tci0,138,248,0|eim8hz,138,248,0|eim8i0,139,313,1|eqjf5z,139,313,1|eqjf60,138,248,0|f1cb5z,138,248,0|f1cb60,139,313,1|f99htz,139,313,1|f99hu0,138,248,0|fkfchz,138,248,0|fkfci0,139,313,1|frzkhz,139,313,1|frzki0,138,248,0|g35f5z,138,248,0|g35f60,139,313,1|gapn5z,139,313,1|gapn60,138,248,0|glvhtz,138,248,0|glvhu0,139,313,1|gtsohz,139,313,1|gtsoi0,138,248,0|h4lkhz,138,248,0|h4lki0,139,313,1|hcir5z,139,313,1|hcir60,138,248,0|hnbn5z,138,248,0|hnbn60,139,313,1|hv8ttz,139,313,1|hv8tu0,138,248,0|i6eohz,138,248,0|i6eoi0,139,313,1|idywhz,139,313,1|idywi0,138,248,0|ip4r5z,138,248,0|ip4r60,139,313,1|ix1xtz,139,313,1|ix1xu0,138,248,0|j7uttz,138,248,0|j7utu0,139,313,1|jff1tz,139,313,1|jff1u0,138,248,0|jqkwhz,138,248,0|jqkwi0,139,313,1|jyv1tz,139,313,1|jyv1u0,138,248,0|k8835z,138,248,0|k88360,139,313,1|khl4hz,139,313,1|khl4i0,138,248,0|kqy5tz,138,248,0|kqy5u0,139,313,1|l0b75z,139,313,1|l0b760,138,248,0|l9o8hz,138,248,0|l9o8i0,139,313,1|lj19tz,139,313,1|lj19u0,138,248,0|lseb5z,138,248,0|lseb60,139,313,1|m1rchz,139,313,1|m1rci0,138,248,0|mbhchz,138,248,0|mbhci0,139,313,1|mkudtz,139,313,1|mkudu0,138,248,0|mu7f5z,138,248,0|mu7f60,139,313,1|n3kghz,139,313,1|n3kgi0,138,248,0|ncxhtz,138,248,0|ncxhu0,139,313,1|nmaj5z,139,313,1|nmaj60,138,248,0|nvnkhz,138,248,0|nvnki0,139,313,1|o50ltz,139,313,1|o50lu0,138,248,0|oedn5z,138,248,0|oedn60,139,313,1|onqohz,139,313,1|onqoi0,138,248,0|ox3ptz,138,248,0|ox3pu0,139,313,1|p6gr5z,139,313,1|p6gr60,138,248,0|pg6r5z,138,248,0|pg6r60,139,313,1|ppjshz,139,313,1|ppjsi0,138,248,0|pywttz,138,248,0|pywtu0,139,313,1|q89v5z,139,313,1|q89v60,138,248,0|qhmwhz,138,248,0|qhmwi0,139,313,1|qqzxtz,139,313,1|qqzxu0,138,248,0|r0cz5z,138,248,0|r0cz60,139,313,1|r9q0hz,139,313,1|r9q0i0,138,248,0|rj31tz,138,248,0|rj31u0,139,313,1|rsg35z,139,313,1|rsg360,138,248,0|s1t4hz,138,248,0|s1t4i0,139,313,1|sbj4hz,139,313,1|sbj4i0,138,248,0|skw5tz,138,248,0|skw5u0,139,313,1|su975z,139,313,1|su9760,138,248,0|t3m8hz,138,248,0|t3m8i0,139,313,1|tcz9tz,139,313,1|tcz9u0,138,248,0|tmcb5z,138,248,0|tmcb60,139,313,1|tvpchz,139,313,1|tvpci0,138,248,0|u52dtz,138,248,0|u52du0,139,313,1|ueff5z,139,313,1|ueff60,138,248,0|unsghz,138,248,0|unsgi0,139,313,1|ux5htz,139,313,1|ux5hu0,138,248,0|v6vhtz,138,248,0|v6vhu0,139,313,1|vg8j5z,139,313,1|vg8j60,138,248,0|vplkhz,138,248,0|vplki0,139,313,1|vyyltz,139,313,1|vyylu0,138,248,0|w8bn5z,138,248,0|w8bn60,139,313,1|whoohz,139,313,1|whooi0,138,248,0|wr1ptz,138,248,0|wr1pu0,139,313,1|x0er5z,139,313,1|x0er60,138,248,0|x9rshz,138,248,0|x9rsi0,139,313,1|xj4ttz,139,313,1|xj4tu0,138,248,0|xshv5z,138,248,0|xshv60,139,313,1|y1uwhz,139,313,1|y1uwi0,138,248,0|ybkwhz,138,248,0|ybkwi0,139,313,1|ykxxtz,139,313,1|ykxxu0,138,248,0|yuaz5z,138,248,0|yuaz60,139,313,1|z3o0hz,139,313,1|z3o0i0,138,248,0|zd11tz,138,248,0|zd11u0,139,313,1\",\"Australia/Currie|,0,316,0|-12smja4,94,195,0|-rsj4w1,94,195,0|-rsj4w0,95,192,1|-rjj281,95,192,1|-rjj280,94,195,0|-r8d7k1,94,195,0|-r8d7k0,95,192,1|-r1vvk1,95,192,1|-r1vvk0,94,195,0|-qpn4w1,94,195,0|-qpn4w0,95,192,1|-qj5sw1,95,192,1|-qj5sw0,94,195,0|-em3i81,94,195,0|-em3i80,95,192,1|-ehme81,95,192,1|-ehme80,94,195,0|-e89cw1,94,195,0|-e89cw0,95,192,1|-dywbk1,95,192,1|-dywbk0,94,195,0|-dp6bk1,94,195,0|-dp6bk0,95,192,1|-dg68w1,95,192,1|-dg68w0,94,195,0|-16cow1,94,195,0|-16cow0,95,192,1|-wznk1,95,192,1|-wznk0,94,195,0|-m6rk1,94,195,0|-m6rk0,95,192,1|-fcgw1,95,192,1|-fcgw0,94,195,0|-3gow1,94,195,0|-3gow0,95,192,1|3dlrz,95,192,1|3dls0,94,195,0|f9drz,94,195,0|f9ds0,95,192,1|mgn3z,95,192,1|mgn40,94,195,0|ycf3z,94,195,0|ycf40,95,192,1|14gsfz,95,192,1|14gsg0,94,195,0|1h2hrz,94,195,0|1h2hs0,95,192,1|1njtrz,95,192,1|1njts0,94,195,0|1zskfz,94,195,0|1zskg0,95,192,1|269wfz,95,192,1|269wg0,94,195,0|2iin3z,94,195,0|2iin40,95,192,1|2ozz3z,95,192,1|2ozz40,94,195,0|318prz,94,195,0|318ps0,95,192,1|3830fz,95,192,1|3830g0,94,195,0|3kbr3z,94,195,0|3kbr40,95,192,1|3qt33z,95,192,1|3qt340,94,195,0|431trz,94,195,0|431ts0,95,192,1|49j5rz,95,192,1|49j5s0,94,195,0|4lrwfz,94,195,0|4lrwg0,95,192,1|4s98fz,95,192,1|4s98g0,94,195,0|54hz3z,94,195,0|54hz40,95,192,1|5azb3z,95,192,1|5azb40,94,195,0|5n81rz,94,195,0|5n81s0,95,192,1|5tpdrz,95,192,1|5tpds0,94,195,0|65y4fz,94,195,0|65y4g0,95,192,1|6dvb3z,95,192,1|6dvb40,94,195,0|6p15rz,94,195,0|6p15s0,95,192,1|6wldrz,95,192,1|6wlds0,94,195,0|77r8fz,94,195,0|77r8g0,95,192,1|7e8kfz,95,192,1|7e8kg0,94,195,0|7qhb3z,94,195,0|7qhb40,95,192,1|7wyn3z,95,192,1|7wyn40,94,195,0|897drz,94,195,0|897ds0,95,192,1|8foprz,95,192,1|8fops0,94,195,0|8rkhrz,94,195,0|8rkhs0,95,192,1|8z4prz,95,192,1|8z4ps0,94,195,0|9anj3z,94,195,0|9anj40,95,192,1|9i7r3z,95,192,1|9i7r40,94,195,0|9tqkfz,94,195,0|9tqkg0,95,192,1|a0xtrz,95,192,1|a0xts0,94,195,0|acgn3z,94,195,0|acgn40,95,192,1|ajnwfz,95,192,1|ajnwg0,94,195,0|av6prz,94,195,0|av6ps0,95,192,1|b33wfz,95,192,1|b33wg0,94,195,0|bctwfz,94,195,0|bctwg0,95,192,1|bltz3z,95,192,1|bltz40,94,195,0|bvjz3z,94,195,0|bvjz40,95,192,1|c4k1rz,95,192,1|c4k1s0,94,195,0|cea1rz,94,195,0|cea1s0,95,192,1|cna4fz,95,192,1|cna4g0,94,195,0|cx04fz,94,195,0|cx04g0,95,192,1|d6073z,95,192,1|d60740,94,195,0|dfq73z,94,195,0|dfq740,95,192,1|dp38fz,95,192,1|dp38g0,94,195,0|dyt8fz,94,195,0|dyt8g0,95,192,1|e7tb3z,95,192,1|e7tb40,94,195,0|ehjb3z,94,195,0|ehjb40,95,192,1|eqjdrz,95,192,1|eqjds0,94,195,0|f09drz,94,195,0|f09ds0,95,192,1|f99gfz,95,192,1|f99gg0,94,195,0|fizgfz,94,195,0|fizgg0,95,192,1|frzj3z,95,192,1|frzj40,94,195,0|fzwprz,94,195,0|fzwps0,95,192,1|gaplrz,95,192,1|gapls0,94,195,0|gkskfz,94,195,0|gkskg0,95,192,1|gtsn3z,95,192,1|gtsn40,94,195,0|h3in3z,94,195,0|h3in40,95,192,1|hciprz,95,192,1|hcips0,94,195,0|hm8prz,94,195,0|hm8ps0,95,192,1|hv8sfz,95,192,1|hv8sg0,94,195,0|i4ysfz,94,195,0|i4ysg0,95,192,1|idyv3z,95,192,1|idyv40,94,195,0|inov3z,94,195,0|inov40,95,192,1|ix1wfz,95,192,1|ix1wg0,94,195,0|j6exrz,94,195,0|j6exs0,95,192,1|jff0fz,95,192,1|jff0g0,94,195,0|jphz3z,94,195,0|jphz40,95,192,1|jyv0fz,95,192,1|jyv0g0,94,195,0|k881rz,94,195,0|k881s0,95,192,1|khl33z,95,192,1|khl340,94,195,0|kqy4fz,94,195,0|kqy4g0,95,192,1|l0b5rz,95,192,1|l0b5s0,94,195,0|l9o73z,94,195,0|l9o740,95,192,1|lj18fz,95,192,1|lj18g0,94,195,0|lse9rz,94,195,0|lse9s0,95,192,1|m1rb3z,95,192,1|m1rb40,94,195,0|mbhb3z,94,195,0|mbhb40,95,192,1|mkucfz,95,192,1|mkucg0,94,195,0|mu7drz,94,195,0|mu7ds0,95,192,1|n3kf3z,95,192,1|n3kf40,94,195,0|ncxgfz,94,195,0|ncxgg0,95,192,1|nmahrz,95,192,1|nmahs0,94,195,0|nvnj3z,94,195,0|nvnj40,95,192,1|o50kfz,95,192,1|o50kg0,94,195,0|oedlrz,94,195,0|oedls0,95,192,1|onqn3z,95,192,1|onqn40,94,195,0|ox3ofz,94,195,0|ox3og0,95,192,1|p6gprz,95,192,1|p6gps0,94,195,0|pg6prz,94,195,0|pg6ps0,95,192,1|ppjr3z,95,192,1|ppjr40,94,195,0|pywsfz,94,195,0|pywsg0,95,192,1|q89trz,95,192,1|q89ts0,94,195,0|qhmv3z,94,195,0|qhmv40,95,192,1|qqzwfz,95,192,1|qqzwg0,94,195,0|r0cxrz,94,195,0|r0cxs0,95,192,1|r9pz3z,95,192,1|r9pz40,94,195,0|rj30fz,94,195,0|rj30g0,95,192,1|rsg1rz,95,192,1|rsg1s0,94,195,0|s1t33z,94,195,0|s1t340,95,192,1|sbj33z,95,192,1|sbj340,94,195,0|skw4fz,94,195,0|skw4g0,95,192,1|su95rz,95,192,1|su95s0,94,195,0|t3m73z,94,195,0|t3m740,95,192,1|tcz8fz,95,192,1|tcz8g0,94,195,0|tmc9rz,94,195,0|tmc9s0,95,192,1|tvpb3z,95,192,1|tvpb40,94,195,0|u52cfz,94,195,0|u52cg0,95,192,1|uefdrz,95,192,1|uefds0,94,195,0|unsf3z,94,195,0|unsf40,95,192,1|ux5gfz,95,192,1|ux5gg0,94,195,0|v6vgfz,94,195,0|v6vgg0,95,192,1|vg8hrz,95,192,1|vg8hs0,94,195,0|vplj3z,94,195,0|vplj40,95,192,1|vyykfz,95,192,1|vyykg0,94,195,0|w8blrz,94,195,0|w8bls0,95,192,1|whon3z,95,192,1|whon40,94,195,0|wr1ofz,94,195,0|wr1og0,95,192,1|x0eprz,95,192,1|x0eps0,94,195,0|x9rr3z,94,195,0|x9rr40,95,192,1|xj4sfz,95,192,1|xj4sg0,94,195,0|xshtrz,94,195,0|xshts0,95,192,1|y1uv3z,95,192,1|y1uv40,94,195,0|ybkv3z,94,195,0|ybkv40,95,192,1|ykxwfz,95,192,1|ykxwg0,94,195,0|yuaxrz,94,195,0|yuaxs0,95,192,1|z3nz3z,95,192,1|z3nz40,94,195,0|zd10fz,94,195,0|zd10g0,95,192,1\",\"Australia/Darwin|,0,317,0|-133j1k8,138,224,0|-10vsp01,138,224,0|-10vsp00,138,248,0|-rnsq61,138,248,0|-rnsq60,139,313,1|-rjj0u1,139,313,1|-rjj0u0,138,248,0|-em3gu1,138,248,0|-em3gu0,139,313,1|-ehmcu1,139,313,1|-ehmcu0,138,248,0|-e89bi1,138,248,0|-e89bi0,139,313,1|-dywa61,139,313,1|-dywa60,138,248,0|-dp6a61,138,248,0|-dp6a60,139,313,1|-dg67i1,139,313,1|-dg67i0,138,248,0\",\"Australia/Eucla|,0,318,0|-12nxx74,140,319,0|-rnso31,140,319,0|-rnso30,141,320,1|-rjiyr1,141,320,1|-rjiyr0,140,319,0|-em3er1,140,319,0|-em3er0,141,320,1|-ehmar1,141,320,1|-ehmar0,140,319,0|-e899f1,140,319,0|-e899f0,141,320,1|-dyw831,141,320,1|-dyw830,140,319,0|2iiqkz,140,319,0|2iiql0,141,320,1|2p02kz,141,320,1|2p02l0,140,319,0|77rbwz,140,319,0|77rbx0,141,320,1|7e8nwz,141,320,1|7e8nx0,140,319,0|bezrwz,140,319,0|bezrx0,141,320,1|bke7wz,141,320,1|bke7x0,140,319,0|j9np8z,140,319,0|j9np90,141,320,1|jff3wz,141,320,1|jff3x0,140,319,0|jqkykz,140,319,0|jqkyl0,141,320,1|jyi58z,141,320,1|jyi590,140,319,0|k9b18z,140,319,0|k9b190,141,320,1|kh87wz,141,320,1|kh87x0,140,319,0\",\"Australia/Hobart|,0,316,0|-12smja4,94,195,0|-rsj4w1,94,195,0|-rsj4w0,95,192,1|-rjj281,95,192,1|-rjj280,94,195,0|-r8d7k1,94,195,0|-r8d7k0,95,192,1|-r1vvk1,95,192,1|-r1vvk0,94,195,0|-qpn4w1,94,195,0|-qpn4w0,95,192,1|-qj5sw1,95,192,1|-qj5sw0,94,195,0|-em3i81,94,195,0|-em3i80,95,192,1|-ehme81,95,192,1|-ehme80,94,195,0|-e89cw1,94,195,0|-e89cw0,95,192,1|-dywbk1,95,192,1|-dywbk0,94,195,0|-dp6bk1,94,195,0|-dp6bk0,95,192,1|-dg68w1,95,192,1|-dg68w0,94,195,0|-16cow1,94,195,0|-16cow0,95,192,1|-wznk1,95,192,1|-wznk0,94,195,0|-m6rk1,94,195,0|-m6rk0,95,192,1|-fcgw1,95,192,1|-fcgw0,94,195,0|-3gow1,94,195,0|-3gow0,95,192,1|3dlrz,95,192,1|3dls0,94,195,0|f9drz,94,195,0|f9ds0,95,192,1|mgn3z,95,192,1|mgn40,94,195,0|ycf3z,94,195,0|ycf40,95,192,1|14gsfz,95,192,1|14gsg0,94,195,0|1h2hrz,94,195,0|1h2hs0,95,192,1|1njtrz,95,192,1|1njts0,94,195,0|1zskfz,94,195,0|1zskg0,95,192,1|269wfz,95,192,1|269wg0,94,195,0|2iin3z,94,195,0|2iin40,95,192,1|2ozz3z,95,192,1|2ozz40,94,195,0|318prz,94,195,0|318ps0,95,192,1|3830fz,95,192,1|3830g0,94,195,0|3kbr3z,94,195,0|3kbr40,95,192,1|3qt33z,95,192,1|3qt340,94,195,0|431trz,94,195,0|431ts0,95,192,1|49j5rz,95,192,1|49j5s0,94,195,0|4lrwfz,94,195,0|4lrwg0,95,192,1|4s98fz,95,192,1|4s98g0,94,195,0|54hz3z,94,195,0|54hz40,95,192,1|5azb3z,95,192,1|5azb40,94,195,0|5n81rz,94,195,0|5n81s0,95,192,1|5tpdrz,95,192,1|5tpds0,94,195,0|65y4fz,94,195,0|65y4g0,95,192,1|6dvb3z,95,192,1|6dvb40,94,195,0|6p15rz,94,195,0|6p15s0,95,192,1|6wldrz,95,192,1|6wlds0,94,195,0|77r8fz,94,195,0|77r8g0,95,192,1|7e8kfz,95,192,1|7e8kg0,94,195,0|7qhb3z,94,195,0|7qhb40,95,192,1|7wyn3z,95,192,1|7wyn40,94,195,0|897drz,94,195,0|897ds0,95,192,1|8foprz,95,192,1|8fops0,94,195,0|8rkhrz,94,195,0|8rkhs0,95,192,1|8z4prz,95,192,1|8z4ps0,94,195,0|9anj3z,94,195,0|9anj40,95,192,1|9i7r3z,95,192,1|9i7r40,94,195,0|9tqkfz,94,195,0|9tqkg0,95,192,1|a0xtrz,95,192,1|a0xts0,94,195,0|acgn3z,94,195,0|acgn40,95,192,1|ajnwfz,95,192,1|ajnwg0,94,195,0|av6prz,94,195,0|av6ps0,95,192,1|b33wfz,95,192,1|b33wg0,94,195,0|bctwfz,94,195,0|bctwg0,95,192,1|bltz3z,95,192,1|bltz40,94,195,0|bvjz3z,94,195,0|bvjz40,95,192,1|c4k1rz,95,192,1|c4k1s0,94,195,0|cea1rz,94,195,0|cea1s0,95,192,1|cna4fz,95,192,1|cna4g0,94,195,0|cx04fz,94,195,0|cx04g0,95,192,1|d6073z,95,192,1|d60740,94,195,0|dfq73z,94,195,0|dfq740,95,192,1|dp38fz,95,192,1|dp38g0,94,195,0|dyt8fz,94,195,0|dyt8g0,95,192,1|e7tb3z,95,192,1|e7tb40,94,195,0|ehjb3z,94,195,0|ehjb40,95,192,1|eqjdrz,95,192,1|eqjds0,94,195,0|f09drz,94,195,0|f09ds0,95,192,1|f99gfz,95,192,1|f99gg0,94,195,0|fizgfz,94,195,0|fizgg0,95,192,1|frzj3z,95,192,1|frzj40,94,195,0|fzwprz,94,195,0|fzwps0,95,192,1|gaplrz,95,192,1|gapls0,94,195,0|gkskfz,94,195,0|gkskg0,95,192,1|gtsn3z,95,192,1|gtsn40,94,195,0|h3in3z,94,195,0|h3in40,95,192,1|hciprz,95,192,1|hcips0,94,195,0|hm8prz,94,195,0|hm8ps0,95,192,1|hv8sfz,95,192,1|hv8sg0,94,195,0|i4ysfz,94,195,0|i4ysg0,95,192,1|idyv3z,95,192,1|idyv40,94,195,0|inov3z,94,195,0|inov40,95,192,1|ix1wfz,95,192,1|ix1wg0,94,195,0|j6exrz,94,195,0|j6exs0,95,192,1|jff0fz,95,192,1|jff0g0,94,195,0|jphz3z,94,195,0|jphz40,95,192,1|jyv0fz,95,192,1|jyv0g0,94,195,0|k881rz,94,195,0|k881s0,95,192,1|khl33z,95,192,1|khl340,94,195,0|kqy4fz,94,195,0|kqy4g0,95,192,1|l0b5rz,95,192,1|l0b5s0,94,195,0|l9o73z,94,195,0|l9o740,95,192,1|lj18fz,95,192,1|lj18g0,94,195,0|lse9rz,94,195,0|lse9s0,95,192,1|m1rb3z,95,192,1|m1rb40,94,195,0|mbhb3z,94,195,0|mbhb40,95,192,1|mkucfz,95,192,1|mkucg0,94,195,0|mu7drz,94,195,0|mu7ds0,95,192,1|n3kf3z,95,192,1|n3kf40,94,195,0|ncxgfz,94,195,0|ncxgg0,95,192,1|nmahrz,95,192,1|nmahs0,94,195,0|nvnj3z,94,195,0|nvnj40,95,192,1|o50kfz,95,192,1|o50kg0,94,195,0|oedlrz,94,195,0|oedls0,95,192,1|onqn3z,95,192,1|onqn40,94,195,0|ox3ofz,94,195,0|ox3og0,95,192,1|p6gprz,95,192,1|p6gps0,94,195,0|pg6prz,94,195,0|pg6ps0,95,192,1|ppjr3z,95,192,1|ppjr40,94,195,0|pywsfz,94,195,0|pywsg0,95,192,1|q89trz,95,192,1|q89ts0,94,195,0|qhmv3z,94,195,0|qhmv40,95,192,1|qqzwfz,95,192,1|qqzwg0,94,195,0|r0cxrz,94,195,0|r0cxs0,95,192,1|r9pz3z,95,192,1|r9pz40,94,195,0|rj30fz,94,195,0|rj30g0,95,192,1|rsg1rz,95,192,1|rsg1s0,94,195,0|s1t33z,94,195,0|s1t340,95,192,1|sbj33z,95,192,1|sbj340,94,195,0|skw4fz,94,195,0|skw4g0,95,192,1|su95rz,95,192,1|su95s0,94,195,0|t3m73z,94,195,0|t3m740,95,192,1|tcz8fz,95,192,1|tcz8g0,94,195,0|tmc9rz,94,195,0|tmc9s0,95,192,1|tvpb3z,95,192,1|tvpb40,94,195,0|u52cfz,94,195,0|u52cg0,95,192,1|uefdrz,95,192,1|uefds0,94,195,0|unsf3z,94,195,0|unsf40,95,192,1|ux5gfz,95,192,1|ux5gg0,94,195,0|v6vgfz,94,195,0|v6vgg0,95,192,1|vg8hrz,95,192,1|vg8hs0,94,195,0|vplj3z,94,195,0|vplj40,95,192,1|vyykfz,95,192,1|vyykg0,94,195,0|w8blrz,94,195,0|w8bls0,95,192,1|whon3z,95,192,1|whon40,94,195,0|wr1ofz,94,195,0|wr1og0,95,192,1|x0eprz,95,192,1|x0eps0,94,195,0|x9rr3z,94,195,0|x9rr40,95,192,1|xj4sfz,95,192,1|xj4sg0,94,195,0|xshtrz,94,195,0|xshts0,95,192,1|y1uv3z,95,192,1|y1uv40,94,195,0|ybkv3z,94,195,0|ybkv40,95,192,1|ykxwfz,95,192,1|ykxwg0,94,195,0|yuaxrz,94,195,0|yuaxs0,95,192,1|z3nz3z,95,192,1|z3nz40,94,195,0|zd10fz,94,195,0|zd10g0,95,192,1\",\"Australia/Lindeman|,0,321,0|-1354jl8,94,195,0|-rnsrk1,94,195,0|-rnsrk0,95,192,1|-rjj281,95,192,1|-rjj280,94,195,0|-em3i81,94,195,0|-em3i80,95,192,1|-ehme81,95,192,1|-ehme80,94,195,0|-e89cw1,94,195,0|-e89cw0,95,192,1|-dywbk1,95,192,1|-dywbk0,94,195,0|-dp6bk1,94,195,0|-dp6bk0,95,192,1|-dg68w1,95,192,1|-dg68w0,94,195,0|ycf3z,94,195,0|ycf40,95,192,1|14gsfz,95,192,1|14gsg0,94,195,0|acgn3z,94,195,0|acgn40,95,192,1|aixz3z,95,192,1|aixz40,94,195,0|av6prz,94,195,0|av6ps0,95,192,1|b1o1rz,95,192,1|b1o1s0,94,195,0|bdwsfz,94,195,0|bdwsg0,95,192,1|bke4fz,95,192,1|bke4g0,94,195,0|bwmv3z,94,195,0|bwmv40,95,192,1|c3h5rz,95,192,1|c3h5s0,94,195,0|cfpwfz,94,195,0|cfpwg0,95,192,1|cm78fz,95,192,1|cm78g0,94,195,0\",\"Australia/Lord_Howe|,0,322,0|-133j6sk,94,195,0|5tp87z,94,195,0|5tp880,142,313,0|65y31z,142,313,0|65y320,143,198,1|6csaxz,143,198,1|6csay0,142,313,0|6p14dz,142,313,0|6p14e0,143,198,1|6vidlz,143,198,1|6vidm0,142,313,0|77r71z,142,313,0|77r720,143,198,1|7e8g9z,143,198,1|7e8ga0,142,313,0|7qh9pz,142,313,0|7qh9q0,143,198,1|7wyixz,143,198,1|7wyiy0,142,313,0|897cdz,142,313,0|897ce0,90,192,1|8gekbz,90,192,1|8gekc0,142,313,0|8rkgdz,142,313,0|8rkge0,90,192,1|8z4mzz,90,192,1|8z4n00,142,313,0|9anhpz,142,313,0|9anhq0,90,192,1|9i7obz,90,192,1|9i7oc0,142,313,0|9tqj1z,142,313,0|9tqj20,90,192,1|a0xqzz,90,192,1|a0xr00,142,313,0|acglpz,142,313,0|acglq0,90,192,1|aixwbz,90,192,1|aixwc0,142,313,0|av6odz,142,313,0|av6oe0,90,192,1|b1nyzz,90,192,1|b1nz00,142,313,0|bdwr1z,142,313,0|bdwr20,90,192,1|bke1nz,90,192,1|bke1o0,142,313,0|bwmtpz,142,313,0|bwmtq0,90,192,1|c3h2zz,90,192,1|c3h300,142,313,0|cfpv1z,142,313,0|cfpv20,90,192,1|cm75nz,90,192,1|cm75o0,142,313,0|cyfxpz,142,313,0|cyfxq0,90,192,1|d4x8bz,90,192,1|d4x8c0,142,313,0|dh60dz,142,313,0|dh60e0,90,192,1|dp35nz,90,192,1|dp35o0,142,313,0|dzw31z,142,313,0|dzw320,90,192,1|e7t8bz,90,192,1|e7t8c0,142,313,0|eim5pz,142,313,0|eim5q0,90,192,1|eqjazz,90,192,1|eqjb00,142,313,0|f1c8dz,142,313,0|f1c8e0,90,192,1|f99dnz,90,192,1|f99do0,142,313,0|fkf9pz,142,313,0|fkf9q0,90,192,1|frzgbz,90,192,1|frzgc0,142,313,0|fzwodz,142,313,0|fzwoe0,90,192,1|gapizz,90,192,1|gapj00,142,313,0|glvf1z,142,313,0|glvf20,90,192,1|gtskbz,90,192,1|gtskc0,142,313,0|h4lhpz,142,313,0|h4lhq0,90,192,1|hcimzz,90,192,1|hcin00,142,313,0|hnbkdz,142,313,0|hnbke0,90,192,1|hv8pnz,90,192,1|hv8po0,142,313,0|i6elpz,142,313,0|i6elq0,90,192,1|idysbz,90,192,1|idysc0,142,313,0|ip4odz,142,313,0|ip4oe0,90,192,1|ix1tnz,90,192,1|ix1to0,142,313,0|j7ur1z,142,313,0|j7ur20,90,192,1|jfexnz,90,192,1|jfexo0,142,313,0|jqktpz,142,313,0|jqktq0,90,192,1|jyuxnz,90,192,1|jyuxo0,142,313,0|k880dz,142,313,0|k880e0,90,192,1|khl0bz,90,192,1|khl0c0,142,313,0|kqy31z,142,313,0|kqy320,90,192,1|l0b2zz,90,192,1|l0b300,142,313,0|l9o5pz,142,313,0|l9o5q0,90,192,1|lj15nz,90,192,1|lj15o0,142,313,0|lse8dz,142,313,0|lse8e0,90,192,1|m1r8bz,90,192,1|m1r8c0,142,313,0|mbh9pz,142,313,0|mbh9q0,90,192,1|mku9nz,90,192,1|mku9o0,142,313,0|mu7cdz,142,313,0|mu7ce0,90,192,1|n3kcbz,90,192,1|n3kcc0,142,313,0|ncxf1z,142,313,0|ncxf20,90,192,1|nmaezz,90,192,1|nmaf00,142,313,0|nvnhpz,142,313,0|nvnhq0,90,192,1|o50hnz,90,192,1|o50ho0,142,313,0|oedkdz,142,313,0|oedke0,90,192,1|onqkbz,90,192,1|onqkc0,142,313,0|ox3n1z,142,313,0|ox3n20,90,192,1|p6gmzz,90,192,1|p6gn00,142,313,0|pg6odz,142,313,0|pg6oe0,90,192,1|ppjobz,90,192,1|ppjoc0,142,313,0|pywr1z,142,313,0|pywr20,90,192,1|q89qzz,90,192,1|q89r00,142,313,0|qhmtpz,142,313,0|qhmtq0,90,192,1|qqztnz,90,192,1|qqzto0,142,313,0|r0cwdz,142,313,0|r0cwe0,90,192,1|r9pwbz,90,192,1|r9pwc0,142,313,0|rj2z1z,142,313,0|rj2z20,90,192,1|rsfyzz,90,192,1|rsfz00,142,313,0|s1t1pz,142,313,0|s1t1q0,90,192,1|sbj0bz,90,192,1|sbj0c0,142,313,0|skw31z,142,313,0|skw320,90,192,1|su92zz,90,192,1|su9300,142,313,0|t3m5pz,142,313,0|t3m5q0,90,192,1|tcz5nz,90,192,1|tcz5o0,142,313,0|tmc8dz,142,313,0|tmc8e0,90,192,1|tvp8bz,90,192,1|tvp8c0,142,313,0|u52b1z,142,313,0|u52b20,90,192,1|uefazz,90,192,1|uefb00,142,313,0|unsdpz,142,313,0|unsdq0,90,192,1|ux5dnz,90,192,1|ux5do0,142,313,0|v6vf1z,142,313,0|v6vf20,90,192,1|vg8ezz,90,192,1|vg8f00,142,313,0|vplhpz,142,313,0|vplhq0,90,192,1|vyyhnz,90,192,1|vyyho0,142,313,0|w8bkdz,142,313,0|w8bke0,90,192,1|whokbz,90,192,1|whokc0,142,313,0|wr1n1z,142,313,0|wr1n20,90,192,1|x0emzz,90,192,1|x0en00,142,313,0|x9rppz,142,313,0|x9rpq0,90,192,1|xj4pnz,90,192,1|xj4po0,142,313,0|xshsdz,142,313,0|xshse0,90,192,1|y1usbz,90,192,1|y1usc0,142,313,0|ybktpz,142,313,0|ybktq0,90,192,1|ykxtnz,90,192,1|ykxto0,142,313,0|yuawdz,142,313,0|yuawe0,90,192,1|z3nwbz,90,192,1|z3nwc0,142,313,0|zd0z1z,142,313,0|zd0z20,90,192,1\",\"Australia/Melbourne|,0,323,0|-133j46g,94,195,0|-rnsrk1,94,195,0|-rnsrk0,95,192,1|-rjj281,95,192,1|-rjj280,94,195,0|-em3i81,94,195,0|-em3i80,95,192,1|-ehme81,95,192,1|-ehme80,94,195,0|-e89cw1,94,195,0|-e89cw0,95,192,1|-dywbk1,95,192,1|-dywbk0,94,195,0|-dp6bk1,94,195,0|-dp6bk0,95,192,1|-dg68w1,95,192,1|-dg68w0,94,195,0|ycf3z,94,195,0|ycf40,95,192,1|14gsfz,95,192,1|14gsg0,94,195,0|1h2hrz,94,195,0|1h2hs0,95,192,1|1njtrz,95,192,1|1njts0,94,195,0|1zskfz,94,195,0|1zskg0,95,192,1|269wfz,95,192,1|269wg0,94,195,0|2iin3z,94,195,0|2iin40,95,192,1|2ozz3z,95,192,1|2ozz40,94,195,0|318prz,94,195,0|318ps0,95,192,1|3830fz,95,192,1|3830g0,94,195,0|3kbr3z,94,195,0|3kbr40,95,192,1|3qt33z,95,192,1|3qt340,94,195,0|431trz,94,195,0|431ts0,95,192,1|49j5rz,95,192,1|49j5s0,94,195,0|4lrwfz,94,195,0|4lrwg0,95,192,1|4s98fz,95,192,1|4s98g0,94,195,0|54hz3z,94,195,0|54hz40,95,192,1|5azb3z,95,192,1|5azb40,94,195,0|5n81rz,94,195,0|5n81s0,95,192,1|5tpdrz,95,192,1|5tpds0,94,195,0|65y4fz,94,195,0|65y4g0,95,192,1|6csf3z,95,192,1|6csf40,94,195,0|6p15rz,94,195,0|6p15s0,95,192,1|6vihrz,95,192,1|6vihs0,94,195,0|77r8fz,94,195,0|77r8g0,95,192,1|7e8kfz,95,192,1|7e8kg0,94,195,0|7qhb3z,94,195,0|7qhb40,95,192,1|7wyn3z,95,192,1|7wyn40,94,195,0|897drz,94,195,0|897ds0,95,192,1|8gen3z,95,192,1|8gen40,94,195,0|8rkhrz,94,195,0|8rkhs0,95,192,1|8z4prz,95,192,1|8z4ps0,94,195,0|9aakfz,94,195,0|9aakg0,95,192,1|9i7r3z,95,192,1|9i7r40,94,195,0|9tqkfz,94,195,0|9tqkg0,95,192,1|a0xtrz,95,192,1|a0xts0,94,195,0|acgn3z,94,195,0|acgn40,95,192,1|ajnwfz,95,192,1|ajnwg0,94,195,0|av6prz,94,195,0|av6ps0,95,192,1|b1o1rz,95,192,1|b1o1s0,94,195,0|bdwsfz,94,195,0|bdwsg0,95,192,1|bke4fz,95,192,1|bke4g0,94,195,0|bwmv3z,94,195,0|bwmv40,95,192,1|c3h5rz,95,192,1|c3h5s0,94,195,0|cfpwfz,94,195,0|cfpwg0,95,192,1|cm78fz,95,192,1|cm78g0,94,195,0|cyfz3z,94,195,0|cyfz40,95,192,1|d6073z,95,192,1|d60740,94,195,0|dh61rz,94,195,0|dh61s0,95,192,1|dp38fz,95,192,1|dp38g0,94,195,0|dzw4fz,94,195,0|dzw4g0,95,192,1|e7tb3z,95,192,1|e7tb40,94,195,0|eim73z,94,195,0|eim740,95,192,1|eqjdrz,95,192,1|eqjds0,94,195,0|f1c9rz,94,195,0|f1c9s0,95,192,1|f99gfz,95,192,1|f99gg0,94,195,0|fkfb3z,94,195,0|fkfb40,95,192,1|frzj3z,95,192,1|frzj40,94,195,0|fzwprz,94,195,0|fzwps0,95,192,1|gaplrz,95,192,1|gapls0,94,195,0|glvgfz,94,195,0|glvgg0,95,192,1|gtsn3z,95,192,1|gtsn40,94,195,0|h4lj3z,94,195,0|h4lj40,95,192,1|hciprz,95,192,1|hcips0,94,195,0|hnblrz,94,195,0|hnbls0,95,192,1|hv8sfz,95,192,1|hv8sg0,94,195,0|i6en3z,94,195,0|i6en40,95,192,1|idyv3z,95,192,1|idyv40,94,195,0|ip4prz,94,195,0|ip4ps0,95,192,1|ix1wfz,95,192,1|ix1wg0,94,195,0|j7usfz,94,195,0|j7usg0,95,192,1|jff0fz,95,192,1|jff0g0,94,195,0|jqkv3z,94,195,0|jqkv40,95,192,1|jyv0fz,95,192,1|jyv0g0,94,195,0|k881rz,94,195,0|k881s0,95,192,1|khl33z,95,192,1|khl340,94,195,0|kqy4fz,94,195,0|kqy4g0,95,192,1|l0b5rz,95,192,1|l0b5s0,94,195,0|l9o73z,94,195,0|l9o740,95,192,1|lj18fz,95,192,1|lj18g0,94,195,0|lse9rz,94,195,0|lse9s0,95,192,1|m1rb3z,95,192,1|m1rb40,94,195,0|mbhb3z,94,195,0|mbhb40,95,192,1|mkucfz,95,192,1|mkucg0,94,195,0|mu7drz,94,195,0|mu7ds0,95,192,1|n3kf3z,95,192,1|n3kf40,94,195,0|ncxgfz,94,195,0|ncxgg0,95,192,1|nmahrz,95,192,1|nmahs0,94,195,0|nvnj3z,94,195,0|nvnj40,95,192,1|o50kfz,95,192,1|o50kg0,94,195,0|oedlrz,94,195,0|oedls0,95,192,1|onqn3z,95,192,1|onqn40,94,195,0|ox3ofz,94,195,0|ox3og0,95,192,1|p6gprz,95,192,1|p6gps0,94,195,0|pg6prz,94,195,0|pg6ps0,95,192,1|ppjr3z,95,192,1|ppjr40,94,195,0|pywsfz,94,195,0|pywsg0,95,192,1|q89trz,95,192,1|q89ts0,94,195,0|qhmv3z,94,195,0|qhmv40,95,192,1|qqzwfz,95,192,1|qqzwg0,94,195,0|r0cxrz,94,195,0|r0cxs0,95,192,1|r9pz3z,95,192,1|r9pz40,94,195,0|rj30fz,94,195,0|rj30g0,95,192,1|rsg1rz,95,192,1|rsg1s0,94,195,0|s1t33z,94,195,0|s1t340,95,192,1|sbj33z,95,192,1|sbj340,94,195,0|skw4fz,94,195,0|skw4g0,95,192,1|su95rz,95,192,1|su95s0,94,195,0|t3m73z,94,195,0|t3m740,95,192,1|tcz8fz,95,192,1|tcz8g0,94,195,0|tmc9rz,94,195,0|tmc9s0,95,192,1|tvpb3z,95,192,1|tvpb40,94,195,0|u52cfz,94,195,0|u52cg0,95,192,1|uefdrz,95,192,1|uefds0,94,195,0|unsf3z,94,195,0|unsf40,95,192,1|ux5gfz,95,192,1|ux5gg0,94,195,0|v6vgfz,94,195,0|v6vgg0,95,192,1|vg8hrz,95,192,1|vg8hs0,94,195,0|vplj3z,94,195,0|vplj40,95,192,1|vyykfz,95,192,1|vyykg0,94,195,0|w8blrz,94,195,0|w8bls0,95,192,1|whon3z,95,192,1|whon40,94,195,0|wr1ofz,94,195,0|wr1og0,95,192,1|x0eprz,95,192,1|x0eps0,94,195,0|x9rr3z,94,195,0|x9rr40,95,192,1|xj4sfz,95,192,1|xj4sg0,94,195,0|xshtrz,94,195,0|xshts0,95,192,1|y1uv3z,95,192,1|y1uv40,94,195,0|ybkv3z,94,195,0|ybkv40,95,192,1|ykxwfz,95,192,1|ykxwg0,94,195,0|yuaxrz,94,195,0|yuaxs0,95,192,1|z3nz3z,95,192,1|z3nz40,94,195,0|zd10fz,94,195,0|zd10g0,95,192,1\",\"Australia/Perth|,0,324,0|-12nxusc,144,191,0|-rnsm01,144,191,0|-rnsm00,145,224,1|-rjiwo1,145,224,1|-rjiwo0,144,191,0|-em3co1,144,191,0|-em3co0,145,224,1|-ehm8o1,145,224,1|-ehm8o0,144,191,0|-e897c1,144,191,0|-e897c0,145,224,1|-dyw601,145,224,1|-dyw600,144,191,0|2iisnz,144,191,0|2iiso0,145,224,1|2p04nz,145,224,1|2p04o0,144,191,0|77rdzz,144,191,0|77re00,145,224,1|7e8pzz,145,224,1|7e8q00,144,191,0|beztzz,144,191,0|bezu00,145,224,1|bke9zz,145,224,1|bkea00,144,191,0|j9nrbz,144,191,0|j9nrc0,145,224,1|jff5zz,145,224,1|jff600,144,191,0|jql0nz,144,191,0|jql0o0,145,224,1|jyi7bz,145,224,1|jyi7c0,144,191,0|k9b3bz,144,191,0|k9b3c0,145,224,1|kh89zz,145,224,1|kh8a00,144,191,0\",\"Australia/Sydney|,0,325,0|-133j5c4,94,195,0|-rnsrk1,94,195,0|-rnsrk0,95,192,1|-rjj281,95,192,1|-rjj280,94,195,0|-em3i81,94,195,0|-em3i80,95,192,1|-ehme81,95,192,1|-ehme80,94,195,0|-e89cw1,94,195,0|-e89cw0,95,192,1|-dywbk1,95,192,1|-dywbk0,94,195,0|-dp6bk1,94,195,0|-dp6bk0,95,192,1|-dg68w1,95,192,1|-dg68w0,94,195,0|ycf3z,94,195,0|ycf40,95,192,1|14gsfz,95,192,1|14gsg0,94,195,0|1h2hrz,94,195,0|1h2hs0,95,192,1|1njtrz,95,192,1|1njts0,94,195,0|1zskfz,94,195,0|1zskg0,95,192,1|269wfz,95,192,1|269wg0,94,195,0|2iin3z,94,195,0|2iin40,95,192,1|2ozz3z,95,192,1|2ozz40,94,195,0|318prz,94,195,0|318ps0,95,192,1|3830fz,95,192,1|3830g0,94,195,0|3kbr3z,94,195,0|3kbr40,95,192,1|3qt33z,95,192,1|3qt340,94,195,0|431trz,94,195,0|431ts0,95,192,1|49j5rz,95,192,1|49j5s0,94,195,0|4lrwfz,94,195,0|4lrwg0,95,192,1|4s98fz,95,192,1|4s98g0,94,195,0|54hz3z,94,195,0|54hz40,95,192,1|5azb3z,95,192,1|5azb40,94,195,0|5n81rz,94,195,0|5n81s0,95,192,1|5tpdrz,95,192,1|5tpds0,94,195,0|65y4fz,94,195,0|65y4g0,95,192,1|6e89rz,95,192,1|6e89s0,94,195,0|6p15rz,94,195,0|6p15s0,95,192,1|6vihrz,95,192,1|6vihs0,94,195,0|77r8fz,94,195,0|77r8g0,95,192,1|7e8kfz,95,192,1|7e8kg0,94,195,0|7qhb3z,94,195,0|7qhb40,95,192,1|7wyn3z,95,192,1|7wyn40,94,195,0|897drz,94,195,0|897ds0,95,192,1|8gen3z,95,192,1|8gen40,94,195,0|8rkhrz,94,195,0|8rkhs0,95,192,1|8z4prz,95,192,1|8z4ps0,94,195,0|9anj3z,94,195,0|9anj40,95,192,1|9i7r3z,95,192,1|9i7r40,94,195,0|9tqkfz,94,195,0|9tqkg0,95,192,1|a0xtrz,95,192,1|a0xts0,94,195,0|acgn3z,94,195,0|acgn40,95,192,1|aixz3z,95,192,1|aixz40,94,195,0|av6prz,94,195,0|av6ps0,95,192,1|b1o1rz,95,192,1|b1o1s0,94,195,0|bdwsfz,94,195,0|bdwsg0,95,192,1|bke4fz,95,192,1|bke4g0,94,195,0|bwmv3z,94,195,0|bwmv40,95,192,1|c3h5rz,95,192,1|c3h5s0,94,195,0|cfpwfz,94,195,0|cfpwg0,95,192,1|cm78fz,95,192,1|cm78g0,94,195,0|cyfz3z,94,195,0|cyfz40,95,192,1|d4xb3z,95,192,1|d4xb40,94,195,0|dh61rz,94,195,0|dh61s0,95,192,1|dp38fz,95,192,1|dp38g0,94,195,0|dzw4fz,94,195,0|dzw4g0,95,192,1|e7tb3z,95,192,1|e7tb40,94,195,0|eim73z,94,195,0|eim740,95,192,1|eqjdrz,95,192,1|eqjds0,94,195,0|f1c9rz,94,195,0|f1c9s0,95,192,1|f99gfz,95,192,1|f99gg0,94,195,0|fkfb3z,94,195,0|fkfb40,95,192,1|frzj3z,95,192,1|frzj40,94,195,0|fzwprz,94,195,0|fzwps0,95,192,1|gaplrz,95,192,1|gapls0,94,195,0|glvgfz,94,195,0|glvgg0,95,192,1|gtsn3z,95,192,1|gtsn40,94,195,0|h4lj3z,94,195,0|h4lj40,95,192,1|hciprz,95,192,1|hcips0,94,195,0|hnblrz,94,195,0|hnbls0,95,192,1|hv8sfz,95,192,1|hv8sg0,94,195,0|i6en3z,94,195,0|i6en40,95,192,1|idyv3z,95,192,1|idyv40,94,195,0|ip4prz,94,195,0|ip4ps0,95,192,1|ix1wfz,95,192,1|ix1wg0,94,195,0|j7usfz,94,195,0|j7usg0,95,192,1|jff0fz,95,192,1|jff0g0,94,195,0|jqkv3z,94,195,0|jqkv40,95,192,1|jyv0fz,95,192,1|jyv0g0,94,195,0|k881rz,94,195,0|k881s0,95,192,1|khl33z,95,192,1|khl340,94,195,0|kqy4fz,94,195,0|kqy4g0,95,192,1|l0b5rz,95,192,1|l0b5s0,94,195,0|l9o73z,94,195,0|l9o740,95,192,1|lj18fz,95,192,1|lj18g0,94,195,0|lse9rz,94,195,0|lse9s0,95,192,1|m1rb3z,95,192,1|m1rb40,94,195,0|mbhb3z,94,195,0|mbhb40,95,192,1|mkucfz,95,192,1|mkucg0,94,195,0|mu7drz,94,195,0|mu7ds0,95,192,1|n3kf3z,95,192,1|n3kf40,94,195,0|ncxgfz,94,195,0|ncxgg0,95,192,1|nmahrz,95,192,1|nmahs0,94,195,0|nvnj3z,94,195,0|nvnj40,95,192,1|o50kfz,95,192,1|o50kg0,94,195,0|oedlrz,94,195,0|oedls0,95,192,1|onqn3z,95,192,1|onqn40,94,195,0|ox3ofz,94,195,0|ox3og0,95,192,1|p6gprz,95,192,1|p6gps0,94,195,0|pg6prz,94,195,0|pg6ps0,95,192,1|ppjr3z,95,192,1|ppjr40,94,195,0|pywsfz,94,195,0|pywsg0,95,192,1|q89trz,95,192,1|q89ts0,94,195,0|qhmv3z,94,195,0|qhmv40,95,192,1|qqzwfz,95,192,1|qqzwg0,94,195,0|r0cxrz,94,195,0|r0cxs0,95,192,1|r9pz3z,95,192,1|r9pz40,94,195,0|rj30fz,94,195,0|rj30g0,95,192,1|rsg1rz,95,192,1|rsg1s0,94,195,0|s1t33z,94,195,0|s1t340,95,192,1|sbj33z,95,192,1|sbj340,94,195,0|skw4fz,94,195,0|skw4g0,95,192,1|su95rz,95,192,1|su95s0,94,195,0|t3m73z,94,195,0|t3m740,95,192,1|tcz8fz,95,192,1|tcz8g0,94,195,0|tmc9rz,94,195,0|tmc9s0,95,192,1|tvpb3z,95,192,1|tvpb40,94,195,0|u52cfz,94,195,0|u52cg0,95,192,1|uefdrz,95,192,1|uefds0,94,195,0|unsf3z,94,195,0|unsf40,95,192,1|ux5gfz,95,192,1|ux5gg0,94,195,0|v6vgfz,94,195,0|v6vgg0,95,192,1|vg8hrz,95,192,1|vg8hs0,94,195,0|vplj3z,94,195,0|vplj40,95,192,1|vyykfz,95,192,1|vyykg0,94,195,0|w8blrz,94,195,0|w8bls0,95,192,1|whon3z,95,192,1|whon40,94,195,0|wr1ofz,94,195,0|wr1og0,95,192,1|x0eprz,95,192,1|x0eps0,94,195,0|x9rr3z,94,195,0|x9rr40,95,192,1|xj4sfz,95,192,1|xj4sg0,94,195,0|xshtrz,94,195,0|xshts0,95,192,1|y1uv3z,95,192,1|y1uv40,94,195,0|ybkv3z,94,195,0|ybkv40,95,192,1|ykxwfz,95,192,1|ykxwg0,94,195,0|yuaxrz,94,195,0|yuaxs0,95,192,1|z3nz3z,95,192,1|z3nz40,94,195,0|zd10fz,94,195,0|zd10g0,95,192,1\",\"Etc/GMT+1|,199,15,0\",\"Etc/GMT+10|,208,36,0\",\"Etc/GMT+11|,209,35,0\",\"Etc/GMT+12|,210,403,0\",\"Etc/GMT+2|,200,45,0\",\"Etc/GMT+3|,201,44,0\",\"Etc/GMT+4|,202,42,0\",\"Etc/GMT+5|,203,63,0\",\"Etc/GMT+6|,204,62,0\",\"Etc/GMT+7|,205,66,0\",\"Etc/GMT+8|,206,40,0\",\"Etc/GMT+9|,207,37,0\",\"Etc/GMT-1|,198,10,0\",\"Etc/GMT-10|,189,195,0\",\"Etc/GMT-11|,188,192,0\",\"Etc/GMT-12|,187,200,0\",\"Etc/GMT-13|,186,201,0\",\"Etc/GMT-14|,185,207,0\",\"Etc/GMT-2|,197,11,0\",\"Etc/GMT-3|,196,6,0\",\"Etc/GMT-4|,195,209,0\",\"Etc/GMT-5|,194,194,0\",\"Etc/GMT-6|,193,196,0\",\"Etc/GMT-7|,192,193,0\",\"Etc/GMT-8|,191,191,0\",\"Etc/GMT-9|,190,224,0\",\"Europe/Amsterdam|,0,326,0|-1ygf4wk,44,326,0|-s0dvkl,44,326,0|-s0dvkk,24,327,1|-rsimcl,24,327,1|-rsimck,44,326,0|-ridkol,44,326,0|-ridkok,24,327,1|-rage0l,24,327,1|-rage0k,44,326,0|-r0dfcl,44,326,0|-r0dfck,24,327,1|-qr0e0l,24,327,1|-qr0e0k,44,326,0|-qhae0l,44,326,0|-qhae0k,24,327,1|-q8abcl,24,327,1|-q8abck,44,326,0|-pykbcl,44,326,0|-pykbck,24,327,1|-ppk8ol,24,327,1|-ppk8ok,44,326,0|-pfu8ol,44,326,0|-pfu8ok,24,327,1|-p6u60l,24,327,1|-p6u60k,44,326,0|-oxizcl,44,326,0|-oxizck,24,327,1|-ong0ol,24,327,1|-ong0ok,44,326,0|-obazcl,44,326,0|-obazck,24,327,1|-o4py0l,24,327,1|-o4py0k,44,326,0|-nvpvcl,44,326,0|-nvpvck,24,327,1|-nlzvcl,24,327,1|-nlzvck,44,326,0|-n9hvcl,44,326,0|-n9hvck,24,327,1|-n39sol,24,327,1|-n39sok,44,326,0|-mrsu0l,44,326,0|-mrsu0k,24,327,1|-mkjq0l,24,327,1|-mkjq0k,44,326,0|-m90wol,44,326,0|-m90wok,24,327,1|-m1tncl,24,327,1|-m1tnck,44,326,0|-lq74ol,44,326,0|-lq74ok,24,327,1|-liqm0l,24,327,1|-liqm0k,44,326,0|-l7f7cl,44,326,0|-l7f7ck,24,327,1|-l00jcl,24,327,1|-l00jck,44,326,0|-kona0l,44,326,0|-kona0k,24,327,1|-khagol,24,327,1|-khagok,44,326,0|-k5vcol,44,326,0|-k5vcok,24,327,1|-jyke0l,24,327,1|-jyke0k,44,326,0|-jmom0l,44,326,0|-jmom0k,24,327,1|-jfubcl,24,327,1|-jfubck,44,326,0|-j49ncl,44,326,0|-j49nck,24,327,1|-iwra0l,24,327,1|-iwra0k,44,326,0|-ilhq0l,44,326,0|-ilhq0k,24,327,1|-ie17cl,24,327,1|-ie17ck,44,326,0|-i2psol,44,326,0|-i2psok,24,327,1|-hvb4ol,24,327,1|-hvb4ok,44,326,0|-hjw0ol,44,326,0|-hjw0ok,24,327,1|-hcl20l,24,327,1|-hcl20k,44,326,0|-h0r4ol,44,326,0|-h0r4ok,24,327,1|-gypacl,24,327,1|-gypack,146,328,1|-gtuzdd,146,328,1|-gtuzdc,2,2,0|-gic61d,2,2,0|-gic61c,146,328,1|-gb4wpd,146,328,1|-gb4wpc,2,2,0|-fzk8pd,2,2,0|-fzk8pc,146,328,1|-fs1vdd,146,328,1|-fs1vdc,2,2,0|-fgorld,2,2,0|-fgorlc,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cofek1,11,11,1|-cofek0,10,10,0|3s9mrz,10,10,0|3s9ms0,11,11,1|419pfz,11,11,1|419pg0,10,10,0|4azpfz,10,10,0|4azpg0,11,11,1|4kcqrz,11,11,1|4kcqs0,10,10,0|4tps3z,10,10,0|4tps40,11,11,1|532tfz,11,11,1|532tg0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Andorra|,0,329,0|-100edm4,8,1,0|-c4xmo1,8,1,0|-c4xmo0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Astrakhan|,0,330,0|-nu2zkc,100,6,0|-kmr4c1,100,6,0|-kmr4c0,105,209,0|5vb3jz,105,209,0|5vb3k0,92,194,1|64pwrz,92,194,1|64pws0,105,209,0|6e30vz,105,209,0|6e30w0,92,194,1|6nhu3z,92,194,1|6nhu40,105,209,0|6wuy7z,105,209,0|6wuy80,92,194,1|769rfz,92,194,1|769rg0,105,209,0|7foq7z,105,209,0|7foq80,92,194,1|7p1x3z,92,194,1|7p1x40,105,209,0|7yeyfz,105,209,0|7yeyg0,92,194,1|87rzrz,92,194,1|87rzs0,105,209,0|8h513z,105,209,0|8h5140,92,194,1|8qi2fz,92,194,1|8qi2g0,105,209,0|8zv3rz,105,209,0|8zv3s0,92,194,1|99853z,92,194,1|998540,105,209,0|9il6fz,105,209,0|9il6g0,92,194,1|9ry7rz,92,194,1|9ry7s0,105,209,0|a1b93z,105,209,0|a1b940,105,209,1|aaod7z,105,209,1|aaod80,100,6,0|ak1ejz,100,6,0|ak1ek0,105,209,1|atrejz,105,209,1|atrek0,100,6,0|b34fvz,100,6,0|b34fw0,105,209,0|blufrz,105,209,0|blufs0,105,209,1|bv7jvz,105,209,1|bv7jw0,100,6,0|c4kl7z,100,6,0|c4kl80,105,209,1|cdxmjz,105,209,1|cdxmk0,100,6,0|cnanvz,100,6,0|cnanw0,105,209,1|cwnp7z,105,209,1|cwnp80,100,6,0|d60qjz,100,6,0|d60qk0,105,209,1|dfdrvz,105,209,1|dfdrw0,100,6,0|dp3rvz,100,6,0|dp3rw0,105,209,1|dzwnvz,105,209,1|dzwnw0,100,6,0|e7tujz,100,6,0|e7tuk0,105,209,1|eimqjz,105,209,1|eimqk0,100,6,0|eqjx7z,100,6,0|eqjx80,105,209,1|f1ct7z,105,209,1|f1ct80,100,6,0|f99zvz,100,6,0|f99zw0,105,209,1|fkfujz,105,209,1|fkfuk0,100,6,0|fs02jz,100,6,0|fs02k0,105,209,1|g35x7z,105,209,1|g35x80,100,6,0|gaq57z,100,6,0|gaq580,105,209,1|glvzvz,105,209,1|glvzw0,100,6,0|gtt6jz,100,6,0|gtt6k0,105,209,1|h4m2jz,105,209,1|h4m2k0,100,6,0|hcj97z,100,6,0|hcj980,105,209,1|hnc57z,105,209,1|hnc580,100,6,0|hv9bvz,100,6,0|hv9bw0,105,209,1|i6f6jz,105,209,1|i6f6k0,100,6,0|idzejz,100,6,0|idzek0,105,209,1|ip597z,105,209,1|ip5980,100,6,0|iwph7z,100,6,0|iwph80,105,209,1|j7vbvz,105,209,1|j7vbw0,100,6,0|jffjvz,100,6,0|jffjw0,105,209,1|jqlejz,105,209,1|jqlek0,100,6,0|jyil7z,100,6,0|jyil80,105,209,1|k9bh7z,105,209,1|k9bh80,100,6,0|kh8nvz,100,6,0|kh8nw0,105,209,1|ks1jvz,105,209,1|ks1jw0,100,6,0|kzyqjz,100,6,0|kzyqk0,105,209,1|lb4l7z,105,209,1|lb4l80,100,6,0|liot7z,100,6,0|liot80,105,209,0|ne0t3z,105,209,0|ne0t40,100,6,0|o4o57z,100,6,0|o4o580,105,209,0\",\"Europe/Athens|,0,331,0|-12rxtq4,44,331,0|-rvv0ch,44,331,0|-rvv0cg,15,11,0|-jkbpk1,15,11,0|-jkbpk0,16,6,1|-jhg301,16,6,1|-jhg300,15,11,0|-ezx6w1,15,11,0|-ezx6w0,16,6,1|-eyqoc1,16,6,1|-eyqoc0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dys2s1,10,10,0|-dys2s0,11,11,1|-dp4081,11,11,1|-dp4080,10,10,0|-dfp1g1,10,10,0|-dfp1g0,15,11,0|-94v1k1,15,11,0|-94v1k0,16,6,1|-8yhho1,16,6,1|-8yhho0,15,11,0|2r4d3z,15,11,0|2r4d40,16,6,1|32ul3z,16,6,1|32ul40,15,11,0|39wfzz,15,11,0|39wg00,16,6,1|3j9hbz,16,6,1|3j9hc0,15,11,0|3s9jzz,15,11,0|3s9k00,16,6,1|41bhbz,16,6,1|41bhc0,15,11,0|4azmnz,15,11,0|4azmo0,16,6,1|4jzs3z,16,6,1|4jzs40,15,11,0|4tq8rz,15,11,0|4tq8s0,16,6,1|530t7z,16,6,1|530t80,15,11,0|5cjbrz,15,11,0|5cjbs0,16,6,1|5lskzz,16,6,1|5lsl00,15,11,0|5v5xfz,15,11,0|5v5xg0,16,6,1|64iyrz,16,6,1|64iys0,15,11,0|6dw03z,15,11,0|6dw040,16,6,1|6n91fz,16,6,1|6n91g0,15,11,0|6wm2rz,15,11,0|6wm2s0,16,6,1|75z43z,16,6,1|75z440,15,11,0|7fc5fz,15,11,0|7fc5g0,16,6,1|7p25fz,16,6,1|7p25g0,15,11,0|7yf6rz,15,11,0|7yf6s0,16,6,1|87s83z,16,6,1|87s840,15,11,0|8h59fz,15,11,0|8h59g0,16,6,1|8qiarz,16,6,1|8qias0,15,11,0|8zvc3z,15,11,0|8zvc40,16,6,1|998dfz,16,6,1|998dg0,15,11,0|9ilerz,15,11,0|9iles0,16,6,1|9ryg3z,16,6,1|9ryg40,15,11,0|a1bhfz,15,11,0|a1bhg0,16,6,1|aaoirz,16,6,1|aaois0,15,11,0|ak1k3z,15,11,0|ak1k40,16,6,1|atrk3z,16,6,1|atrk40,15,11,0|b34lfz,15,11,0|b34lg0,16,6,1|bchmrz,16,6,1|bchms0,15,11,0|bluo3z,15,11,0|bluo40,16,6,1|bv7pfz,16,6,1|bv7pg0,15,11,0|c4kqrz,15,11,0|c4kqs0,16,6,1|cdxs3z,16,6,1|cdxs40,15,11,0|cnatfz,15,11,0|cnatg0,16,6,1|cwnurz,16,6,1|cwnus0,15,11,0|d60w3z,15,11,0|d60w40,16,6,1|dfdxfz,16,6,1|dfdxg0,15,11,0|dp3xfz,15,11,0|dp3xg0,16,6,1|dzwtfz,16,6,1|dzwtg0,15,11,0|e7u03z,15,11,0|e7u040,16,6,1|eimw3z,16,6,1|eimw40,15,11,0|eqk2rz,15,11,0|eqk2s0,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|fs083z,15,11,0|fs0840,16,6,1|g362rz,16,6,1|g362s0,15,11,0|gaqarz,15,11,0|gaqas0,16,6,1|glw5fz,16,6,1|glw5g0,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|ofu5fz,16,6,1|ofu5g0,15,11,0|onedfz,15,11,0|onedg0,16,6,1|oyk83z,16,6,1|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Europe/Belgrade|,0,332,0|-18vsmgo,10,10,0|-ezayw1,10,10,0|-ezayw0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cv5zw1,10,10,0|-cv5zw0,11,11,1|-cofek1,11,11,1|-cofek0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Berlin|,0,333,0|-1421154,10,10,0|-s0e081,10,10,0|-s0e080,11,11,1|-rsilg1,11,11,1|-rsilg0,10,10,0|-ridmk1,10,10,0|-ridmk0,11,11,1|-ragfw1,11,11,1|-ragfw0,10,10,0|-qznjw1,10,10,0|-qznjw0,11,11,1|-qrqd81,11,11,1|-qrqd80,10,10,0|-fizzw1,10,10,0|-fizzw0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cucg01,11,11,1|-cucg00,147,6,1|-co0o01,147,6,1|-co0o00,11,11,1|-cl6qk1,11,11,1|-cl6qk0,10,10,0|-cdmik1,10,10,0|-cdmik0,11,11,1|-c4kl81,11,11,1|-c4kl80,10,10,0|-bv9bs1,10,10,0|-bv9bs0,11,11,1|-btgl81,11,11,1|-btgl80,147,6,1|-bqxxc1,147,6,1|-bqxxc0,11,11,1|-blwd81,11,11,1|-blwd80,10,10,0|-bbtek1,10,10,0|-bbtek0,11,11,1|-b36ak1,11,11,1|-b36ak0,10,10,0|-atgak1,10,10,0|-atgak0,11,11,1|-akg7w1,11,11,1|-akg7w0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Bratislava|,0,334,0|-1qmkw08,7,334,0|-14u7uo9,7,334,0|-14u7uo8,10,10,0|-s0e081,10,10,0|-s0e080,11,11,1|-rsilg1,11,11,1|-rsilg0,10,10,0|-ridmk1,10,10,0|-ridmk0,11,11,1|-ragfw1,11,11,1|-ragfw0,10,10,0|-qznjw1,10,10,0|-qznjw0,11,11,1|-qrqd81,11,11,1|-qrqd80,10,10,0|-fizzw1,10,10,0|-fizzw0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cnnmk1,11,11,1|-cnnmk0,10,10,0|-cchrw1,10,10,0|-cchrw0,11,11,1|-c4mfw1,11,11,1|-c4mfw0,10,10,0|-c1qns1,10,10,0|-c1qns0,1,1,1|-bxf3s1,1,1,1|-bxf3s0,10,10,0|-bujh81,10,10,0|-bujh80,11,11,1|-blwd81,11,11,1|-blwd80,10,10,0|-bbtek1,10,10,0|-bbtek0,11,11,1|-b36ak1,11,11,1|-b36ak0,10,10,0|-ati581,10,10,0|-ati580,11,11,1|-akg7w1,11,11,1|-akg7w0,10,10,0|4tps3z,10,10,0|4tps40,11,11,1|532tfz,11,11,1|532tg0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Brussels|,0,335,0|-1ayy3h6,53,335,0|-14j9c01,53,335,0|-14j9c00,8,1,0|-ss5uo1,8,1,0|-ss5uo0,10,10,0|-s0dxg1,10,10,0|-s0dxg0,11,11,1|-rsilg1,11,11,1|-rsilg0,10,10,0|-ridmk1,10,10,0|-ridmk0,11,11,1|-ragfw1,11,11,1|-ragfw0,10,10,0|-qznjw1,10,10,0|-qznjw0,11,11,1|-qrqd81,11,11,1|-qrqd80,10,10,0|-qotw41,10,10,0|-qotw40,8,1,0|-qj59g1,8,1,0|-qj59g0,9,10,1|-q7zes1,9,10,1|-q7zes0,8,1,0|-q15441,8,1,0|-q15440,9,10,1|-po6g41,9,10,1|-po6g40,8,1,0|-pgvhg1,8,1,0|-pgvhg0,9,10,1|-p5atg1,9,10,1|-p5atg0,8,1,0|-oxj6s1,8,1,0|-oxj6s0,9,10,1|-ong841,9,10,1|-ong840,8,1,0|-odd9g1,8,1,0|-odd9g0,9,10,1|-o4q5g1,9,10,1|-o4q5g0,8,1,0|-nvq2s1,8,1,0|-nvq2s0,9,10,1|-nm02s1,9,10,1|-nm02s0,8,1,0|-ncn1g1,8,1,0|-ncn1g0,9,10,1|-n3a041,9,10,1|-n3a040,8,1,0|-mt71g1,8,1,0|-mt71g0,9,10,1|-mkjxg1,9,10,1|-mkjxg0,8,1,0|-matxg1,8,1,0|-matxg0,9,10,1|-m1tus1,9,10,1|-m1tus0,8,1,0|-lrqw41,8,1,0|-lrqw40,9,10,1|-liql41,9,10,1|-liql40,8,1,0|-l8nmg1,8,1,0|-l8nmg0,9,10,1|-l00ig1,9,10,1|-l00ig0,8,1,0|-kqaig1,8,1,0|-kqaig0,9,10,1|-khafs1,9,10,1|-khafs0,8,1,0|-k77h41,8,1,0|-k77h40,9,10,1|-jykd41,9,10,1|-jykd40,8,1,0|-jp7bs1,8,1,0|-jp7bs0,9,10,1|-jfuag1,9,10,1|-jfuag0,8,1,0|-j6u7s1,8,1,0|-j6u7s0,9,10,1|-iwr941,9,10,1|-iwr940,8,1,0|-ine7s1,8,1,0|-ine7s0,9,10,1|-ie16g1,9,10,1|-ie16g0,8,1,0|-i513s1,8,1,0|-i513s0,9,10,1|-hvb3s1,9,10,1|-hvb3s0,8,1,0|-hl8541,8,1,0|-hl8540,9,10,1|-hcl141,9,10,1|-hcl140,8,1,0|-h37zs1,8,1,0|-h37zs0,9,10,1|-gtuyg1,9,10,1|-gtuyg0,8,1,0|-gkuvs1,8,1,0|-gkuvs0,9,10,1|-gb4vs1,9,10,1|-gb4vs0,8,1,0|-g11x41,8,1,0|-g11x40,9,10,1|-fpw2g1,9,10,1|-fpw2g0,8,1,0|-fkul41,8,1,0|-fkul40,9,10,1|-fgh6g1,9,10,1|-fgh6g0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d75h81,11,11,1|-d75h80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cofek1,11,11,1|-cofek0,10,10,0|-cbtp81,10,10,0|-cbtp80,11,11,1|-c4kl81,11,11,1|-c4kl80,10,10,0|3s9mrz,10,10,0|3s9ms0,11,11,1|419pfz,11,11,1|419pg0,10,10,0|4azpfz,10,10,0|4azpg0,11,11,1|4kcqrz,11,11,1|4kcqs0,10,10,0|4tps3z,10,10,0|4tps40,11,11,1|532tfz,11,11,1|532tg0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Bucharest|,0,336,0|-14u7wu0,53,336,0|-k29zi1,53,336,0|-k29zi0,15,11,0|-jmqqw1,15,11,0|-jmqqw0,16,6,1|-jfulk1,16,6,1|-jfulk0,15,11,0|-j6hk81,15,11,0|-j6hk80,16,6,1|-ix4iw1,16,6,1|-ix4iw0,15,11,0|-ineiw1,15,11,0|-ineiw0,16,6,1|-ie1hk1,16,6,1|-ie1hk0,15,11,0|-i4og81,15,11,0|-i4og80,16,6,1|-hvbew1,16,6,1|-hvbew0,15,11,0|-hlydk1,15,11,0|-hlydk0,16,6,1|-hclc81,16,6,1|-hclc80,15,11,0|-h38aw1,15,11,0|-h38aw0,16,6,1|-gtv9k1,16,6,1|-gtv9k0,15,11,0|-gki881,15,11,0|-gki880,16,6,1|-gb56w1,16,6,1|-gb56w0,15,11,0|-g1s5k1,15,11,0|-g1s5k0,16,6,1|-fsf481,16,6,1|-fsf480,15,11,0|4wl93z,15,11,0|4wl940,16,6,1|532ibz,16,6,1|532ic0,15,11,0|5csibz,15,11,0|5csic0,16,6,1|5lsnrz,16,6,1|5lsns0,15,11,0|5v5unz,15,11,0|5v5uo0,16,6,1|64ivzz,16,6,1|64iw00,15,11,0|6dvxbz,15,11,0|6dvxc0,16,6,1|6n8ynz,16,6,1|6n8yo0,15,11,0|6wlzzz,15,11,0|6wm000,16,6,1|75z1bz,16,6,1|75z1c0,15,11,0|7fc2nz,15,11,0|7fc2o0,16,6,1|7p22nz,16,6,1|7p22o0,15,11,0|7yf3zz,15,11,0|7yf400,16,6,1|87s5bz,16,6,1|87s5c0,15,11,0|8h56nz,15,11,0|8h56o0,16,6,1|8qi7zz,16,6,1|8qi800,15,11,0|8zv9bz,15,11,0|8zv9c0,16,6,1|998anz,16,6,1|998ao0,15,11,0|9ilbzz,15,11,0|9ilc00,16,6,1|9rydbz,16,6,1|9rydc0,15,11,0|a1benz,15,11,0|a1beo0,16,6,1|aaofzz,16,6,1|aaog00,15,11,0|ak1hbz,15,11,0|ak1hc0,16,6,1|atrhbz,16,6,1|atrhc0,15,11,0|b34d3z,15,11,0|b34d40,16,6,1|bchefz,16,6,1|bcheg0,15,11,0|blufrz,15,11,0|blufs0,16,6,1|bv7h3z,16,6,1|bv7h40,15,11,0|c4kifz,15,11,0|c4kig0,16,6,1|cdxjrz,16,6,1|cdxjs0,15,11,0|cnal3z,15,11,0|cnal40,16,6,1|cwnjnz,16,6,1|cwnjo0,15,11,0|d60nrz,15,11,0|d60ns0,16,6,1|dfdmbz,16,6,1|dfdmc0,15,11,0|dp3p3z,15,11,0|dp3p40,16,6,1|dzwibz,16,6,1|dzwic0,15,11,0|e7u03z,15,11,0|e7u040,16,6,1|eimw3z,16,6,1|eimw40,15,11,0|eqk2rz,15,11,0|eqk2s0,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|fs083z,15,11,0|fs0840,16,6,1|g362rz,16,6,1|g362s0,15,11,0|gaqarz,15,11,0|gaqas0,16,6,1|glw5fz,16,6,1|glw5g0,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|ofu5fz,16,6,1|ofu5g0,15,11,0|onedfz,15,11,0|onedg0,16,6,1|oyk83z,16,6,1|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Europe/Budapest|,0,337,0|-15bee78,10,10,0|-s0e081,10,10,0|-s0e080,11,11,1|-rsilg1,11,11,1|-rsilg0,10,10,0|-ridmk1,10,10,0|-ridmk0,11,11,1|-ragfw1,11,11,1|-ragfw0,10,10,0|-qznjw1,10,10,0|-qznjw0,11,11,1|-qrqd81,11,11,1|-qrqd80,10,10,0|-qgvmk1,10,10,0|-qgvmk0,11,11,1|-q90ak1,11,11,1|-q90ak0,10,10,0|-pykd81,10,10,0|-pykd80,11,11,1|-ppx981,11,11,1|-ppx980,10,10,0|-ezvc81,10,10,0|-ezvc80,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cvhc81,10,10,0|-cvhc80,11,11,1|-cm2dg1,11,11,1|-cm2dg0,10,10,0|-cecfw1,10,10,0|-cecfw0,11,11,1|-c4ko01,11,11,1|-c4ko00,10,10,0|-bv9ek1,10,10,0|-bv9ek0,11,11,1|-blwd81,11,11,1|-blwd80,10,10,0|-bcjbw1,10,10,0|-bcjbw0,11,11,1|-b36ak1,11,11,1|-b36ak0,10,10,0|-atgak1,10,10,0|-atgak0,11,11,1|-akg7w1,11,11,1|-akg7w0,10,10,0|-85bc41,10,10,0|-85bc40,11,11,1|-7yh481,11,11,1|-7yh480,10,10,0|-7ml3w1,10,10,0|-7ml3w0,11,11,1|-7fqt81,11,11,1|-7fqt80,10,10,0|-7353w1,10,10,0|-7353w0,11,11,1|-6x0qk1,11,11,1|-6x0qk0,10,10,0|-6kf181,10,10,0|-6kf180,11,11,1|-6eanw1,11,11,1|-6eanw0,10,10,0|5csnvz,10,10,0|5csnw0,11,11,1|5lsqjz,11,11,1|5lsqk0,10,10,0|5v5rvz,10,10,0|5v5rw0,11,11,1|64it7z,11,11,1|64it80,10,10,0|6dvujz,10,10,0|6dvuk0,11,11,1|6n8vvz,11,11,1|6n8vw0,10,10,0|6wlx7z,10,10,0|6wlx80,11,11,1|75yyjz,11,11,1|75yyk0,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Busingen|,0,338,0|-1os49kw,53,339,0|-13g441n,53,339,0|-13g441m,10,10,0|-eyh6o1,10,10,0|-eyh6o0,11,11,1|-eqk001,11,11,1|-eqk000,10,10,0|-efr401,10,10,0|-efr400,11,11,1|-e7txc1,11,11,1|-e7txc0,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Chisinau|,0,340,0|-1ayy808,41,341,0|-r2p1bp,41,341,0|-r2p1bo,53,336,0|-k29zi1,53,336,0|-k29zi0,15,11,0|-jmqqw1,15,11,0|-jmqqw0,16,6,1|-jfulk1,16,6,1|-jfulk0,15,11,0|-j6hk81,15,11,0|-j6hk80,16,6,1|-ix4iw1,16,6,1|-ix4iw0,15,11,0|-ineiw1,15,11,0|-ineiw0,16,6,1|-ie1hk1,16,6,1|-ie1hk0,15,11,0|-i4og81,15,11,0|-i4og80,16,6,1|-hvbew1,16,6,1|-hvbew0,15,11,0|-hlydk1,15,11,0|-hlydk0,16,6,1|-hclc81,16,6,1|-hclc80,15,11,0|-h38aw1,15,11,0|-h38aw0,16,6,1|-gtv9k1,16,6,1|-gtv9k0,15,11,0|-gki881,15,11,0|-gki880,16,6,1|-gb56w1,16,6,1|-gb56w0,15,11,0|-g1s5k1,15,11,0|-g1s5k0,16,6,1|-fsf481,16,6,1|-fsf480,15,11,0|-fc0dk1,15,11,0|-fc0dk0,16,6,1|-euq8c1,16,6,1|-euq8c0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d8e5k1,11,11,1|-d8e5k0,148,6,0|5vb6bz,148,6,0|5vb6c0,149,209,1|64pzjz,149,209,1|64pzk0,148,6,0|6e33nz,148,6,0|6e33o0,149,209,1|6nhwvz,149,209,1|6nhww0,148,6,0|6wv0zz,148,6,0|6wv100,149,209,1|769u7z,149,209,1|769u80,148,6,0|7foszz,148,6,0|7fot00,149,209,1|7p1zvz,149,209,1|7p1zw0,148,6,0|7yf17z,148,6,0|7yf180,149,209,1|87s2jz,149,209,1|87s2k0,148,6,0|8h53vz,148,6,0|8h53w0,149,209,1|8qi57z,149,209,1|8qi580,148,6,0|8zv6jz,148,6,0|8zv6k0,149,209,1|9987vz,149,209,1|9987w0,148,6,0|9il97z,148,6,0|9il980,149,209,1|9ryajz,149,209,1|9ryak0,148,6,0|a1bbvz,148,6,0|a1bbw0,149,209,1|aaod7z,149,209,1|aaod80,148,6,0|ak1ejz,148,6,0|ak1ek0,149,209,1|am73rz,149,209,1|am73s0,16,6,1|atrhbz,16,6,1|atrhc0,15,11,0|b34inz,15,11,0|b34io0,16,6,1|bchjzz,16,6,1|bchk00,15,11,0|blufrz,15,11,0|blufs0,16,6,1|bv7ebz,16,6,1|bv7ec0,15,11,0|c4kifz,15,11,0|c4kig0,16,6,1|cdxgzz,16,6,1|cdxh00,15,11,0|cnal3z,15,11,0|cnal40,16,6,1|cwnjnz,16,6,1|cwnjo0,15,11,0|d60nrz,15,11,0|d60ns0,16,6,1|dfdmbz,16,6,1|dfdmc0,15,11,0|dp3p3z,15,11,0|dp3p40,16,6,1|dzwibz,16,6,1|dzwic0,15,11,0|e7txbz,15,11,0|e7txc0,16,6,1|eimtbz,16,6,1|eimtc0,15,11,0|eqjzzz,15,11,0|eqk000,16,6,1|f1cvzz,16,6,1|f1cw00,15,11,0|f9a2nz,15,11,0|f9a2o0,16,6,1|fkfxbz,16,6,1|fkfxc0,15,11,0|fs05bz,15,11,0|fs05c0,16,6,1|g35zzz,16,6,1|g36000,15,11,0|gaq7zz,15,11,0|gaq800,16,6,1|glw2nz,16,6,1|glw2o0,15,11,0|gtt9bz,15,11,0|gtt9c0,16,6,1|h4m5bz,16,6,1|h4m5c0,15,11,0|hcjbzz,15,11,0|hcjc00,16,6,1|hnc7zz,16,6,1|hnc800,15,11,0|hv9enz,15,11,0|hv9eo0,16,6,1|i6f9bz,16,6,1|i6f9c0,15,11,0|idzhbz,15,11,0|idzhc0,16,6,1|ip5bzz,16,6,1|ip5c00,15,11,0|iwpjzz,15,11,0|iwpk00,16,6,1|j7venz,16,6,1|j7veo0,15,11,0|jffmnz,15,11,0|jffmo0,16,6,1|jqlhbz,16,6,1|jqlhc0,15,11,0|jyinzz,15,11,0|jyio00,16,6,1|k9bjzz,16,6,1|k9bk00,15,11,0|kh8qnz,15,11,0|kh8qo0,16,6,1|ks1mnz,16,6,1|ks1mo0,15,11,0|kzytbz,15,11,0|kzytc0,16,6,1|lb4nzz,16,6,1|lb4o00,15,11,0|liovzz,15,11,0|liow00,16,6,1|ltuqnz,16,6,1|ltuqo0,15,11,0|m1eynz,15,11,0|m1eyo0,16,6,1|mcktbz,16,6,1|mcktc0,15,11,0|mkhzzz,15,11,0|mki000,16,6,1|mvavzz,16,6,1|mvaw00,15,11,0|n382nz,15,11,0|n382o0,16,6,1|ne0ynz,16,6,1|ne0yo0,15,11,0|nly5bz,15,11,0|nly5c0,16,6,1|nwr1bz,16,6,1|nwr1c0,15,11,0|o4o7zz,15,11,0|o4o800,16,6,1|ofu2nz,16,6,1|ofu2o0,15,11,0|oneanz,15,11,0|oneao0,16,6,1|oyk5bz,16,6,1|oyk5c0,15,11,0|p64dbz,15,11,0|p64dc0,16,6,1|pha7zz,16,6,1|pha800,15,11,0|pp7enz,15,11,0|pp7eo0,16,6,1|q00anz,16,6,1|q00ao0,15,11,0|q7xhbz,15,11,0|q7xhc0,16,6,1|qiqdbz,16,6,1|qiqdc0,15,11,0|qqnjzz,15,11,0|qqnk00,16,6,1|r1tenz,16,6,1|r1teo0,15,11,0|r9dmnz,15,11,0|r9dmo0,16,6,1|rkjhbz,16,6,1|rkjhc0,15,11,0|rs3pbz,15,11,0|rs3pc0,16,6,1|s39jzz,16,6,1|s39k00,15,11,0|sb6qnz,15,11,0|sb6qo0,16,6,1|slzmnz,16,6,1|slzmo0,15,11,0|stwtbz,15,11,0|stwtc0,16,6,1|t4ppbz,16,6,1|t4ppc0,15,11,0|tcmvzz,15,11,0|tcmw00,16,6,1|tnfrzz,16,6,1|tnfs00,15,11,0|tvcynz,15,11,0|tvcyo0,16,6,1|u6itbz,16,6,1|u6itc0,15,11,0|ue31bz,15,11,0|ue31c0,16,6,1|up8vzz,16,6,1|up8w00,15,11,0|uwt3zz,15,11,0|uwt400,16,6,1|v7yynz,16,6,1|v7yyo0,15,11,0|vfw5bz,15,11,0|vfw5c0,16,6,1|vqp1bz,16,6,1|vqp1c0,15,11,0|vym7zz,15,11,0|vym800,16,6,1|w9f3zz,16,6,1|w9f400,15,11,0|whcanz,15,11,0|whcao0,16,6,1|wsi5bz,16,6,1|wsi5c0,15,11,0|x02dbz,15,11,0|x02dc0,16,6,1|xb87zz,16,6,1|xb8800,15,11,0|xisfzz,15,11,0|xisg00,16,6,1|xtyanz,16,6,1|xtyao0,15,11,0|y1iinz,15,11,0|y1iio0,16,6,1|ycodbz,16,6,1|ycodc0,15,11,0|ykljzz,15,11,0|yklk00,16,6,1|yvefzz,16,6,1|yveg00,15,11,0|z3bmnz,15,11,0|z3bmo0,16,6,1|ze4inz,16,6,1|ze4io0,15,11,0\",\"Europe/Copenhagen|,0,342,0|-15r1bnw,41,342,0|-13nvrnx,41,342,0|-13nvrnw,10,10,0|-rzo2w1,10,10,0|-rzo2w0,11,11,1|-rsir01,11,11,1|-rsir00,10,10,0|-fgqo41,10,10,0|-fgqo40,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cq2nw1,11,11,1|-cq2nw0,10,10,0|-ccr181,10,10,0|-ccr180,11,11,1|-c6f981,11,11,1|-c6f980,10,10,0|-bttjw1,10,10,0|-bttjw0,11,11,1|-bos2k1,11,11,1|-bos2k0,10,10,0|-baqik1,10,10,0|-baqik0,11,11,1|-b61zw1,11,11,1|-b61zw0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Dublin|,0,343,0|-1anxquc,150,344,0|-rzcmls,150,344,0|-rzcmlr,110,345,1|-rsibxs,110,345,1|-rsibxr,1,1,0|-risd41,1,1,0|-risd40,27,10,1|-ragd41,27,10,1|-ragd40,1,1,0|-r0s7s1,1,1,0|-r0s7s0,27,10,1|-qr0d41,27,10,1|-qr0d40,1,1,0|-qhp6g1,1,1,0|-qhp6g0,27,10,1|-q8aag1,27,10,1|-q8aag0,1,1,0|-pyz3s1,1,1,0|-pyz3s0,27,10,1|-po4d41,27,10,1|-po4d40,1,1,0|-pfw2g1,1,1,0|-pfw2g0,27,10,1|-p6h6g1,27,10,1|-p6h6g0,1,1,0|-oxiyg1,1,1,0|-oxiyg0,110,10,1|-onfzs1,110,10,1|-onfzs0,1,1,0|-odd141,1,1,0|-odd140,110,10,1|-o5st41,110,10,1|-o5st40,1,1,0|-nuzx41,1,1,0|-nuzx40,110,10,1|-nmprs1,110,10,1|-nmprs0,1,1,0|-nbwvs1,1,1,0|-nbwvs0,110,10,1|-n39rs1,110,10,1|-n39rs0,1,1,0|-mt6t41,1,1,0|-mt6t40,110,10,1|-mkjp41,110,10,1|-mkjp40,1,1,0|-matp41,1,1,0|-matp40,110,10,1|-m1tmg1,110,10,1|-m1tmg0,1,1,0|-lrdp41,1,1,0|-lrdp40,110,10,1|-liql41,110,10,1|-liql40,1,1,0|-l8nmg1,1,1,0|-l8nmg0,110,10,1|-l00ig1,110,10,1|-l00ig0,1,1,0|-kqaig1,1,1,0|-kqaig0,110,10,1|-khafs1,110,10,1|-khafs0,1,1,0|-k77h41,1,1,0|-k77h40,110,10,1|-jykd41,110,10,1|-jykd40,1,1,0|-joheg1,1,1,0|-joheg0,110,10,1|-jfuag1,110,10,1|-jfuag0,1,1,0|-j64ag1,1,1,0|-j64ag0,110,10,1|-iwr941,110,10,1|-iwr940,1,1,0|-imoag1,1,1,0|-imoag0,110,10,1|-ie16g1,110,10,1|-ie16g0,1,1,0|-i4b6g1,1,1,0|-i4b6g0,110,10,1|-hvb3s1,110,10,1|-hvb3s0,1,1,0|-hl8541,1,1,0|-hl8540,110,10,1|-hcl141,110,10,1|-hcl140,1,1,0|-h2i2g1,1,1,0|-h2i2g0,110,10,1|-gtuyg1,110,10,1|-gtuyg0,1,1,0|-gk4yg1,1,1,0|-gk4yg0,110,10,1|-gb4vs1,110,10,1|-gb4vs0,1,1,0|-g11x41,1,1,0|-g11x40,110,10,1|-fpw2g1,110,10,1|-fpw2g0,1,1,0|-fkul41,1,1,0|-fkul40,110,10,1|-c4md41,110,10,1|-c4md40,1,1,0|-bwc7s1,1,1,0|-bwc7s0,110,10,1|-bkgfs1,110,10,1|-bkgfs0,1,1,0|-bbtbs1,1,1,0|-bbtbs0,110,10,1|-b1qd41,110,10,1|-b1qd40,1,1,0|-att6g1,1,1,0|-att6g0,110,10,1|-aj0ag1,110,10,1|-aj0ag0,1,1,0|-aad6g1,1,1,0|-aad6g0,110,10,1|-a0n6g1,110,10,1|-a0n6g0,1,1,0|-9rn3s1,1,1,0|-9rn3s0,110,10,1|-9hx3s1,110,10,1|-9hx3s0,1,1,0|-98k2g1,1,1,0|-98k2g0,110,10,1|-8yu2g1,110,10,1|-8yu2g0,1,1,0|-8ptzs1,1,1,0|-8ptzs0,110,10,1|-8h6vs1,110,10,1|-8h6vs0,1,1,0|-87gvs1,1,1,0|-87gvs0,110,10,1|-7ygt41,110,10,1|-7ygt40,1,1,0|-7odug1,1,1,0|-7odug0,110,10,1|-7fqqg1,110,10,1|-7fqqg0,1,1,0|-75at41,1,1,0|-75at40,110,10,1|-6wnp41,110,10,1|-6wnp40,1,1,0|-6mxp41,1,1,0|-6mxp40,110,10,1|-6dxmg1,110,10,1|-6dxmg0,1,1,0|-63uns1,1,1,0|-63uns0,110,10,1|-5v7js1,110,10,1|-5v7js0,1,1,0|-5l4l41,1,1,0|-5l4l40,110,10,1|-5chh41,110,10,1|-5chh40,1,1,0|-52rh41,1,1,0|-52rh40,110,10,1|-4treg1,110,10,1|-4treg0,1,1,0|-4krbs1,1,1,0|-4krbs0,110,10,1|-49lh41,110,10,1|-49lh40,1,1,0|-421941,1,1,0|-421940,110,10,1|-3qveg1,110,10,1|-3qveg0,1,1,0|-3iy7s1,1,1,0|-3iy7s0,110,10,1|-385bs1,110,10,1|-385bs0,1,1,0|-30l3s1,1,1,0|-30l3s0,110,10,1|-2pf941,110,10,1|-2pf940,1,1,0|-2hv141,1,1,0|-2hv140,110,10,1|-26p6g1,110,10,1|-26p6g0,1,1,0|-1z4yg1,1,1,0|-1z4yg0,110,10,1|-1nz3s1,110,10,1|-1nz3s0,1,1,0|-1gevs1,1,1,0|-1gevs0,110,10,1|-14w2g1,110,10,1|-14w2g0,1,1,0|-z4ns1,1,1,0|-z4ns0,110,10,1|-m6841,110,10,1|-m6840,110,10,0|yd6vz,110,10,0|yd6w0,1,1,1|15kg7z,1,1,1|15kg80,110,10,0|1h39jz,110,10,0|1h39k0,1,1,1|1oaivz,1,1,1|1oaiw0,110,10,0|1ztc7z,110,10,0|1ztc80,1,1,1|270ljz,1,1,1|270lk0,110,10,0|2ijevz,110,10,0|2ijew0,1,1,1|2pqo7z,1,1,1|2pqo80,110,10,0|319hjz,110,10,0|319hk0,1,1,1|38tpjz,1,1,1|38tpk0,110,10,0|3jzk7z,110,10,0|3jzk80,1,1,1|3rjs7z,1,1,1|3rjs80,110,10,0|42pmvz,110,10,0|42pmw0,1,1,1|4a9uvz,1,1,1|4a9uw0,110,10,0|4lso7z,110,10,0|4lso80,1,1,1|4szxjz,1,1,1|4szxk0,110,10,0|54iqvz,110,10,0|54iqw0,1,1,1|5bq07z,1,1,1|5bq080,110,10,0|5n8tjz,110,10,0|5n8tk0,1,1,1|5v5xfz,1,1,1|5v5xg0,110,10,0|65ytfz,110,10,0|65ytg0,1,1,1|6dw03z,1,1,1|6dw040,110,10,0|6oow3z,110,10,0|6oow40,1,1,1|6wm2rz,1,1,1|6wm2s0,110,10,0|77eyrz,110,10,0|77eys0,1,1,1|7fc5fz,1,1,1|7fc5g0,110,10,0|7qi03z,110,10,0|7qi040,1,1,1|7yf6rz,1,1,1|7yf6s0,110,10,0|8982rz,110,10,0|8982s0,1,1,1|8h59fz,1,1,1|8h59g0,110,10,0|8ry5fz,110,10,0|8ry5g0,1,1,1|8zvc3z,1,1,1|8zvc40,110,10,0|9ao83z,110,10,0|9ao840,1,1,1|9ilerz,1,1,1|9iles0,110,10,0|9tearz,110,10,0|9teas0,1,1,1|a1bhfz,1,1,1|a1bhg0,110,10,0|achc3z,110,10,0|achc40,1,1,1|ak1k3z,1,1,1|ak1k40,110,10,0|av7erz,110,10,0|av7es0,1,1,1|b34lfz,1,1,1|b34lg0,110,10,0|bdxhfz,110,10,0|bdxhg0,1,1,1|bluo3z,1,1,1|bluo40,110,10,0|bwnk3z,110,10,0|bwnk40,1,1,1|c4kqrz,1,1,1|c4kqs0,110,10,0|cfdmrz,110,10,0|cfdms0,1,1,1|cnatfz,1,1,1|cnatg0,110,10,0|cy3pfz,110,10,0|cy3pg0,1,1,1|d60w3z,1,1,1|d60w40,110,10,0|dgts3z,110,10,0|dgts40,1,1,1|dp3xfz,1,1,1|dp3xg0,110,10,0|dzwtfz,110,10,0|dzwtg0,1,1,1|e7u03z,1,1,1|e7u040,110,10,0|eimw3z,110,10,0|eimw40,1,1,1|eqk2rz,1,1,1|eqk2s0,110,10,0|f1cyrz,110,10,0|f1cys0,1,1,1|f9a5fz,1,1,1|f9a5g0,110,10,0|fkg03z,110,10,0|fkg040,1,1,1|fs083z,1,1,1|fs0840,110,10,0|g362rz,110,10,0|g362s0,1,1,1|gaqarz,1,1,1|gaqas0,110,10,0|glw5fz,110,10,0|glw5g0,1,1,1|gttc3z,1,1,1|gttc40,110,10,0|h4m83z,110,10,0|h4m840,1,1,1|hcjerz,1,1,1|hcjes0,110,10,0|hncarz,110,10,0|hncas0,1,1,1|hv9hfz,1,1,1|hv9hg0,110,10,0|i6fc3z,110,10,0|i6fc40,1,1,1|idzk3z,1,1,1|idzk40,110,10,0|ip5erz,110,10,0|ip5es0,1,1,1|iwpmrz,1,1,1|iwpms0,110,10,0|j7vhfz,110,10,0|j7vhg0,1,1,1|jffpfz,1,1,1|jffpg0,110,10,0|jqlk3z,110,10,0|jqlk40,1,1,1|jyiqrz,1,1,1|jyiqs0,110,10,0|k9bmrz,110,10,0|k9bms0,1,1,1|kh8tfz,1,1,1|kh8tg0,110,10,0|ks1pfz,110,10,0|ks1pg0,1,1,1|kzyw3z,1,1,1|kzyw40,110,10,0|lb4qrz,110,10,0|lb4qs0,1,1,1|lioyrz,1,1,1|lioys0,110,10,0|ltutfz,110,10,0|ltutg0,1,1,1|m1f1fz,1,1,1|m1f1g0,110,10,0|mckw3z,110,10,0|mckw40,1,1,1|mki2rz,1,1,1|mki2s0,110,10,0|mvayrz,110,10,0|mvays0,1,1,1|n385fz,1,1,1|n385g0,110,10,0|ne11fz,110,10,0|ne11g0,1,1,1|nly83z,1,1,1|nly840,110,10,0|nwr43z,110,10,0|nwr440,1,1,1|o4oarz,1,1,1|o4oas0,110,10,0|ofu5fz,110,10,0|ofu5g0,1,1,1|onedfz,1,1,1|onedg0,110,10,0|oyk83z,110,10,0|oyk840,1,1,1|p64g3z,1,1,1|p64g40,110,10,0|phaarz,110,10,0|phaas0,1,1,1|pp7hfz,1,1,1|pp7hg0,110,10,0|q00dfz,110,10,0|q00dg0,1,1,1|q7xk3z,1,1,1|q7xk40,110,10,0|qiqg3z,110,10,0|qiqg40,1,1,1|qqnmrz,1,1,1|qqnms0,110,10,0|r1thfz,110,10,0|r1thg0,1,1,1|r9dpfz,1,1,1|r9dpg0,110,10,0|rkjk3z,110,10,0|rkjk40,1,1,1|rs3s3z,1,1,1|rs3s40,110,10,0|s39mrz,110,10,0|s39ms0,1,1,1|sb6tfz,1,1,1|sb6tg0,110,10,0|slzpfz,110,10,0|slzpg0,1,1,1|stww3z,1,1,1|stww40,110,10,0|t4ps3z,110,10,0|t4ps40,1,1,1|tcmyrz,1,1,1|tcmys0,110,10,0|tnfurz,110,10,0|tnfus0,1,1,1|tvd1fz,1,1,1|tvd1g0,110,10,0|u6iw3z,110,10,0|u6iw40,1,1,1|ue343z,1,1,1|ue3440,110,10,0|up8yrz,110,10,0|up8ys0,1,1,1|uwt6rz,1,1,1|uwt6s0,110,10,0|v7z1fz,110,10,0|v7z1g0,1,1,1|vfw83z,1,1,1|vfw840,110,10,0|vqp43z,110,10,0|vqp440,1,1,1|vymarz,1,1,1|vymas0,110,10,0|w9f6rz,110,10,0|w9f6s0,1,1,1|whcdfz,1,1,1|whcdg0,110,10,0|wsi83z,110,10,0|wsi840,1,1,1|x02g3z,1,1,1|x02g40,110,10,0|xb8arz,110,10,0|xb8as0,1,1,1|xisirz,1,1,1|xisis0,110,10,0|xtydfz,110,10,0|xtydg0,1,1,1|y1ilfz,1,1,1|y1ilg0,110,10,0|ycog3z,110,10,0|ycog40,1,1,1|yklmrz,1,1,1|yklms0,110,10,0|yveirz,110,10,0|yveis0,1,1,1|z3bpfz,1,1,1|z3bpg0,110,10,0|ze4lfz,110,10,0|ze4lg0,1,1,1\",\"Europe/Gibraltar|,0,346,0|-1anxr0c,1,1,0|-rzcns1,1,1,0|-rzcns0,27,10,1|-rsid41,27,10,1|-rsid40,1,1,0|-risd41,1,1,0|-risd40,27,10,1|-ragd41,27,10,1|-ragd40,1,1,0|-r0s7s1,1,1,0|-r0s7s0,27,10,1|-qr0d41,27,10,1|-qr0d40,1,1,0|-qhp6g1,1,1,0|-qhp6g0,27,10,1|-q8aag1,27,10,1|-q8aag0,1,1,0|-pyz3s1,1,1,0|-pyz3s0,27,10,1|-po4d41,27,10,1|-po4d40,1,1,0|-pfw2g1,1,1,0|-pfw2g0,27,10,1|-p6h6g1,27,10,1|-p6h6g0,1,1,0|-oxiyg1,1,1,0|-oxiyg0,27,10,1|-onfzs1,27,10,1|-onfzs0,1,1,0|-odd141,1,1,0|-odd140,27,10,1|-o5st41,27,10,1|-o5st40,1,1,0|-nuzx41,1,1,0|-nuzx40,27,10,1|-nmprs1,27,10,1|-nmprs0,1,1,0|-nbwvs1,1,1,0|-nbwvs0,27,10,1|-n39rs1,27,10,1|-n39rs0,1,1,0|-mt6t41,1,1,0|-mt6t40,27,10,1|-mkjp41,27,10,1|-mkjp40,1,1,0|-matp41,1,1,0|-matp40,27,10,1|-m1tmg1,27,10,1|-m1tmg0,1,1,0|-lrdp41,1,1,0|-lrdp40,27,10,1|-liql41,27,10,1|-liql40,1,1,0|-l8nmg1,1,1,0|-l8nmg0,27,10,1|-l00ig1,27,10,1|-l00ig0,1,1,0|-kqaig1,1,1,0|-kqaig0,27,10,1|-khafs1,27,10,1|-khafs0,1,1,0|-k77h41,1,1,0|-k77h40,27,10,1|-jykd41,27,10,1|-jykd40,1,1,0|-joheg1,1,1,0|-joheg0,27,10,1|-jfuag1,27,10,1|-jfuag0,1,1,0|-j64ag1,1,1,0|-j64ag0,27,10,1|-iwr941,27,10,1|-iwr940,1,1,0|-imoag1,1,1,0|-imoag0,27,10,1|-ie16g1,27,10,1|-ie16g0,1,1,0|-i4b6g1,1,1,0|-i4b6g0,27,10,1|-hvb3s1,27,10,1|-hvb3s0,1,1,0|-hl8541,1,1,0|-hl8540,27,10,1|-hcl141,27,10,1|-hcl140,1,1,0|-h2i2g1,1,1,0|-h2i2g0,27,10,1|-gtuyg1,27,10,1|-gtuyg0,1,1,0|-gk4yg1,1,1,0|-gk4yg0,27,10,1|-gb4vs1,27,10,1|-gb4vs0,1,1,0|-g11x41,1,1,0|-g11x40,27,10,1|-fpw2g1,27,10,1|-fpw2g0,1,1,0|-fkul41,1,1,0|-fkul40,27,10,1|-eyiyk1,27,10,1|-eyiyk0,151,11,1|-ethh81,151,11,1|-ethh80,27,10,1|-eh8qk1,27,10,1|-eh8qk0,151,11,1|-earek1,151,11,1|-earek0,27,10,1|-dyinw1,27,10,1|-dyinw0,151,11,1|-drod81,151,11,1|-drod80,27,10,1|-dfsl81,27,10,1|-dfsl80,151,11,1|-d75h81,151,11,1|-d75h80,27,10,1|-cx0nw1,27,10,1|-cx0nw0,151,11,1|-cro2k1,151,11,1|-cro2k0,27,10,1|-cncfs1,27,10,1|-cncfs0,1,1,0|-cdmfs1,1,1,0|-cdmfs0,27,10,1|-c4md41,27,10,1|-c4md40,1,1,0|-bwc7s1,1,1,0|-bwc7s0,27,10,1|-buwfw1,27,10,1|-buwfw0,151,11,1|-bos2k1,151,11,1|-bos2k0,27,10,1|-bkgfs1,27,10,1|-bkgfs0,1,1,0|-bdm541,1,1,0|-bdm540,27,10,1|-b1qd41,27,10,1|-b1qd40,1,1,0|-att6g1,1,1,0|-att6g0,27,10,1|-aj0ag1,27,10,1|-aj0ag0,1,1,0|-aad6g1,1,1,0|-aad6g0,27,10,1|-a0n6g1,27,10,1|-a0n6g0,1,1,0|-9rn3s1,1,1,0|-9rn3s0,27,10,1|-9hx3s1,27,10,1|-9hx3s0,1,1,0|-98k2g1,1,1,0|-98k2g0,27,10,1|-8yu2g1,27,10,1|-8yu2g0,1,1,0|-8ptzs1,1,1,0|-8ptzs0,27,10,1|-8h6vs1,27,10,1|-8h6vs0,1,1,0|-87gvs1,1,1,0|-87gvs0,27,10,1|-7ygt41,27,10,1|-7ygt40,1,1,0|-7odug1,1,1,0|-7odug0,27,10,1|-7fqqg1,27,10,1|-7fqqg0,1,1,0|-75at41,1,1,0|-75at40,27,10,1|-6wnp41,27,10,1|-6wnp40,1,1,0|-6mxp41,1,1,0|-6mxp40,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Guernsey|,0,347,0|-1rprx9x,1,1,0|-rzcns1,1,1,0|-rzcns0,27,10,1|-rsid41,27,10,1|-rsid40,1,1,0|-risd41,1,1,0|-risd40,27,10,1|-ragd41,27,10,1|-ragd40,1,1,0|-r0s7s1,1,1,0|-r0s7s0,27,10,1|-qr0d41,27,10,1|-qr0d40,1,1,0|-qhp6g1,1,1,0|-qhp6g0,27,10,1|-q8aag1,27,10,1|-q8aag0,1,1,0|-pyz3s1,1,1,0|-pyz3s0,27,10,1|-po4d41,27,10,1|-po4d40,1,1,0|-pfw2g1,1,1,0|-pfw2g0,27,10,1|-p6h6g1,27,10,1|-p6h6g0,1,1,0|-oxiyg1,1,1,0|-oxiyg0,27,10,1|-onfzs1,27,10,1|-onfzs0,1,1,0|-odd141,1,1,0|-odd140,27,10,1|-o5st41,27,10,1|-o5st40,1,1,0|-nuzx41,1,1,0|-nuzx40,27,10,1|-nmprs1,27,10,1|-nmprs0,1,1,0|-nbwvs1,1,1,0|-nbwvs0,27,10,1|-n39rs1,27,10,1|-n39rs0,1,1,0|-mt6t41,1,1,0|-mt6t40,27,10,1|-mkjp41,27,10,1|-mkjp40,1,1,0|-matp41,1,1,0|-matp40,27,10,1|-m1tmg1,27,10,1|-m1tmg0,1,1,0|-lrdp41,1,1,0|-lrdp40,27,10,1|-liql41,27,10,1|-liql40,1,1,0|-l8nmg1,1,1,0|-l8nmg0,27,10,1|-l00ig1,27,10,1|-l00ig0,1,1,0|-kqaig1,1,1,0|-kqaig0,27,10,1|-khafs1,27,10,1|-khafs0,1,1,0|-k77h41,1,1,0|-k77h40,27,10,1|-jykd41,27,10,1|-jykd40,1,1,0|-joheg1,1,1,0|-joheg0,27,10,1|-jfuag1,27,10,1|-jfuag0,1,1,0|-j64ag1,1,1,0|-j64ag0,27,10,1|-iwr941,27,10,1|-iwr940,1,1,0|-imoag1,1,1,0|-imoag0,27,10,1|-ie16g1,27,10,1|-ie16g0,1,1,0|-i4b6g1,1,1,0|-i4b6g0,27,10,1|-hvb3s1,27,10,1|-hvb3s0,1,1,0|-hl8541,1,1,0|-hl8540,27,10,1|-hcl141,27,10,1|-hcl140,1,1,0|-h2i2g1,1,1,0|-h2i2g0,27,10,1|-gtuyg1,27,10,1|-gtuyg0,1,1,0|-gk4yg1,1,1,0|-gk4yg0,27,10,1|-gb4vs1,27,10,1|-gb4vs0,1,1,0|-g11x41,1,1,0|-g11x40,27,10,1|-fpw2g1,27,10,1|-fpw2g0,1,1,0|-fkul41,1,1,0|-fkul40,27,10,1|-eyiyk1,27,10,1|-eyiyk0,151,11,1|-ethh81,151,11,1|-ethh80,27,10,1|-eh8qk1,27,10,1|-eh8qk0,151,11,1|-earek1,151,11,1|-earek0,27,10,1|-dyinw1,27,10,1|-dyinw0,151,11,1|-drod81,151,11,1|-drod80,27,10,1|-dfsl81,27,10,1|-dfsl80,151,11,1|-d75h81,151,11,1|-d75h80,27,10,1|-cx0nw1,27,10,1|-cx0nw0,151,11,1|-cro2k1,151,11,1|-cro2k0,27,10,1|-cncfs1,27,10,1|-cncfs0,1,1,0|-cdmfs1,1,1,0|-cdmfs0,27,10,1|-c4md41,27,10,1|-c4md40,1,1,0|-bwc7s1,1,1,0|-bwc7s0,27,10,1|-buwfw1,27,10,1|-buwfw0,151,11,1|-bos2k1,151,11,1|-bos2k0,27,10,1|-bkgfs1,27,10,1|-bkgfs0,1,1,0|-bdm541,1,1,0|-bdm540,27,10,1|-b1qd41,27,10,1|-b1qd40,1,1,0|-att6g1,1,1,0|-att6g0,27,10,1|-aj0ag1,27,10,1|-aj0ag0,1,1,0|-aad6g1,1,1,0|-aad6g0,27,10,1|-a0n6g1,27,10,1|-a0n6g0,1,1,0|-9rn3s1,1,1,0|-9rn3s0,27,10,1|-9hx3s1,27,10,1|-9hx3s0,1,1,0|-98k2g1,1,1,0|-98k2g0,27,10,1|-8yu2g1,27,10,1|-8yu2g0,1,1,0|-8ptzs1,1,1,0|-8ptzs0,27,10,1|-8h6vs1,27,10,1|-8h6vs0,1,1,0|-87gvs1,1,1,0|-87gvs0,27,10,1|-7ygt41,27,10,1|-7ygt40,1,1,0|-7odug1,1,1,0|-7odug0,27,10,1|-7fqqg1,27,10,1|-7fqqg0,1,1,0|-75at41,1,1,0|-75at40,27,10,1|-6wnp41,27,10,1|-6wnp40,1,1,0|-6mxp41,1,1,0|-6mxp40,27,10,1|-6dxmg1,27,10,1|-6dxmg0,1,1,0|-63uns1,1,1,0|-63uns0,27,10,1|-5v7js1,27,10,1|-5v7js0,1,1,0|-5l4l41,1,1,0|-5l4l40,27,10,1|-5chh41,27,10,1|-5chh40,1,1,0|-52rh41,1,1,0|-52rh40,27,10,1|-4treg1,27,10,1|-4treg0,1,1,0|-4krbs1,1,1,0|-4krbs0,27,10,1|-49lh41,27,10,1|-49lh40,1,1,0|-421941,1,1,0|-421940,27,10,1|-3qveg1,27,10,1|-3qveg0,1,1,0|-3iy7s1,1,1,0|-3iy7s0,27,10,1|-385bs1,27,10,1|-385bs0,1,1,0|-30l3s1,1,1,0|-30l3s0,27,10,1|-2pf941,27,10,1|-2pf940,1,1,0|-2hv141,1,1,0|-2hv140,27,10,1|-26p6g1,27,10,1|-26p6g0,1,1,0|-1z4yg1,1,1,0|-1z4yg0,27,10,1|-1nz3s1,27,10,1|-1nz3s0,1,1,0|-1gevs1,1,1,0|-1gevs0,27,10,1|-14w2g1,27,10,1|-14w2g0,1,1,0|-z4ns1,1,1,0|-z4ns0,27,10,1|-m6841,27,10,1|-m6840,27,10,0|yd6vz,27,10,0|yd6w0,1,1,0|15kg7z,1,1,0|15kg80,27,10,1|1h39jz,27,10,1|1h39k0,1,1,0|1oaivz,1,1,0|1oaiw0,27,10,1|1ztc7z,27,10,1|1ztc80,1,1,0|270ljz,1,1,0|270lk0,27,10,1|2ijevz,27,10,1|2ijew0,1,1,0|2pqo7z,1,1,0|2pqo80,27,10,1|319hjz,27,10,1|319hk0,1,1,0|38tpjz,1,1,0|38tpk0,27,10,1|3jzk7z,27,10,1|3jzk80,1,1,0|3rjs7z,1,1,0|3rjs80,27,10,1|42pmvz,27,10,1|42pmw0,1,1,0|4a9uvz,1,1,0|4a9uw0,27,10,1|4lso7z,27,10,1|4lso80,1,1,0|4szxjz,1,1,0|4szxk0,27,10,1|54iqvz,27,10,1|54iqw0,1,1,0|5bq07z,1,1,0|5bq080,27,10,1|5n8tjz,27,10,1|5n8tk0,1,1,0|5v5xfz,1,1,0|5v5xg0,27,10,1|65ytfz,27,10,1|65ytg0,1,1,0|6dw03z,1,1,0|6dw040,27,10,1|6oow3z,27,10,1|6oow40,1,1,0|6wm2rz,1,1,0|6wm2s0,27,10,1|77eyrz,27,10,1|77eys0,1,1,0|7fc5fz,1,1,0|7fc5g0,27,10,1|7qi03z,27,10,1|7qi040,1,1,0|7yf6rz,1,1,0|7yf6s0,27,10,1|8982rz,27,10,1|8982s0,1,1,0|8h59fz,1,1,0|8h59g0,27,10,1|8ry5fz,27,10,1|8ry5g0,1,1,0|8zvc3z,1,1,0|8zvc40,27,10,1|9ao83z,27,10,1|9ao840,1,1,0|9ilerz,1,1,0|9iles0,27,10,1|9tearz,27,10,1|9teas0,1,1,0|a1bhfz,1,1,0|a1bhg0,27,10,1|achc3z,27,10,1|achc40,1,1,0|ak1k3z,1,1,0|ak1k40,27,10,1|av7erz,27,10,1|av7es0,1,1,0|b34lfz,1,1,0|b34lg0,27,10,1|bdxhfz,27,10,1|bdxhg0,1,1,0|bluo3z,1,1,0|bluo40,27,10,1|bwnk3z,27,10,1|bwnk40,1,1,0|c4kqrz,1,1,0|c4kqs0,27,10,1|cfdmrz,27,10,1|cfdms0,1,1,0|cnatfz,1,1,0|cnatg0,27,10,1|cy3pfz,27,10,1|cy3pg0,1,1,0|d60w3z,1,1,0|d60w40,27,10,1|dgts3z,27,10,1|dgts40,1,1,0|dp3xfz,1,1,0|dp3xg0,27,10,1|dzwtfz,27,10,1|dzwtg0,1,1,0|e7u03z,1,1,0|e7u040,27,10,1|eimw3z,27,10,1|eimw40,1,1,0|eqk2rz,1,1,0|eqk2s0,27,10,1|f1cyrz,27,10,1|f1cys0,1,1,0|f9a5fz,1,1,0|f9a5g0,27,10,1|fkg03z,27,10,1|fkg040,1,1,0|fs083z,1,1,0|fs0840,27,10,1|g362rz,27,10,1|g362s0,1,1,0|gaqarz,1,1,0|gaqas0,27,10,1|glw5fz,27,10,1|glw5g0,1,1,0|gttc3z,1,1,0|gttc40,27,10,1|h4m83z,27,10,1|h4m840,1,1,0|hcjerz,1,1,0|hcjes0,27,10,1|hncarz,27,10,1|hncas0,1,1,0|hv9hfz,1,1,0|hv9hg0,27,10,1|i6fc3z,27,10,1|i6fc40,1,1,0|idzk3z,1,1,0|idzk40,27,10,1|ip5erz,27,10,1|ip5es0,1,1,0|iwpmrz,1,1,0|iwpms0,27,10,1|j7vhfz,27,10,1|j7vhg0,1,1,0|jffpfz,1,1,0|jffpg0,27,10,1|jqlk3z,27,10,1|jqlk40,1,1,0|jyiqrz,1,1,0|jyiqs0,27,10,1|k9bmrz,27,10,1|k9bms0,1,1,0|kh8tfz,1,1,0|kh8tg0,27,10,1|ks1pfz,27,10,1|ks1pg0,1,1,0|kzyw3z,1,1,0|kzyw40,27,10,1|lb4qrz,27,10,1|lb4qs0,1,1,0|lioyrz,1,1,0|lioys0,27,10,1|ltutfz,27,10,1|ltutg0,1,1,0|m1f1fz,1,1,0|m1f1g0,27,10,1|mckw3z,27,10,1|mckw40,1,1,0|mki2rz,1,1,0|mki2s0,27,10,1|mvayrz,27,10,1|mvays0,1,1,0|n385fz,1,1,0|n385g0,27,10,1|ne11fz,27,10,1|ne11g0,1,1,0|nly83z,1,1,0|nly840,27,10,1|nwr43z,27,10,1|nwr440,1,1,0|o4oarz,1,1,0|o4oas0,27,10,1|ofu5fz,27,10,1|ofu5g0,1,1,0|onedfz,1,1,0|onedg0,27,10,1|oyk83z,27,10,1|oyk840,1,1,0|p64g3z,1,1,0|p64g40,27,10,1|phaarz,27,10,1|phaas0,1,1,0|pp7hfz,1,1,0|pp7hg0,27,10,1|q00dfz,27,10,1|q00dg0,1,1,0|q7xk3z,1,1,0|q7xk40,27,10,1|qiqg3z,27,10,1|qiqg40,1,1,0|qqnmrz,1,1,0|qqnms0,27,10,1|r1thfz,27,10,1|r1thg0,1,1,0|r9dpfz,1,1,0|r9dpg0,27,10,1|rkjk3z,27,10,1|rkjk40,1,1,0|rs3s3z,1,1,0|rs3s40,27,10,1|s39mrz,27,10,1|s39ms0,1,1,0|sb6tfz,1,1,0|sb6tg0,27,10,1|slzpfz,27,10,1|slzpg0,1,1,0|stww3z,1,1,0|stww40,27,10,1|t4ps3z,27,10,1|t4ps40,1,1,0|tcmyrz,1,1,0|tcmys0,27,10,1|tnfurz,27,10,1|tnfus0,1,1,0|tvd1fz,1,1,0|tvd1g0,27,10,1|u6iw3z,27,10,1|u6iw40,1,1,0|ue343z,1,1,0|ue3440,27,10,1|up8yrz,27,10,1|up8ys0,1,1,0|uwt6rz,1,1,0|uwt6s0,27,10,1|v7z1fz,27,10,1|v7z1g0,1,1,0|vfw83z,1,1,0|vfw840,27,10,1|vqp43z,27,10,1|vqp440,1,1,0|vymarz,1,1,0|vymas0,27,10,1|w9f6rz,27,10,1|w9f6s0,1,1,0|whcdfz,1,1,0|whcdg0,27,10,1|wsi83z,27,10,1|wsi840,1,1,0|x02g3z,1,1,0|x02g40,27,10,1|xb8arz,27,10,1|xb8as0,1,1,0|xisirz,1,1,0|xisis0,27,10,1|xtydfz,27,10,1|xtydg0,1,1,0|y1ilfz,1,1,0|y1ilg0,27,10,1|ycog3z,27,10,1|ycog40,1,1,0|yklmrz,1,1,0|yklms0,27,10,1|yveirz,27,10,1|yveis0,1,1,0|z3bpfz,1,1,0|z3bpg0,27,10,1|ze4lfz,27,10,1|ze4lg0,1,1,0\",\"Europe/Helsinki|,0,348,0|-1bss9yd,77,348,0|-peghye,77,348,0|-peghyd,15,11,0|-ehco81,15,11,0|-ehco80,16,6,1|-e7vxk1,16,6,1|-e7vxk0,15,11,0|5v5unz,15,11,0|5v5uo0,16,6,1|64ivzz,16,6,1|64iw00,15,11,0|6dvxbz,15,11,0|6dvxc0,16,6,1|6n8ynz,16,6,1|6n8yo0,15,11,0|6wm2rz,15,11,0|6wm2s0,16,6,1|75z43z,16,6,1|75z440,15,11,0|7fc5fz,15,11,0|7fc5g0,16,6,1|7p25fz,16,6,1|7p25g0,15,11,0|7yf6rz,15,11,0|7yf6s0,16,6,1|87s83z,16,6,1|87s840,15,11,0|8h59fz,15,11,0|8h59g0,16,6,1|8qiarz,16,6,1|8qias0,15,11,0|8zvc3z,15,11,0|8zvc40,16,6,1|998dfz,16,6,1|998dg0,15,11,0|9ilerz,15,11,0|9iles0,16,6,1|9ryg3z,16,6,1|9ryg40,15,11,0|a1bhfz,15,11,0|a1bhg0,16,6,1|aaoirz,16,6,1|aaois0,15,11,0|ak1k3z,15,11,0|ak1k40,16,6,1|atrk3z,16,6,1|atrk40,15,11,0|b34lfz,15,11,0|b34lg0,16,6,1|bchmrz,16,6,1|bchms0,15,11,0|bluo3z,15,11,0|bluo40,16,6,1|bv7pfz,16,6,1|bv7pg0,15,11,0|c4kqrz,15,11,0|c4kqs0,16,6,1|cdxs3z,16,6,1|cdxs40,15,11,0|cnatfz,15,11,0|cnatg0,16,6,1|cwnurz,16,6,1|cwnus0,15,11,0|d60w3z,15,11,0|d60w40,16,6,1|dfdxfz,16,6,1|dfdxg0,15,11,0|dp3xfz,15,11,0|dp3xg0,16,6,1|dzwtfz,16,6,1|dzwtg0,15,11,0|e7u03z,15,11,0|e7u040,16,6,1|eimw3z,16,6,1|eimw40,15,11,0|eqk2rz,15,11,0|eqk2s0,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|fs083z,15,11,0|fs0840,16,6,1|g362rz,16,6,1|g362s0,15,11,0|gaqarz,15,11,0|gaqas0,16,6,1|glw5fz,16,6,1|glw5g0,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|ofu5fz,16,6,1|ofu5g0,15,11,0|onedfz,15,11,0|onedg0,16,6,1|oyk83z,16,6,1|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Europe/Isle_of_Man|,0,347,0|-1rprx9x,1,1,0|-rzcns1,1,1,0|-rzcns0,27,10,1|-rsid41,27,10,1|-rsid40,1,1,0|-risd41,1,1,0|-risd40,27,10,1|-ragd41,27,10,1|-ragd40,1,1,0|-r0s7s1,1,1,0|-r0s7s0,27,10,1|-qr0d41,27,10,1|-qr0d40,1,1,0|-qhp6g1,1,1,0|-qhp6g0,27,10,1|-q8aag1,27,10,1|-q8aag0,1,1,0|-pyz3s1,1,1,0|-pyz3s0,27,10,1|-po4d41,27,10,1|-po4d40,1,1,0|-pfw2g1,1,1,0|-pfw2g0,27,10,1|-p6h6g1,27,10,1|-p6h6g0,1,1,0|-oxiyg1,1,1,0|-oxiyg0,27,10,1|-onfzs1,27,10,1|-onfzs0,1,1,0|-odd141,1,1,0|-odd140,27,10,1|-o5st41,27,10,1|-o5st40,1,1,0|-nuzx41,1,1,0|-nuzx40,27,10,1|-nmprs1,27,10,1|-nmprs0,1,1,0|-nbwvs1,1,1,0|-nbwvs0,27,10,1|-n39rs1,27,10,1|-n39rs0,1,1,0|-mt6t41,1,1,0|-mt6t40,27,10,1|-mkjp41,27,10,1|-mkjp40,1,1,0|-matp41,1,1,0|-matp40,27,10,1|-m1tmg1,27,10,1|-m1tmg0,1,1,0|-lrdp41,1,1,0|-lrdp40,27,10,1|-liql41,27,10,1|-liql40,1,1,0|-l8nmg1,1,1,0|-l8nmg0,27,10,1|-l00ig1,27,10,1|-l00ig0,1,1,0|-kqaig1,1,1,0|-kqaig0,27,10,1|-khafs1,27,10,1|-khafs0,1,1,0|-k77h41,1,1,0|-k77h40,27,10,1|-jykd41,27,10,1|-jykd40,1,1,0|-joheg1,1,1,0|-joheg0,27,10,1|-jfuag1,27,10,1|-jfuag0,1,1,0|-j64ag1,1,1,0|-j64ag0,27,10,1|-iwr941,27,10,1|-iwr940,1,1,0|-imoag1,1,1,0|-imoag0,27,10,1|-ie16g1,27,10,1|-ie16g0,1,1,0|-i4b6g1,1,1,0|-i4b6g0,27,10,1|-hvb3s1,27,10,1|-hvb3s0,1,1,0|-hl8541,1,1,0|-hl8540,27,10,1|-hcl141,27,10,1|-hcl140,1,1,0|-h2i2g1,1,1,0|-h2i2g0,27,10,1|-gtuyg1,27,10,1|-gtuyg0,1,1,0|-gk4yg1,1,1,0|-gk4yg0,27,10,1|-gb4vs1,27,10,1|-gb4vs0,1,1,0|-g11x41,1,1,0|-g11x40,27,10,1|-fpw2g1,27,10,1|-fpw2g0,1,1,0|-fkul41,1,1,0|-fkul40,27,10,1|-eyiyk1,27,10,1|-eyiyk0,151,11,1|-ethh81,151,11,1|-ethh80,27,10,1|-eh8qk1,27,10,1|-eh8qk0,151,11,1|-earek1,151,11,1|-earek0,27,10,1|-dyinw1,27,10,1|-dyinw0,151,11,1|-drod81,151,11,1|-drod80,27,10,1|-dfsl81,27,10,1|-dfsl80,151,11,1|-d75h81,151,11,1|-d75h80,27,10,1|-cx0nw1,27,10,1|-cx0nw0,151,11,1|-cro2k1,151,11,1|-cro2k0,27,10,1|-cncfs1,27,10,1|-cncfs0,1,1,0|-cdmfs1,1,1,0|-cdmfs0,27,10,1|-c4md41,27,10,1|-c4md40,1,1,0|-bwc7s1,1,1,0|-bwc7s0,27,10,1|-buwfw1,27,10,1|-buwfw0,151,11,1|-bos2k1,151,11,1|-bos2k0,27,10,1|-bkgfs1,27,10,1|-bkgfs0,1,1,0|-bdm541,1,1,0|-bdm540,27,10,1|-b1qd41,27,10,1|-b1qd40,1,1,0|-att6g1,1,1,0|-att6g0,27,10,1|-aj0ag1,27,10,1|-aj0ag0,1,1,0|-aad6g1,1,1,0|-aad6g0,27,10,1|-a0n6g1,27,10,1|-a0n6g0,1,1,0|-9rn3s1,1,1,0|-9rn3s0,27,10,1|-9hx3s1,27,10,1|-9hx3s0,1,1,0|-98k2g1,1,1,0|-98k2g0,27,10,1|-8yu2g1,27,10,1|-8yu2g0,1,1,0|-8ptzs1,1,1,0|-8ptzs0,27,10,1|-8h6vs1,27,10,1|-8h6vs0,1,1,0|-87gvs1,1,1,0|-87gvs0,27,10,1|-7ygt41,27,10,1|-7ygt40,1,1,0|-7odug1,1,1,0|-7odug0,27,10,1|-7fqqg1,27,10,1|-7fqqg0,1,1,0|-75at41,1,1,0|-75at40,27,10,1|-6wnp41,27,10,1|-6wnp40,1,1,0|-6mxp41,1,1,0|-6mxp40,27,10,1|-6dxmg1,27,10,1|-6dxmg0,1,1,0|-63uns1,1,1,0|-63uns0,27,10,1|-5v7js1,27,10,1|-5v7js0,1,1,0|-5l4l41,1,1,0|-5l4l40,27,10,1|-5chh41,27,10,1|-5chh40,1,1,0|-52rh41,1,1,0|-52rh40,27,10,1|-4treg1,27,10,1|-4treg0,1,1,0|-4krbs1,1,1,0|-4krbs0,27,10,1|-49lh41,27,10,1|-49lh40,1,1,0|-421941,1,1,0|-421940,27,10,1|-3qveg1,27,10,1|-3qveg0,1,1,0|-3iy7s1,1,1,0|-3iy7s0,27,10,1|-385bs1,27,10,1|-385bs0,1,1,0|-30l3s1,1,1,0|-30l3s0,27,10,1|-2pf941,27,10,1|-2pf940,1,1,0|-2hv141,1,1,0|-2hv140,27,10,1|-26p6g1,27,10,1|-26p6g0,1,1,0|-1z4yg1,1,1,0|-1z4yg0,27,10,1|-1nz3s1,27,10,1|-1nz3s0,1,1,0|-1gevs1,1,1,0|-1gevs0,27,10,1|-14w2g1,27,10,1|-14w2g0,1,1,0|-z4ns1,1,1,0|-z4ns0,27,10,1|-m6841,27,10,1|-m6840,27,10,0|yd6vz,27,10,0|yd6w0,1,1,0|15kg7z,1,1,0|15kg80,27,10,1|1h39jz,27,10,1|1h39k0,1,1,0|1oaivz,1,1,0|1oaiw0,27,10,1|1ztc7z,27,10,1|1ztc80,1,1,0|270ljz,1,1,0|270lk0,27,10,1|2ijevz,27,10,1|2ijew0,1,1,0|2pqo7z,1,1,0|2pqo80,27,10,1|319hjz,27,10,1|319hk0,1,1,0|38tpjz,1,1,0|38tpk0,27,10,1|3jzk7z,27,10,1|3jzk80,1,1,0|3rjs7z,1,1,0|3rjs80,27,10,1|42pmvz,27,10,1|42pmw0,1,1,0|4a9uvz,1,1,0|4a9uw0,27,10,1|4lso7z,27,10,1|4lso80,1,1,0|4szxjz,1,1,0|4szxk0,27,10,1|54iqvz,27,10,1|54iqw0,1,1,0|5bq07z,1,1,0|5bq080,27,10,1|5n8tjz,27,10,1|5n8tk0,1,1,0|5v5xfz,1,1,0|5v5xg0,27,10,1|65ytfz,27,10,1|65ytg0,1,1,0|6dw03z,1,1,0|6dw040,27,10,1|6oow3z,27,10,1|6oow40,1,1,0|6wm2rz,1,1,0|6wm2s0,27,10,1|77eyrz,27,10,1|77eys0,1,1,0|7fc5fz,1,1,0|7fc5g0,27,10,1|7qi03z,27,10,1|7qi040,1,1,0|7yf6rz,1,1,0|7yf6s0,27,10,1|8982rz,27,10,1|8982s0,1,1,0|8h59fz,1,1,0|8h59g0,27,10,1|8ry5fz,27,10,1|8ry5g0,1,1,0|8zvc3z,1,1,0|8zvc40,27,10,1|9ao83z,27,10,1|9ao840,1,1,0|9ilerz,1,1,0|9iles0,27,10,1|9tearz,27,10,1|9teas0,1,1,0|a1bhfz,1,1,0|a1bhg0,27,10,1|achc3z,27,10,1|achc40,1,1,0|ak1k3z,1,1,0|ak1k40,27,10,1|av7erz,27,10,1|av7es0,1,1,0|b34lfz,1,1,0|b34lg0,27,10,1|bdxhfz,27,10,1|bdxhg0,1,1,0|bluo3z,1,1,0|bluo40,27,10,1|bwnk3z,27,10,1|bwnk40,1,1,0|c4kqrz,1,1,0|c4kqs0,27,10,1|cfdmrz,27,10,1|cfdms0,1,1,0|cnatfz,1,1,0|cnatg0,27,10,1|cy3pfz,27,10,1|cy3pg0,1,1,0|d60w3z,1,1,0|d60w40,27,10,1|dgts3z,27,10,1|dgts40,1,1,0|dp3xfz,1,1,0|dp3xg0,27,10,1|dzwtfz,27,10,1|dzwtg0,1,1,0|e7u03z,1,1,0|e7u040,27,10,1|eimw3z,27,10,1|eimw40,1,1,0|eqk2rz,1,1,0|eqk2s0,27,10,1|f1cyrz,27,10,1|f1cys0,1,1,0|f9a5fz,1,1,0|f9a5g0,27,10,1|fkg03z,27,10,1|fkg040,1,1,0|fs083z,1,1,0|fs0840,27,10,1|g362rz,27,10,1|g362s0,1,1,0|gaqarz,1,1,0|gaqas0,27,10,1|glw5fz,27,10,1|glw5g0,1,1,0|gttc3z,1,1,0|gttc40,27,10,1|h4m83z,27,10,1|h4m840,1,1,0|hcjerz,1,1,0|hcjes0,27,10,1|hncarz,27,10,1|hncas0,1,1,0|hv9hfz,1,1,0|hv9hg0,27,10,1|i6fc3z,27,10,1|i6fc40,1,1,0|idzk3z,1,1,0|idzk40,27,10,1|ip5erz,27,10,1|ip5es0,1,1,0|iwpmrz,1,1,0|iwpms0,27,10,1|j7vhfz,27,10,1|j7vhg0,1,1,0|jffpfz,1,1,0|jffpg0,27,10,1|jqlk3z,27,10,1|jqlk40,1,1,0|jyiqrz,1,1,0|jyiqs0,27,10,1|k9bmrz,27,10,1|k9bms0,1,1,0|kh8tfz,1,1,0|kh8tg0,27,10,1|ks1pfz,27,10,1|ks1pg0,1,1,0|kzyw3z,1,1,0|kzyw40,27,10,1|lb4qrz,27,10,1|lb4qs0,1,1,0|lioyrz,1,1,0|lioys0,27,10,1|ltutfz,27,10,1|ltutg0,1,1,0|m1f1fz,1,1,0|m1f1g0,27,10,1|mckw3z,27,10,1|mckw40,1,1,0|mki2rz,1,1,0|mki2s0,27,10,1|mvayrz,27,10,1|mvays0,1,1,0|n385fz,1,1,0|n385g0,27,10,1|ne11fz,27,10,1|ne11g0,1,1,0|nly83z,1,1,0|nly840,27,10,1|nwr43z,27,10,1|nwr440,1,1,0|o4oarz,1,1,0|o4oas0,27,10,1|ofu5fz,27,10,1|ofu5g0,1,1,0|onedfz,1,1,0|onedg0,27,10,1|oyk83z,27,10,1|oyk840,1,1,0|p64g3z,1,1,0|p64g40,27,10,1|phaarz,27,10,1|phaas0,1,1,0|pp7hfz,1,1,0|pp7hg0,27,10,1|q00dfz,27,10,1|q00dg0,1,1,0|q7xk3z,1,1,0|q7xk40,27,10,1|qiqg3z,27,10,1|qiqg40,1,1,0|qqnmrz,1,1,0|qqnms0,27,10,1|r1thfz,27,10,1|r1thg0,1,1,0|r9dpfz,1,1,0|r9dpg0,27,10,1|rkjk3z,27,10,1|rkjk40,1,1,0|rs3s3z,1,1,0|rs3s40,27,10,1|s39mrz,27,10,1|s39ms0,1,1,0|sb6tfz,1,1,0|sb6tg0,27,10,1|slzpfz,27,10,1|slzpg0,1,1,0|stww3z,1,1,0|stww40,27,10,1|t4ps3z,27,10,1|t4ps40,1,1,0|tcmyrz,1,1,0|tcmys0,27,10,1|tnfurz,27,10,1|tnfus0,1,1,0|tvd1fz,1,1,0|tvd1g0,27,10,1|u6iw3z,27,10,1|u6iw40,1,1,0|ue343z,1,1,0|ue3440,27,10,1|up8yrz,27,10,1|up8ys0,1,1,0|uwt6rz,1,1,0|uwt6s0,27,10,1|v7z1fz,27,10,1|v7z1g0,1,1,0|vfw83z,1,1,0|vfw840,27,10,1|vqp43z,27,10,1|vqp440,1,1,0|vymarz,1,1,0|vymas0,27,10,1|w9f6rz,27,10,1|w9f6s0,1,1,0|whcdfz,1,1,0|whcdg0,27,10,1|wsi83z,27,10,1|wsi840,1,1,0|x02g3z,1,1,0|x02g40,27,10,1|xb8arz,27,10,1|xb8as0,1,1,0|xisirz,1,1,0|xisis0,27,10,1|xtydfz,27,10,1|xtydg0,1,1,0|y1ilfz,1,1,0|y1ilg0,27,10,1|ycog3z,27,10,1|ycog40,1,1,0|yklmrz,1,1,0|yklms0,27,10,1|yveirz,27,10,1|yveis0,1,1,0|z3bpfz,1,1,0|z3bpg0,27,10,1|ze4lfz,27,10,1|ze4lg0,1,1,0\",\"Europe/Istanbul|,0,349,0|-1ayy814,117,350,0|-ux9xex,117,350,0|-ux9xew,15,11,0|-s0e081,15,11,0|-s0e080,16,6,1|-rsir01,16,6,1|-rsir00,15,11,0|-pyzew1,15,11,0|-pyzew0,16,6,1|-po4r01,16,6,1|-po4r00,15,11,0|-pfwdk1,15,11,0|-pfwdk0,16,6,1|-p6hkc1,16,6,1|-p6hkc0,15,11,0|-oxj9k1,15,11,0|-oxj9k0,16,6,1|-ongdo1,16,6,1|-ongdo0,15,11,0|-ntgo81,15,11,0|-ntgo80,16,6,1|-nm7n01,16,6,1|-nm7n00,15,11,0|-nbayw1,15,11,0|-nbayw0,16,6,1|-n3fpo1,16,6,1|-n3fpo0,15,11,0|-febpk1,15,11,0|-febpk0,16,6,1|-f9c5o1,16,6,1|-f9c5o0,15,11,0|-f6gdk1,15,11,0|-f6gdk0,16,6,1|-erc0c1,16,6,1|-erc0c0,15,11,0|-ehgdk1,15,11,0|-ehgdk0,16,6,1|-cnaz01,16,6,1|-cnaz00,15,11,0|-cb5uw1,15,11,0|-cb5uw0,16,6,1|-c4w0c1,16,6,1|-c4w0c0,15,11,0|-bujpk1,15,11,0|-bujpk0,16,6,1|-blwoc1,16,6,1|-blwoc0,15,11,0|-bbtmw1,15,11,0|-bbtmw0,16,6,1|-b36lo1,16,6,1|-b36lo0,15,11,0|-atgiw1,15,11,0|-atgiw0,16,6,1|-akgj01,16,6,1|-akgj00,15,11,0|-aadhk1,15,11,0|-aadhk0,16,6,1|-a1dho1,16,6,1|-a1dho0,15,11,0|-9rag81,15,11,0|-9rag80,16,6,1|-9inf01,16,6,1|-9inf00,15,11,0|-3wa5k1,15,11,0|-3wa5k0,16,6,1|-3805o1,16,6,1|-3805o0,15,11,0|-2xtew1,15,11,0|-2xtew0,16,6,1|-2qo301,16,6,1|-2qo300,15,11,0|1s8vvz,15,11,0|1s8vw0,16,6,1|2062jz,16,6,1|2062k0,15,11,0|27qdbz,15,11,0|27qdc0,16,6,1|2iw57z,16,6,1|2iw580,15,11,0|2q1mnz,15,11,0|2q1mo0,16,6,1|31m7vz,16,6,1|31m7w0,15,11,0|38tjzz,15,11,0|38tk00,16,6,1|3kcajz,16,6,1|3kcak0,15,11,0|3s9jzz,15,11,0|3s9k00,16,6,1|42cfvz,16,6,1|42cfw0,15,11,0|4azmnz,15,11,0|4azmo0,16,6,1|4ficzz,16,6,1|4fid00,100,6,0|73397z,100,6,0|733980,105,209,1|76bufz,105,209,1|76bug0,100,6,0|7qp97z,100,6,0|7qp980,15,11,0|7zg2jz,15,11,0|7zg2k0,16,6,1|87q7vz,16,6,1|87q7w0,15,11,0|8h53vz,15,11,0|8h53w0,16,6,1|8qi57z,16,6,1|8qi580,15,11,0|8zv6jz,15,11,0|8zv6k0,16,6,1|9987vz,16,6,1|9987w0,15,11,0|9il97z,15,11,0|9il980,16,6,1|9ryajz,16,6,1|9ryak0,15,11,0|a1bbvz,15,11,0|a1bbw0,16,6,1|aaod7z,16,6,1|aaod80,15,11,0|ak1ejz,15,11,0|ak1ek0,16,6,1|atrejz,16,6,1|atrek0,15,11,0|b34fvz,15,11,0|b34fw0,16,6,1|bchh7z,16,6,1|bchh80,15,11,0|bluijz,15,11,0|bluik0,16,6,1|bv7jvz,16,6,1|bv7jw0,15,11,0|c4kl7z,15,11,0|c4kl80,16,6,1|cdxmjz,16,6,1|cdxmk0,15,11,0|cmxp7z,15,11,0|cmxp80,16,6,1|cwnp7z,16,6,1|cwnp80,15,11,0|d60qjz,15,11,0|d60qk0,16,6,1|dfdrvz,16,6,1|dfdrw0,15,11,0|dp3rvz,15,11,0|dp3rw0,16,6,1|dzwnvz,16,6,1|dzwnw0,15,11,0|e7tujz,15,11,0|e7tuk0,16,6,1|eimqjz,16,6,1|eimqk0,15,11,0|eqjx7z,15,11,0|eqjx80,16,6,1|f1ct7z,16,6,1|f1ct80,15,11,0|f99zvz,15,11,0|f99zw0,16,6,1|fkfujz,16,6,1|fkfuk0,15,11,0|fs02jz,15,11,0|fs02k0,16,6,1|g35x7z,16,6,1|g35x80,15,11,0|gaq57z,15,11,0|gaq580,16,6,1|glvzvz,16,6,1|glvzw0,15,11,0|gtt6jz,15,11,0|gtt6k0,16,6,1|h4m2jz,16,6,1|h4m2k0,15,11,0|hcj97z,15,11,0|hcj980,16,6,1|hnc57z,16,6,1|hnc580,15,11,0|hv9bvz,15,11,0|hv9bw0,16,6,1|i6f6jz,16,6,1|i6f6k0,15,11,0|idzejz,15,11,0|idzek0,16,6,1|ip597z,16,6,1|ip5980,15,11,0|iwph7z,15,11,0|iwph80,16,6,1|j7vbvz,16,6,1|j7vbw0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|liqtfz,15,11,0|liqtg0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n3a03z,15,11,0|n3a040,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nxh1fz,16,6,1|nxh1g0,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|od3ozz,16,6,1|od3p00,100,6,0\",\"Europe/Jersey|,0,347,0|-1rprx9x,1,1,0|-rzcns1,1,1,0|-rzcns0,27,10,1|-rsid41,27,10,1|-rsid40,1,1,0|-risd41,1,1,0|-risd40,27,10,1|-ragd41,27,10,1|-ragd40,1,1,0|-r0s7s1,1,1,0|-r0s7s0,27,10,1|-qr0d41,27,10,1|-qr0d40,1,1,0|-qhp6g1,1,1,0|-qhp6g0,27,10,1|-q8aag1,27,10,1|-q8aag0,1,1,0|-pyz3s1,1,1,0|-pyz3s0,27,10,1|-po4d41,27,10,1|-po4d40,1,1,0|-pfw2g1,1,1,0|-pfw2g0,27,10,1|-p6h6g1,27,10,1|-p6h6g0,1,1,0|-oxiyg1,1,1,0|-oxiyg0,27,10,1|-onfzs1,27,10,1|-onfzs0,1,1,0|-odd141,1,1,0|-odd140,27,10,1|-o5st41,27,10,1|-o5st40,1,1,0|-nuzx41,1,1,0|-nuzx40,27,10,1|-nmprs1,27,10,1|-nmprs0,1,1,0|-nbwvs1,1,1,0|-nbwvs0,27,10,1|-n39rs1,27,10,1|-n39rs0,1,1,0|-mt6t41,1,1,0|-mt6t40,27,10,1|-mkjp41,27,10,1|-mkjp40,1,1,0|-matp41,1,1,0|-matp40,27,10,1|-m1tmg1,27,10,1|-m1tmg0,1,1,0|-lrdp41,1,1,0|-lrdp40,27,10,1|-liql41,27,10,1|-liql40,1,1,0|-l8nmg1,1,1,0|-l8nmg0,27,10,1|-l00ig1,27,10,1|-l00ig0,1,1,0|-kqaig1,1,1,0|-kqaig0,27,10,1|-khafs1,27,10,1|-khafs0,1,1,0|-k77h41,1,1,0|-k77h40,27,10,1|-jykd41,27,10,1|-jykd40,1,1,0|-joheg1,1,1,0|-joheg0,27,10,1|-jfuag1,27,10,1|-jfuag0,1,1,0|-j64ag1,1,1,0|-j64ag0,27,10,1|-iwr941,27,10,1|-iwr940,1,1,0|-imoag1,1,1,0|-imoag0,27,10,1|-ie16g1,27,10,1|-ie16g0,1,1,0|-i4b6g1,1,1,0|-i4b6g0,27,10,1|-hvb3s1,27,10,1|-hvb3s0,1,1,0|-hl8541,1,1,0|-hl8540,27,10,1|-hcl141,27,10,1|-hcl140,1,1,0|-h2i2g1,1,1,0|-h2i2g0,27,10,1|-gtuyg1,27,10,1|-gtuyg0,1,1,0|-gk4yg1,1,1,0|-gk4yg0,27,10,1|-gb4vs1,27,10,1|-gb4vs0,1,1,0|-g11x41,1,1,0|-g11x40,27,10,1|-fpw2g1,27,10,1|-fpw2g0,1,1,0|-fkul41,1,1,0|-fkul40,27,10,1|-eyiyk1,27,10,1|-eyiyk0,151,11,1|-ethh81,151,11,1|-ethh80,27,10,1|-eh8qk1,27,10,1|-eh8qk0,151,11,1|-earek1,151,11,1|-earek0,27,10,1|-dyinw1,27,10,1|-dyinw0,151,11,1|-drod81,151,11,1|-drod80,27,10,1|-dfsl81,27,10,1|-dfsl80,151,11,1|-d75h81,151,11,1|-d75h80,27,10,1|-cx0nw1,27,10,1|-cx0nw0,151,11,1|-cro2k1,151,11,1|-cro2k0,27,10,1|-cncfs1,27,10,1|-cncfs0,1,1,0|-cdmfs1,1,1,0|-cdmfs0,27,10,1|-c4md41,27,10,1|-c4md40,1,1,0|-bwc7s1,1,1,0|-bwc7s0,27,10,1|-buwfw1,27,10,1|-buwfw0,151,11,1|-bos2k1,151,11,1|-bos2k0,27,10,1|-bkgfs1,27,10,1|-bkgfs0,1,1,0|-bdm541,1,1,0|-bdm540,27,10,1|-b1qd41,27,10,1|-b1qd40,1,1,0|-att6g1,1,1,0|-att6g0,27,10,1|-aj0ag1,27,10,1|-aj0ag0,1,1,0|-aad6g1,1,1,0|-aad6g0,27,10,1|-a0n6g1,27,10,1|-a0n6g0,1,1,0|-9rn3s1,1,1,0|-9rn3s0,27,10,1|-9hx3s1,27,10,1|-9hx3s0,1,1,0|-98k2g1,1,1,0|-98k2g0,27,10,1|-8yu2g1,27,10,1|-8yu2g0,1,1,0|-8ptzs1,1,1,0|-8ptzs0,27,10,1|-8h6vs1,27,10,1|-8h6vs0,1,1,0|-87gvs1,1,1,0|-87gvs0,27,10,1|-7ygt41,27,10,1|-7ygt40,1,1,0|-7odug1,1,1,0|-7odug0,27,10,1|-7fqqg1,27,10,1|-7fqqg0,1,1,0|-75at41,1,1,0|-75at40,27,10,1|-6wnp41,27,10,1|-6wnp40,1,1,0|-6mxp41,1,1,0|-6mxp40,27,10,1|-6dxmg1,27,10,1|-6dxmg0,1,1,0|-63uns1,1,1,0|-63uns0,27,10,1|-5v7js1,27,10,1|-5v7js0,1,1,0|-5l4l41,1,1,0|-5l4l40,27,10,1|-5chh41,27,10,1|-5chh40,1,1,0|-52rh41,1,1,0|-52rh40,27,10,1|-4treg1,27,10,1|-4treg0,1,1,0|-4krbs1,1,1,0|-4krbs0,27,10,1|-49lh41,27,10,1|-49lh40,1,1,0|-421941,1,1,0|-421940,27,10,1|-3qveg1,27,10,1|-3qveg0,1,1,0|-3iy7s1,1,1,0|-3iy7s0,27,10,1|-385bs1,27,10,1|-385bs0,1,1,0|-30l3s1,1,1,0|-30l3s0,27,10,1|-2pf941,27,10,1|-2pf940,1,1,0|-2hv141,1,1,0|-2hv140,27,10,1|-26p6g1,27,10,1|-26p6g0,1,1,0|-1z4yg1,1,1,0|-1z4yg0,27,10,1|-1nz3s1,27,10,1|-1nz3s0,1,1,0|-1gevs1,1,1,0|-1gevs0,27,10,1|-14w2g1,27,10,1|-14w2g0,1,1,0|-z4ns1,1,1,0|-z4ns0,27,10,1|-m6841,27,10,1|-m6840,27,10,0|yd6vz,27,10,0|yd6w0,1,1,0|15kg7z,1,1,0|15kg80,27,10,1|1h39jz,27,10,1|1h39k0,1,1,0|1oaivz,1,1,0|1oaiw0,27,10,1|1ztc7z,27,10,1|1ztc80,1,1,0|270ljz,1,1,0|270lk0,27,10,1|2ijevz,27,10,1|2ijew0,1,1,0|2pqo7z,1,1,0|2pqo80,27,10,1|319hjz,27,10,1|319hk0,1,1,0|38tpjz,1,1,0|38tpk0,27,10,1|3jzk7z,27,10,1|3jzk80,1,1,0|3rjs7z,1,1,0|3rjs80,27,10,1|42pmvz,27,10,1|42pmw0,1,1,0|4a9uvz,1,1,0|4a9uw0,27,10,1|4lso7z,27,10,1|4lso80,1,1,0|4szxjz,1,1,0|4szxk0,27,10,1|54iqvz,27,10,1|54iqw0,1,1,0|5bq07z,1,1,0|5bq080,27,10,1|5n8tjz,27,10,1|5n8tk0,1,1,0|5v5xfz,1,1,0|5v5xg0,27,10,1|65ytfz,27,10,1|65ytg0,1,1,0|6dw03z,1,1,0|6dw040,27,10,1|6oow3z,27,10,1|6oow40,1,1,0|6wm2rz,1,1,0|6wm2s0,27,10,1|77eyrz,27,10,1|77eys0,1,1,0|7fc5fz,1,1,0|7fc5g0,27,10,1|7qi03z,27,10,1|7qi040,1,1,0|7yf6rz,1,1,0|7yf6s0,27,10,1|8982rz,27,10,1|8982s0,1,1,0|8h59fz,1,1,0|8h59g0,27,10,1|8ry5fz,27,10,1|8ry5g0,1,1,0|8zvc3z,1,1,0|8zvc40,27,10,1|9ao83z,27,10,1|9ao840,1,1,0|9ilerz,1,1,0|9iles0,27,10,1|9tearz,27,10,1|9teas0,1,1,0|a1bhfz,1,1,0|a1bhg0,27,10,1|achc3z,27,10,1|achc40,1,1,0|ak1k3z,1,1,0|ak1k40,27,10,1|av7erz,27,10,1|av7es0,1,1,0|b34lfz,1,1,0|b34lg0,27,10,1|bdxhfz,27,10,1|bdxhg0,1,1,0|bluo3z,1,1,0|bluo40,27,10,1|bwnk3z,27,10,1|bwnk40,1,1,0|c4kqrz,1,1,0|c4kqs0,27,10,1|cfdmrz,27,10,1|cfdms0,1,1,0|cnatfz,1,1,0|cnatg0,27,10,1|cy3pfz,27,10,1|cy3pg0,1,1,0|d60w3z,1,1,0|d60w40,27,10,1|dgts3z,27,10,1|dgts40,1,1,0|dp3xfz,1,1,0|dp3xg0,27,10,1|dzwtfz,27,10,1|dzwtg0,1,1,0|e7u03z,1,1,0|e7u040,27,10,1|eimw3z,27,10,1|eimw40,1,1,0|eqk2rz,1,1,0|eqk2s0,27,10,1|f1cyrz,27,10,1|f1cys0,1,1,0|f9a5fz,1,1,0|f9a5g0,27,10,1|fkg03z,27,10,1|fkg040,1,1,0|fs083z,1,1,0|fs0840,27,10,1|g362rz,27,10,1|g362s0,1,1,0|gaqarz,1,1,0|gaqas0,27,10,1|glw5fz,27,10,1|glw5g0,1,1,0|gttc3z,1,1,0|gttc40,27,10,1|h4m83z,27,10,1|h4m840,1,1,0|hcjerz,1,1,0|hcjes0,27,10,1|hncarz,27,10,1|hncas0,1,1,0|hv9hfz,1,1,0|hv9hg0,27,10,1|i6fc3z,27,10,1|i6fc40,1,1,0|idzk3z,1,1,0|idzk40,27,10,1|ip5erz,27,10,1|ip5es0,1,1,0|iwpmrz,1,1,0|iwpms0,27,10,1|j7vhfz,27,10,1|j7vhg0,1,1,0|jffpfz,1,1,0|jffpg0,27,10,1|jqlk3z,27,10,1|jqlk40,1,1,0|jyiqrz,1,1,0|jyiqs0,27,10,1|k9bmrz,27,10,1|k9bms0,1,1,0|kh8tfz,1,1,0|kh8tg0,27,10,1|ks1pfz,27,10,1|ks1pg0,1,1,0|kzyw3z,1,1,0|kzyw40,27,10,1|lb4qrz,27,10,1|lb4qs0,1,1,0|lioyrz,1,1,0|lioys0,27,10,1|ltutfz,27,10,1|ltutg0,1,1,0|m1f1fz,1,1,0|m1f1g0,27,10,1|mckw3z,27,10,1|mckw40,1,1,0|mki2rz,1,1,0|mki2s0,27,10,1|mvayrz,27,10,1|mvays0,1,1,0|n385fz,1,1,0|n385g0,27,10,1|ne11fz,27,10,1|ne11g0,1,1,0|nly83z,1,1,0|nly840,27,10,1|nwr43z,27,10,1|nwr440,1,1,0|o4oarz,1,1,0|o4oas0,27,10,1|ofu5fz,27,10,1|ofu5g0,1,1,0|onedfz,1,1,0|onedg0,27,10,1|oyk83z,27,10,1|oyk840,1,1,0|p64g3z,1,1,0|p64g40,27,10,1|phaarz,27,10,1|phaas0,1,1,0|pp7hfz,1,1,0|pp7hg0,27,10,1|q00dfz,27,10,1|q00dg0,1,1,0|q7xk3z,1,1,0|q7xk40,27,10,1|qiqg3z,27,10,1|qiqg40,1,1,0|qqnmrz,1,1,0|qqnms0,27,10,1|r1thfz,27,10,1|r1thg0,1,1,0|r9dpfz,1,1,0|r9dpg0,27,10,1|rkjk3z,27,10,1|rkjk40,1,1,0|rs3s3z,1,1,0|rs3s40,27,10,1|s39mrz,27,10,1|s39ms0,1,1,0|sb6tfz,1,1,0|sb6tg0,27,10,1|slzpfz,27,10,1|slzpg0,1,1,0|stww3z,1,1,0|stww40,27,10,1|t4ps3z,27,10,1|t4ps40,1,1,0|tcmyrz,1,1,0|tcmys0,27,10,1|tnfurz,27,10,1|tnfus0,1,1,0|tvd1fz,1,1,0|tvd1g0,27,10,1|u6iw3z,27,10,1|u6iw40,1,1,0|ue343z,1,1,0|ue3440,27,10,1|up8yrz,27,10,1|up8ys0,1,1,0|uwt6rz,1,1,0|uwt6s0,27,10,1|v7z1fz,27,10,1|v7z1g0,1,1,0|vfw83z,1,1,0|vfw840,27,10,1|vqp43z,27,10,1|vqp440,1,1,0|vymarz,1,1,0|vymas0,27,10,1|w9f6rz,27,10,1|w9f6s0,1,1,0|whcdfz,1,1,0|whcdg0,27,10,1|wsi83z,27,10,1|wsi840,1,1,0|x02g3z,1,1,0|x02g40,27,10,1|xb8arz,27,10,1|xb8as0,1,1,0|xisirz,1,1,0|xisis0,27,10,1|xtydfz,27,10,1|xtydg0,1,1,0|y1ilfz,1,1,0|y1ilg0,27,10,1|ycog3z,27,10,1|ycog40,1,1,0|yklmrz,1,1,0|yklms0,27,10,1|yveirz,27,10,1|yveis0,1,1,0|z3bpfz,1,1,0|z3bpg0,27,10,1|ze4lfz,27,10,1|ze4lg0,1,1,0\",\"Europe/Kaliningrad|,0,332,0|-14212go,10,10,0|-s0e081,10,10,0|-s0e080,11,11,1|-rsilg1,11,11,1|-rsilg0,10,10,0|-ridmk1,10,10,0|-ridmk0,11,11,1|-ragfw1,11,11,1|-ragfw0,10,10,0|-qznjw1,10,10,0|-qznjw0,11,11,1|-qrqd81,11,11,1|-qrqd80,10,10,0|-fizzw1,10,10,0|-fizzw0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cwm2w1,11,11,1|-cwm2w0,15,11,0|-cvmw81,15,11,0|-cvmw80,16,6,1|-cm2j01,16,6,1|-cm2j00,15,11,0|-cdzpk1,15,11,0|-cdzpk0,148,6,0|5vb6bz,148,6,0|5vb6c0,149,209,1|64pzjz,149,209,1|64pzk0,148,6,0|6e33nz,148,6,0|6e33o0,149,209,1|6nhwvz,149,209,1|6nhww0,148,6,0|6wv0zz,148,6,0|6wv100,149,209,1|769u7z,149,209,1|769u80,148,6,0|7foszz,148,6,0|7fot00,149,209,1|7p1zvz,149,209,1|7p1zw0,148,6,0|7yf17z,148,6,0|7yf180,149,209,1|87s2jz,149,209,1|87s2k0,148,6,0|8h53vz,148,6,0|8h53w0,149,209,1|8qi57z,149,209,1|8qi580,148,6,0|8zv6jz,148,6,0|8zv6k0,149,209,1|9987vz,149,209,1|9987w0,148,6,0|9il97z,148,6,0|9il980,149,209,1|9ryajz,149,209,1|9ryak0,148,6,0|a1bbvz,148,6,0|a1bbw0,16,6,1|aaofzz,16,6,1|aaog00,15,11,0|ak1hbz,15,11,0|ak1hc0,16,6,1|atrhbz,16,6,1|atrhc0,15,11,0|b34inz,15,11,0|b34io0,16,6,1|bchjzz,16,6,1|bchk00,15,11,0|blulbz,15,11,0|blulc0,16,6,1|bv7mnz,16,6,1|bv7mo0,15,11,0|c4knzz,15,11,0|c4ko00,16,6,1|cdxpbz,16,6,1|cdxpc0,15,11,0|cnaqnz,15,11,0|cnaqo0,16,6,1|cwnrzz,16,6,1|cwns00,15,11,0|d60tbz,15,11,0|d60tc0,16,6,1|dfdunz,16,6,1|dfduo0,15,11,0|dp3unz,15,11,0|dp3uo0,16,6,1|dzwqnz,16,6,1|dzwqo0,15,11,0|e7txbz,15,11,0|e7txc0,16,6,1|eimtbz,16,6,1|eimtc0,15,11,0|eqjzzz,15,11,0|eqk000,16,6,1|f1cvzz,16,6,1|f1cw00,15,11,0|f9a2nz,15,11,0|f9a2o0,16,6,1|fkfxbz,16,6,1|fkfxc0,15,11,0|fs05bz,15,11,0|fs05c0,16,6,1|g35zzz,16,6,1|g36000,15,11,0|gaq7zz,15,11,0|gaq800,16,6,1|glw2nz,16,6,1|glw2o0,15,11,0|gtt9bz,15,11,0|gtt9c0,16,6,1|h4m5bz,16,6,1|h4m5c0,15,11,0|hcjbzz,15,11,0|hcjc00,16,6,1|hnc7zz,16,6,1|hnc800,15,11,0|hv9enz,15,11,0|hv9eo0,16,6,1|i6f9bz,16,6,1|i6f9c0,15,11,0|idzhbz,15,11,0|idzhc0,16,6,1|ip5bzz,16,6,1|ip5c00,15,11,0|iwpjzz,15,11,0|iwpk00,16,6,1|j7venz,16,6,1|j7veo0,15,11,0|jffmnz,15,11,0|jffmo0,16,6,1|jqlhbz,16,6,1|jqlhc0,15,11,0|jyinzz,15,11,0|jyio00,16,6,1|k9bjzz,16,6,1|k9bk00,15,11,0|kh8qnz,15,11,0|kh8qo0,16,6,1|ks1mnz,16,6,1|ks1mo0,15,11,0|kzytbz,15,11,0|kzytc0,16,6,1|lb4nzz,16,6,1|lb4o00,15,11,0|liovzz,15,11,0|liow00,100,6,0|ne0vvz,100,6,0|ne0vw0,15,11,0\",\"Europe/Kiev|,0,351,0|-1ayy8bg,74,351,0|-nu11nh,74,351,0|-nu11ng,15,11,0|-kmr1k1,15,11,0|-kmr1k0,148,6,0|-erdv01,148,6,0|-erdv00,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dnetg1,10,10,0|-dnetg0,148,6,0|5vb6bz,148,6,0|5vb6c0,149,209,1|64pzjz,149,209,1|64pzk0,148,6,0|6e33nz,148,6,0|6e33o0,149,209,1|6nhwvz,149,209,1|6nhww0,148,6,0|6wv0zz,148,6,0|6wv100,149,209,1|769u7z,149,209,1|769u80,148,6,0|7foszz,148,6,0|7fot00,149,209,1|7p1zvz,149,209,1|7p1zw0,148,6,0|7yf17z,148,6,0|7yf180,149,209,1|87s2jz,149,209,1|87s2k0,148,6,0|8h53vz,148,6,0|8h53w0,149,209,1|8qi57z,149,209,1|8qi580,148,6,0|8zv6jz,148,6,0|8zv6k0,149,209,1|9987vz,149,209,1|9987w0,148,6,0|9il97z,148,6,0|9il980,149,209,1|9ryajz,149,209,1|9ryak0,148,6,0|a1bbvz,148,6,0|a1bbw0,149,209,1|aaod7z,149,209,1|aaod80,148,6,0|ak1ejz,148,6,0|ak1ek0,149,209,1|ap2t3z,149,209,1|ap2t40,16,6,1|bchjzz,16,6,1|bchk00,15,11,0|blufrz,15,11,0|blufs0,16,6,1|bv7ebz,16,6,1|bv7ec0,15,11,0|c4kifz,15,11,0|c4kig0,16,6,1|cdxgzz,16,6,1|cdxh00,15,11,0|cnal3z,15,11,0|cnal40,16,6,1|cwnjnz,16,6,1|cwnjo0,15,11,0|d60w3z,15,11,0|d60w40,16,6,1|dfdxfz,16,6,1|dfdxg0,15,11,0|dp3xfz,15,11,0|dp3xg0,16,6,1|dzwtfz,16,6,1|dzwtg0,15,11,0|e7u03z,15,11,0|e7u040,16,6,1|eimw3z,16,6,1|eimw40,15,11,0|eqk2rz,15,11,0|eqk2s0,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|fs083z,15,11,0|fs0840,16,6,1|g362rz,16,6,1|g362s0,15,11,0|gaqarz,15,11,0|gaqas0,16,6,1|glw5fz,16,6,1|glw5g0,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|ofu5fz,16,6,1|ofu5g0,15,11,0|onedfz,15,11,0|onedg0,16,6,1|oyk83z,16,6,1|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Europe/Kirov|,0,352,0|-qcx400,100,6,0|-kmr4c1,100,6,0|-kmr4c0,105,209,0|5vb3jz,105,209,0|5vb3k0,92,194,1|64pwrz,92,194,1|64pws0,105,209,0|6e30vz,105,209,0|6e30w0,92,194,1|6nhu3z,92,194,1|6nhu40,105,209,0|6wuy7z,105,209,0|6wuy80,92,194,1|769rfz,92,194,1|769rg0,105,209,0|7foq7z,105,209,0|7foq80,92,194,1|7p1x3z,92,194,1|7p1x40,105,209,0|7yeyfz,105,209,0|7yeyg0,92,194,1|87rzrz,92,194,1|87rzs0,105,209,0|8h513z,105,209,0|8h5140,92,194,1|8qi2fz,92,194,1|8qi2g0,105,209,0|8zv3rz,105,209,0|8zv3s0,92,194,1|99853z,92,194,1|998540,105,209,0|9il6fz,105,209,0|9il6g0,92,194,1|9ry7rz,92,194,1|9ry7s0,105,209,0|a1b93z,105,209,0|a1b940,105,209,1|aaod7z,105,209,1|aaod80,100,6,0|ak1ejz,100,6,0|ak1ek0,105,209,1|atrejz,105,209,1|atrek0,100,6,0|b34fvz,100,6,0|b34fw0,105,209,0|blufrz,105,209,0|blufs0,105,209,1|bv7jvz,105,209,1|bv7jw0,100,6,0|c4kl7z,100,6,0|c4kl80,105,209,1|cdxmjz,105,209,1|cdxmk0,100,6,0|cnanvz,100,6,0|cnanw0,105,209,1|cwnp7z,105,209,1|cwnp80,100,6,0|d60qjz,100,6,0|d60qk0,105,209,1|dfdrvz,105,209,1|dfdrw0,100,6,0|dp3rvz,100,6,0|dp3rw0,105,209,1|dzwnvz,105,209,1|dzwnw0,100,6,0|e7tujz,100,6,0|e7tuk0,105,209,1|eimqjz,105,209,1|eimqk0,100,6,0|eqjx7z,100,6,0|eqjx80,105,209,1|f1ct7z,105,209,1|f1ct80,100,6,0|f99zvz,100,6,0|f99zw0,105,209,1|fkfujz,105,209,1|fkfuk0,100,6,0|fs02jz,100,6,0|fs02k0,105,209,1|g35x7z,105,209,1|g35x80,100,6,0|gaq57z,100,6,0|gaq580,105,209,1|glvzvz,105,209,1|glvzw0,100,6,0|gtt6jz,100,6,0|gtt6k0,105,209,1|h4m2jz,105,209,1|h4m2k0,100,6,0|hcj97z,100,6,0|hcj980,105,209,1|hnc57z,105,209,1|hnc580,100,6,0|hv9bvz,100,6,0|hv9bw0,105,209,1|i6f6jz,105,209,1|i6f6k0,100,6,0|idzejz,100,6,0|idzek0,105,209,1|ip597z,105,209,1|ip5980,100,6,0|iwph7z,100,6,0|iwph80,105,209,1|j7vbvz,105,209,1|j7vbw0,100,6,0|jffjvz,100,6,0|jffjw0,105,209,1|jqlejz,105,209,1|jqlek0,100,6,0|jyil7z,100,6,0|jyil80,105,209,1|k9bh7z,105,209,1|k9bh80,100,6,0|kh8nvz,100,6,0|kh8nw0,105,209,1|ks1jvz,105,209,1|ks1jw0,100,6,0|kzyqjz,100,6,0|kzyqk0,105,209,1|lb4l7z,105,209,1|lb4l80,100,6,0|liot7z,100,6,0|liot80,105,209,0|ne0t3z,105,209,0|ne0t40,100,6,0\",\"Europe/Lisbon|,0,29,0|-u9rhc0,8,1,0|-rxx1g1,8,1,0|-rxx1g0,9,10,1|-rqx401,9,10,1|-rqx400,8,1,0|-rkqys1,8,1,0|-rkqys0,9,10,1|-r90qs1,9,10,1|-r90qs0,8,1,0|-r1x6s1,8,1,0|-r1x6s0,9,10,1|-qq8tg1,9,10,1|-qq8tg0,8,1,0|-qj7441,8,1,0|-qj7440,9,10,1|-q7gw41,9,10,1|-q7gw40,8,1,0|-q0dc41,8,1,0|-q0dc40,9,10,1|-pon441,9,10,1|-pon440,8,1,0|-phles1,8,1,0|-phles0,9,10,1|-p5v6s1,9,10,1|-p5v6s0,8,1,0|-nusqs1,8,1,0|-nusqs0,9,10,1|-nlhk41,9,10,1|-nlhk40,8,1,0|-mt71g1,8,1,0|-mt71g0,9,10,1|-mkjxg1,9,10,1|-mkjxg0,8,1,0|-matxg1,8,1,0|-matxg0,9,10,1|-m1tus1,9,10,1|-m1tus0,8,1,0|-lrqw41,8,1,0|-lrqw40,9,10,1|-liqtg1,9,10,1|-liqtg0,8,1,0|-l8nus1,8,1,0|-l8nus0,9,10,1|-l00qs1,9,10,1|-l00qs0,8,1,0|-k77pg1,8,1,0|-k77pg0,9,10,1|-jyklg1,9,10,1|-jyklg0,8,1,0|-jp7k41,8,1,0|-jp7k40,9,10,1|-jfuis1,9,10,1|-jfuis0,8,1,0|-ineg41,8,1,0|-ineg40,9,10,1|-ie1es1,9,10,1|-ie1es0,8,1,0|-i51c41,8,1,0|-i51c40,9,10,1|-hvbc41,9,10,1|-hvbc40,8,1,0|-hl8dg1,8,1,0|-hl8dg0,9,10,1|-hcl9g1,9,10,1|-hcl9g0,8,1,0|-h38841,8,1,0|-h38840,9,10,1|-gtv6s1,9,10,1|-gtv6s0,8,1,0|-gkv441,8,1,0|-gkv440,9,10,1|-gb5441,9,10,1|-gb5440,8,1,0|-g125g1,8,1,0|-g125g0,9,10,1|-fpwas1,9,10,1|-fpwas0,8,1,0|-fkutg1,8,1,0|-fkutg0,9,10,1|-f9c041,9,10,1|-f9c040,8,1,0|-ezyys1,8,1,0|-ezyys0,9,10,1|-eqk2s1,9,10,1|-eqk2s0,8,1,0|-eibs41,8,1,0|-eibs40,9,10,1|-eg62w1,9,10,1|-eg62w0,152,11,1|-eaeo81,152,11,1|-eaeo80,9,10,1|-e6sys1,9,10,1|-e6sys0,8,1,0|-dzlpg1,8,1,0|-dzlpg0,9,10,1|-dxsyw1,9,10,1|-dxsyw0,152,11,1|-dqyo81,152,11,1|-dqyo80,9,10,1|-dnpxg1,9,10,1|-dnpxg0,8,1,0|-dgvms1,8,1,0|-dgvms0,9,10,1|-depxk1,9,10,1|-depxk0,152,11,1|-d88lk1,152,11,1|-d88lk0,9,10,1|-d4zus1,9,10,1|-d4zus0,8,1,0|-cy5k41,8,1,0|-cy5k40,9,10,1|-cvzuw1,9,10,1|-cvzuw0,152,11,1|-cpiiw1,152,11,1|-cpiiw0,9,10,1|-cm9s41,9,10,1|-cm9s40,8,1,0|-cdzms1,8,1,0|-cdzms0,9,10,1|-c4mlg1,9,10,1|-c4mlg0,8,1,0|-bv9bs1,8,1,0|-bv9bs0,9,10,1|-blwag1,9,10,1|-blwag0,8,1,0|-bcj941,8,1,0|-bcj940,9,10,1|-b367s1,9,10,1|-b367s0,8,1,0|-att6g1,8,1,0|-att6g0,9,10,1|-akg541,9,10,1|-akg540,8,1,0|-9sd141,8,1,0|-9sd140,9,10,1|-9in141,9,10,1|-9in140,8,1,0|-999zs1,8,1,0|-999zs0,9,10,1|-8zwyg1,9,10,1|-8zwyg0,8,1,0|-8qjx41,8,1,0|-8qjx40,9,10,1|-8h6vs1,9,10,1|-8h6vs0,8,1,0|-87tug1,8,1,0|-87tug0,9,10,1|-7ygt41,9,10,1|-7ygt40,8,1,0|-7p3rs1,8,1,0|-7p3rs0,9,10,1|-7fqqg1,9,10,1|-7fqqg0,8,1,0|-76dp41,8,1,0|-76dp40,9,10,1|-6wnp41,9,10,1|-6wnp40,8,1,0|-6nans1,8,1,0|-6nans0,9,10,1|-6dxmg1,9,10,1|-6dxmg0,8,1,0|-64kl41,8,1,0|-64kl40,9,10,1|-5v7js1,9,10,1|-5v7js0,8,1,0|-5luig1,8,1,0|-5luig0,9,10,1|-5chh41,9,10,1|-5chh40,8,1,0|-534fs1,8,1,0|-534fs0,9,10,1|-4treg1,9,10,1|-4treg0,8,1,0|-4ked41,8,1,0|-4ked40,9,10,1|-4b1bs1,9,10,1|-4b1bs0,8,1,0|-41oag1,8,1,0|-41oag0,9,10,1|-3ryag1,9,10,1|-3ryag0,8,1,0|-3il941,8,1,0|-3il940,9,10,1|-3987s1,9,10,1|-3987s0,8,1,0|-2zv6g1,8,1,0|-2zv6g0,9,10,1|-2qi541,9,10,1|-2qi540,8,1,0|-2h53s1,8,1,0|-2h53s0,9,10,1|-27s2g1,9,10,1|-27s2g0,8,1,0|-1yf141,8,1,0|-1yf140,10,10,0|3ijjzz,10,10,0|3ijk00,8,1,0|3rwlbz,8,1,0|3rwlc0,9,10,1|419mnz,9,10,1|419mo0,8,1,0|4azmnz,8,1,0|4azmo0,9,10,1|4kcnzz,9,10,1|4kco00,8,1,0|4tppbz,8,1,0|4tppc0,9,10,1|532tfz,9,10,1|532tg0,8,1,0|5cfrzz,8,1,0|5cfs00,9,10,1|5lsw3z,9,10,1|5lsw40,8,1,0|5v5xfz,8,1,0|5v5xg0,9,10,1|64iyrz,9,10,1|64iys0,8,1,0|6dw03z,8,1,0|6dw040,9,10,1|6n91fz,9,10,1|6n91g0,8,1,0|6wm5jz,8,1,0|6wm5k0,9,10,1|75z43z,9,10,1|75z440,8,1,0|7fc5fz,8,1,0|7fc5g0,9,10,1|7p25fz,9,10,1|7p25g0,8,1,0|7yf6rz,8,1,0|7yf6s0,9,10,1|87s83z,9,10,1|87s840,8,1,0|8h59fz,8,1,0|8h59g0,9,10,1|8qiarz,9,10,1|8qias0,8,1,0|8zvc3z,8,1,0|8zvc40,9,10,1|998dfz,9,10,1|998dg0,8,1,0|9ilerz,8,1,0|9iles0,9,10,1|9ryg3z,9,10,1|9ryg40,8,1,0|a1bhfz,8,1,0|a1bhg0,9,10,1|aaoirz,9,10,1|aaois0,8,1,0|ak1k3z,8,1,0|ak1k40,9,10,1|atrk3z,9,10,1|atrk40,8,1,0|b34lfz,8,1,0|b34lg0,9,10,1|bchmrz,9,10,1|bchms0,8,1,0|bluo3z,8,1,0|bluo40,9,10,1|bv7pfz,9,10,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,9,10,1|dzwtfz,9,10,1|dzwtg0,8,1,0|e7u03z,8,1,0|e7u040,9,10,1|eimw3z,9,10,1|eimw40,8,1,0|eqk2rz,8,1,0|eqk2s0,9,10,1|f1cyrz,9,10,1|f1cys0,8,1,0|f9a5fz,8,1,0|f9a5g0,9,10,1|fkg03z,9,10,1|fkg040,8,1,0|fs083z,8,1,0|fs0840,9,10,1|g362rz,9,10,1|g362s0,8,1,0|gaqarz,8,1,0|gaqas0,9,10,1|glw5fz,9,10,1|glw5g0,8,1,0|gttc3z,8,1,0|gttc40,9,10,1|h4m83z,9,10,1|h4m840,8,1,0|hcjerz,8,1,0|hcjes0,9,10,1|hncarz,9,10,1|hncas0,8,1,0|hv9hfz,8,1,0|hv9hg0,9,10,1|i6fc3z,9,10,1|i6fc40,8,1,0|idzk3z,8,1,0|idzk40,9,10,1|ip5erz,9,10,1|ip5es0,8,1,0|iwpmrz,8,1,0|iwpms0,9,10,1|j7vhfz,9,10,1|j7vhg0,8,1,0|jffpfz,8,1,0|jffpg0,9,10,1|jqlk3z,9,10,1|jqlk40,8,1,0|jyiqrz,8,1,0|jyiqs0,9,10,1|k9bmrz,9,10,1|k9bms0,8,1,0|kh8tfz,8,1,0|kh8tg0,9,10,1|ks1pfz,9,10,1|ks1pg0,8,1,0|kzyw3z,8,1,0|kzyw40,9,10,1|lb4qrz,9,10,1|lb4qs0,8,1,0|lioyrz,8,1,0|lioys0,9,10,1|ltutfz,9,10,1|ltutg0,8,1,0|m1f1fz,8,1,0|m1f1g0,9,10,1|mckw3z,9,10,1|mckw40,8,1,0|mki2rz,8,1,0|mki2s0,9,10,1|mvayrz,9,10,1|mvays0,8,1,0|n385fz,8,1,0|n385g0,9,10,1|ne11fz,9,10,1|ne11g0,8,1,0|nly83z,8,1,0|nly840,9,10,1|nwr43z,9,10,1|nwr440,8,1,0|o4oarz,8,1,0|o4oas0,9,10,1|ofu5fz,9,10,1|ofu5g0,8,1,0|onedfz,8,1,0|onedg0,9,10,1|oyk83z,9,10,1|oyk840,8,1,0|p64g3z,8,1,0|p64g40,9,10,1|phaarz,9,10,1|phaas0,8,1,0|pp7hfz,8,1,0|pp7hg0,9,10,1|q00dfz,9,10,1|q00dg0,8,1,0|q7xk3z,8,1,0|q7xk40,9,10,1|qiqg3z,9,10,1|qiqg40,8,1,0|qqnmrz,8,1,0|qqnms0,9,10,1|r1thfz,9,10,1|r1thg0,8,1,0|r9dpfz,8,1,0|r9dpg0,9,10,1|rkjk3z,9,10,1|rkjk40,8,1,0|rs3s3z,8,1,0|rs3s40,9,10,1|s39mrz,9,10,1|s39ms0,8,1,0|sb6tfz,8,1,0|sb6tg0,9,10,1|slzpfz,9,10,1|slzpg0,8,1,0|stww3z,8,1,0|stww40,9,10,1|t4ps3z,9,10,1|t4ps40,8,1,0|tcmyrz,8,1,0|tcmys0,9,10,1|tnfurz,9,10,1|tnfus0,8,1,0|tvd1fz,8,1,0|tvd1g0,9,10,1|u6iw3z,9,10,1|u6iw40,8,1,0|ue343z,8,1,0|ue3440,9,10,1|up8yrz,9,10,1|up8ys0,8,1,0|uwt6rz,8,1,0|uwt6s0,9,10,1|v7z1fz,9,10,1|v7z1g0,8,1,0|vfw83z,8,1,0|vfw840,9,10,1|vqp43z,9,10,1|vqp440,8,1,0|vymarz,8,1,0|vymas0,9,10,1|w9f6rz,9,10,1|w9f6s0,8,1,0|whcdfz,8,1,0|whcdg0,9,10,1|wsi83z,9,10,1|wsi840,8,1,0|x02g3z,8,1,0|x02g40,9,10,1|xb8arz,9,10,1|xb8as0,8,1,0|xisirz,8,1,0|xisis0,9,10,1|xtydfz,9,10,1|xtydg0,8,1,0|y1ilfz,8,1,0|y1ilg0,9,10,1|ycog3z,9,10,1|ycog40,8,1,0|yklmrz,8,1,0|yklms0,9,10,1|yveirz,9,10,1|yveis0,8,1,0|z3bpfz,8,1,0|z3bpg0,9,10,1|ze4lfz,9,10,1|ze4lg0,8,1,0\",\"Europe/Ljubljana|,0,332,0|-18vsmgo,10,10,0|-ezayw1,10,10,0|-ezayw0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cv5zw1,10,10,0|-cv5zw0,11,11,1|-cofek1,11,11,1|-cofek0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/London|,0,347,0|-1rprx9x,1,1,0|-rzcns1,1,1,0|-rzcns0,27,10,1|-rsid41,27,10,1|-rsid40,1,1,0|-risd41,1,1,0|-risd40,27,10,1|-ragd41,27,10,1|-ragd40,1,1,0|-r0s7s1,1,1,0|-r0s7s0,27,10,1|-qr0d41,27,10,1|-qr0d40,1,1,0|-qhp6g1,1,1,0|-qhp6g0,27,10,1|-q8aag1,27,10,1|-q8aag0,1,1,0|-pyz3s1,1,1,0|-pyz3s0,27,10,1|-po4d41,27,10,1|-po4d40,1,1,0|-pfw2g1,1,1,0|-pfw2g0,27,10,1|-p6h6g1,27,10,1|-p6h6g0,1,1,0|-oxiyg1,1,1,0|-oxiyg0,27,10,1|-onfzs1,27,10,1|-onfzs0,1,1,0|-odd141,1,1,0|-odd140,27,10,1|-o5st41,27,10,1|-o5st40,1,1,0|-nuzx41,1,1,0|-nuzx40,27,10,1|-nmprs1,27,10,1|-nmprs0,1,1,0|-nbwvs1,1,1,0|-nbwvs0,27,10,1|-n39rs1,27,10,1|-n39rs0,1,1,0|-mt6t41,1,1,0|-mt6t40,27,10,1|-mkjp41,27,10,1|-mkjp40,1,1,0|-matp41,1,1,0|-matp40,27,10,1|-m1tmg1,27,10,1|-m1tmg0,1,1,0|-lrdp41,1,1,0|-lrdp40,27,10,1|-liql41,27,10,1|-liql40,1,1,0|-l8nmg1,1,1,0|-l8nmg0,27,10,1|-l00ig1,27,10,1|-l00ig0,1,1,0|-kqaig1,1,1,0|-kqaig0,27,10,1|-khafs1,27,10,1|-khafs0,1,1,0|-k77h41,1,1,0|-k77h40,27,10,1|-jykd41,27,10,1|-jykd40,1,1,0|-joheg1,1,1,0|-joheg0,27,10,1|-jfuag1,27,10,1|-jfuag0,1,1,0|-j64ag1,1,1,0|-j64ag0,27,10,1|-iwr941,27,10,1|-iwr940,1,1,0|-imoag1,1,1,0|-imoag0,27,10,1|-ie16g1,27,10,1|-ie16g0,1,1,0|-i4b6g1,1,1,0|-i4b6g0,27,10,1|-hvb3s1,27,10,1|-hvb3s0,1,1,0|-hl8541,1,1,0|-hl8540,27,10,1|-hcl141,27,10,1|-hcl140,1,1,0|-h2i2g1,1,1,0|-h2i2g0,27,10,1|-gtuyg1,27,10,1|-gtuyg0,1,1,0|-gk4yg1,1,1,0|-gk4yg0,27,10,1|-gb4vs1,27,10,1|-gb4vs0,1,1,0|-g11x41,1,1,0|-g11x40,27,10,1|-fpw2g1,27,10,1|-fpw2g0,1,1,0|-fkul41,1,1,0|-fkul40,27,10,1|-eyiyk1,27,10,1|-eyiyk0,151,11,1|-ethh81,151,11,1|-ethh80,27,10,1|-eh8qk1,27,10,1|-eh8qk0,151,11,1|-earek1,151,11,1|-earek0,27,10,1|-dyinw1,27,10,1|-dyinw0,151,11,1|-drod81,151,11,1|-drod80,27,10,1|-dfsl81,27,10,1|-dfsl80,151,11,1|-d75h81,151,11,1|-d75h80,27,10,1|-cx0nw1,27,10,1|-cx0nw0,151,11,1|-cro2k1,151,11,1|-cro2k0,27,10,1|-cncfs1,27,10,1|-cncfs0,1,1,0|-cdmfs1,1,1,0|-cdmfs0,27,10,1|-c4md41,27,10,1|-c4md40,1,1,0|-bwc7s1,1,1,0|-bwc7s0,27,10,1|-buwfw1,27,10,1|-buwfw0,151,11,1|-bos2k1,151,11,1|-bos2k0,27,10,1|-bkgfs1,27,10,1|-bkgfs0,1,1,0|-bdm541,1,1,0|-bdm540,27,10,1|-b1qd41,27,10,1|-b1qd40,1,1,0|-att6g1,1,1,0|-att6g0,27,10,1|-aj0ag1,27,10,1|-aj0ag0,1,1,0|-aad6g1,1,1,0|-aad6g0,27,10,1|-a0n6g1,27,10,1|-a0n6g0,1,1,0|-9rn3s1,1,1,0|-9rn3s0,27,10,1|-9hx3s1,27,10,1|-9hx3s0,1,1,0|-98k2g1,1,1,0|-98k2g0,27,10,1|-8yu2g1,27,10,1|-8yu2g0,1,1,0|-8ptzs1,1,1,0|-8ptzs0,27,10,1|-8h6vs1,27,10,1|-8h6vs0,1,1,0|-87gvs1,1,1,0|-87gvs0,27,10,1|-7ygt41,27,10,1|-7ygt40,1,1,0|-7odug1,1,1,0|-7odug0,27,10,1|-7fqqg1,27,10,1|-7fqqg0,1,1,0|-75at41,1,1,0|-75at40,27,10,1|-6wnp41,27,10,1|-6wnp40,1,1,0|-6mxp41,1,1,0|-6mxp40,27,10,1|-6dxmg1,27,10,1|-6dxmg0,1,1,0|-63uns1,1,1,0|-63uns0,27,10,1|-5v7js1,27,10,1|-5v7js0,1,1,0|-5l4l41,1,1,0|-5l4l40,27,10,1|-5chh41,27,10,1|-5chh40,1,1,0|-52rh41,1,1,0|-52rh40,27,10,1|-4treg1,27,10,1|-4treg0,1,1,0|-4krbs1,1,1,0|-4krbs0,27,10,1|-49lh41,27,10,1|-49lh40,1,1,0|-421941,1,1,0|-421940,27,10,1|-3qveg1,27,10,1|-3qveg0,1,1,0|-3iy7s1,1,1,0|-3iy7s0,27,10,1|-385bs1,27,10,1|-385bs0,1,1,0|-30l3s1,1,1,0|-30l3s0,27,10,1|-2pf941,27,10,1|-2pf940,1,1,0|-2hv141,1,1,0|-2hv140,27,10,1|-26p6g1,27,10,1|-26p6g0,1,1,0|-1z4yg1,1,1,0|-1z4yg0,27,10,1|-1nz3s1,27,10,1|-1nz3s0,1,1,0|-1gevs1,1,1,0|-1gevs0,27,10,1|-14w2g1,27,10,1|-14w2g0,1,1,0|-z4ns1,1,1,0|-z4ns0,27,10,1|-m6841,27,10,1|-m6840,27,10,0|yd6vz,27,10,0|yd6w0,1,1,0|15kg7z,1,1,0|15kg80,27,10,1|1h39jz,27,10,1|1h39k0,1,1,0|1oaivz,1,1,0|1oaiw0,27,10,1|1ztc7z,27,10,1|1ztc80,1,1,0|270ljz,1,1,0|270lk0,27,10,1|2ijevz,27,10,1|2ijew0,1,1,0|2pqo7z,1,1,0|2pqo80,27,10,1|319hjz,27,10,1|319hk0,1,1,0|38tpjz,1,1,0|38tpk0,27,10,1|3jzk7z,27,10,1|3jzk80,1,1,0|3rjs7z,1,1,0|3rjs80,27,10,1|42pmvz,27,10,1|42pmw0,1,1,0|4a9uvz,1,1,0|4a9uw0,27,10,1|4lso7z,27,10,1|4lso80,1,1,0|4szxjz,1,1,0|4szxk0,27,10,1|54iqvz,27,10,1|54iqw0,1,1,0|5bq07z,1,1,0|5bq080,27,10,1|5n8tjz,27,10,1|5n8tk0,1,1,0|5v5xfz,1,1,0|5v5xg0,27,10,1|65ytfz,27,10,1|65ytg0,1,1,0|6dw03z,1,1,0|6dw040,27,10,1|6oow3z,27,10,1|6oow40,1,1,0|6wm2rz,1,1,0|6wm2s0,27,10,1|77eyrz,27,10,1|77eys0,1,1,0|7fc5fz,1,1,0|7fc5g0,27,10,1|7qi03z,27,10,1|7qi040,1,1,0|7yf6rz,1,1,0|7yf6s0,27,10,1|8982rz,27,10,1|8982s0,1,1,0|8h59fz,1,1,0|8h59g0,27,10,1|8ry5fz,27,10,1|8ry5g0,1,1,0|8zvc3z,1,1,0|8zvc40,27,10,1|9ao83z,27,10,1|9ao840,1,1,0|9ilerz,1,1,0|9iles0,27,10,1|9tearz,27,10,1|9teas0,1,1,0|a1bhfz,1,1,0|a1bhg0,27,10,1|achc3z,27,10,1|achc40,1,1,0|ak1k3z,1,1,0|ak1k40,27,10,1|av7erz,27,10,1|av7es0,1,1,0|b34lfz,1,1,0|b34lg0,27,10,1|bdxhfz,27,10,1|bdxhg0,1,1,0|bluo3z,1,1,0|bluo40,27,10,1|bwnk3z,27,10,1|bwnk40,1,1,0|c4kqrz,1,1,0|c4kqs0,27,10,1|cfdmrz,27,10,1|cfdms0,1,1,0|cnatfz,1,1,0|cnatg0,27,10,1|cy3pfz,27,10,1|cy3pg0,1,1,0|d60w3z,1,1,0|d60w40,27,10,1|dgts3z,27,10,1|dgts40,1,1,0|dp3xfz,1,1,0|dp3xg0,27,10,1|dzwtfz,27,10,1|dzwtg0,1,1,0|e7u03z,1,1,0|e7u040,27,10,1|eimw3z,27,10,1|eimw40,1,1,0|eqk2rz,1,1,0|eqk2s0,27,10,1|f1cyrz,27,10,1|f1cys0,1,1,0|f9a5fz,1,1,0|f9a5g0,27,10,1|fkg03z,27,10,1|fkg040,1,1,0|fs083z,1,1,0|fs0840,27,10,1|g362rz,27,10,1|g362s0,1,1,0|gaqarz,1,1,0|gaqas0,27,10,1|glw5fz,27,10,1|glw5g0,1,1,0|gttc3z,1,1,0|gttc40,27,10,1|h4m83z,27,10,1|h4m840,1,1,0|hcjerz,1,1,0|hcjes0,27,10,1|hncarz,27,10,1|hncas0,1,1,0|hv9hfz,1,1,0|hv9hg0,27,10,1|i6fc3z,27,10,1|i6fc40,1,1,0|idzk3z,1,1,0|idzk40,27,10,1|ip5erz,27,10,1|ip5es0,1,1,0|iwpmrz,1,1,0|iwpms0,27,10,1|j7vhfz,27,10,1|j7vhg0,1,1,0|jffpfz,1,1,0|jffpg0,27,10,1|jqlk3z,27,10,1|jqlk40,1,1,0|jyiqrz,1,1,0|jyiqs0,27,10,1|k9bmrz,27,10,1|k9bms0,1,1,0|kh8tfz,1,1,0|kh8tg0,27,10,1|ks1pfz,27,10,1|ks1pg0,1,1,0|kzyw3z,1,1,0|kzyw40,27,10,1|lb4qrz,27,10,1|lb4qs0,1,1,0|lioyrz,1,1,0|lioys0,27,10,1|ltutfz,27,10,1|ltutg0,1,1,0|m1f1fz,1,1,0|m1f1g0,27,10,1|mckw3z,27,10,1|mckw40,1,1,0|mki2rz,1,1,0|mki2s0,27,10,1|mvayrz,27,10,1|mvays0,1,1,0|n385fz,1,1,0|n385g0,27,10,1|ne11fz,27,10,1|ne11g0,1,1,0|nly83z,1,1,0|nly840,27,10,1|nwr43z,27,10,1|nwr440,1,1,0|o4oarz,1,1,0|o4oas0,27,10,1|ofu5fz,27,10,1|ofu5g0,1,1,0|onedfz,1,1,0|onedg0,27,10,1|oyk83z,27,10,1|oyk840,1,1,0|p64g3z,1,1,0|p64g40,27,10,1|phaarz,27,10,1|phaas0,1,1,0|pp7hfz,1,1,0|pp7hg0,27,10,1|q00dfz,27,10,1|q00dg0,1,1,0|q7xk3z,1,1,0|q7xk40,27,10,1|qiqg3z,27,10,1|qiqg40,1,1,0|qqnmrz,1,1,0|qqnms0,27,10,1|r1thfz,27,10,1|r1thg0,1,1,0|r9dpfz,1,1,0|r9dpg0,27,10,1|rkjk3z,27,10,1|rkjk40,1,1,0|rs3s3z,1,1,0|rs3s40,27,10,1|s39mrz,27,10,1|s39ms0,1,1,0|sb6tfz,1,1,0|sb6tg0,27,10,1|slzpfz,27,10,1|slzpg0,1,1,0|stww3z,1,1,0|stww40,27,10,1|t4ps3z,27,10,1|t4ps40,1,1,0|tcmyrz,1,1,0|tcmys0,27,10,1|tnfurz,27,10,1|tnfus0,1,1,0|tvd1fz,1,1,0|tvd1g0,27,10,1|u6iw3z,27,10,1|u6iw40,1,1,0|ue343z,1,1,0|ue3440,27,10,1|up8yrz,27,10,1|up8ys0,1,1,0|uwt6rz,1,1,0|uwt6s0,27,10,1|v7z1fz,27,10,1|v7z1g0,1,1,0|vfw83z,1,1,0|vfw840,27,10,1|vqp43z,27,10,1|vqp440,1,1,0|vymarz,1,1,0|vymas0,27,10,1|w9f6rz,27,10,1|w9f6s0,1,1,0|whcdfz,1,1,0|whcdg0,27,10,1|wsi83z,27,10,1|wsi840,1,1,0|x02g3z,1,1,0|x02g40,27,10,1|xb8arz,27,10,1|xb8as0,1,1,0|xisirz,1,1,0|xisis0,27,10,1|xtydfz,27,10,1|xtydg0,1,1,0|y1ilfz,1,1,0|y1ilg0,27,10,1|ycog3z,27,10,1|ycog40,1,1,0|yklmrz,1,1,0|yklms0,27,10,1|yveirz,27,10,1|yveis0,1,1,0|z3bpfz,1,1,0|z3bpg0,27,10,1|ze4lfz,27,10,1|ze4lg0,1,1,0\",\"Europe/Luxembourg|,0,353,0|-y89550,10,10,0|-rzo2w1,10,10,0|-rzo2w0,11,11,1|-rsilg1,11,11,1|-rsilg0,10,10,0|-rhps81,10,10,0|-rhps80,11,11,1|-raglg1,11,11,1|-raglg0,10,10,0|-qznjw1,10,10,0|-qznjw0,11,11,1|-qrqd81,11,11,1|-qrqd80,10,10,0|-qo4w41,10,10,0|-qo4w40,8,1,0|-qj59g1,8,1,0|-qj59g0,9,10,1|-q7z6g1,9,10,1|-q7z6g0,8,1,0|-q15441,8,1,0|-q15440,9,10,1|-po6ak1,9,10,1|-po6ak0,8,1,0|-pgvhg1,8,1,0|-pgvhg0,9,10,1|-p5anw1,9,10,1|-p5anw0,8,1,0|-oxj6s1,8,1,0|-oxj6s0,9,10,1|-ong5c1,9,10,1|-ong5c0,8,1,0|-odd9g1,8,1,0|-odd9g0,9,10,1|-o4pzw1,9,10,1|-o4pzw0,8,1,0|-nvq2s1,8,1,0|-nvq2s0,9,10,1|-nm0001,9,10,1|-nm0000,8,1,0|-ncl6s1,8,1,0|-ncl6s0,9,10,1|-n39xc1,9,10,1|-n39xc0,8,1,0|-mt71g1,8,1,0|-mt71g0,9,10,1|-mkjuo1,9,10,1|-mkjuo0,8,1,0|-matxg1,8,1,0|-matxg0,9,10,1|-m1ts01,9,10,1|-m1ts00,8,1,0|-lrqw41,8,1,0|-lrqw40,9,10,1|-liqqo1,9,10,1|-liqqo0,8,1,0|-l8nus1,8,1,0|-l8nus0,9,10,1|-l00ig1,9,10,1|-l00ig0,8,1,0|-kqaig1,8,1,0|-kqaig0,9,10,1|-khafs1,9,10,1|-khafs0,8,1,0|-k77h41,8,1,0|-k77h40,9,10,1|-jykd41,9,10,1|-jykd40,8,1,0|-jp7bs1,8,1,0|-jp7bs0,9,10,1|-jfuag1,9,10,1|-jfuag0,8,1,0|-j6u7s1,8,1,0|-j6u7s0,9,10,1|-iwr941,9,10,1|-iwr940,8,1,0|-ine7s1,8,1,0|-ine7s0,9,10,1|-ie16g1,9,10,1|-ie16g0,8,1,0|-i513s1,8,1,0|-i513s0,9,10,1|-hvb3s1,9,10,1|-hvb3s0,8,1,0|-hl8541,8,1,0|-hl8540,9,10,1|-hcl141,9,10,1|-hcl140,8,1,0|-h37zs1,8,1,0|-h37zs0,9,10,1|-gtuyg1,9,10,1|-gtuyg0,8,1,0|-gkuvs1,8,1,0|-gkuvs0,9,10,1|-gb4vs1,9,10,1|-gb4vs0,8,1,0|-g11x41,8,1,0|-g11x40,9,10,1|-fpw2g1,9,10,1|-fpw2g0,8,1,0|-fkul41,8,1,0|-fkul40,9,10,1|-fgsag1,9,10,1|-fgsag0,9,11,1|-e6dzw1,9,11,1|-e6dzw0,8,10,0|-dytrw1,8,10,0|-dytrw0,9,11,1|-dp3rw1,9,11,1|-dp3rw0,8,10,0|-dfqqk1,8,10,0|-dfqqk0,9,11,1|-d73mk1,9,11,1|-d73mk0,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cofek1,11,11,1|-cofek0,10,10,0|-cbtp81,10,10,0|-cbtp80,11,11,1|-c4kl81,11,11,1|-c4kl80,10,10,0|3s9mrz,10,10,0|3s9ms0,11,11,1|419pfz,11,11,1|419pg0,10,10,0|4azpfz,10,10,0|4azpg0,11,11,1|4kcqrz,11,11,1|4kcqs0,10,10,0|4tps3z,10,10,0|4tps40,11,11,1|532tfz,11,11,1|532tg0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Madrid|,0,354,0|-100edc0,8,1,0|-qzlus1,8,1,0|-qzlus0,9,10,1|-qqnk01,9,10,1|-qqnk00,8,1,0|-qhalg1,8,1,0|-qhalg0,9,10,1|-q7vmo1,9,10,1|-q7vmo0,8,1,0|-nusqs1,8,1,0|-nusqs0,9,10,1|-nm0001,9,10,1|-nm0000,8,1,0|-mt71g1,8,1,0|-mt71g0,9,10,1|-mkjuo1,9,10,1|-mkjuo0,8,1,0|-matxg1,8,1,0|-matxg0,9,10,1|-m1ts01,9,10,1|-m1ts00,8,1,0|-lrqtc1,8,1,0|-lrqtc0,9,10,1|-liqqo1,9,10,1|-liqqo0,8,1,0|-l8nus1,8,1,0|-l8nus0,9,10,1|-l00o01,9,10,1|-l00o00,8,1,0|-gzf6s1,8,1,0|-gzf6s0,9,10,1|-gtv401,9,10,1|-gtv400,8,1,0|-gki5g1,8,1,0|-gki5g0,9,10,1|-gj2dk1,9,10,1|-gj2dk0,152,11,1|-gb3c81,152,11,1|-gb3c80,9,10,1|-fs2001,9,10,1|-fs2000,8,1,0|-fjrxg1,8,1,0|-fjrxg0,10,10,0|-eft481,10,10,0|-eft480,11,11,1|-e9kys1,11,11,1|-e9kys0,10,10,0|-dxsyw1,10,10,0|-dxsyw0,11,11,1|-dp5s41,11,11,1|-dp5s40,10,10,0|-df2w81,10,10,0|-df2w80,11,11,1|-d6fpg1,11,11,1|-d6fpg0,10,10,0|-cwctk1,10,10,0|-cwctk0,11,11,1|-cnpms1,11,11,1|-cnpms0,10,10,0|-cdmqw1,10,10,0|-cdmqw0,11,11,1|-c4zk41,11,11,1|-c4zk40,10,10,0|-asdmw1,10,10,0|-asdmw0,11,11,1|-akgdg1,11,11,1|-akgdg0,10,10,0|28g53z,10,10,0|28g540,11,11,1|2hgajz,11,11,1|2hgak0,10,10,0|2r67rz,10,10,0|2r67s0,11,11,1|306d7z,11,11,1|306d80,10,10,0|396d3z,10,10,0|396d40,11,11,1|3ijh7z,11,11,1|3ijh80,10,10,0|3s9efz,10,10,0|3s9eg0,11,11,1|419jvz,11,11,1|419jw0,10,10,0|4azpfz,10,10,0|4azpg0,11,11,1|4kcqrz,11,11,1|4kcqs0,10,10,0|4tps3z,10,10,0|4tps40,11,11,1|532tfz,11,11,1|532tg0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Malta|,0,355,0|-13qyw0s,10,10,0|-rymys1,10,10,0|-rymys0,11,11,1|-rsio81,11,11,1|-rsio80,10,10,0|-rj5k41,10,10,0|-rj5k40,11,11,1|-r9qqw1,11,11,1|-r9qqw0,10,10,0|-r1idg1,10,10,0|-r1idg0,11,11,1|-qqnpk1,11,11,1|-qqnpk0,10,10,0|-qj59g1,10,10,0|-qj59g0,11,11,1|-q7zhk1,11,11,1|-q7zhk0,10,10,0|-pzcas1,10,10,0|-pzcas0,11,11,1|-ppzc81,11,11,1|-ppzc80,10,10,0|-ff59g1,10,10,0|-ff59g0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfsl81,10,10,0|-dfsl80,11,11,1|-d75h81,11,11,1|-d75h80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cohes1,11,11,1|-cohes0,10,10,0|-cf2d81,10,10,0|-cf2d80,11,11,1|-c4mfw1,11,11,1|-c4mfw0,10,10,0|-bwcg41,10,10,0|-bwcg40,11,11,1|-blwis1,11,11,1|-blwis0,10,10,0|-bec581,10,10,0|-bec580,11,11,1|-b36ak1,11,11,1|-b36ak0,10,10,0|-1vwis1,10,10,0|-1vwis0,11,11,1|-1pf9k1,11,11,1|-1pf9k0,10,10,0|-1cthg1,10,10,0|-1cthg0,11,11,1|-16p441,11,11,1|-16p440,10,10,0|-u3es1,10,10,0|-u3es0,11,11,1|-nz1g1,11,11,1|-nz1g0,10,10,0|-b0dg1,10,10,0|-b0dg0,11,11,1|-4w041,11,11,1|-4w040,10,10,0|7pp7z,10,10,0|7pp80,11,11,1|du2jz,11,11,1|du2k0,10,10,0|q2t7z,10,10,0|q2t80,11,11,1|wk57z,11,11,1|wk580,10,10,0|195ujz,10,10,0|195uk0,11,11,1|1fn6jz,11,11,1|1fn6k0,10,10,0|1oyd7z,10,10,0|1oyd80,11,11,1|1ybejz,11,11,1|1ybek0,10,10,0|28t6jz,10,10,0|28t6k0,11,11,1|2gf97z,11,11,1|2gf980,10,10,0|2rjerz,10,10,0|2rjes0,11,11,1|2zginz,11,11,1|2zgio0,10,10,0|3a9hfz,10,10,0|3a9hg0,11,11,1|3i6lbz,11,11,1|3i6lc0,10,10,0|3szk3z,10,10,0|3szk40,11,11,1|40wnzz,11,11,1|40wo00,10,10,0|4bpmrz,10,10,0|4bpms0,11,11,1|4jmqnz,11,11,1|4jmqo0,10,10,0|4ufpfz,10,10,0|4ufpg0,11,11,1|52ctbz,11,11,1|52ctc0,10,10,0|5chpfz,10,10,0|5chpg0,11,11,1|5lfunz,11,11,1|5lfuo0,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Mariehamn|,0,348,0|-1bss9yd,77,348,0|-peghye,77,348,0|-peghyd,15,11,0|-ehco81,15,11,0|-ehco80,16,6,1|-e7vxk1,16,6,1|-e7vxk0,15,11,0|5v5unz,15,11,0|5v5uo0,16,6,1|64ivzz,16,6,1|64iw00,15,11,0|6dvxbz,15,11,0|6dvxc0,16,6,1|6n8ynz,16,6,1|6n8yo0,15,11,0|6wm2rz,15,11,0|6wm2s0,16,6,1|75z43z,16,6,1|75z440,15,11,0|7fc5fz,15,11,0|7fc5g0,16,6,1|7p25fz,16,6,1|7p25g0,15,11,0|7yf6rz,15,11,0|7yf6s0,16,6,1|87s83z,16,6,1|87s840,15,11,0|8h59fz,15,11,0|8h59g0,16,6,1|8qiarz,16,6,1|8qias0,15,11,0|8zvc3z,15,11,0|8zvc40,16,6,1|998dfz,16,6,1|998dg0,15,11,0|9ilerz,15,11,0|9iles0,16,6,1|9ryg3z,16,6,1|9ryg40,15,11,0|a1bhfz,15,11,0|a1bhg0,16,6,1|aaoirz,16,6,1|aaois0,15,11,0|ak1k3z,15,11,0|ak1k40,16,6,1|atrk3z,16,6,1|atrk40,15,11,0|b34lfz,15,11,0|b34lg0,16,6,1|bchmrz,16,6,1|bchms0,15,11,0|bluo3z,15,11,0|bluo40,16,6,1|bv7pfz,16,6,1|bv7pg0,15,11,0|c4kqrz,15,11,0|c4kqs0,16,6,1|cdxs3z,16,6,1|cdxs40,15,11,0|cnatfz,15,11,0|cnatg0,16,6,1|cwnurz,16,6,1|cwnus0,15,11,0|d60w3z,15,11,0|d60w40,16,6,1|dfdxfz,16,6,1|dfdxg0,15,11,0|dp3xfz,15,11,0|dp3xg0,16,6,1|dzwtfz,16,6,1|dzwtg0,15,11,0|e7u03z,15,11,0|e7u040,16,6,1|eimw3z,16,6,1|eimw40,15,11,0|eqk2rz,15,11,0|eqk2s0,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|fs083z,15,11,0|fs0840,16,6,1|g362rz,16,6,1|g362s0,15,11,0|gaqarz,15,11,0|gaqas0,16,6,1|glw5fz,16,6,1|glw5g0,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|ofu5fz,16,6,1|ofu5g0,15,11,0|onedfz,15,11,0|onedg0,16,6,1|oyk83z,16,6,1|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Europe/Minsk|,0,356,0|-1ayy7rs,21,357,0|-nu113d,21,357,0|-nu113c,15,11,0|-kmr1k1,15,11,0|-kmr1k0,148,6,0|-evpf01,148,6,0|-evpf00,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-db2g81,11,11,1|-db2g80,148,6,0|5vb6bz,148,6,0|5vb6c0,149,209,1|64pzjz,149,209,1|64pzk0,148,6,0|6e33nz,148,6,0|6e33o0,149,209,1|6nhwvz,149,209,1|6nhww0,148,6,0|6wv0zz,148,6,0|6wv100,149,209,1|769u7z,149,209,1|769u80,148,6,0|7foszz,148,6,0|7fot00,149,209,1|7p1zvz,149,209,1|7p1zw0,148,6,0|7yf17z,148,6,0|7yf180,149,209,1|87s2jz,149,209,1|87s2k0,148,6,0|8h53vz,148,6,0|8h53w0,149,209,1|8qi57z,149,209,1|8qi580,148,6,0|8zv6jz,148,6,0|8zv6k0,149,209,1|9987vz,149,209,1|9987w0,148,6,0|9il97z,148,6,0|9il980,149,209,1|9ryajz,149,209,1|9ryak0,148,6,0|a1bbvz,148,6,0|a1bbw0,149,209,1|aaod7z,149,209,1|aaod80,148,6,0|b34fvz,148,6,0|b34fw0,16,6,1|bchjzz,16,6,1|bchk00,15,11,0|blulbz,15,11,0|blulc0,16,6,1|bv7mnz,16,6,1|bv7mo0,15,11,0|c4knzz,15,11,0|c4ko00,16,6,1|cdxpbz,16,6,1|cdxpc0,15,11,0|cnaqnz,15,11,0|cnaqo0,16,6,1|cwnrzz,16,6,1|cwns00,15,11,0|d60tbz,15,11,0|d60tc0,16,6,1|dfdunz,16,6,1|dfduo0,15,11,0|dp3unz,15,11,0|dp3uo0,16,6,1|dzwqnz,16,6,1|dzwqo0,15,11,0|e7txbz,15,11,0|e7txc0,16,6,1|eimtbz,16,6,1|eimtc0,15,11,0|eqjzzz,15,11,0|eqk000,16,6,1|f1cvzz,16,6,1|f1cw00,15,11,0|f9a2nz,15,11,0|f9a2o0,16,6,1|fkfxbz,16,6,1|fkfxc0,15,11,0|fs05bz,15,11,0|fs05c0,16,6,1|g35zzz,16,6,1|g36000,15,11,0|gaq7zz,15,11,0|gaq800,16,6,1|glw2nz,16,6,1|glw2o0,15,11,0|gtt9bz,15,11,0|gtt9c0,16,6,1|h4m5bz,16,6,1|h4m5c0,15,11,0|hcjbzz,15,11,0|hcjc00,16,6,1|hnc7zz,16,6,1|hnc800,15,11,0|hv9enz,15,11,0|hv9eo0,16,6,1|i6f9bz,16,6,1|i6f9c0,15,11,0|idzhbz,15,11,0|idzhc0,16,6,1|ip5bzz,16,6,1|ip5c00,15,11,0|iwpjzz,15,11,0|iwpk00,16,6,1|j7venz,16,6,1|j7veo0,15,11,0|jffmnz,15,11,0|jffmo0,16,6,1|jqlhbz,16,6,1|jqlhc0,15,11,0|jyinzz,15,11,0|jyio00,16,6,1|k9bjzz,16,6,1|k9bk00,15,11,0|kh8qnz,15,11,0|kh8qo0,16,6,1|ks1mnz,16,6,1|ks1mo0,15,11,0|kzytbz,15,11,0|kzytc0,16,6,1|lb4nzz,16,6,1|lb4o00,15,11,0|liovzz,15,11,0|liow00,100,6,0\",\"Europe/Monaco|,0,358,0|-14hnyp8,7,9,0|-uo2b3m,7,9,0|-uo2b3l,8,1,0|-ry2lg1,8,1,0|-ry2lg0,9,10,1|-rsgqs1,9,10,1|-rsgqs0,8,1,0|-rjiis1,8,1,0|-rjiis0,9,10,1|-r9dpg1,9,10,1|-r9dpg0,8,1,0|-r1idg1,8,1,0|-r1idg0,9,10,1|-qqnms1,9,10,1|-qqnms0,8,1,0|-qj59g1,8,1,0|-qj59g0,9,10,1|-q7xk41,9,10,1|-q7xk40,8,1,0|-q15441,8,1,0|-q15440,9,10,1|-po6g41,9,10,1|-po6g40,8,1,0|-pgvhg1,8,1,0|-pgvhg0,9,10,1|-p5atg1,9,10,1|-p5atg0,8,1,0|-oxj6s1,8,1,0|-oxj6s0,9,10,1|-ong841,9,10,1|-ong840,8,1,0|-obkg41,8,1,0|-obkg40,9,10,1|-o4q5g1,9,10,1|-o4q5g0,8,1,0|-nvq2s1,8,1,0|-nvq2s0,9,10,1|-nm02s1,9,10,1|-nm02s0,8,1,0|-ncn1g1,8,1,0|-ncn1g0,9,10,1|-n3a041,9,10,1|-n3a040,8,1,0|-mt71g1,8,1,0|-mt71g0,9,10,1|-mkjxg1,9,10,1|-mkjxg0,8,1,0|-matxg1,8,1,0|-matxg0,9,10,1|-m1tus1,9,10,1|-m1tus0,8,1,0|-lrqw41,8,1,0|-lrqw40,9,10,1|-liqtg1,9,10,1|-liqtg0,8,1,0|-l8nus1,8,1,0|-l8nus0,9,10,1|-l00qs1,9,10,1|-l00qs0,8,1,0|-kqaqs1,8,1,0|-kqaqs0,9,10,1|-khao41,9,10,1|-khao40,8,1,0|-k77pg1,8,1,0|-k77pg0,9,10,1|-jyklg1,9,10,1|-jyklg0,8,1,0|-jp7k41,8,1,0|-jp7k40,9,10,1|-jfuis1,9,10,1|-jfuis0,8,1,0|-j6ug41,8,1,0|-j6ug40,9,10,1|-iwrhg1,9,10,1|-iwrhg0,8,1,0|-ineg41,8,1,0|-ineg40,9,10,1|-ie1es1,9,10,1|-ie1es0,8,1,0|-i51c41,8,1,0|-i51c40,9,10,1|-hvbc41,9,10,1|-hvbc40,8,1,0|-hl8dg1,8,1,0|-hl8dg0,9,10,1|-hcl9g1,9,10,1|-hcl9g0,8,1,0|-h38841,8,1,0|-h38840,9,10,1|-gtv6s1,9,10,1|-gtv6s0,8,1,0|-gkv441,8,1,0|-gkv440,9,10,1|-gb5441,9,10,1|-gb5440,8,1,0|-g125g1,8,1,0|-g125g0,9,10,1|-fpwas1,9,10,1|-fpwas0,8,1,0|-fkul41,8,1,0|-fkul40,9,10,1|-eyh9g1,9,10,1|-eyh9g0,152,11,1|-eqk5k1,152,11,1|-eqk5k0,9,10,1|-eimw41,9,10,1|-eimw40,152,11,1|-e6dzw1,152,11,1|-e6dzw0,9,10,1|-dytrw1,9,10,1|-dytrw0,152,11,1|-dp3rw1,152,11,1|-dp3rw0,9,10,1|-dfqqk1,9,10,1|-dfqqk0,152,11,1|-d62qs1,152,11,1|-d62qs0,9,10,1|-cx0nw1,9,10,1|-cx0nw0,152,11,1|-cofek1,152,11,1|-cofek0,10,10,0|396inz,10,10,0|396io0,11,11,1|3ijh7z,11,11,1|3ijh80,10,10,0|3s9mrz,10,10,0|3s9ms0,11,11,1|419pfz,11,11,1|419pg0,10,10,0|4azpfz,10,10,0|4azpg0,11,11,1|4kcqrz,11,11,1|4kcqs0,10,10,0|4tps3z,10,10,0|4tps40,11,11,1|532tfz,11,11,1|532tg0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Moscow|,0,359,0|-1ayy9mh,21,359,0|-rx5dmi,21,359,0|-rx5dmh,21,360,0|-refds8,21,360,0|-refds7,50,361,1|-r57wg8,50,361,1|-r57wg7,21,360,0|-qx8xw8,21,360,0|-qx8xw7,153,362,1|-qrqps8,153,362,1|-qrqps7,50,361,1|-qeh0k8,50,361,1|-qeh0k7,153,362,1|-qcx401,153,362,1|-qcx400,149,209,1|-qak8g1,149,209,1|-qak8g0,148,6,0|-pibkg1,148,6,0|-pibkg0,149,209,1|-pgkok1,149,209,1|-pgkok0,92,194,1|-p84z81,92,194,1|-p84z80,149,209,1|-p6lcg1,149,209,1|-p6lcg0,148,6,0|-ontcc1,148,6,0|-ontcc0,15,11,0|-kmr1k1,15,11,0|-kmr1k0,148,6,0|5vb6bz,148,6,0|5vb6c0,149,209,1|64pzjz,149,209,1|64pzk0,148,6,0|6e33nz,148,6,0|6e33o0,149,209,1|6nhwvz,149,209,1|6nhww0,148,6,0|6wv0zz,148,6,0|6wv100,149,209,1|769u7z,149,209,1|769u80,148,6,0|7foszz,148,6,0|7fot00,149,209,1|7p1zvz,149,209,1|7p1zw0,148,6,0|7yf17z,148,6,0|7yf180,149,209,1|87s2jz,149,209,1|87s2k0,148,6,0|8h53vz,148,6,0|8h53w0,149,209,1|8qi57z,149,209,1|8qi580,148,6,0|8zv6jz,148,6,0|8zv6k0,149,209,1|9987vz,149,209,1|9987w0,148,6,0|9il97z,148,6,0|9il980,149,209,1|9ryajz,149,209,1|9ryak0,148,6,0|a1bbvz,148,6,0|a1bbw0,149,209,1|aaod7z,149,209,1|aaod80,148,6,0|ak1ejz,148,6,0|ak1ek0,149,209,1|atrejz,149,209,1|atrek0,148,6,0|b34fvz,148,6,0|b34fw0,16,6,1|bchjzz,16,6,1|bchk00,15,11,0|bi8ynz,15,11,0|bi8yo0,148,6,0|bluijz,148,6,0|bluik0,149,209,1|bv7jvz,149,209,1|bv7jw0,148,6,0|c4kl7z,148,6,0|c4kl80,149,209,1|cdxmjz,149,209,1|cdxmk0,148,6,0|cnanvz,148,6,0|cnanw0,149,209,1|cwnp7z,149,209,1|cwnp80,148,6,0|d60qjz,148,6,0|d60qk0,149,209,1|dfdrvz,149,209,1|dfdrw0,148,6,0|dp3rvz,148,6,0|dp3rw0,149,209,1|dzwnvz,149,209,1|dzwnw0,148,6,0|e7tujz,148,6,0|e7tuk0,149,209,1|eimqjz,149,209,1|eimqk0,148,6,0|eqjx7z,148,6,0|eqjx80,149,209,1|f1ct7z,149,209,1|f1ct80,148,6,0|f99zvz,148,6,0|f99zw0,149,209,1|fkfujz,149,209,1|fkfuk0,148,6,0|fs02jz,148,6,0|fs02k0,149,209,1|g35x7z,149,209,1|g35x80,148,6,0|gaq57z,148,6,0|gaq580,149,209,1|glvzvz,149,209,1|glvzw0,148,6,0|gtt6jz,148,6,0|gtt6k0,149,209,1|h4m2jz,149,209,1|h4m2k0,148,6,0|hcj97z,148,6,0|hcj980,149,209,1|hnc57z,149,209,1|hnc580,148,6,0|hv9bvz,148,6,0|hv9bw0,149,209,1|i6f6jz,149,209,1|i6f6k0,148,6,0|idzejz,148,6,0|idzek0,149,209,1|ip597z,149,209,1|ip5980,148,6,0|iwph7z,148,6,0|iwph80,149,209,1|j7vbvz,149,209,1|j7vbw0,148,6,0|jffjvz,148,6,0|jffjw0,149,209,1|jqlejz,149,209,1|jqlek0,148,6,0|jyil7z,148,6,0|jyil80,149,209,1|k9bh7z,149,209,1|k9bh80,148,6,0|kh8nvz,148,6,0|kh8nw0,149,209,1|ks1jvz,149,209,1|ks1jw0,148,6,0|kzyqjz,148,6,0|kzyqk0,149,209,1|lb4l7z,149,209,1|lb4l80,148,6,0|liot7z,148,6,0|liot80,148,209,0|ne0t3z,148,209,0|ne0t40,148,6,0\",\"Europe/Oslo|,0,202,0|-1353tzo,10,10,0|-rzayo1,10,10,0|-rzayo0,11,11,1|-rskiw1,11,11,1|-rskiw0,10,10,0|-fc7s81,10,10,0|-fc7s80,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cnnmk1,11,11,1|-cnnmk0,10,10,0|-5mxh81,10,10,0|-5mxh80,11,11,1|-5d7h81,11,11,1|-5d7h80,10,10,0|-53ufw1,10,10,0|-53ufw0,11,11,1|-4uhek1,11,11,1|-4uhek0,10,10,0|-4l4d81,10,10,0|-4l4d80,11,11,1|-4brbw1,11,11,1|-4brbw0,10,10,0|-42eak1,10,10,0|-42eak0,11,11,1|-3t1981,11,11,1|-3t1980,10,10,0|-3jo7w1,10,10,0|-3jo7w0,11,11,1|-3ab6k1,11,11,1|-3ab6k0,10,10,0|-30y581,10,10,0|-30y580,11,11,1|-2r8581,11,11,1|-2r8580,10,10,0|-2g2ak1,10,10,0|-2g2ak0,11,11,1|-28i2k1,11,11,1|-28i2k0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Paris|,0,9,0|-154gb3l,7,9,0|-uozn3m,7,9,0|-uozn3l,8,1,0|-ry2lg1,8,1,0|-ry2lg0,9,10,1|-rsgqs1,9,10,1|-rsgqs0,8,1,0|-rjiis1,8,1,0|-rjiis0,9,10,1|-r9dpg1,9,10,1|-r9dpg0,8,1,0|-r1idg1,8,1,0|-r1idg0,9,10,1|-qqnms1,9,10,1|-qqnms0,8,1,0|-qj59g1,8,1,0|-qj59g0,9,10,1|-q7xk41,9,10,1|-q7xk40,8,1,0|-q15441,8,1,0|-q15440,9,10,1|-po6g41,9,10,1|-po6g40,8,1,0|-pgvhg1,8,1,0|-pgvhg0,9,10,1|-p5atg1,9,10,1|-p5atg0,8,1,0|-oxj6s1,8,1,0|-oxj6s0,9,10,1|-ong841,9,10,1|-ong840,8,1,0|-obkg41,8,1,0|-obkg40,9,10,1|-o4q5g1,9,10,1|-o4q5g0,8,1,0|-nvq2s1,8,1,0|-nvq2s0,9,10,1|-nm02s1,9,10,1|-nm02s0,8,1,0|-ncn1g1,8,1,0|-ncn1g0,9,10,1|-n3a041,9,10,1|-n3a040,8,1,0|-mt71g1,8,1,0|-mt71g0,9,10,1|-mkjxg1,9,10,1|-mkjxg0,8,1,0|-matxg1,8,1,0|-matxg0,9,10,1|-m1tus1,9,10,1|-m1tus0,8,1,0|-lrqw41,8,1,0|-lrqw40,9,10,1|-liqtg1,9,10,1|-liqtg0,8,1,0|-l8nus1,8,1,0|-l8nus0,9,10,1|-l00qs1,9,10,1|-l00qs0,8,1,0|-kqaqs1,8,1,0|-kqaqs0,9,10,1|-khao41,9,10,1|-khao40,8,1,0|-k77pg1,8,1,0|-k77pg0,9,10,1|-jyklg1,9,10,1|-jyklg0,8,1,0|-jp7k41,8,1,0|-jp7k40,9,10,1|-jfuis1,9,10,1|-jfuis0,8,1,0|-j6ug41,8,1,0|-j6ug40,9,10,1|-iwrhg1,9,10,1|-iwrhg0,8,1,0|-ineg41,8,1,0|-ineg40,9,10,1|-ie1es1,9,10,1|-ie1es0,8,1,0|-i51c41,8,1,0|-i51c40,9,10,1|-hvbc41,9,10,1|-hvbc40,8,1,0|-hl8dg1,8,1,0|-hl8dg0,9,10,1|-hcl9g1,9,10,1|-hcl9g0,8,1,0|-h38841,8,1,0|-h38840,9,10,1|-gtv6s1,9,10,1|-gtv6s0,8,1,0|-gkv441,8,1,0|-gkv440,9,10,1|-gb5441,9,10,1|-gb5440,8,1,0|-g125g1,8,1,0|-g125g0,9,10,1|-fpwas1,9,10,1|-fpwas0,8,1,0|-fkul41,8,1,0|-fkul40,9,10,1|-ff5c81,9,10,1|-ff5c80,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d8caw1,11,11,1|-d8caw0,152,11,1|-d62qs1,152,11,1|-d62qs0,9,10,1|-cx0nw1,9,10,1|-cx0nw0,152,11,1|-cofek1,152,11,1|-cofek0,10,10,0|396inz,10,10,0|396io0,11,11,1|3ijh7z,11,11,1|3ijh80,10,10,0|3s9mrz,10,10,0|3s9ms0,11,11,1|419pfz,11,11,1|419pg0,10,10,0|4azpfz,10,10,0|4azpg0,11,11,1|4kcqrz,11,11,1|4kcqs0,10,10,0|4tps3z,10,10,0|4tps40,11,11,1|532tfz,11,11,1|532tg0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Podgorica|,0,332,0|-18vsmgo,10,10,0|-ezayw1,10,10,0|-ezayw0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cv5zw1,10,10,0|-cv5zw0,11,11,1|-cofek1,11,11,1|-cofek0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Prague|,0,334,0|-1qmkw08,7,334,0|-14u7uo9,7,334,0|-14u7uo8,10,10,0|-s0e081,10,10,0|-s0e080,11,11,1|-rsilg1,11,11,1|-rsilg0,10,10,0|-ridmk1,10,10,0|-ridmk0,11,11,1|-ragfw1,11,11,1|-ragfw0,10,10,0|-qznjw1,10,10,0|-qznjw0,11,11,1|-qrqd81,11,11,1|-qrqd80,10,10,0|-fizzw1,10,10,0|-fizzw0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cnnmk1,11,11,1|-cnnmk0,10,10,0|-cchrw1,10,10,0|-cchrw0,11,11,1|-c4mfw1,11,11,1|-c4mfw0,10,10,0|-c1qns1,10,10,0|-c1qns0,1,1,1|-bxf3s1,1,1,1|-bxf3s0,10,10,0|-bujh81,10,10,0|-bujh80,11,11,1|-blwd81,11,11,1|-blwd80,10,10,0|-bbtek1,10,10,0|-bbtek0,11,11,1|-b36ak1,11,11,1|-b36ak0,10,10,0|-ati581,10,10,0|-ati580,11,11,1|-akg7w1,11,11,1|-akg7w0,10,10,0|4tps3z,10,10,0|4tps40,11,11,1|532tfz,11,11,1|532tg0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Riga|,0,363,0|-1ayy74y,136,363,0|-qznlkz,136,363,0|-qznlky,154,364,1|-qrqewz,154,364,1|-qrqewy,136,363,0|-qhllkz,136,363,0|-qhllky,154,364,1|-qez5kz,154,364,1|-qez5ky,136,363,0|-ms0hsz,136,363,0|-ms0hsy,15,11,0|-fciw81,15,11,0|-fciw80,148,6,0|-evjv01,148,6,0|-evjv00,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-d5thg1,10,10,0|-d5thg0,148,6,0|5vb6bz,148,6,0|5vb6c0,149,209,1|64pzjz,149,209,1|64pzk0,148,6,0|6e33nz,148,6,0|6e33o0,149,209,1|6nhwvz,149,209,1|6nhww0,148,6,0|6wv0zz,148,6,0|6wv100,149,209,1|769u7z,149,209,1|769u80,148,6,0|7foszz,148,6,0|7fot00,149,209,1|7p1zvz,149,209,1|7p1zw0,148,6,0|7yf17z,148,6,0|7yf180,149,209,1|87s2jz,149,209,1|87s2k0,148,6,0|8h53vz,148,6,0|8h53w0,149,209,1|8qi57z,149,209,1|8qi580,148,6,0|8zv6jz,148,6,0|8zv6k0,149,209,1|9987vz,149,209,1|9987w0,148,6,0|9il97z,148,6,0|9il980,149,209,1|9ryajz,149,209,1|9ryak0,148,6,0|a1bbvz,148,6,0|a1bbw0,16,6,1|aaofzz,16,6,1|aaog00,15,11,0|ak1hbz,15,11,0|ak1hc0,16,6,1|atrhbz,16,6,1|atrhc0,15,11,0|b34inz,15,11,0|b34io0,16,6,1|bchjzz,16,6,1|bchk00,15,11,0|blulbz,15,11,0|blulc0,16,6,1|bv7mnz,16,6,1|bv7mo0,15,11,0|c4knzz,15,11,0|c4ko00,16,6,1|cdxpbz,16,6,1|cdxpc0,15,11,0|cnaqnz,15,11,0|cnaqo0,16,6,1|cwnrzz,16,6,1|cwns00,15,11,0|d60tbz,15,11,0|d60tc0,16,6,1|dfdunz,16,6,1|dfduo0,15,11,0|dp3unz,15,11,0|dp3uo0,16,6,1|dygvzz,16,6,1|dygw00,15,11,0|e7u03z,15,11,0|e7u040,16,6,1|eimw3z,16,6,1|eimw40,15,11,0|eqk2rz,15,11,0|eqk2s0,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|gaqarz,15,11,0|gaqas0,16,6,1|glw5fz,16,6,1|glw5g0,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|ofu5fz,16,6,1|ofu5g0,15,11,0|onedfz,15,11,0|onedg0,16,6,1|oyk83z,16,6,1|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Europe/Rome|,0,365,0|-1hs7rn8,136,365,0|-13r0qs1,136,365,0|-13r0qs0,10,10,0|-rymys1,10,10,0|-rymys0,11,11,1|-rsio81,11,11,1|-rsio80,10,10,0|-rj5k41,10,10,0|-rj5k40,11,11,1|-r9qqw1,11,11,1|-r9qqw0,10,10,0|-r1idg1,10,10,0|-r1idg0,11,11,1|-qqnpk1,11,11,1|-qqnpk0,10,10,0|-qj59g1,10,10,0|-qj59g0,11,11,1|-q7zhk1,11,11,1|-q7zhk0,10,10,0|-pzcas1,10,10,0|-pzcas0,11,11,1|-ppzc81,11,11,1|-ppzc80,10,10,0|-ff59g1,10,10,0|-ff59g0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d75h81,11,11,1|-d75h80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cohes1,11,11,1|-cohes0,10,10,0|-cf2d81,10,10,0|-cf2d80,11,11,1|-c4mfw1,11,11,1|-c4mfw0,10,10,0|-bwcg41,10,10,0|-bwcg40,11,11,1|-blwis1,11,11,1|-blwis0,10,10,0|-bec581,10,10,0|-bec580,11,11,1|-b36ak1,11,11,1|-b36ak0,10,10,0|-1vwis1,10,10,0|-1vwis0,11,11,1|-1pf9k1,11,11,1|-1pf9k0,10,10,0|-1cthg1,10,10,0|-1cthg0,11,11,1|-16p441,11,11,1|-16p440,10,10,0|-u3es1,10,10,0|-u3es0,11,11,1|-nz1g1,11,11,1|-nz1g0,10,10,0|-b0dg1,10,10,0|-b0dg0,11,11,1|-4w041,11,11,1|-4w040,10,10,0|7pp7z,10,10,0|7pp80,11,11,1|du2jz,11,11,1|du2k0,10,10,0|q2t7z,10,10,0|q2t80,11,11,1|wk57z,11,11,1|wk580,10,10,0|195ujz,10,10,0|195uk0,11,11,1|1fn6jz,11,11,1|1fn6k0,10,10,0|1s8vvz,10,10,0|1s8vw0,11,11,1|1yd97z,11,11,1|1yd980,10,10,0|2alzvz,10,10,0|2alzw0,11,11,1|2h3bvz,11,11,1|2h3bw0,10,10,0|2tp17z,10,10,0|2tp180,11,11,1|2ztejz,11,11,1|2ztek0,10,10,0|3cf3vz,10,10,0|3cf3w0,11,11,1|3ijh7z,11,11,1|3ijh80,10,10,0|3us7vz,10,10,0|3us7w0,11,11,1|419jvz,11,11,1|419jw0,10,10,0|4dv97z,10,10,0|4dv980,11,11,1|4kcl7z,11,11,1|4kcl80,10,10,0|4wlbvz,10,10,0|4wlbw0,11,11,1|532nvz,11,11,1|532nw0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Samara|,0,366,0|-qcx400,100,6,0|-kmr4c1,100,6,0|-kmr4c0,105,209,0|5vb3jz,105,209,0|5vb3k0,92,194,1|64pwrz,92,194,1|64pws0,105,209,0|6e30vz,105,209,0|6e30w0,92,194,1|6nhu3z,92,194,1|6nhu40,105,209,0|6wuy7z,105,209,0|6wuy80,92,194,1|769rfz,92,194,1|769rg0,105,209,0|7foq7z,105,209,0|7foq80,92,194,1|7p1x3z,92,194,1|7p1x40,105,209,0|7yeyfz,105,209,0|7yeyg0,92,194,1|87rzrz,92,194,1|87rzs0,105,209,0|8h513z,105,209,0|8h5140,92,194,1|8qi2fz,92,194,1|8qi2g0,105,209,0|8zv3rz,105,209,0|8zv3s0,92,194,1|99853z,92,194,1|998540,105,209,0|9il6fz,105,209,0|9il6g0,92,194,1|9ry7rz,92,194,1|9ry7s0,105,209,0|a1b93z,105,209,0|a1b940,105,209,1|aaod7z,105,209,1|aaod80,100,6,0|ak1ejz,100,6,0|ak1ek0,105,209,1|atrejz,105,209,1|atrek0,100,6,0|b34fvz,100,6,0|b34fw0,100,6,1|bchjzz,100,6,1|bchk00,100,6,0|bdkfzz,100,6,0|bdkg00,105,209,0|blufrz,105,209,0|blufs0,92,194,1|bv7h3z,92,194,1|bv7h40,105,209,0|c4kifz,105,209,0|c4kig0,92,194,1|cdxjrz,92,194,1|cdxjs0,105,209,0|cnal3z,105,209,0|cnal40,92,194,1|cwnmfz,92,194,1|cwnmg0,105,209,0|d60nrz,105,209,0|d60ns0,92,194,1|dfdp3z,92,194,1|dfdp40,105,209,0|dp3p3z,105,209,0|dp3p40,92,194,1|dzwl3z,92,194,1|dzwl40,105,209,0|e7trrz,105,209,0|e7trs0,92,194,1|eimnrz,92,194,1|eimns0,105,209,0|eqjufz,105,209,0|eqjug0,92,194,1|f1cqfz,92,194,1|f1cqg0,105,209,0|f99x3z,105,209,0|f99x40,92,194,1|fkfrrz,92,194,1|fkfrs0,105,209,0|frzzrz,105,209,0|frzzs0,92,194,1|g35ufz,92,194,1|g35ug0,105,209,0|gaq2fz,105,209,0|gaq2g0,92,194,1|glvx3z,92,194,1|glvx40,105,209,0|gtt3rz,105,209,0|gtt3s0,92,194,1|h4lzrz,92,194,1|h4lzs0,105,209,0|hcj6fz,105,209,0|hcj6g0,92,194,1|hnc2fz,92,194,1|hnc2g0,105,209,0|hv993z,105,209,0|hv9940,92,194,1|i6f3rz,92,194,1|i6f3s0,105,209,0|idzbrz,105,209,0|idzbs0,92,194,1|ip56fz,92,194,1|ip56g0,105,209,0|iwpefz,105,209,0|iwpeg0,92,194,1|j7v93z,92,194,1|j7v940,105,209,0|jffh3z,105,209,0|jffh40,92,194,1|jqlbrz,92,194,1|jqlbs0,105,209,0|jyiifz,105,209,0|jyiig0,92,194,1|k9befz,92,194,1|k9beg0,105,209,0|kh8l3z,105,209,0|kh8l40,92,194,1|ks1h3z,92,194,1|ks1h40,105,209,0|kzynrz,105,209,0|kzyns0,105,209,1|lb4l7z,105,209,1|lb4l80,100,6,0|liot7z,100,6,0|liot80,105,209,0\",\"Europe/San_Marino|,0,365,0|-1hs7rn8,136,365,0|-13r0qs1,136,365,0|-13r0qs0,10,10,0|-rymys1,10,10,0|-rymys0,11,11,1|-rsio81,11,11,1|-rsio80,10,10,0|-rj5k41,10,10,0|-rj5k40,11,11,1|-r9qqw1,11,11,1|-r9qqw0,10,10,0|-r1idg1,10,10,0|-r1idg0,11,11,1|-qqnpk1,11,11,1|-qqnpk0,10,10,0|-qj59g1,10,10,0|-qj59g0,11,11,1|-q7zhk1,11,11,1|-q7zhk0,10,10,0|-pzcas1,10,10,0|-pzcas0,11,11,1|-ppzc81,11,11,1|-ppzc80,10,10,0|-ff59g1,10,10,0|-ff59g0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d75h81,11,11,1|-d75h80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cohes1,11,11,1|-cohes0,10,10,0|-cf2d81,10,10,0|-cf2d80,11,11,1|-c4mfw1,11,11,1|-c4mfw0,10,10,0|-bwcg41,10,10,0|-bwcg40,11,11,1|-blwis1,11,11,1|-blwis0,10,10,0|-bec581,10,10,0|-bec580,11,11,1|-b36ak1,11,11,1|-b36ak0,10,10,0|-1vwis1,10,10,0|-1vwis0,11,11,1|-1pf9k1,11,11,1|-1pf9k0,10,10,0|-1cthg1,10,10,0|-1cthg0,11,11,1|-16p441,11,11,1|-16p440,10,10,0|-u3es1,10,10,0|-u3es0,11,11,1|-nz1g1,11,11,1|-nz1g0,10,10,0|-b0dg1,10,10,0|-b0dg0,11,11,1|-4w041,11,11,1|-4w040,10,10,0|7pp7z,10,10,0|7pp80,11,11,1|du2jz,11,11,1|du2k0,10,10,0|q2t7z,10,10,0|q2t80,11,11,1|wk57z,11,11,1|wk580,10,10,0|195ujz,10,10,0|195uk0,11,11,1|1fn6jz,11,11,1|1fn6k0,10,10,0|1s8vvz,10,10,0|1s8vw0,11,11,1|1yd97z,11,11,1|1yd980,10,10,0|2alzvz,10,10,0|2alzw0,11,11,1|2h3bvz,11,11,1|2h3bw0,10,10,0|2tp17z,10,10,0|2tp180,11,11,1|2ztejz,11,11,1|2ztek0,10,10,0|3cf3vz,10,10,0|3cf3w0,11,11,1|3ijh7z,11,11,1|3ijh80,10,10,0|3us7vz,10,10,0|3us7w0,11,11,1|419jvz,11,11,1|419jw0,10,10,0|4dv97z,10,10,0|4dv980,11,11,1|4kcl7z,11,11,1|4kcl80,10,10,0|4wlbvz,10,10,0|4wlbw0,11,11,1|532nvz,11,11,1|532nw0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Sarajevo|,0,332,0|-18vsmgo,10,10,0|-ezayw1,10,10,0|-ezayw0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cv5zw1,10,10,0|-cv5zw0,11,11,1|-cofek1,11,11,1|-cofek0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Saratov|,0,367,0|-qcx400,100,6,0|-kmr4c1,100,6,0|-kmr4c0,105,209,0|5vb3jz,105,209,0|5vb3k0,92,194,1|64pwrz,92,194,1|64pws0,105,209,0|6e30vz,105,209,0|6e30w0,92,194,1|6nhu3z,92,194,1|6nhu40,105,209,0|6wuy7z,105,209,0|6wuy80,92,194,1|769rfz,92,194,1|769rg0,105,209,0|7foq7z,105,209,0|7foq80,92,194,1|7p1x3z,92,194,1|7p1x40,105,209,0|7yeyfz,105,209,0|7yeyg0,92,194,1|87rzrz,92,194,1|87rzs0,105,209,0|8h513z,105,209,0|8h5140,92,194,1|8qi2fz,92,194,1|8qi2g0,105,209,0|8zv3rz,105,209,0|8zv3s0,92,194,1|99853z,92,194,1|998540,105,209,0|9il6fz,105,209,0|9il6g0,105,209,1|9ryajz,105,209,1|9ryak0,100,6,0|a1bbvz,100,6,0|a1bbw0,105,209,1|aaod7z,105,209,1|aaod80,100,6,0|ak1ejz,100,6,0|ak1ek0,105,209,1|atrejz,105,209,1|atrek0,100,6,0|b34fvz,100,6,0|b34fw0,105,209,0|blufrz,105,209,0|blufs0,105,209,1|bv7jvz,105,209,1|bv7jw0,100,6,0|c4kl7z,100,6,0|c4kl80,105,209,1|cdxmjz,105,209,1|cdxmk0,100,6,0|cnanvz,100,6,0|cnanw0,105,209,1|cwnp7z,105,209,1|cwnp80,100,6,0|d60qjz,100,6,0|d60qk0,105,209,1|dfdrvz,105,209,1|dfdrw0,100,6,0|dp3rvz,100,6,0|dp3rw0,105,209,1|dzwnvz,105,209,1|dzwnw0,100,6,0|e7tujz,100,6,0|e7tuk0,105,209,1|eimqjz,105,209,1|eimqk0,100,6,0|eqjx7z,100,6,0|eqjx80,105,209,1|f1ct7z,105,209,1|f1ct80,100,6,0|f99zvz,100,6,0|f99zw0,105,209,1|fkfujz,105,209,1|fkfuk0,100,6,0|fs02jz,100,6,0|fs02k0,105,209,1|g35x7z,105,209,1|g35x80,100,6,0|gaq57z,100,6,0|gaq580,105,209,1|glvzvz,105,209,1|glvzw0,100,6,0|gtt6jz,100,6,0|gtt6k0,105,209,1|h4m2jz,105,209,1|h4m2k0,100,6,0|hcj97z,100,6,0|hcj980,105,209,1|hnc57z,105,209,1|hnc580,100,6,0|hv9bvz,100,6,0|hv9bw0,105,209,1|i6f6jz,105,209,1|i6f6k0,100,6,0|idzejz,100,6,0|idzek0,105,209,1|ip597z,105,209,1|ip5980,100,6,0|iwph7z,100,6,0|iwph80,105,209,1|j7vbvz,105,209,1|j7vbw0,100,6,0|jffjvz,100,6,0|jffjw0,105,209,1|jqlejz,105,209,1|jqlek0,100,6,0|jyil7z,100,6,0|jyil80,105,209,1|k9bh7z,105,209,1|k9bh80,100,6,0|kh8nvz,100,6,0|kh8nw0,105,209,1|ks1jvz,105,209,1|ks1jw0,100,6,0|kzyqjz,100,6,0|kzyqk0,105,209,1|lb4l7z,105,209,1|lb4l80,100,6,0|liot7z,100,6,0|liot80,105,209,0|ne0t3z,105,209,0|ne0t40,100,6,0|ohmt7z,100,6,0|ohmt80,105,209,0\",\"Europe/Simferopol|,0,368,0|-1ayy8zc,85,369,0|-nu12ap,85,369,0|-nu12ao,15,11,0|-kmr1k1,15,11,0|-kmr1k0,148,6,0|-ep8301,148,6,0|-ep8300,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-df8g81,11,11,1|-df8g80,148,6,0|5vb6bz,148,6,0|5vb6c0,149,209,1|64pzjz,149,209,1|64pzk0,148,6,0|6e33nz,148,6,0|6e33o0,149,209,1|6nhwvz,149,209,1|6nhww0,148,6,0|6wv0zz,148,6,0|6wv100,149,209,1|769u7z,149,209,1|769u80,148,6,0|7foszz,148,6,0|7fot00,149,209,1|7p1zvz,149,209,1|7p1zw0,148,6,0|7yf17z,148,6,0|7yf180,149,209,1|87s2jz,149,209,1|87s2k0,148,6,0|8h53vz,148,6,0|8h53w0,149,209,1|8qi57z,149,209,1|8qi580,148,6,0|8zv6jz,148,6,0|8zv6k0,149,209,1|9987vz,149,209,1|9987w0,148,6,0|9il97z,148,6,0|9il980,149,209,1|9ryajz,149,209,1|9ryak0,148,6,0|a1bbvz,148,6,0|a1bbw0,149,209,1|aaod7z,149,209,1|aaod80,148,6,0|ap2vvz,148,6,0|ap2vw0,15,11,0|blufrz,15,11,0|blufs0,16,6,1|bv7ebz,16,6,1|bv7ec0,15,11,0|c4kifz,15,11,0|c4kig0,16,6,1|cdxgzz,16,6,1|cdxh00,15,11,0|cnal3z,15,11,0|cnal40,16,6,1|cp3bnz,16,6,1|cp3bo0,149,209,1|cwngvz,149,209,1|cwngw0,148,6,0|d60kzz,148,6,0|d60l00,149,209,1|dfdjjz,149,209,1|dfdjk0,148,6,0|dp3mbz,148,6,0|dp3mc0,149,209,1|dzwqnz,149,209,1|dzwqo0,148,6,0|e7u03z,148,6,0|e7u040,16,6,1|eimw3z,16,6,1|eimw40,15,11,0|eqk2rz,15,11,0|eqk2s0,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|fs083z,15,11,0|fs0840,16,6,1|g362rz,16,6,1|g362s0,15,11,0|gaqarz,15,11,0|gaqas0,16,6,1|glw5fz,16,6,1|glw5g0,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n382nz,15,11,0|n382o0,148,209,0|ne0t3z,148,209,0|ne0t40,148,6,0\",\"Europe/Skopje|,0,332,0|-18vsmgo,10,10,0|-ezayw1,10,10,0|-ezayw0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cv5zw1,10,10,0|-cv5zw0,11,11,1|-cofek1,11,11,1|-cofek0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Sofia|,0,370,0|-1ayy6zg,117,350,0|-136r6qx,117,350,0|-136r6qw,15,11,0|-e6dzw1,15,11,0|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cx0l41,10,10,0|-cx0l40,15,11,0|4tpgzz,15,11,0|4tph00,16,6,1|534frz,16,6,1|534fs0,15,11,0|5csibz,15,11,0|5csic0,16,6,1|5luifz,16,6,1|5luig0,15,11,0|5vikzz,15,11,0|5vil00,16,6,1|64it7z,16,6,1|64it80,15,11,0|6e8nnz,15,11,0|6e8no0,16,6,1|6n8ynz,16,6,1|6n8yo0,15,11,0|6wlzzz,15,11,0|6wm000,16,6,1|75z1bz,16,6,1|75z1c0,15,11,0|7fc2nz,15,11,0|7fc2o0,16,6,1|7p22nz,16,6,1|7p22o0,15,11,0|7yf3zz,15,11,0|7yf400,16,6,1|87s5bz,16,6,1|87s5c0,15,11,0|8h56nz,15,11,0|8h56o0,16,6,1|8qi7zz,16,6,1|8qi800,15,11,0|8zv9bz,15,11,0|8zv9c0,16,6,1|998anz,16,6,1|998ao0,15,11,0|9ilbzz,15,11,0|9ilc00,16,6,1|9rydbz,16,6,1|9rydc0,15,11,0|a1benz,15,11,0|a1beo0,16,6,1|aaofzz,16,6,1|aaog00,15,11,0|ak1hbz,15,11,0|ak1hc0,16,6,1|atrhbz,16,6,1|atrhc0,15,11,0|b34d3z,15,11,0|b34d40,16,6,1|bchbnz,16,6,1|bchbo0,15,11,0|blufrz,15,11,0|blufs0,16,6,1|bv7ebz,16,6,1|bv7ec0,15,11,0|c4kifz,15,11,0|c4kig0,16,6,1|cdxgzz,16,6,1|cdxh00,15,11,0|cnal3z,15,11,0|cnal40,16,6,1|cwnjnz,16,6,1|cwnjo0,15,11,0|d60nrz,15,11,0|d60ns0,16,6,1|dfdmbz,16,6,1|dfdmc0,15,11,0|dp3p3z,15,11,0|dp3p40,16,6,1|dzwibz,16,6,1|dzwic0,15,11,0|e7u03z,15,11,0|e7u040,16,6,1|eimw3z,16,6,1|eimw40,15,11,0|eqk2rz,15,11,0|eqk2s0,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|fs083z,15,11,0|fs0840,16,6,1|g362rz,16,6,1|g362s0,15,11,0|gaqarz,15,11,0|gaqas0,16,6,1|glw5fz,16,6,1|glw5g0,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|ofu5fz,16,6,1|ofu5g0,15,11,0|onedfz,15,11,0|onedg0,16,6,1|oyk83z,16,6,1|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Europe/Stockholm|,0,371,0|-1bhq3cc,155,372,0|-10j6dgf,155,372,0|-10j6dge,10,10,0|-rzo2w1,10,10,0|-rzo2w0,11,11,1|-rsilg1,11,11,1|-rsilg0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Tallinn|,0,373,0|-1ayy790,133,373,0|-r3exx1,133,373,0|-r3exx0,10,10,0|-qznjw1,10,10,0|-qznjw0,11,11,1|-qrqd81,11,11,1|-qrqd80,10,10,0|-qcx6s1,10,10,0|-qcx6s0,133,373,0|-peghx1,133,373,0|-peghx0,15,11,0|-fch1k1,15,11,0|-fch1k0,148,6,0|-ern4c1,148,6,0|-ern4c0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6wg81,11,11,1|-d6wg80,148,6,0|5vb6bz,148,6,0|5vb6c0,149,209,1|64pzjz,149,209,1|64pzk0,148,6,0|6e33nz,148,6,0|6e33o0,149,209,1|6nhwvz,149,209,1|6nhww0,148,6,0|6wv0zz,148,6,0|6wv100,149,209,1|769u7z,149,209,1|769u80,148,6,0|7foszz,148,6,0|7fot00,149,209,1|7p1zvz,149,209,1|7p1zw0,148,6,0|7yf17z,148,6,0|7yf180,149,209,1|87s2jz,149,209,1|87s2k0,148,6,0|8h53vz,148,6,0|8h53w0,149,209,1|8qi57z,149,209,1|8qi580,148,6,0|8zv6jz,148,6,0|8zv6k0,149,209,1|9987vz,149,209,1|9987w0,148,6,0|9il97z,148,6,0|9il980,149,209,1|9ryajz,149,209,1|9ryak0,148,6,0|a1bbvz,148,6,0|a1bbw0,16,6,1|aaofzz,16,6,1|aaog00,15,11,0|ak1hbz,15,11,0|ak1hc0,16,6,1|atrhbz,16,6,1|atrhc0,15,11,0|b34inz,15,11,0|b34io0,16,6,1|bchjzz,16,6,1|bchk00,15,11,0|blulbz,15,11,0|blulc0,16,6,1|bv7mnz,16,6,1|bv7mo0,15,11,0|c4knzz,15,11,0|c4ko00,16,6,1|cdxpbz,16,6,1|cdxpc0,15,11,0|cnaqnz,15,11,0|cnaqo0,16,6,1|cwnrzz,16,6,1|cwns00,15,11,0|d60tbz,15,11,0|d60tc0,16,6,1|dfdunz,16,6,1|dfduo0,15,11,0|dp3unz,15,11,0|dp3uo0,16,6,1|dzwqnz,16,6,1|dzwqo0,15,11,0|e7txbz,15,11,0|e7txc0,16,6,1|eimtbz,16,6,1|eimtc0,15,11,0|eqjzzz,15,11,0|eqk000,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|ofu5fz,16,6,1|ofu5g0,15,11,0|onedfz,15,11,0|onedg0,16,6,1|oyk83z,16,6,1|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Europe/Tirane|,0,374,0|-t85vo8,10,10,0|-ff3es1,10,10,0|-ff3es0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dy7jw1,11,11,1|-dy7jw0,10,10,0|29h97z,10,10,0|29h980,11,11,1|2h8t3z,11,11,1|2h8t40,10,10,0|2s3mjz,10,10,0|2s3mk0,11,11,1|300qfz,11,11,1|300qg0,10,10,0|3az97z,10,10,0|3az980,11,11,1|3iwd3z,11,11,1|3iwd40,10,10,0|3u2ajz,10,10,0|3u2ak0,11,11,1|41mfrz,11,11,1|41mfs0,10,10,0|4cqijz,10,10,0|4cqik0,11,11,1|4kcifz,11,11,1|4kcig0,10,10,0|4vgl7z,10,10,0|4vgl80,11,11,1|532l3z,11,11,1|532l40,10,10,0|5e6nvz,10,10,0|5e6nw0,11,11,1|5m3rrz,11,11,1|5m3rs0,10,10,0|5wlmjz,10,10,0|5wlmk0,11,11,1|64iqfz,11,11,1|64iqg0,10,10,0|6fonvz,10,10,0|6fonw0,11,11,1|6nlrrz,11,11,1|6nlrs0,10,10,0|6xqnvz,10,10,0|6xqnw0,11,11,1|769zrz,11,11,1|769zs0,10,10,0|7foyjz,10,10,0|7foyk0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Ulyanovsk|,0,375,0|-qcx400,100,6,0|-kmr4c1,100,6,0|-kmr4c0,105,209,0|5vb3jz,105,209,0|5vb3k0,92,194,1|64pwrz,92,194,1|64pws0,105,209,0|6e30vz,105,209,0|6e30w0,92,194,1|6nhu3z,92,194,1|6nhu40,105,209,0|6wuy7z,105,209,0|6wuy80,92,194,1|769rfz,92,194,1|769rg0,105,209,0|7foq7z,105,209,0|7foq80,92,194,1|7p1x3z,92,194,1|7p1x40,105,209,0|7yeyfz,105,209,0|7yeyg0,92,194,1|87rzrz,92,194,1|87rzs0,105,209,0|8h513z,105,209,0|8h5140,92,194,1|8qi2fz,92,194,1|8qi2g0,105,209,0|8zv3rz,105,209,0|8zv3s0,92,194,1|99853z,92,194,1|998540,105,209,0|9il6fz,105,209,0|9il6g0,92,194,1|9ry7rz,92,194,1|9ry7s0,105,209,0|a1b93z,105,209,0|a1b940,105,209,1|aaod7z,105,209,1|aaod80,100,6,0|ak1ejz,100,6,0|ak1ek0,105,209,1|atrejz,105,209,1|atrek0,100,6,0|b34fvz,100,6,0|b34fw0,100,6,1|bchjzz,100,6,1|bchk00,101,11,0|bi8ynz,101,11,0|bi8yo0,100,6,0|bluijz,100,6,0|bluik0,105,209,1|bv7jvz,105,209,1|bv7jw0,100,6,0|c4kl7z,100,6,0|c4kl80,105,209,1|cdxmjz,105,209,1|cdxmk0,100,6,0|cnanvz,100,6,0|cnanw0,105,209,1|cwnp7z,105,209,1|cwnp80,100,6,0|d60qjz,100,6,0|d60qk0,105,209,1|dfdrvz,105,209,1|dfdrw0,100,6,0|dp3rvz,100,6,0|dp3rw0,105,209,1|dzwnvz,105,209,1|dzwnw0,100,6,0|e7tujz,100,6,0|e7tuk0,105,209,1|eimqjz,105,209,1|eimqk0,100,6,0|eqjx7z,100,6,0|eqjx80,105,209,1|f1ct7z,105,209,1|f1ct80,100,6,0|f99zvz,100,6,0|f99zw0,105,209,1|fkfujz,105,209,1|fkfuk0,100,6,0|fs02jz,100,6,0|fs02k0,105,209,1|g35x7z,105,209,1|g35x80,100,6,0|gaq57z,100,6,0|gaq580,105,209,1|glvzvz,105,209,1|glvzw0,100,6,0|gtt6jz,100,6,0|gtt6k0,105,209,1|h4m2jz,105,209,1|h4m2k0,100,6,0|hcj97z,100,6,0|hcj980,105,209,1|hnc57z,105,209,1|hnc580,100,6,0|hv9bvz,100,6,0|hv9bw0,105,209,1|i6f6jz,105,209,1|i6f6k0,100,6,0|idzejz,100,6,0|idzek0,105,209,1|ip597z,105,209,1|ip5980,100,6,0|iwph7z,100,6,0|iwph80,105,209,1|j7vbvz,105,209,1|j7vbw0,100,6,0|jffjvz,100,6,0|jffjw0,105,209,1|jqlejz,105,209,1|jqlek0,100,6,0|jyil7z,100,6,0|jyil80,105,209,1|k9bh7z,105,209,1|k9bh80,100,6,0|kh8nvz,100,6,0|kh8nw0,105,209,1|ks1jvz,105,209,1|ks1jw0,100,6,0|kzyqjz,100,6,0|kzyqk0,105,209,1|lb4l7z,105,209,1|lb4l80,100,6,0|liot7z,100,6,0|liot80,105,209,0|ne0t3z,105,209,0|ne0t40,100,6,0|o4o57z,100,6,0|o4o580,105,209,0\",\"Europe/Uzhgorod|,0,376,0|-15cztgo,10,10,0|-fizzw1,10,10,0|-fizzw0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d55hk1,11,11,1|-d55hk0,10,10,0|-cshus1,10,10,0|-cshus0,148,6,0|5vb6bz,148,6,0|5vb6c0,149,209,1|64pzjz,149,209,1|64pzk0,148,6,0|6e33nz,148,6,0|6e33o0,149,209,1|6nhwvz,149,209,1|6nhww0,148,6,0|6wv0zz,148,6,0|6wv100,149,209,1|769u7z,149,209,1|769u80,148,6,0|7foszz,148,6,0|7fot00,149,209,1|7p1zvz,149,209,1|7p1zw0,148,6,0|7yf17z,148,6,0|7yf180,149,209,1|87s2jz,149,209,1|87s2k0,148,6,0|8h53vz,148,6,0|8h53w0,149,209,1|8qi57z,149,209,1|8qi580,148,6,0|8zv6jz,148,6,0|8zv6k0,149,209,1|9987vz,149,209,1|9987w0,148,6,0|9il97z,148,6,0|9il980,149,209,1|9ryajz,149,209,1|9ryak0,148,6,0|a1bbvz,148,6,0|a1bbw0,149,209,1|aaod7z,149,209,1|aaod80,148,6,0|ap2vvz,148,6,0|ap2vw0,10,10,0|b34o7z,10,10,0|b34o80,15,11,0|blufrz,15,11,0|blufs0,16,6,1|bv7ebz,16,6,1|bv7ec0,15,11,0|c4kifz,15,11,0|c4kig0,16,6,1|cdxgzz,16,6,1|cdxh00,15,11,0|cnal3z,15,11,0|cnal40,16,6,1|cwnjnz,16,6,1|cwnjo0,15,11,0|d60w3z,15,11,0|d60w40,16,6,1|dfdxfz,16,6,1|dfdxg0,15,11,0|dp3xfz,15,11,0|dp3xg0,16,6,1|dzwtfz,16,6,1|dzwtg0,15,11,0|e7u03z,15,11,0|e7u040,16,6,1|eimw3z,16,6,1|eimw40,15,11,0|eqk2rz,15,11,0|eqk2s0,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|fs083z,15,11,0|fs0840,16,6,1|g362rz,16,6,1|g362s0,15,11,0|gaqarz,15,11,0|gaqas0,16,6,1|glw5fz,16,6,1|glw5g0,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|ofu5fz,16,6,1|ofu5g0,15,11,0|onedfz,15,11,0|onedg0,16,6,1|oyk83z,16,6,1|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Europe/Vaduz|,0,338,0|-1os49kw,53,339,0|-13g441n,53,339,0|-13g441m,10,10,0|-eyh6o1,10,10,0|-eyh6o0,11,11,1|-eqk001,11,11,1|-eqk000,10,10,0|-efr401,10,10,0|-efr400,11,11,1|-e7txc1,11,11,1|-e7txc0,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Vatican|,0,365,0|-1hs7rn8,136,365,0|-13r0qs1,136,365,0|-13r0qs0,10,10,0|-rymys1,10,10,0|-rymys0,11,11,1|-rsio81,11,11,1|-rsio80,10,10,0|-rj5k41,10,10,0|-rj5k40,11,11,1|-r9qqw1,11,11,1|-r9qqw0,10,10,0|-r1idg1,10,10,0|-r1idg0,11,11,1|-qqnpk1,11,11,1|-qqnpk0,10,10,0|-qj59g1,10,10,0|-qj59g0,11,11,1|-q7zhk1,11,11,1|-q7zhk0,10,10,0|-pzcas1,10,10,0|-pzcas0,11,11,1|-ppzc81,11,11,1|-ppzc80,10,10,0|-ff59g1,10,10,0|-ff59g0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d75h81,11,11,1|-d75h80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cohes1,11,11,1|-cohes0,10,10,0|-cf2d81,10,10,0|-cf2d80,11,11,1|-c4mfw1,11,11,1|-c4mfw0,10,10,0|-bwcg41,10,10,0|-bwcg40,11,11,1|-blwis1,11,11,1|-blwis0,10,10,0|-bec581,10,10,0|-bec580,11,11,1|-b36ak1,11,11,1|-b36ak0,10,10,0|-1vwis1,10,10,0|-1vwis0,11,11,1|-1pf9k1,11,11,1|-1pf9k0,10,10,0|-1cthg1,10,10,0|-1cthg0,11,11,1|-16p441,11,11,1|-16p440,10,10,0|-u3es1,10,10,0|-u3es0,11,11,1|-nz1g1,11,11,1|-nz1g0,10,10,0|-b0dg1,10,10,0|-b0dg0,11,11,1|-4w041,11,11,1|-4w040,10,10,0|7pp7z,10,10,0|7pp80,11,11,1|du2jz,11,11,1|du2k0,10,10,0|q2t7z,10,10,0|q2t80,11,11,1|wk57z,11,11,1|wk580,10,10,0|195ujz,10,10,0|195uk0,11,11,1|1fn6jz,11,11,1|1fn6k0,10,10,0|1s8vvz,10,10,0|1s8vw0,11,11,1|1yd97z,11,11,1|1yd980,10,10,0|2alzvz,10,10,0|2alzw0,11,11,1|2h3bvz,11,11,1|2h3bw0,10,10,0|2tp17z,10,10,0|2tp180,11,11,1|2ztejz,11,11,1|2ztek0,10,10,0|3cf3vz,10,10,0|3cf3w0,11,11,1|3ijh7z,11,11,1|3ijh80,10,10,0|3us7vz,10,10,0|3us7w0,11,11,1|419jvz,11,11,1|419jw0,10,10,0|4dv97z,10,10,0|4dv980,11,11,1|4kcl7z,11,11,1|4kcl80,10,10,0|4wlbvz,10,10,0|4wlbw0,11,11,1|532nvz,11,11,1|532nw0,10,10,0|5cstfz,10,10,0|5cstg0,11,11,1|5lsw3z,11,11,1|5lsw40,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Vienna|,0,377,0|-14211ox,10,10,0|-s0e081,10,10,0|-s0e080,11,11,1|-rsilg1,11,11,1|-rsilg0,10,10,0|-ridmk1,10,10,0|-ridmk0,11,11,1|-ragfw1,11,11,1|-ragfw0,10,10,0|-qznjw1,10,10,0|-qznjw0,11,11,1|-qrqd81,11,11,1|-qrqd80,10,10,0|-pykd81,10,10,0|-pykd80,11,11,1|-pqa7w1,11,11,1|-pqa7w0,10,10,0|-fizzw1,10,10,0|-fizzw0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cx0nw1,10,10,0|-cx0nw0,11,11,1|-cwi581,11,11,1|-cwi580,10,10,0|-cdmik1,10,10,0|-cdmik0,11,11,1|-c4kl81,11,11,1|-c4kl80,10,10,0|-bv9ek1,10,10,0|-bv9ek0,11,11,1|-blwd81,11,11,1|-blwd80,10,10,0|-bbtek1,10,10,0|-bbtek0,11,11,1|-b36ak1,11,11,1|-b36ak0,10,10,0|5csnvz,10,10,0|5csnw0,11,11,1|5lsnrz,11,11,1|5lsns0,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Vilnius|,0,378,0|-1ayy7cs,156,379,0|-rns981,156,379,0|-rns980,74,380,0|-q7q73d,74,380,0|-q7q73c,10,10,0|-ptj1g1,10,10,0|-ptj1g0,15,11,0|-poyaw1,15,11,0|-poyaw0,10,10,0|-fcmis1,10,10,0|-fcmis0,148,6,0|-evwto1,148,6,0|-evwto0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d9kqw1,11,11,1|-d9kqw0,148,6,0|5vb6bz,148,6,0|5vb6c0,149,209,1|64pzjz,149,209,1|64pzk0,148,6,0|6e33nz,148,6,0|6e33o0,149,209,1|6nhwvz,149,209,1|6nhww0,148,6,0|6wv0zz,148,6,0|6wv100,149,209,1|769u7z,149,209,1|769u80,148,6,0|7foszz,148,6,0|7fot00,149,209,1|7p1zvz,149,209,1|7p1zw0,148,6,0|7yf17z,148,6,0|7yf180,149,209,1|87s2jz,149,209,1|87s2k0,148,6,0|8h53vz,148,6,0|8h53w0,149,209,1|8qi57z,149,209,1|8qi580,148,6,0|8zv6jz,148,6,0|8zv6k0,149,209,1|9987vz,149,209,1|9987w0,148,6,0|9il97z,148,6,0|9il980,149,209,1|9ryajz,149,209,1|9ryak0,148,6,0|a1bbvz,148,6,0|a1bbw0,16,6,1|aaofzz,16,6,1|aaog00,15,11,0|ak1hbz,15,11,0|ak1hc0,16,6,1|atrhbz,16,6,1|atrhc0,15,11,0|b34inz,15,11,0|b34io0,16,6,1|bchjzz,16,6,1|bchk00,15,11,0|blulbz,15,11,0|blulc0,16,6,1|bv7mnz,16,6,1|bv7mo0,15,11,0|c4knzz,15,11,0|c4ko00,16,6,1|cdxpbz,16,6,1|cdxpc0,15,11,0|cnaqnz,15,11,0|cnaqo0,16,6,1|cwnrzz,16,6,1|cwns00,15,11,0|d60tbz,15,11,0|d60tc0,16,6,1|dfdunz,16,6,1|dfduo0,15,11,0|dp3unz,15,11,0|dp3uo0,16,6,1|dzwqnz,16,6,1|dzwqo0,15,11,0|e7txbz,15,11,0|e7txc0,16,6,1|eimtbz,16,6,1|eimtc0,15,11,0|eqk2rz,15,11,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|ofu5fz,16,6,1|ofu5g0,15,11,0|onedfz,15,11,0|onedg0,16,6,1|oyk83z,16,6,1|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Europe/Volgograd|,0,213,0|-q3cw84,100,6,0|-kmr4c1,100,6,0|-kmr4c0,105,209,0|5vb3jz,105,209,0|5vb3k0,92,194,1|64pwrz,92,194,1|64pws0,105,209,0|6e30vz,105,209,0|6e30w0,92,194,1|6nhu3z,92,194,1|6nhu40,105,209,0|6wuy7z,105,209,0|6wuy80,92,194,1|769rfz,92,194,1|769rg0,105,209,0|7foq7z,105,209,0|7foq80,92,194,1|7p1x3z,92,194,1|7p1x40,105,209,0|7yeyfz,105,209,0|7yeyg0,92,194,1|87rzrz,92,194,1|87rzs0,105,209,0|8h513z,105,209,0|8h5140,92,194,1|8qi2fz,92,194,1|8qi2g0,105,209,0|8zv3rz,105,209,0|8zv3s0,92,194,1|99853z,92,194,1|998540,105,209,0|9il6fz,105,209,0|9il6g0,105,209,1|9ryajz,105,209,1|9ryak0,100,6,0|a1bbvz,100,6,0|a1bbw0,105,209,1|aaod7z,105,209,1|aaod80,100,6,0|ak1ejz,100,6,0|ak1ek0,105,209,1|atrejz,105,209,1|atrek0,100,6,0|b34fvz,100,6,0|b34fw0,105,209,0|blufrz,105,209,0|blufs0,105,209,1|bv7jvz,105,209,1|bv7jw0,100,6,0|c4kl7z,100,6,0|c4kl80,105,209,1|cdxmjz,105,209,1|cdxmk0,100,6,0|cnanvz,100,6,0|cnanw0,105,209,1|cwnp7z,105,209,1|cwnp80,100,6,0|d60qjz,100,6,0|d60qk0,105,209,1|dfdrvz,105,209,1|dfdrw0,100,6,0|dp3rvz,100,6,0|dp3rw0,105,209,1|dzwnvz,105,209,1|dzwnw0,100,6,0|e7tujz,100,6,0|e7tuk0,105,209,1|eimqjz,105,209,1|eimqk0,100,6,0|eqjx7z,100,6,0|eqjx80,105,209,1|f1ct7z,105,209,1|f1ct80,100,6,0|f99zvz,100,6,0|f99zw0,105,209,1|fkfujz,105,209,1|fkfuk0,100,6,0|fs02jz,100,6,0|fs02k0,105,209,1|g35x7z,105,209,1|g35x80,100,6,0|gaq57z,100,6,0|gaq580,105,209,1|glvzvz,105,209,1|glvzw0,100,6,0|gtt6jz,100,6,0|gtt6k0,105,209,1|h4m2jz,105,209,1|h4m2k0,100,6,0|hcj97z,100,6,0|hcj980,105,209,1|hnc57z,105,209,1|hnc580,100,6,0|hv9bvz,100,6,0|hv9bw0,105,209,1|i6f6jz,105,209,1|i6f6k0,100,6,0|idzejz,100,6,0|idzek0,105,209,1|ip597z,105,209,1|ip5980,100,6,0|iwph7z,100,6,0|iwph80,105,209,1|j7vbvz,105,209,1|j7vbw0,100,6,0|jffjvz,100,6,0|jffjw0,105,209,1|jqlejz,105,209,1|jqlek0,100,6,0|jyil7z,100,6,0|jyil80,105,209,1|k9bh7z,105,209,1|k9bh80,100,6,0|kh8nvz,100,6,0|kh8nw0,105,209,1|ks1jvz,105,209,1|ks1jw0,100,6,0|kzyqjz,100,6,0|kzyqk0,105,209,1|lb4l7z,105,209,1|lb4l80,100,6,0|liot7z,100,6,0|liot80,105,209,0|ne0t3z,105,209,0|ne0t40,100,6,0|pha57z,100,6,0|pha580,105,209,0|qlyvrz,105,209,0|qlyvs0,100,6,0\",\"Europe/Warsaw|,0,379,0|-1ayy6k0,156,379,0|-se9yk1,156,379,0|-se9yk0,10,10,0|-s0e081,10,10,0|-s0e080,11,11,1|-rsilg1,11,11,1|-rsilg0,10,10,0|-ridmk1,10,10,0|-ridmk0,11,11,1|-ragfw1,11,11,1|-ragfw0,10,10,0|-qznjw1,10,10,0|-qznjw0,11,11,1|-qrqd81,11,11,1|-qrqd80,15,11,0|-qgvpc1,15,11,0|-qgvpc0,16,6,1|-q8yio1,16,6,1|-q8yio0,15,11,0|-ou36w1,15,11,0|-ou36w0,10,10,0|-feqak1,10,10,0|-feqak0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6a2o1,11,11,1|-d6a2o0,10,10,0|-cvmtg1,10,10,0|-cvmtg0,11,11,1|-cm2g81,11,11,1|-cm2g80,10,10,0|-cdmo41,10,10,0|-cdmo40,11,11,1|-c4kl81,11,11,1|-c4kl80,10,10,0|-bttjw1,10,10,0|-bttjw0,11,11,1|-blwd81,11,11,1|-blwd80,10,10,0|-bbtek1,10,10,0|-bbtek0,11,11,1|-b36ak1,11,11,1|-b36ak0,10,10,0|-atgak1,10,10,0|-atgak0,11,11,1|-akg7w1,11,11,1|-akg7w0,10,10,0|-6kf401,10,10,0|-6kf400,11,11,1|-6eaqo1,11,11,1|-6eaqo0,10,10,0|-64xpc1,10,10,0|-64xpc0,11,11,1|-5vko01,11,11,1|-5vko00,10,10,0|-5iyyo1,10,10,0|-5iyyo0,11,11,1|-5chmo1,11,11,1|-5chmo0,10,10,0|-534lc1,10,10,0|-534lc0,11,11,1|-4trk01,11,11,1|-4trk00,10,10,0|-4hitc1,10,10,0|-4hitc0,11,11,1|-4b1hc1,11,11,1|-4b1hc0,10,10,0|-3ysqo1,10,10,0|-3ysqo0,11,11,1|-3sbeo1,11,11,1|-3sbeo0,10,10,0|-3g2o01,10,10,0|-3g2o00,11,11,1|-39lc01,11,11,1|-39lc00,10,10,0|-2wzmo1,10,10,0|-2wzmo0,11,11,1|-2qv9c1,11,11,1|-2qv9c0,10,10,0|3s9jzz,10,10,0|3s9k00,11,11,1|419mnz,11,11,1|419mo0,10,10,0|4azmnz,10,10,0|4azmo0,11,11,1|4kcnzz,11,11,1|4kco00,10,10,0|4tppbz,10,10,0|4tppc0,11,11,1|532qnz,11,11,1|532qo0,10,10,0|5csqnz,10,10,0|5csqo0,11,11,1|5lstbz,11,11,1|5lstc0,10,10,0|5v5unz,10,10,0|5v5uo0,11,11,1|64ivzz,11,11,1|64iw00,10,10,0|6dvxbz,10,10,0|6dvxc0,11,11,1|6n8ynz,11,11,1|6n8yo0,10,10,0|6wlzzz,10,10,0|6wm000,11,11,1|75z1bz,11,11,1|75z1c0,10,10,0|7fc2nz,10,10,0|7fc2o0,11,11,1|7p22nz,11,11,1|7p22o0,10,10,0|7yf3zz,10,10,0|7yf400,11,11,1|87s5bz,11,11,1|87s5c0,10,10,0|8h56nz,10,10,0|8h56o0,11,11,1|8qi7zz,11,11,1|8qi800,10,10,0|8zv9bz,10,10,0|8zv9c0,11,11,1|998anz,11,11,1|998ao0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Zagreb|,0,332,0|-18vsmgo,10,10,0|-ezayw1,10,10,0|-ezayw0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-dfqqk1,10,10,0|-dfqqk0,11,11,1|-d6dp81,11,11,1|-d6dp80,10,10,0|-cv5zw1,10,10,0|-cv5zw0,11,11,1|-cofek1,11,11,1|-cofek0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Europe/Zaporozhye|,0,250,0|-1ayy96g,157,381,0|-nu12hd,157,381,0|-nu12hc,15,11,0|-kmr1k1,15,11,0|-kmr1k0,148,6,0|-esq0c1,148,6,0|-esq0c0,11,11,1|-e6dzw1,11,11,1|-e6dzw0,10,10,0|-dytrw1,10,10,0|-dytrw0,11,11,1|-dp3rw1,11,11,1|-dp3rw0,10,10,0|-do11g1,10,10,0|-do11g0,148,6,0|5vb6bz,148,6,0|5vb6c0,149,209,1|64pzjz,149,209,1|64pzk0,148,6,0|6e33nz,148,6,0|6e33o0,149,209,1|6nhwvz,149,209,1|6nhww0,148,6,0|6wv0zz,148,6,0|6wv100,149,209,1|769u7z,149,209,1|769u80,148,6,0|7foszz,148,6,0|7fot00,149,209,1|7p1zvz,149,209,1|7p1zw0,148,6,0|7yf17z,148,6,0|7yf180,149,209,1|87s2jz,149,209,1|87s2k0,148,6,0|8h53vz,148,6,0|8h53w0,149,209,1|8qi57z,149,209,1|8qi580,148,6,0|8zv6jz,148,6,0|8zv6k0,149,209,1|9987vz,149,209,1|9987w0,148,6,0|9il97z,148,6,0|9il980,149,209,1|9ryajz,149,209,1|9ryak0,148,6,0|a1bbvz,148,6,0|a1bbw0,149,209,1|aaod7z,149,209,1|aaod80,148,6,0|ak1ejz,148,6,0|ak1ek0,149,209,1|atrejz,149,209,1|atrek0,148,6,0|b34fvz,148,6,0|b34fw0,16,6,1|bchbnz,16,6,1|bchbo0,15,11,0|blufrz,15,11,0|blufs0,16,6,1|bv7ebz,16,6,1|bv7ec0,15,11,0|c4kifz,15,11,0|c4kig0,16,6,1|cdxgzz,16,6,1|cdxh00,15,11,0|cnal3z,15,11,0|cnal40,16,6,1|cwnjnz,16,6,1|cwnjo0,15,11,0|d60w3z,15,11,0|d60w40,16,6,1|dfdxfz,16,6,1|dfdxg0,15,11,0|dp3xfz,15,11,0|dp3xg0,16,6,1|dzwtfz,16,6,1|dzwtg0,15,11,0|e7u03z,15,11,0|e7u040,16,6,1|eimw3z,16,6,1|eimw40,15,11,0|eqk2rz,15,11,0|eqk2s0,16,6,1|f1cyrz,16,6,1|f1cys0,15,11,0|f9a5fz,15,11,0|f9a5g0,16,6,1|fkg03z,16,6,1|fkg040,15,11,0|fs083z,15,11,0|fs0840,16,6,1|g362rz,16,6,1|g362s0,15,11,0|gaqarz,15,11,0|gaqas0,16,6,1|glw5fz,16,6,1|glw5g0,15,11,0|gttc3z,15,11,0|gttc40,16,6,1|h4m83z,16,6,1|h4m840,15,11,0|hcjerz,15,11,0|hcjes0,16,6,1|hncarz,16,6,1|hncas0,15,11,0|hv9hfz,15,11,0|hv9hg0,16,6,1|i6fc3z,16,6,1|i6fc40,15,11,0|idzk3z,15,11,0|idzk40,16,6,1|ip5erz,16,6,1|ip5es0,15,11,0|iwpmrz,15,11,0|iwpms0,16,6,1|j7vhfz,16,6,1|j7vhg0,15,11,0|jffpfz,15,11,0|jffpg0,16,6,1|jqlk3z,16,6,1|jqlk40,15,11,0|jyiqrz,15,11,0|jyiqs0,16,6,1|k9bmrz,16,6,1|k9bms0,15,11,0|kh8tfz,15,11,0|kh8tg0,16,6,1|ks1pfz,16,6,1|ks1pg0,15,11,0|kzyw3z,15,11,0|kzyw40,16,6,1|lb4qrz,16,6,1|lb4qs0,15,11,0|lioyrz,15,11,0|lioys0,16,6,1|ltutfz,16,6,1|ltutg0,15,11,0|m1f1fz,15,11,0|m1f1g0,16,6,1|mckw3z,16,6,1|mckw40,15,11,0|mki2rz,15,11,0|mki2s0,16,6,1|mvayrz,16,6,1|mvays0,15,11,0|n385fz,15,11,0|n385g0,16,6,1|ne11fz,16,6,1|ne11g0,15,11,0|nly83z,15,11,0|nly840,16,6,1|nwr43z,16,6,1|nwr440,15,11,0|o4oarz,15,11,0|o4oas0,16,6,1|ofu5fz,16,6,1|ofu5g0,15,11,0|onedfz,15,11,0|onedg0,16,6,1|oyk83z,16,6,1|oyk840,15,11,0|p64g3z,15,11,0|p64g40,16,6,1|phaarz,16,6,1|phaas0,15,11,0|pp7hfz,15,11,0|pp7hg0,16,6,1|q00dfz,16,6,1|q00dg0,15,11,0|q7xk3z,15,11,0|q7xk40,16,6,1|qiqg3z,16,6,1|qiqg40,15,11,0|qqnmrz,15,11,0|qqnms0,16,6,1|r1thfz,16,6,1|r1thg0,15,11,0|r9dpfz,15,11,0|r9dpg0,16,6,1|rkjk3z,16,6,1|rkjk40,15,11,0|rs3s3z,15,11,0|rs3s40,16,6,1|s39mrz,16,6,1|s39ms0,15,11,0|sb6tfz,15,11,0|sb6tg0,16,6,1|slzpfz,16,6,1|slzpg0,15,11,0|stww3z,15,11,0|stww40,16,6,1|t4ps3z,16,6,1|t4ps40,15,11,0|tcmyrz,15,11,0|tcmys0,16,6,1|tnfurz,16,6,1|tnfus0,15,11,0|tvd1fz,15,11,0|tvd1g0,16,6,1|u6iw3z,16,6,1|u6iw40,15,11,0|ue343z,15,11,0|ue3440,16,6,1|up8yrz,16,6,1|up8ys0,15,11,0|uwt6rz,15,11,0|uwt6s0,16,6,1|v7z1fz,16,6,1|v7z1g0,15,11,0|vfw83z,15,11,0|vfw840,16,6,1|vqp43z,16,6,1|vqp440,15,11,0|vymarz,15,11,0|vymas0,16,6,1|w9f6rz,16,6,1|w9f6s0,15,11,0|whcdfz,15,11,0|whcdg0,16,6,1|wsi83z,16,6,1|wsi840,15,11,0|x02g3z,15,11,0|x02g40,16,6,1|xb8arz,16,6,1|xb8as0,15,11,0|xisirz,15,11,0|xisis0,16,6,1|xtydfz,16,6,1|xtydg0,15,11,0|y1ilfz,15,11,0|y1ilg0,16,6,1|ycog3z,16,6,1|ycog40,15,11,0|yklmrz,15,11,0|yklms0,16,6,1|yveirz,16,6,1|yveis0,15,11,0|z3bpfz,15,11,0|z3bpg0,16,6,1|ze4lfz,16,6,1|ze4lg0,15,11,0\",\"Europe/Zurich|,0,338,0|-1os49kw,53,339,0|-13g441n,53,339,0|-13g441m,10,10,0|-eyh6o1,10,10,0|-eyh6o0,11,11,1|-eqk001,11,11,1|-eqk000,10,10,0|-efr401,10,10,0|-efr400,11,11,1|-e7txc1,11,11,1|-e7txc0,10,10,0|5v5xfz,10,10,0|5v5xg0,11,11,1|64iyrz,11,11,1|64iys0,10,10,0|6dw03z,10,10,0|6dw040,11,11,1|6n91fz,11,11,1|6n91g0,10,10,0|6wm2rz,10,10,0|6wm2s0,11,11,1|75z43z,11,11,1|75z440,10,10,0|7fc5fz,10,10,0|7fc5g0,11,11,1|7p25fz,11,11,1|7p25g0,10,10,0|7yf6rz,10,10,0|7yf6s0,11,11,1|87s83z,11,11,1|87s840,10,10,0|8h59fz,10,10,0|8h59g0,11,11,1|8qiarz,11,11,1|8qias0,10,10,0|8zvc3z,10,10,0|8zvc40,11,11,1|998dfz,11,11,1|998dg0,10,10,0|9ilerz,10,10,0|9iles0,11,11,1|9ryg3z,11,11,1|9ryg40,10,10,0|a1bhfz,10,10,0|a1bhg0,11,11,1|aaoirz,11,11,1|aaois0,10,10,0|ak1k3z,10,10,0|ak1k40,11,11,1|atrk3z,11,11,1|atrk40,10,10,0|b34lfz,10,10,0|b34lg0,11,11,1|bchmrz,11,11,1|bchms0,10,10,0|bluo3z,10,10,0|bluo40,11,11,1|bv7pfz,11,11,1|bv7pg0,10,10,0|c4kqrz,10,10,0|c4kqs0,11,11,1|cdxs3z,11,11,1|cdxs40,10,10,0|cnatfz,10,10,0|cnatg0,11,11,1|cwnurz,11,11,1|cwnus0,10,10,0|d60w3z,10,10,0|d60w40,11,11,1|dfdxfz,11,11,1|dfdxg0,10,10,0|dp3xfz,10,10,0|dp3xg0,11,11,1|dzwtfz,11,11,1|dzwtg0,10,10,0|e7u03z,10,10,0|e7u040,11,11,1|eimw3z,11,11,1|eimw40,10,10,0|eqk2rz,10,10,0|eqk2s0,11,11,1|f1cyrz,11,11,1|f1cys0,10,10,0|f9a5fz,10,10,0|f9a5g0,11,11,1|fkg03z,11,11,1|fkg040,10,10,0|fs083z,10,10,0|fs0840,11,11,1|g362rz,11,11,1|g362s0,10,10,0|gaqarz,10,10,0|gaqas0,11,11,1|glw5fz,11,11,1|glw5g0,10,10,0|gttc3z,10,10,0|gttc40,11,11,1|h4m83z,11,11,1|h4m840,10,10,0|hcjerz,10,10,0|hcjes0,11,11,1|hncarz,11,11,1|hncas0,10,10,0|hv9hfz,10,10,0|hv9hg0,11,11,1|i6fc3z,11,11,1|i6fc40,10,10,0|idzk3z,10,10,0|idzk40,11,11,1|ip5erz,11,11,1|ip5es0,10,10,0|iwpmrz,10,10,0|iwpms0,11,11,1|j7vhfz,11,11,1|j7vhg0,10,10,0|jffpfz,10,10,0|jffpg0,11,11,1|jqlk3z,11,11,1|jqlk40,10,10,0|jyiqrz,10,10,0|jyiqs0,11,11,1|k9bmrz,11,11,1|k9bms0,10,10,0|kh8tfz,10,10,0|kh8tg0,11,11,1|ks1pfz,11,11,1|ks1pg0,10,10,0|kzyw3z,10,10,0|kzyw40,11,11,1|lb4qrz,11,11,1|lb4qs0,10,10,0|lioyrz,10,10,0|lioys0,11,11,1|ltutfz,11,11,1|ltutg0,10,10,0|m1f1fz,10,10,0|m1f1g0,11,11,1|mckw3z,11,11,1|mckw40,10,10,0|mki2rz,10,10,0|mki2s0,11,11,1|mvayrz,11,11,1|mvays0,10,10,0|n385fz,10,10,0|n385g0,11,11,1|ne11fz,11,11,1|ne11g0,10,10,0|nly83z,10,10,0|nly840,11,11,1|nwr43z,11,11,1|nwr440,10,10,0|o4oarz,10,10,0|o4oas0,11,11,1|ofu5fz,11,11,1|ofu5g0,10,10,0|onedfz,10,10,0|onedg0,11,11,1|oyk83z,11,11,1|oyk840,10,10,0|p64g3z,10,10,0|p64g40,11,11,1|phaarz,11,11,1|phaas0,10,10,0|pp7hfz,10,10,0|pp7hg0,11,11,1|q00dfz,11,11,1|q00dg0,10,10,0|q7xk3z,10,10,0|q7xk40,11,11,1|qiqg3z,11,11,1|qiqg40,10,10,0|qqnmrz,10,10,0|qqnms0,11,11,1|r1thfz,11,11,1|r1thg0,10,10,0|r9dpfz,10,10,0|r9dpg0,11,11,1|rkjk3z,11,11,1|rkjk40,10,10,0|rs3s3z,10,10,0|rs3s40,11,11,1|s39mrz,11,11,1|s39ms0,10,10,0|sb6tfz,10,10,0|sb6tg0,11,11,1|slzpfz,11,11,1|slzpg0,10,10,0|stww3z,10,10,0|stww40,11,11,1|t4ps3z,11,11,1|t4ps40,10,10,0|tcmyrz,10,10,0|tcmys0,11,11,1|tnfurz,11,11,1|tnfus0,10,10,0|tvd1fz,10,10,0|tvd1g0,11,11,1|u6iw3z,11,11,1|u6iw40,10,10,0|ue343z,10,10,0|ue3440,11,11,1|up8yrz,11,11,1|up8ys0,10,10,0|uwt6rz,10,10,0|uwt6s0,11,11,1|v7z1fz,11,11,1|v7z1g0,10,10,0|vfw83z,10,10,0|vfw840,11,11,1|vqp43z,11,11,1|vqp440,10,10,0|vymarz,10,10,0|vymas0,11,11,1|w9f6rz,11,11,1|w9f6s0,10,10,0|whcdfz,10,10,0|whcdg0,11,11,1|wsi83z,11,11,1|wsi840,10,10,0|x02g3z,10,10,0|x02g40,11,11,1|xb8arz,11,11,1|xb8as0,10,10,0|xisirz,10,10,0|xisis0,11,11,1|xtydfz,11,11,1|xtydg0,10,10,0|y1ilfz,10,10,0|y1ilg0,11,11,1|ycog3z,11,11,1|ycog40,10,10,0|yklmrz,10,10,0|yklms0,11,11,1|yveirz,11,11,1|yveis0,10,10,0|z3bpfz,10,10,0|z3bpg0,11,11,1|ze4lfz,11,11,1|ze4lg0,10,10,0\",\"Indian/Antananarivo|,0,4,0|-w6p5hg,4,5,0|-lnsey1,4,5,0|-lnsey0,5,6,0|-kvcdo1,5,6,0|-kvcdo0,4,5,0|-h80ka1,4,5,0|-h80ka0,6,7,0|-eb6ib1,6,7,0|-eb6ib0,5,6,0\",\"Indian/Chagos|,0,382,0|-wvpc2s,92,194,0|dkgsrz,92,194,0|dkgss0,96,196,0\",\"Indian/Christmas|,0,383,0|-133iwws,91,193,0\",\"Indian/Cocos|,0,384,0|-10j6sm4,109,229,0\",\"Indian/Comoro|,0,4,0|-w6p5hg,4,5,0|-lnsey1,4,5,0|-lnsey0,5,6,0|-kvcdo1,5,6,0|-kvcdo0,4,5,0|-h80ka1,4,5,0|-h80ka0,6,7,0|-eb6ib1,6,7,0|-eb6ib0,5,6,0\",\"Indian/Kerguelen|,60,1,0|-afrs00,92,194,0\",\"Indian/Mahe|,0,385,0|-wvp8xo,105,209,0\",\"Indian/Maldives|,0,386,0|-1ayyga0,21,386,0|-57x6y1,21,386,0|-57x6y0,92,194,0\",\"Indian/Mauritius|,0,387,0|-wvp9bc,105,209,0|6nykvz,105,209,0|6nykw0,92,194,1|6wai3z,92,194,1|6wai40,105,209,0|k9befz,105,209,0|k9beg0,92,194,1|kh8ibz,92,194,1|kh8ic0,105,209,0\",\"Indian/Mayotte|,0,4,0|-w6p5hg,4,5,0|-lnsey1,4,5,0|-lnsey0,5,6,0|-kvcdo1,5,6,0|-kvcdo0,4,5,0|-h80ka1,4,5,0|-h80ka0,6,7,0|-eb6ib1,6,7,0|-eb6ib0,5,6,0\",\"Indian/Reunion|,0,388,0|-uks29s,105,209,0\",\"Pacific/Apia|,0,389,0|-14fxxj4,0,390,0|-usiiv5,0,390,0|-usiiv4,158,391,0|-afqw21,158,391,0|-afqw20,159,35,0|l9cp7z,159,35,0|l9cp80,160,36,1|lj12vz,160,36,1|lj12w0,159,35,0|ls15jz,159,35,0|ls15k0,160,36,1|lx0h3z,160,36,1|lx0h40,104,207,1|m1r5jz,104,207,1|m1r5k0,103,201,0|mb46vz,103,201,0|mb46w0,104,207,1|mku6vz,104,207,1|mku6w0,103,201,0|mtu9jz,103,201,0|mtu9k0,104,207,1|n3k9jz,104,207,1|n3k9k0,103,201,0|nckc7z,103,201,0|nckc80,104,207,1|nmac7z,104,207,1|nmac80,103,201,0|nvaevz,103,201,0|nvaew0,104,207,1|o50evz,104,207,1|o50ew0,103,201,0|oe0hjz,103,201,0|oe0hk0,104,207,1|onqhjz,104,207,1|onqhk0,103,201,0|owqk7z,103,201,0|owqk80,104,207,1|p6gk7z,104,207,1|p6gk80,103,201,0|pftljz,103,201,0|pftlk0,104,207,1|ppjljz,104,207,1|ppjlk0,103,201,0|pyjo7z,103,201,0|pyjo80,104,207,1|q89o7z,104,207,1|q89o80,103,201,0|qh9qvz,103,201,0|qh9qw0,104,207,1|qqzqvz,104,207,1|qqzqw0,103,201,0|qzztjz,103,201,0|qzztk0,104,207,1|r9ptjz,104,207,1|r9ptk0,103,201,0|ripw7z,103,201,0|ripw80,104,207,1|rsfw7z,104,207,1|rsfw80,103,201,0|s1fyvz,103,201,0|s1fyw0,104,207,1|sbixjz,104,207,1|sbixk0,103,201,0|skj07z,103,201,0|skj080,104,207,1|su907z,104,207,1|su9080,103,201,0|t392vz,103,201,0|t392w0,104,207,1|tcz2vz,104,207,1|tcz2w0,103,201,0|tlz5jz,103,201,0|tlz5k0,104,207,1|tvp5jz,104,207,1|tvp5k0,103,201,0|u4p87z,103,201,0|u4p880,104,207,1|uef87z,104,207,1|uef880,103,201,0|unfavz,103,201,0|unfaw0,104,207,1|ux5avz,104,207,1|ux5aw0,103,201,0|v6ic7z,103,201,0|v6ic80,104,207,1|vg8c7z,104,207,1|vg8c80,103,201,0|vp8evz,103,201,0|vp8ew0,104,207,1|vyyevz,104,207,1|vyyew0,103,201,0|w7yhjz,103,201,0|w7yhk0,104,207,1|whohjz,104,207,1|whohk0,103,201,0|wqok7z,103,201,0|wqok80,104,207,1|x0ek7z,104,207,1|x0ek80,103,201,0|x9emvz,103,201,0|x9emw0,104,207,1|xj4mvz,104,207,1|xj4mw0,103,201,0|xs4pjz,103,201,0|xs4pk0,104,207,1|y1upjz,104,207,1|y1upk0,103,201,0|yb7qvz,103,201,0|yb7qw0,104,207,1|ykxqvz,104,207,1|ykxqw0,103,201,0|ytxtjz,103,201,0|ytxtk0,104,207,1|z3ntjz,104,207,1|z3ntk0,103,201,0|zcnw7z,103,201,0|zcnw80,104,207,1\",\"Pacific/Auckland|,0,197,0|-1gsoz14,97,198,0|-m01p21,97,198,0|-m01p20,98,199,1|-ltxei1,98,199,1|-ltxei0,97,198,0|-lieie1,97,198,0|-lieie0,98,200,1|-lahd41,98,200,1|-lahd40,97,198,0|-kzofq1,97,198,0|-kzofq0,98,200,1|-krrag1,98,200,1|-krrag0,97,198,0|-kgyd21,97,198,0|-kgyd20,98,200,1|-k917s1,98,200,1|-k917s0,97,198,0|-jy8ae1,97,198,0|-jy8ae0,98,200,1|-jpy6g1,98,200,1|-jpy6g0,97,198,0|-jfi7q1,97,198,0|-jfi7q0,98,200,1|-j783s1,98,200,1|-j783s0,97,198,0|-iws521,97,198,0|-iws520,98,200,1|-imc941,98,200,1|-imc940,97,198,0|-ief121,97,198,0|-ief120,98,200,1|-i3m6g1,98,200,1|-i3m6g0,97,198,0|-hvoye1,97,198,0|-hvoye0,98,200,1|-hkw3s1,98,200,1|-hkw3s0,97,198,0|-hcyvq1,97,198,0|-hcyvq0,98,200,1|-h26141,98,200,1|-h26140,97,198,0|-gu8t21,97,198,0|-gu8t20,98,200,1|-gjfyg1,98,200,1|-gjfyg0,97,198,0|-gbiqe1,97,198,0|-gbiqe0,98,200,1|-g0cx41,98,200,1|-g0cx40,97,198,0|-fssnq1,97,198,0|-fssnq0,98,200,1|-fhmug1,98,200,1|-fhmug0,97,198,0|-f9pme1,97,198,0|-f9pme0,98,200,1|-ciy9c1,98,200,1|-ciy9c0,98,200,0|2ivg7z,98,200,0|2ivg80,99,201,1|2omuvz,99,201,1|2omuw0,98,200,0|318k7z,98,200,0|318k80,99,201,1|382uvz,99,201,1|382uw0,98,200,0|3kbljz,98,200,0|3kblk0,99,201,1|3qsxjz,99,201,1|3qsxk0,98,200,0|431o7z,98,200,0|431o80,99,201,1|49j07z,99,201,1|49j080,98,200,0|4lrqvz,98,200,0|4lrqw0,99,201,1|4s92vz,99,201,1|4s92w0,98,200,0|54htjz,98,200,0|54htk0,99,201,1|5az5jz,99,201,1|5az5k0,98,200,0|5n7w7z,98,200,0|5n7w80,99,201,1|5tp87z,99,201,1|5tp880,98,200,0|65xyvz,98,200,0|65xyw0,99,201,1|6cs9jz,99,201,1|6cs9k0,98,200,0|6p107z,98,200,0|6p1080,99,201,1|6vic7z,99,201,1|6vic80,98,200,0|77r2vz,98,200,0|77r2w0,99,201,1|7e8evz,99,201,1|7e8ew0,98,200,0|7qh5jz,98,200,0|7qh5k0,99,201,1|7wyhjz,99,201,1|7wyhk0,98,200,0|89787z,98,200,0|897880,99,201,1|8fok7z,99,201,1|8fok80,98,200,0|8rxavz,98,200,0|8rxaw0,99,201,1|8yemvz,99,201,1|8yemw0,98,200,0|9andjz,98,200,0|9andk0,99,201,1|9hho7z,99,201,1|9hho80,98,200,0|9tqevz,98,200,0|9tqew0,99,201,1|a07qvz,99,201,1|a07qw0,98,200,0|abdljz,98,200,0|abdlk0,99,201,1|ajnqvz,99,201,1|ajnqw0,98,200,0|au3o7z,98,200,0|au3o80,99,201,1|b2dtjz,99,201,1|b2dtk0,98,200,0|bctqvz,98,200,0|bctqw0,99,201,1|bl3w7z,99,201,1|bl3w80,98,200,0|bvjtjz,98,200,0|bvjtk0,99,201,1|c46xjz,99,201,1|c46xk0,98,200,0|ce9w7z,98,200,0|ce9w80,99,201,1|cmx07z,99,201,1|cmx080,98,200,0|cwzyvz,98,200,0|cwzyw0,99,201,1|d5n2vz,99,201,1|d5n2w0,98,200,0|dfq1jz,98,200,0|dfq1k0,99,201,1|dod5jz,99,201,1|dod5k0,98,200,0|dyt2vz,98,200,0|dyt2w0,99,201,1|e7387z,99,201,1|e73880,98,200,0|ehj5jz,98,200,0|ehj5k0,99,201,1|eptavz,99,201,1|eptaw0,98,200,0|f0987z,98,200,0|f09880,99,201,1|f8wc7z,99,201,1|f8wc80,98,200,0|fizavz,98,200,0|fizaw0,99,201,1|frmevz,99,201,1|frmew0,98,200,0|g1pdjz,98,200,0|g1pdk0,99,201,1|gachjz,99,201,1|gachk0,98,200,0|gksevz,98,200,0|gksew0,99,201,1|gt2k7z,99,201,1|gt2k80,98,200,0|h3ihjz,98,200,0|h3ihk0,99,201,1|hbsmvz,99,201,1|hbsmw0,98,200,0|hm8k7z,98,200,0|hm8k80,99,201,1|huvo7z,99,201,1|huvo80,98,200,0|i4ymvz,98,200,0|i4ymw0,99,201,1|idlqvz,99,201,1|idlqw0,98,200,0|inopjz,98,200,0|inopk0,99,201,1|iwbtjz,99,201,1|iwbtk0,98,200,0|j6es7z,98,200,0|j6es80,99,201,1|jf1w7z,99,201,1|jf1w80,98,200,0|jp4uvz,98,200,0|jp4uw0,99,201,1|jyuuvz,99,201,1|jyuuw0,98,200,0|k7uxjz,98,200,0|k7uxk0,99,201,1|khkxjz,99,201,1|khkxk0,98,200,0|kql07z,98,200,0|kql080,99,201,1|l0b07z,99,201,1|l0b080,98,200,0|l9b2vz,98,200,0|l9b2w0,99,201,1|lj12vz,99,201,1|lj12w0,98,200,0|ls15jz,98,200,0|ls15k0,99,201,1|m1r5jz,99,201,1|m1r5k0,98,200,0|mb46vz,98,200,0|mb46w0,99,201,1|mku6vz,99,201,1|mku6w0,98,200,0|mtu9jz,98,200,0|mtu9k0,99,201,1|n3k9jz,99,201,1|n3k9k0,98,200,0|nckc7z,98,200,0|nckc80,99,201,1|nmac7z,99,201,1|nmac80,98,200,0|nvaevz,98,200,0|nvaew0,99,201,1|o50evz,99,201,1|o50ew0,98,200,0|oe0hjz,98,200,0|oe0hk0,99,201,1|onqhjz,99,201,1|onqhk0,98,200,0|owqk7z,98,200,0|owqk80,99,201,1|p6gk7z,99,201,1|p6gk80,98,200,0|pftljz,98,200,0|pftlk0,99,201,1|ppjljz,99,201,1|ppjlk0,98,200,0|pyjo7z,98,200,0|pyjo80,99,201,1|q89o7z,99,201,1|q89o80,98,200,0|qh9qvz,98,200,0|qh9qw0,99,201,1|qqzqvz,99,201,1|qqzqw0,98,200,0|qzztjz,98,200,0|qzztk0,99,201,1|r9ptjz,99,201,1|r9ptk0,98,200,0|ripw7z,98,200,0|ripw80,99,201,1|rsfw7z,99,201,1|rsfw80,98,200,0|s1fyvz,98,200,0|s1fyw0,99,201,1|sbixjz,99,201,1|sbixk0,98,200,0|skj07z,98,200,0|skj080,99,201,1|su907z,99,201,1|su9080,98,200,0|t392vz,98,200,0|t392w0,99,201,1|tcz2vz,99,201,1|tcz2w0,98,200,0|tlz5jz,98,200,0|tlz5k0,99,201,1|tvp5jz,99,201,1|tvp5k0,98,200,0|u4p87z,98,200,0|u4p880,99,201,1|uef87z,99,201,1|uef880,98,200,0|unfavz,98,200,0|unfaw0,99,201,1|ux5avz,99,201,1|ux5aw0,98,200,0|v6ic7z,98,200,0|v6ic80,99,201,1|vg8c7z,99,201,1|vg8c80,98,200,0|vp8evz,98,200,0|vp8ew0,99,201,1|vyyevz,99,201,1|vyyew0,98,200,0|w7yhjz,98,200,0|w7yhk0,99,201,1|whohjz,99,201,1|whohk0,98,200,0|wqok7z,98,200,0|wqok80,99,201,1|x0ek7z,99,201,1|x0ek80,98,200,0|x9emvz,98,200,0|x9emw0,99,201,1|xj4mvz,99,201,1|xj4mw0,98,200,0|xs4pjz,98,200,0|xs4pk0,99,201,1|y1upjz,99,201,1|y1upk0,98,200,0|yb7qvz,98,200,0|yb7qw0,99,201,1|ykxqvz,99,201,1|ykxqw0,98,200,0|ytxtjz,98,200,0|ytxtk0,99,201,1|z3ntjz,99,201,1|z3ntk0,98,200,0|zcnw7z,98,200,0|zcnw80,99,201,1\",\"Pacific/Bougainville|,0,392,0|-1ayyvh4,161,393,0|-1354j8x,161,393,0|-1354j8w,93,195,0|-ecsh41,93,195,0|-ecsh40,107,224,0|-cpsbo1,107,224,0|-cpsbo0,93,195,0|nh90fz,93,195,0|nh90g0,90,192,0\",\"Pacific/Chatham|,0,394,0|-1gsp0n0,162,395,0|-ciya11,162,395,0|-ciya10,163,396,0|2ivg7z,163,396,0|2ivg80,164,397,1|2omuvz,164,397,1|2omuw0,163,396,0|318k7z,163,396,0|318k80,164,397,1|382uvz,164,397,1|382uw0,163,396,0|3kbljz,163,396,0|3kblk0,164,397,1|3qsxjz,164,397,1|3qsxk0,163,396,0|431o7z,163,396,0|431o80,164,397,1|49j07z,164,397,1|49j080,163,396,0|4lrqvz,163,396,0|4lrqw0,164,397,1|4s92vz,164,397,1|4s92w0,163,396,0|54htjz,163,396,0|54htk0,164,397,1|5az5jz,164,397,1|5az5k0,163,396,0|5n7w7z,163,396,0|5n7w80,164,397,1|5tp87z,164,397,1|5tp880,163,396,0|65xyvz,163,396,0|65xyw0,164,397,1|6cs9jz,164,397,1|6cs9k0,163,396,0|6p107z,163,396,0|6p1080,164,397,1|6vic7z,164,397,1|6vic80,163,396,0|77r2vz,163,396,0|77r2w0,164,397,1|7e8evz,164,397,1|7e8ew0,163,396,0|7qh5jz,163,396,0|7qh5k0,164,397,1|7wyhjz,164,397,1|7wyhk0,163,396,0|89787z,163,396,0|897880,164,397,1|8fok7z,164,397,1|8fok80,163,396,0|8rxavz,163,396,0|8rxaw0,164,397,1|8yemvz,164,397,1|8yemw0,163,396,0|9andjz,163,396,0|9andk0,164,397,1|9hho7z,164,397,1|9hho80,163,396,0|9tqevz,163,396,0|9tqew0,164,397,1|a07qvz,164,397,1|a07qw0,163,396,0|abdljz,163,396,0|abdlk0,164,397,1|ajnqvz,164,397,1|ajnqw0,163,396,0|au3o7z,163,396,0|au3o80,164,397,1|b2dtjz,164,397,1|b2dtk0,163,396,0|bctqvz,163,396,0|bctqw0,164,397,1|bl3w7z,164,397,1|bl3w80,163,396,0|bvjtjz,163,396,0|bvjtk0,164,397,1|c46xjz,164,397,1|c46xk0,163,396,0|ce9w7z,163,396,0|ce9w80,164,397,1|cmx07z,164,397,1|cmx080,163,396,0|cwzyvz,163,396,0|cwzyw0,164,397,1|d5n2vz,164,397,1|d5n2w0,163,396,0|dfq1jz,163,396,0|dfq1k0,164,397,1|dod5jz,164,397,1|dod5k0,163,396,0|dyt2vz,163,396,0|dyt2w0,164,397,1|e7387z,164,397,1|e73880,163,396,0|ehj5jz,163,396,0|ehj5k0,164,397,1|eptavz,164,397,1|eptaw0,163,396,0|f0987z,163,396,0|f09880,164,397,1|f8wc7z,164,397,1|f8wc80,163,396,0|fizavz,163,396,0|fizaw0,164,397,1|frmevz,164,397,1|frmew0,163,396,0|g1pdjz,163,396,0|g1pdk0,164,397,1|gachjz,164,397,1|gachk0,163,396,0|gksevz,163,396,0|gksew0,164,397,1|gt2k7z,164,397,1|gt2k80,163,396,0|h3ihjz,163,396,0|h3ihk0,164,397,1|hbsmvz,164,397,1|hbsmw0,163,396,0|hm8k7z,163,396,0|hm8k80,164,397,1|huvo7z,164,397,1|huvo80,163,396,0|i4ymvz,163,396,0|i4ymw0,164,397,1|idlqvz,164,397,1|idlqw0,163,396,0|inopjz,163,396,0|inopk0,164,397,1|iwbtjz,164,397,1|iwbtk0,163,396,0|j6es7z,163,396,0|j6es80,164,397,1|jf1w7z,164,397,1|jf1w80,163,396,0|jp4uvz,163,396,0|jp4uw0,164,397,1|jyuuvz,164,397,1|jyuuw0,163,396,0|k7uxjz,163,396,0|k7uxk0,164,397,1|khkxjz,164,397,1|khkxk0,163,396,0|kql07z,163,396,0|kql080,164,397,1|l0b07z,164,397,1|l0b080,163,396,0|l9b2vz,163,396,0|l9b2w0,164,397,1|lj12vz,164,397,1|lj12w0,163,396,0|ls15jz,163,396,0|ls15k0,164,397,1|m1r5jz,164,397,1|m1r5k0,163,396,0|mb46vz,163,396,0|mb46w0,164,397,1|mku6vz,164,397,1|mku6w0,163,396,0|mtu9jz,163,396,0|mtu9k0,164,397,1|n3k9jz,164,397,1|n3k9k0,163,396,0|nckc7z,163,396,0|nckc80,164,397,1|nmac7z,164,397,1|nmac80,163,396,0|nvaevz,163,396,0|nvaew0,164,397,1|o50evz,164,397,1|o50ew0,163,396,0|oe0hjz,163,396,0|oe0hk0,164,397,1|onqhjz,164,397,1|onqhk0,163,396,0|owqk7z,163,396,0|owqk80,164,397,1|p6gk7z,164,397,1|p6gk80,163,396,0|pftljz,163,396,0|pftlk0,164,397,1|ppjljz,164,397,1|ppjlk0,163,396,0|pyjo7z,163,396,0|pyjo80,164,397,1|q89o7z,164,397,1|q89o80,163,396,0|qh9qvz,163,396,0|qh9qw0,164,397,1|qqzqvz,164,397,1|qqzqw0,163,396,0|qzztjz,163,396,0|qzztk0,164,397,1|r9ptjz,164,397,1|r9ptk0,163,396,0|ripw7z,163,396,0|ripw80,164,397,1|rsfw7z,164,397,1|rsfw80,163,396,0|s1fyvz,163,396,0|s1fyw0,164,397,1|sbixjz,164,397,1|sbixk0,163,396,0|skj07z,163,396,0|skj080,164,397,1|su907z,164,397,1|su9080,163,396,0|t392vz,163,396,0|t392w0,164,397,1|tcz2vz,164,397,1|tcz2w0,163,396,0|tlz5jz,163,396,0|tlz5k0,164,397,1|tvp5jz,164,397,1|tvp5k0,163,396,0|u4p87z,163,396,0|u4p880,164,397,1|uef87z,164,397,1|uef880,163,396,0|unfavz,163,396,0|unfaw0,164,397,1|ux5avz,164,397,1|ux5aw0,163,396,0|v6ic7z,163,396,0|v6ic80,164,397,1|vg8c7z,164,397,1|vg8c80,163,396,0|vp8evz,163,396,0|vp8ew0,164,397,1|vyyevz,164,397,1|vyyew0,163,396,0|w7yhjz,163,396,0|w7yhk0,164,397,1|whohjz,164,397,1|whohk0,163,396,0|wqok7z,163,396,0|wqok80,164,397,1|x0ek7z,164,397,1|x0ek80,163,396,0|x9emvz,163,396,0|x9emw0,164,397,1|xj4mvz,164,397,1|xj4mw0,163,396,0|xs4pjz,163,396,0|xs4pk0,164,397,1|y1upjz,164,397,1|y1upk0,163,396,0|yb7qvz,163,396,0|yb7qw0,164,397,1|ykxqvz,164,397,1|ykxqw0,163,396,0|ytxtjz,163,396,0|ytxtk0,164,397,1|z3ntjz,164,397,1|z3ntk0,163,396,0|zcnw7z,163,396,0|zcnw80,164,397,1\",\"Pacific/Chuuk|,0,398,0|-1t8j2rw,0,399,0|-100f5fx,0,399,0|-100f5fw,93,195,0|-su4zs1,93,195,0|-su4zs0,107,224,0|-qknl01,107,224,0|-qknl00,93,195,0|-f08x41,93,195,0|-f08x40,107,224,0|-cqtd01,107,224,0|-cqtd00,93,195,0\",\"Pacific/Easter|,0,400,0|-15r0p2w,165,400,0|-jhfaex,165,400,0|-jhfaew,166,66,0|-lsvk1,166,66,0|-lsvk0,167,62,1|-e8qc1,167,62,1|-e8qc0,166,66,0|-1zww1,166,66,0|-1zww0,167,62,1|4hcbz,167,62,1|4hcc0,166,66,0|ekdrz,166,66,0|ekds0,167,62,1|mhhnz,167,62,1|mhho0,166,66,0|xagfz,166,66,0|xagg0,167,62,1|157kbz,167,62,1|157kc0,166,66,0|1gdhrz,166,66,0|1gdhs0,167,62,1|1nxmzz,167,62,1|1nxn00,166,66,0|1ydn3z,166,66,0|1ydn40,167,62,1|26npnz,167,62,1|26npo0,166,66,0|2htn3z,166,66,0|2htn40,167,62,1|2pdsbz,167,62,1|2pdsc0,166,66,0|30jprz,166,66,0|30jps0,167,62,1|38gtnz,167,62,1|38gto0,166,66,0|3j9sfz,166,66,0|3j9sg0,167,62,1|3r6wbz,167,62,1|3r6wc0,166,66,0|41zv3z,166,66,0|41zv40,167,62,1|49wyzz,167,62,1|49wz00,166,66,0|4l2wfz,166,66,0|4l2wg0,167,62,1|4sn1nz,167,62,1|4sn1o0,166,66,0|53sz3z,166,66,0|53sz40,167,62,1|5bd4bz,167,62,1|5bd4c0,166,66,0|5mj1rz,166,66,0|5mj1s0,167,62,1|5ug5nz,167,62,1|5ug5o0,166,66,0|6594fz,166,66,0|6594g0,167,62,1|6d68bz,167,62,1|6d68c0,167,62,0|6nz73z,167,62,0|6nz740,56,63,1|6vwazz,56,63,1|6vwb00,167,62,0|76p9rz,167,62,0|76p9s0,56,63,1|7emdnz,56,63,1|7emdo0,167,62,0|7psb3z,167,62,0|7psb40,56,63,1|7xcgbz,56,63,1|7xcgc0,167,62,0|88idrz,167,62,0|88ids0,56,63,1|8g2izz,56,63,1|8g2j00,167,62,0|8r8gfz,167,62,0|8r8gg0,56,63,1|90lezz,56,63,1|90lf00,167,62,0|99yj3z,167,62,0|99yj40,56,63,1|9hvmzz,56,63,1|9hvn00,167,62,0|9solrz,167,62,0|9sols0,56,63,1|a0lpnz,56,63,1|a0lpo0,167,62,0|abrn3z,167,62,0|abrn40,56,63,1|ajbsbz,56,63,1|ajbsc0,167,62,0|at1v3z,167,62,0|at1v40,56,63,1|b21uzz,56,63,1|b21v00,167,62,0|bd7sfz,167,62,0|bd7sg0,56,63,1|bl4wbz,56,63,1|bl4wc0,167,62,0|bvxv3z,167,62,0|bvxv40,56,63,1|c3uyzz,56,63,1|c3uz00,167,62,0|cenxrz,167,62,0|cenxs0,56,63,1|cml1nz,56,63,1|cml1o0,167,62,0|cxe0fz,167,62,0|cxe0g0,56,63,1|d5b4bz,56,63,1|d5b4c0,167,62,0|dgh1rz,167,62,0|dgh1s0,56,63,1|do16zz,56,63,1|do1700,167,62,0|dz74fz,167,62,0|dz74g0,56,63,1|e7u5nz,56,63,1|e7u5o0,167,62,0|ehx73z,167,62,0|ehx740,56,63,1|epuazz,56,63,1|epub00,167,62,0|ezxcfz,167,62,0|ezxcg0,56,63,1|f9n9nz,56,63,1|f9n9o0,167,62,0|fjdcfz,167,62,0|fjdcg0,56,63,1|fragbz,56,63,1|fragc0,167,62,0|g2gdrz,167,62,0|g2gds0,56,63,1|ga0izz,56,63,1|ga0j00,167,62,0|gl6gfz,167,62,0|gl6gg0,56,63,1|gsqlnz,56,63,1|gsqlo0,167,62,0|h3wj3z,167,62,0|h3wj40,56,63,1|hbgobz,56,63,1|hbgoc0,167,62,0|hmmlrz,167,62,0|hmmls0,56,63,1|hujpnz,56,63,1|hujpo0,167,62,0|i5cofz,167,62,0|i5cog0,56,63,1|id9sbz,56,63,1|id9sc0,167,62,0|io2r3z,167,62,0|io2r40,56,63,1|ivzuzz,56,63,1|ivzv00,167,62,0|j75sfz,167,62,0|j75sg0,56,63,1|jepxnz,56,63,1|jepxo0,167,62,0|jpvv3z,167,62,0|jpvv40,56,63,1|jyiwbz,56,63,1|jyiwc0,167,62,0|k8lxrz,167,62,0|k8lxs0,56,63,1|kgj1nz,56,63,1|kgj1o0,167,62,0|krc0fz,167,62,0|krc0g0,56,63,1|l0c0bz,56,63,1|l0c0c0,167,62,0|la233z,167,62,0|la2340,56,63,1|lkuwbz,56,63,1|lkuwc0,167,62,0|lq9f3z,167,62,0|lq9f40,56,63,1|m380bz,56,63,1|m380c0,167,62,0|m9pf3z,167,62,0|m9pf40,56,63,1|mly2zz,56,63,1|mly300,167,62,0|mssgfz,167,62,0|mssgg0,56,63,1|n4o5nz,56,63,1|n4o5o0,167,62,0|nbij3z,167,62,0|nbij40,56,63,1|o776zz,56,63,1|o77700,167,62,0|obvsfz,167,62,0|obvsg0,56,63,1|opx9nz,56,63,1|opx9o0,167,62,0|oulv3z,167,62,0|oulv40,56,63,1|p8ncbz,56,63,1|p8ncc0,167,62,0|pdbxrz,167,62,0|pdbxs0,56,63,1|ppklnz,56,63,1|ppklo0,167,62,0|pxhv3z,167,62,0|pxhv40,56,63,1|q8aobz,56,63,1|q8aoc0,167,62,0|qg7xrz,167,62,0|qg7xs0,56,63,1|qr0qzz,56,63,1|qr0r00,167,62,0|qyy0fz,167,62,0|qyy0g0,56,63,1|r9qtnz,56,63,1|r9qto0,167,62,0|rho33z,167,62,0|rho340,56,63,1|rsgwbz,56,63,1|rsgwc0,167,62,0|s0e5rz,167,62,0|s0e5s0,56,63,1|sbjxnz,56,63,1|sbjxo0,167,62,0|sjh73z,167,62,0|sjh740,56,63,1|sua0bz,56,63,1|sua0c0,167,62,0|t279rz,167,62,0|t279s0,56,63,1|td02zz,56,63,1|td0300,167,62,0|tkxcfz,167,62,0|tkxcg0,56,63,1|tvq5nz,56,63,1|tvq5o0,167,62,0|u3nf3z,167,62,0|u3nf40,56,63,1|ueg8bz,56,63,1|ueg8c0,167,62,0|umdhrz,167,62,0|umdhs0,56,63,1|uxj9nz,56,63,1|uxj9o0,167,62,0|v53kfz,167,62,0|v53kg0,56,63,1|vg9cbz,56,63,1|vg9cc0,167,62,0|vo6lrz,167,62,0|vo6ls0,56,63,1|vyzezz,56,63,1|vyzf00,167,62,0|w6wofz,167,62,0|w6wog0,56,63,1|whphnz,56,63,1|whpho0,167,62,0|wpmr3z,167,62,0|wpmr40,56,63,1|x0fkbz,56,63,1|x0fkc0,167,62,0|x8ctrz,167,62,0|x8cts0,56,63,1|xj5mzz,56,63,1|xj5n00,167,62,0|xr2wfz,167,62,0|xr2wg0,56,63,1|y28obz,56,63,1|y28oc0,167,62,0|y9sz3z,167,62,0|y9sz40,56,63,1|ykyqzz,56,63,1|ykyr00,167,62,0|ysw0fz,167,62,0|ysw0g0,56,63,1|z3otnz,56,63,1|z3oto0,167,62,0|zbm33z,167,62,0|zbm340,56,63,1\",\"Pacific/Efate|,0,401,0|-u964i4,90,192,0|22nynz,90,192,0|22nyo0,102,200,1|27pfzz,102,200,1|27pg00,90,192,0|75y6rz,90,192,0|75y6s0,102,200,1|7fb5bz,102,200,1|7fb5c0,90,192,0|7oo9fz,90,192,0|7oo9g0,102,200,1|7y17zz,102,200,1|7y1800,90,192,0|87rarz,90,192,0|87ras0,102,200,1|8granz,102,200,1|8grao0,90,192,0|8qhdfz,90,192,0|8qhdg0,102,200,1|8zubzz,102,200,1|8zuc00,90,192,0|997g3z,90,192,0|997g40,102,200,1|9ikenz,102,200,1|9ikeo0,90,192,0|9rxirz,90,192,0|9rxis0,102,200,1|a1ahbz,102,200,1|a1ahc0,90,192,0|aanlfz,90,192,0|aanlg0,102,200,1|ak0jzz,102,200,1|ak0k00,90,192,0|atdo3z,90,192,0|atdo40,102,200,1|b2qmnz,102,200,1|b2qmo0,90,192,0|bcgpfz,90,192,0|bcgpg0,102,200,1|bikzzz,102,200,1|bil000,90,192,0|bwmmrz,90,192,0|bwmms0,102,200,1|c1b2nz,102,200,1|c1b2o0,90,192,0\",\"Pacific/Enderbury|,0,402,0|-100dhng,168,403,0|535inz,168,403,0|535io0,159,35,0|d1o97z,159,35,0|d1o980,103,201,0\",\"Pacific/Fakaofo|,0,404,0|-100dhmg,159,35,0|lx0jvz,159,35,0|lx0jw0,103,201,0\",\"Pacific/Fiji|,0,405,0|-sa2x4w,102,200,0|f1p2vz,102,200,0|f1p2w0,103,201,1|f7tg7z,103,201,1|f7tg80,102,200,0|fks47z,102,200,0|fks480,103,201,1|fqjivz,103,201,1|fqjiw0,102,200,0|ktto7z,102,200,0|ktto80,103,201,1|kzy1jz,103,201,1|kzy1k0,102,200,0|laqxjz,102,200,0|laqxk0,103,201,1|lhl87z,103,201,1|lhl880,102,200,0|lth07z,102,200,0|lth080,103,201,1|ly5ivz,103,201,1|ly5iw0,102,200,0|mc72vz,102,200,0|mc72w0,103,201,1|mgvljz,103,201,1|mgvlk0,102,200,0|mva47z,102,200,0|mva480,103,201,1|mzllfz,103,201,1|mzllg0,102,200,0|ned5jz,102,200,0|ned5k0,103,201,1|nibqvz,103,201,1|nibqw0,102,200,0|nx387z,102,200,0|nx3880,103,201,1|o11tjz,103,201,1|o11tk0,102,200,0|og69jz,102,200,0|og69k0,103,201,1|ojrw7z,103,201,1|ojrw80,102,200,0|oywc7z,102,200,0|oywc80,103,201,1|p2hyvz,103,201,1|p2hyw0,102,200,0|phmevz,102,200,0|phmew0,103,201,1|pl81jz,103,201,1|pl81k0,102,200,0|q0pg7z,102,200,0|q0pg80,103,201,1|q3y47z,103,201,1|q3y480,102,200,0|qllavz,102,200,0|qllaw0,103,201,1|qn15jz,103,201,1|qn15k0,102,200,0|r2ik7z,102,200,0|r2ik80,103,201,1|r5r87z,103,201,1|r5r880,102,200,0|rl8mvz,102,200,0|rl8mw0,103,201,1|rohavz,103,201,1|rohaw0,102,200,0|s3ypjz,102,200,0|s3ypk0,103,201,1|s77djz,103,201,1|s77dk0,102,200,0|smos7z,102,200,0|smos80,103,201,1|spxg7z,103,201,1|spxg80,102,200,0|t5euvz,102,200,0|t5euw0,103,201,1|t90hjz,103,201,1|t90hk0,102,200,0|to4xjz,102,200,0|to4xk0,103,201,1|trqk7z,103,201,1|trqk80,102,200,0|u77yvz,102,200,0|u77yw0,103,201,1|uagmvz,103,201,1|uagmw0,102,200,0|upy1jz,102,200,0|upy1k0,103,201,1|ut6pjz,103,201,1|ut6pk0,102,200,0|v8o47z,102,200,0|v8o480,103,201,1|vbws7z,103,201,1|vbws80,102,200,0|vre6vz,102,200,0|vre6w0,103,201,1|vumuvz,103,201,1|vumuw0,102,200,0|wa49jz,102,200,0|wa49k0,103,201,1|wdpw7z,103,201,1|wdpw80,102,200,0|wt7avz,102,200,0|wt7aw0,103,201,1|wwfyvz,103,201,1|wwfyw0,102,200,0|xbxdjz,102,200,0|xbxdk0,103,201,1|xf61jz,103,201,1|xf61k0,102,200,0|xung7z,102,200,0|xung80,103,201,1|xxw47z,103,201,1|xxw480,102,200,0|yddivz,102,200,0|yddiw0,103,201,1|ygm6vz,103,201,1|ygm6w0,102,200,0|yw3ljz,102,200,0|yw3lk0,103,201,1|yzp87z,103,201,1|yzp880,102,200,0|zeto7z,102,200,0|zeto80,103,201,1\",\"Pacific/Funafuti|,0,406,0|-100fais,102,200,0\",\"Pacific/Galapagos|,0,407,0|-kcr62o,56,63,0|8cmlvz,56,63,0|8cmlw0,167,62,0|byewnz,167,62,0|byewo0,56,63,1|c1ylvz,56,63,1|c1ylw0,167,62,0\",\"Pacific/Gambier|,0,408,0|-tvndoc,169,37,0\",\"Pacific/Guadalcanal|,0,409,0|-tvowac,90,192,0\",\"Pacific/Guam|,0,410,0|-1t8j1h0,0,411,0|-100f451,0,411,0|-100f450,170,195,0|-en8eg1,170,195,0|-en8eg0,107,224,0|-d9n501,107,224,0|-d9n500,170,195,0|-5hlkw1,170,195,0|-5hlkw0,171,192,1|-4nnvo1,171,192,1|-4nnvo0,170,195,0|-17w8w1,170,195,0|-17w8w0,171,192,1|-hih6d,171,192,1|-hih6c,170,195,0|-9y0w1,170,195,0|-9y0w0,171,192,1|-6ch01,171,192,1|-6ch00,170,195,0|5wcfz,170,195,0|5wcg0,171,192,1|cqkbz,171,192,1|cqkc0,170,195,0|omf3z,170,195,0|omf40,171,192,1|vgmzz,171,192,1|vgn00,170,195,0|22bb3z,170,195,0|22bb40,171,192,1|25wuzz,171,192,1|25wv00,170,195,0|3c75rz,170,195,0|3c75s0,171,192,1|3gq1pn,171,192,1|3gq1po,170,195,0|3tbtrz,170,195,0|3tbts0,171,192,1|3zt2zz,171,192,1|3zt300,170,195,0|g5z2vz,170,195,0|g5z2w0,172,195,0\",\"Pacific/Honolulu|,0,412,0|-12lnw3m,30,413,0|-j50la1,30,413,0|-j50la0,31,414,1|-j3x0a1,31,414,1|-j3x0a0,30,413,0|-ek1pa1,30,413,0|-ek1pa0,173,414,1|-cq2tg1,173,414,1|-cq2tg0,174,414,1|-cnoo21,174,414,1|-cnoo20,30,413,0|-brzum1,30,413,0|-brzum0,30,36,0\",\"Pacific/Kiritimati|,0,415,0|-100dk74,175,416,0|535eyn,175,416,0|535eyo,160,36,0|d1o6fz,160,36,0|d1o6g0,104,207,0\",\"Pacific/Kosrae|,0,417,0|-1t8j4uk,0,418,0|-100f7il,0,418,0|-100f7ik,90,192,0|-su52k1,90,192,0|-su52k0,107,224,0|-qknl01,107,224,0|-qknl00,90,192,0|-h817w1,90,192,0|-h817w0,93,195,0|-f08x41,93,195,0|-f08x40,107,224,0|-cqtd01,107,224,0|-cqtd00,90,192,0|-4r7w1,90,192,0|-4r7w0,102,200,0|f4tvzz,102,200,0|f4tw00,90,192,0\",\"Pacific/Kwajalein|,0,419,0|-100f8bk,90,192,0|-h817w1,90,192,0|-h817w0,93,195,0|-f08x41,93,195,0|-f08x40,107,224,0|-dip2c1,107,224,0|-dip2c0,90,192,0|-4r7w1,90,192,0|-4r7w0,168,403,0|cc3ynz,168,403,0|cc3yo0,102,200,0\",\"Pacific/Majuro|,0,420,0|-100f91c,90,192,0|-su52k1,90,192,0|-su52k0,107,224,0|-qknl01,107,224,0|-qknl00,90,192,0|-h817w1,90,192,0|-h817w0,93,195,0|-f08x41,93,195,0|-f08x40,107,224,0|-dj2101,107,224,0|-dj2100,90,192,0|-4r7w1,90,192,0|-4r7w0,102,200,0\",\"Pacific/Marquesas|,0,421,0|-tvncu0,176,414,0\",\"Pacific/Midway|,0,422,0|-14fxxq0,0,423,0|-usij21,0,423,0|-usij20,177,35,0\",\"Pacific/Nauru|,0,424,0|-pjxiws,143,198,0|-e9rby1,143,198,0|-e9rby0,107,224,0|-couzo1,107,224,0|-couzo0,143,198,0|4r4dlz,143,198,0|4r4dm0,102,200,0\",\"Pacific/Niue|,0,425,0|-100dhv8,178,426,0|-9wyz6p,178,426,0|-9wyz6o,158,391,0|4kdjxz,158,391,0|4kdjy0,159,35,0\",\"Pacific/Norfolk|,0,427,0|-100f8fs,179,428,0|-9x0ps1,179,428,0|-9x0ps0,143,198,0|2iiixz,143,198,0|2iiiy0,180,199,1|2ozuxz,180,199,1|2ozuy0,143,198,0|nvnexz,143,198,0|nvney0,90,192,0|pywpnz,90,192,0|pywpo0,102,200,1|q89qzz,102,200,1|q89r00,90,192,0|qhmsbz,90,192,0|qhmsc0,102,200,1|qqztnz,102,200,1|qqzto0,90,192,0|r0cuzz,90,192,0|r0cv00,102,200,1|r9pwbz,102,200,1|r9pwc0,90,192,0|rj2xnz,90,192,0|rj2xo0,102,200,1|rsfyzz,102,200,1|rsfz00,90,192,0|s1t0bz,90,192,0|s1t0c0,102,200,1|sbj0bz,102,200,1|sbj0c0,90,192,0|skw1nz,90,192,0|skw1o0,102,200,1|su92zz,102,200,1|su9300,90,192,0|t3m4bz,90,192,0|t3m4c0,102,200,1|tcz5nz,102,200,1|tcz5o0,90,192,0|tmc6zz,90,192,0|tmc700,102,200,1|tvp8bz,102,200,1|tvp8c0,90,192,0|u529nz,90,192,0|u529o0,102,200,1|uefazz,102,200,1|uefb00,90,192,0|unscbz,90,192,0|unscc0,102,200,1|ux5dnz,102,200,1|ux5do0,90,192,0|v6vdnz,90,192,0|v6vdo0,102,200,1|vg8ezz,102,200,1|vg8f00,90,192,0|vplgbz,90,192,0|vplgc0,102,200,1|vyyhnz,102,200,1|vyyho0,90,192,0|w8bizz,90,192,0|w8bj00,102,200,1|whokbz,102,200,1|whokc0,90,192,0|wr1lnz,90,192,0|wr1lo0,102,200,1|x0emzz,102,200,1|x0en00,90,192,0|x9robz,90,192,0|x9roc0,102,200,1|xj4pnz,102,200,1|xj4po0,90,192,0|xshqzz,90,192,0|xshr00,102,200,1|y1usbz,102,200,1|y1usc0,90,192,0|ybksbz,90,192,0|ybksc0,102,200,1|ykxtnz,102,200,1|ykxto0,90,192,0|yuauzz,90,192,0|yuav00,102,200,1|z3nwbz,102,200,1|z3nwc0,90,192,0|zd0xnz,90,192,0|zd0xo0,102,200,1\",\"Pacific/Noumea|,0,429,0|-u9645o,90,192,0|44uerz,90,192,0|44ues0,102,200,1|497qnz,102,200,1|497qo0,90,192,0|4nkhfz,90,192,0|4nkhg0,102,200,1|4rznzz,102,200,1|4rzo00,90,192,0|e1ouzz,90,192,0|e1ov00,102,200,1|e6ddnz,102,200,1|e6ddo0,90,192,0\",\"Pacific/Pago_Pago|,0,422,0|-14fxxq0,0,423,0|-usij21,0,423,0|-usij20,177,35,0\",\"Pacific/Palau|,0,430,0|-1t8izkk,0,431,0|-100f28l,0,431,0|-100f28k,107,224,0\",\"Pacific/Pitcairn|,0,432,0|-100dp8s,181,433,0|es2cxz,181,433,0|es2cy0,182,40,0\",\"Pacific/Pohnpei|,0,434,0|-1t8j3ys,0,435,0|-100f6mt,0,435,0|-100f6ms,90,192,0|-su52k1,90,192,0|-su52k0,107,224,0|-qknl01,107,224,0|-qknl00,90,192,0|-h817w1,90,192,0|-h817w0,93,195,0|-f08x41,93,195,0|-f08x40,107,224,0|-cqtd01,107,224,0|-cqtd00,90,192,0\",\"Pacific/Port_Moresby|,0,436,0|-1ayytx4,161,393,0|-1354j8x,161,393,0|-1354j8w,93,195,0\",\"Pacific/Rarotonga|,0,437,0|-100djqw,183,413,0|4mj95z,183,413,0|4mj960,176,414,1|4sal1z,176,414,1|4sal20,160,36,0|54jd3z,160,36,0|54jd40,176,414,1|5b0npz,176,414,1|5b0nq0,160,36,0|5n9frz,160,36,0|5n9fs0,176,414,1|5tqqdz,176,414,1|5tqqe0,160,36,0|65zifz,160,36,0|65zig0,176,414,1|6ctrpz,176,414,1|6ctrq0,160,36,0|6p2jrz,160,36,0|6p2js0,176,414,1|6vjudz,176,414,1|6vjue0,160,36,0|77smfz,160,36,0|77smg0,176,414,1|7e9x1z,176,414,1|7e9x20,160,36,0|7qip3z,160,36,0|7qip40,176,414,1|7wzzpz,176,414,1|7wzzq0,160,36,0|898rrz,160,36,0|898rs0,176,414,1|8fq2dz,176,414,1|8fq2e0,160,36,0|8ryufz,160,36,0|8ryug0,176,414,1|8yg51z,176,414,1|8yg520,160,36,0|9aox3z,160,36,0|9aox40,176,414,1|9hj6dz,176,414,1|9hj6e0,160,36,0|9tryfz,160,36,0|9tryg0,176,414,1|a0991z,176,414,1|a09920,160,36,0|aci13z,160,36,0|aci140,176,414,1|aizbpz,176,414,1|aizbq0,160,36,0|av83rz,160,36,0|av83s0,176,414,1|b1pedz,176,414,1|b1pee0,160,36,0\",\"Pacific/Saipan|,0,410,0|-1t8j1h0,0,411,0|-100f451,0,411,0|-100f450,170,195,0|-en8eg1,170,195,0|-en8eg0,107,224,0|-d9n501,107,224,0|-d9n500,170,195,0|-5hlkw1,170,195,0|-5hlkw0,171,192,1|-4nnvo1,171,192,1|-4nnvo0,170,195,0|-17w8w1,170,195,0|-17w8w0,171,192,1|-hih6d,171,192,1|-hih6c,170,195,0|-9y0w1,170,195,0|-9y0w0,171,192,1|-6ch01,171,192,1|-6ch00,170,195,0|5wcfz,170,195,0|5wcg0,171,192,1|cqkbz,171,192,1|cqkc0,170,195,0|omf3z,170,195,0|omf40,171,192,1|vgmzz,171,192,1|vgn00,170,195,0|22bb3z,170,195,0|22bb40,171,192,1|25wuzz,171,192,1|25wv00,170,195,0|3c75rz,170,195,0|3c75s0,171,192,1|3gq1pn,171,192,1|3gq1po,170,195,0|3tbtrz,170,195,0|3tbts0,171,192,1|3zt2zz,171,192,1|3zt300,170,195,0|g5z2vz,170,195,0|g5z2w0,172,195,0\",\"Pacific/Tahiti|,0,438,0|-tvnayw,160,36,0\",\"Pacific/Tarawa|,0,439,0|-100f9dg,102,200,0\",\"Pacific/Tongatapu|,0,440,0|-100fbk8,184,441,0|-f4vrld,184,441,0|-f4vrlc,103,201,0|fj6mrz,103,201,0|fj6ms0,104,207,1|frmc3z,104,207,1|frmc40,103,201,0|g3i43z,103,201,0|g3i440,104,207,1|g7tlbz,104,207,1|g7tlc0,103,201,0|gm86rz,103,201,0|gm86s0,104,207,1|gqjnzz,104,207,1|gqjo00,103,201,0|og66rz,103,201,0|og66s0,104,207,1|ojrtfz,104,207,1|ojrtg0,103,201,0\",\"Pacific/Wake|,0,442,0|-100f86s,102,200,0\",\"Pacific/Wallis|,0,443,0|-100fbdk,102,200,0\"],\"abbrvs\":\"LMT|GMT|+0020|+0030|+0230|EAT|+0245|PMT|WET|WEST|CET|CEST|WAT|-01|CAT|EET|EEST|+00|+01|SAST|CAST|MMT|WAST|+0130|NST|NWT|NPT|BST|BDT|AHST|HST|HDT|AST|AWT|APT|AHDT|YST|AKST|AKDT|-03|-02|CMT|-04|-0430|AMT|CST|CDT|CWT|CPT|EST|MST|PST|MDT|BMT|ADT|-0530|-05|PDT|MWT|MPT|-00|MDDT|EDT|SJMT|YDT|YWT|YPT|YDDT|PWT|PPT|EWT|EPT|NDT|ADDT|KMT|QMT|-0345|HMT|PDDT|EDDT|FFMT|-0330|-0230|-0130|PPMT|SMT|CDDT|SDMT|NDDT|+08|+11|+07|+05|+10|AEST|AEDT|+06|NZMT|NZST|NZDT|+03|+02|+12|+13|+14|+04|+0730|+09|+0530|+0630|IST|IDT|PLMT|HKT|HKST|HKWT|JST|IMT|+0720|WIB|+0930|WIT|JMT|IDDT|+0430|PKT|PKST|+0545|+0820|WITA|KST|KDT|TBMT|TMT|+0330|JDT|RMT|FMT|ACST|ACDT|+0845|+0945|+1030|+1130|AWST|AWDT|+0120|CEMT|MSK|MSD|DMT|BDST|WEMT|MDST|LST|SET|WMT|+0220|-1130|-11|-10|PMMT|+1215|+1245|+1345|EMT|-07|-06|-12|-09|GST|GDT|ChST|HWT|HPT|-1040|-0930|SST|-1120|+1112|+1230|-0830|-08|-1030|+1220|GMT+14|GMT+13|GMT+12|GMT+11|GMT+10|GMT+9|GMT+8|GMT+7|GMT+6|GMT+5|GMT+4|GMT+3|GMT+2|GMT+1|GMT-1|GMT-2|GMT-3|GMT-4|GMT-5|GMT-6|GMT-7|GMT-8|GMT-9|GMT-10|GMT-11|GMT-12\",\"offsets\":\"-1g|0|xc|1e0|6tg|6y0|8c0|7n0|kc|fl|2s0|5k0|-qw|mn|-2vw|-2s0|618|5sl|-1ek|-zg|-2g0|56o|460|5us|60w|-1zw|-226|2sc|18w|-1p9|2fw|1vw|360|xya|-wpq|-uk0|-rs0|-p00|12wo|-rrc|-m80|-be4|-b40|-8xc|-8c0|-5k0|-ato|-bw0|-c6k|-c3c|-cdo|-cqs|-ctg|-c44|-cos|-cac|-c2s|-cnc|-crn|-ci0|-aog|-gys|-go0|-dw0|-74s|-jho|-jg0|-b1h|-8z8|-gc0|-fa0|-aks|-b8g|-dps|-lip|-a44|-g2g|-ce8|-ce4|-9ow|-eq8|-eso|-g8c|-jn8|-fkd|-lks|-adw|-3gg|-ptg|-m9k|-jfw|-fdn|-l0g|-cxs|-gio|-mpz|-74o|-b3o|-b6s|-9rg|-6zg|-9q0|-6y0|-d68|-e7y|-grg|-es8|-ejc|-ars|-af0|-bs0|-f94|-f9c|-kjs|-fye|-g1i|-fzn|-g5v|-g2f|-fr4|-g7j|-g1d|15rv|-ow5|-fvq|-fpo|-cmc|-9uc|-e9o|-eac|-lwa|-6m4|-fz8|-fzc|-b44|-bb8|-iio|-jpg|-g83|-glg|16au|-od6|-id0|-aeg|-bzw|-iks|-aer|-460|-ebu|-dpe|-gcg|101a|-umq|-604|-iuj|-irc|-is3|-9kw|-jc4|-a7s|-a84|-a7o|-kr6|-de8|-ddo|-bu0|-c8p|-d4s|-d3a|-hig|-6go|-jdo|-ck0|-a4o|-cy0|-cyo|-8ms|-42g|15lz|-p21|-jyw|-g5g|-cqk|-gj0|-lo4|-ep8|-mss|-p0c|-hzo|14sh|-pvj|m80|uk0|jg0|dw0|rs0|go0|wd4|vy0|yq0|xc0|1040|1zo|8ng|e90|6nk|wv8|12w0|9b4|b40|al4|at8|9m8|884|880|9jk|98c|im4|fic|6ko|dtc|la4|ku0|l0g|p00|l7c|esc|esk|fa0|i20|6q0|gqs|gcw|n98|a8o|cqo|6ac|6ds|6hz|jr4|jqu|l56|nm0|gz0|jb5|js0|kdc|q20|qe0|6iu|6ig|ctc|ci0|tdo|cf0|fss|fz0|p3p|gd4|eva|h72|ity|j8d|kfk|n5c|l0y|rxc|m40|-189c|meo|66g|g5c|fcs|dl6|9ic|k8w|nac|bs4|c4g|qfc|ceh|nig|mhj|sgs|mi0|ctz|8an|9iw|9q0|glo|pvn|fqf|jsk|g7w|qiu|of7|o0y|htb|b89|af5|88o|-4r4|-5aw|-c06|-986|-2uo|-4cs|-194|-34o|-42o|-6rk|-apo|pnw|t60|sc8|q70|ra4|o88|nv4|ob0|r30|rl8|tgk|qug|lgc|s04|wk|3ok|3pc|a4|8wc|4e4|3so|2h4|2o8|t6|4u0|3j8|1kw|1dm|5c8|5bo|2bw|-15o|-169|1lr|-zo|-23|4md|5d4|5ew|5ng|97c|150|-ok|2os|53s|53c|1d8|6yh|707|9s7|ck7|4gy|78y|2b8|99w|8j6|6bc|6ao|4bg|3cc|2se|4l0|3o8|8yo|44o|30x|4os|3w0|4fc|6hc|des|jks|hy4|a9o|dm0|anc|a9s|yv4|-vsw|-vy0|st4|r8w|xz0|y10|zf0|1270|-12k4|s3w|-k94|v64|-vok|-xc0|-vpk|x4w|x6s|-glc|-ozo|tmc|-13v0|qt0|-t8e|-t60|-qe0|-t4w|-tmo|-10hg|u6k|uzk|vpc|-pu0|z20|-vm0|uws|-vgs|-vhc|v3s|v40|uto|-15rg|owk|-o38|-nm0|-11d8|tas|r94|-tl4|-rp4|w1g|y88|y9c|uus|y1k\"}) \n}","// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.\nrequire('../../js/transition.js')\nrequire('../../js/alert.js')\nrequire('../../js/button.js')\nrequire('../../js/carousel.js')\nrequire('../../js/collapse.js')\nrequire('../../js/dropdown.js')\nrequire('../../js/modal.js')\nrequire('../../js/tooltip.js')\nrequire('../../js/popover.js')\nrequire('../../js/scrollspy.js')\nrequire('../../js/tab.js')\nrequire('../../js/affix.js')","/* ========================================================================\n * Bootstrap: transition.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#transitions\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n 'use strict';\n\n // CSS TRANSITION SUPPORT (Shoutout: https://modernizr.com/)\n // ============================================================\n\n function transitionEnd() {\n var el = document.createElement('bootstrap')\n\n var transEndEventNames = {\n WebkitTransition : 'webkitTransitionEnd',\n MozTransition : 'transitionend',\n OTransition : 'oTransitionEnd otransitionend',\n transition : 'transitionend'\n }\n\n for (var name in transEndEventNames) {\n if (el.style[name] !== undefined) {\n return { end: transEndEventNames[name] }\n }\n }\n\n return false // explicit for ie8 ( ._.)\n }\n\n // https://blog.alexmaccaw.com/css-transitions\n $.fn.emulateTransitionEnd = function (duration) {\n var called = false\n var $el = this\n $(this).one('bsTransitionEnd', function () { called = true })\n var callback = function () { if (!called) $($el).trigger($.support.transition.end) }\n setTimeout(callback, duration)\n return this\n }\n\n $(function () {\n $.support.transition = transitionEnd()\n\n if (!$.support.transition) return\n\n $.event.special.bsTransitionEnd = {\n bindType: $.support.transition.end,\n delegateType: $.support.transition.end,\n handle: function (e) {\n if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)\n }\n }\n })\n\n}(jQuery);\n","/* ========================================================================\n * Bootstrap: alert.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#alerts\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n 'use strict';\n\n // ALERT CLASS DEFINITION\n // ======================\n\n var dismiss = '[data-dismiss=\"alert\"]'\n var Alert = function (el) {\n $(el).on('click', dismiss, this.close)\n }\n\n Alert.VERSION = '3.4.1'\n\n Alert.TRANSITION_DURATION = 150\n\n Alert.prototype.close = function (e) {\n var $this = $(this)\n var selector = $this.attr('data-target')\n\n if (!selector) {\n selector = $this.attr('href')\n selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n }\n\n selector = selector === '#' ? [] : selector\n var $parent = $(document).find(selector)\n\n if (e) e.preventDefault()\n\n if (!$parent.length) {\n $parent = $this.closest('.alert')\n }\n\n $parent.trigger(e = $.Event('close.bs.alert'))\n\n if (e.isDefaultPrevented()) return\n\n $parent.removeClass('in')\n\n function removeElement() {\n // detach from parent, fire event then clean up data\n $parent.detach().trigger('closed.bs.alert').remove()\n }\n\n $.support.transition && $parent.hasClass('fade') ?\n $parent\n .one('bsTransitionEnd', removeElement)\n .emulateTransitionEnd(Alert.TRANSITION_DURATION) :\n removeElement()\n }\n\n\n // ALERT PLUGIN DEFINITION\n // =======================\n\n function Plugin(option) {\n return this.each(function () {\n var $this = $(this)\n var data = $this.data('bs.alert')\n\n if (!data) $this.data('bs.alert', (data = new Alert(this)))\n if (typeof option == 'string') data[option].call($this)\n })\n }\n\n var old = $.fn.alert\n\n $.fn.alert = Plugin\n $.fn.alert.Constructor = Alert\n\n\n // ALERT NO CONFLICT\n // =================\n\n $.fn.alert.noConflict = function () {\n $.fn.alert = old\n return this\n }\n\n\n // ALERT DATA-API\n // ==============\n\n $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)\n\n}(jQuery);\n","/* ========================================================================\n * Bootstrap: button.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#buttons\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n 'use strict';\n\n // BUTTON PUBLIC CLASS DEFINITION\n // ==============================\n\n var Button = function (element, options) {\n this.$element = $(element)\n this.options = $.extend({}, Button.DEFAULTS, options)\n this.isLoading = false\n }\n\n Button.VERSION = '3.4.1'\n\n Button.DEFAULTS = {\n loadingText: 'loading...'\n }\n\n Button.prototype.setState = function (state) {\n var d = 'disabled'\n var $el = this.$element\n var val = $el.is('input') ? 'val' : 'html'\n var data = $el.data()\n\n state += 'Text'\n\n if (data.resetText == null) $el.data('resetText', $el[val]())\n\n // push to event loop to allow forms to submit\n setTimeout($.proxy(function () {\n $el[val](data[state] == null ? this.options[state] : data[state])\n\n if (state == 'loadingText') {\n this.isLoading = true\n $el.addClass(d).attr(d, d).prop(d, true)\n } else if (this.isLoading) {\n this.isLoading = false\n $el.removeClass(d).removeAttr(d).prop(d, false)\n }\n }, this), 0)\n }\n\n Button.prototype.toggle = function () {\n var changed = true\n var $parent = this.$element.closest('[data-toggle=\"buttons\"]')\n\n if ($parent.length) {\n var $input = this.$element.find('input')\n if ($input.prop('type') == 'radio') {\n if ($input.prop('checked')) changed = false\n $parent.find('.active').removeClass('active')\n this.$element.addClass('active')\n } else if ($input.prop('type') == 'checkbox') {\n if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false\n this.$element.toggleClass('active')\n }\n $input.prop('checked', this.$element.hasClass('active'))\n if (changed) $input.trigger('change')\n } else {\n this.$element.attr('aria-pressed', !this.$element.hasClass('active'))\n this.$element.toggleClass('active')\n }\n }\n\n\n // BUTTON PLUGIN DEFINITION\n // ========================\n\n function Plugin(option) {\n return this.each(function () {\n var $this = $(this)\n var data = $this.data('bs.button')\n var options = typeof option == 'object' && option\n\n if (!data) $this.data('bs.button', (data = new Button(this, options)))\n\n if (option == 'toggle') data.toggle()\n else if (option) data.setState(option)\n })\n }\n\n var old = $.fn.button\n\n $.fn.button = Plugin\n $.fn.button.Constructor = Button\n\n\n // BUTTON NO CONFLICT\n // ==================\n\n $.fn.button.noConflict = function () {\n $.fn.button = old\n return this\n }\n\n\n // BUTTON DATA-API\n // ===============\n\n $(document)\n .on('click.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n var $btn = $(e.target).closest('.btn')\n Plugin.call($btn, 'toggle')\n if (!($(e.target).is('input[type=\"radio\"], input[type=\"checkbox\"]'))) {\n // Prevent double click on radios, and the double selections (so cancellation) on checkboxes\n e.preventDefault()\n // The target component still receive the focus\n if ($btn.is('input,button')) $btn.trigger('focus')\n else $btn.find('input:visible,button:visible').first().trigger('focus')\n }\n })\n .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^=\"button\"]', function (e) {\n $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))\n })\n\n}(jQuery);\n","/* ========================================================================\n * Bootstrap: carousel.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#carousel\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n 'use strict';\n\n // CAROUSEL CLASS DEFINITION\n // =========================\n\n var Carousel = function (element, options) {\n this.$element = $(element)\n this.$indicators = this.$element.find('.carousel-indicators')\n this.options = options\n this.paused = null\n this.sliding = null\n this.interval = null\n this.$active = null\n this.$items = null\n\n this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))\n\n this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element\n .on('mouseenter.bs.carousel', $.proxy(this.pause, this))\n .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))\n }\n\n Carousel.VERSION = '3.4.1'\n\n Carousel.TRANSITION_DURATION = 600\n\n Carousel.DEFAULTS = {\n interval: 5000,\n pause: 'hover',\n wrap: true,\n keyboard: true\n }\n\n Carousel.prototype.keydown = function (e) {\n if (/input|textarea/i.test(e.target.tagName)) return\n switch (e.which) {\n case 37: this.prev(); break\n case 39: this.next(); break\n default: return\n }\n\n e.preventDefault()\n }\n\n Carousel.prototype.cycle = function (e) {\n e || (this.paused = false)\n\n this.interval && clearInterval(this.interval)\n\n this.options.interval\n && !this.paused\n && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))\n\n return this\n }\n\n Carousel.prototype.getItemIndex = function (item) {\n this.$items = item.parent().children('.item')\n return this.$items.index(item || this.$active)\n }\n\n Carousel.prototype.getItemForDirection = function (direction, active) {\n var activeIndex = this.getItemIndex(active)\n var willWrap = (direction == 'prev' && activeIndex === 0)\n || (direction == 'next' && activeIndex == (this.$items.length - 1))\n if (willWrap && !this.options.wrap) return active\n var delta = direction == 'prev' ? -1 : 1\n var itemIndex = (activeIndex + delta) % this.$items.length\n return this.$items.eq(itemIndex)\n }\n\n Carousel.prototype.to = function (pos) {\n var that = this\n var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))\n\n if (pos > (this.$items.length - 1) || pos < 0) return\n\n if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, \"slid\"\n if (activeIndex == pos) return this.pause().cycle()\n\n return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))\n }\n\n Carousel.prototype.pause = function (e) {\n e || (this.paused = true)\n\n if (this.$element.find('.next, .prev').length && $.support.transition) {\n this.$element.trigger($.support.transition.end)\n this.cycle(true)\n }\n\n this.interval = clearInterval(this.interval)\n\n return this\n }\n\n Carousel.prototype.next = function () {\n if (this.sliding) return\n return this.slide('next')\n }\n\n Carousel.prototype.prev = function () {\n if (this.sliding) return\n return this.slide('prev')\n }\n\n Carousel.prototype.slide = function (type, next) {\n var $active = this.$element.find('.item.active')\n var $next = next || this.getItemForDirection(type, $active)\n var isCycling = this.interval\n var direction = type == 'next' ? 'left' : 'right'\n var that = this\n\n if ($next.hasClass('active')) return (this.sliding = false)\n\n var relatedTarget = $next[0]\n var slideEvent = $.Event('slide.bs.carousel', {\n relatedTarget: relatedTarget,\n direction: direction\n })\n this.$element.trigger(slideEvent)\n if (slideEvent.isDefaultPrevented()) return\n\n this.sliding = true\n\n isCycling && this.pause()\n\n if (this.$indicators.length) {\n this.$indicators.find('.active').removeClass('active')\n var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])\n $nextIndicator && $nextIndicator.addClass('active')\n }\n\n var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, \"slid\"\n if ($.support.transition && this.$element.hasClass('slide')) {\n $next.addClass(type)\n if (typeof $next === 'object' && $next.length) {\n $next[0].offsetWidth // force reflow\n }\n $active.addClass(direction)\n $next.addClass(direction)\n $active\n .one('bsTransitionEnd', function () {\n $next.removeClass([type, direction].join(' ')).addClass('active')\n $active.removeClass(['active', direction].join(' '))\n that.sliding = false\n setTimeout(function () {\n that.$element.trigger(slidEvent)\n }, 0)\n })\n .emulateTransitionEnd(Carousel.TRANSITION_DURATION)\n } else {\n $active.removeClass('active')\n $next.addClass('active')\n this.sliding = false\n this.$element.trigger(slidEvent)\n }\n\n isCycling && this.cycle()\n\n return this\n }\n\n\n // CAROUSEL PLUGIN DEFINITION\n // ==========================\n\n function Plugin(option) {\n return this.each(function () {\n var $this = $(this)\n var data = $this.data('bs.carousel')\n var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)\n var action = typeof option == 'string' ? option : options.slide\n\n if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))\n if (typeof option == 'number') data.to(option)\n else if (action) data[action]()\n else if (options.interval) data.pause().cycle()\n })\n }\n\n var old = $.fn.carousel\n\n $.fn.carousel = Plugin\n $.fn.carousel.Constructor = Carousel\n\n\n // CAROUSEL NO CONFLICT\n // ====================\n\n $.fn.carousel.noConflict = function () {\n $.fn.carousel = old\n return this\n }\n\n\n // CAROUSEL DATA-API\n // =================\n\n var clickHandler = function (e) {\n var $this = $(this)\n var href = $this.attr('href')\n if (href) {\n href = href.replace(/.*(?=#[^\\s]+$)/, '') // strip for ie7\n }\n\n var target = $this.attr('data-target') || href\n var $target = $(document).find(target)\n\n if (!$target.hasClass('carousel')) return\n\n var options = $.extend({}, $target.data(), $this.data())\n var slideIndex = $this.attr('data-slide-to')\n if (slideIndex) options.interval = false\n\n Plugin.call($target, options)\n\n if (slideIndex) {\n $target.data('bs.carousel').to(slideIndex)\n }\n\n e.preventDefault()\n }\n\n $(document)\n .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)\n .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)\n\n $(window).on('load', function () {\n $('[data-ride=\"carousel\"]').each(function () {\n var $carousel = $(this)\n Plugin.call($carousel, $carousel.data())\n })\n })\n\n}(jQuery);\n","/* ========================================================================\n * Bootstrap: collapse.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#collapse\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n/* jshint latedef: false */\n\n+function ($) {\n 'use strict';\n\n // COLLAPSE PUBLIC CLASS DEFINITION\n // ================================\n\n var Collapse = function (element, options) {\n this.$element = $(element)\n this.options = $.extend({}, Collapse.DEFAULTS, options)\n this.$trigger = $('[data-toggle=\"collapse\"][href=\"#' + element.id + '\"],' +\n '[data-toggle=\"collapse\"][data-target=\"#' + element.id + '\"]')\n this.transitioning = null\n\n if (this.options.parent) {\n this.$parent = this.getParent()\n } else {\n this.addAriaAndCollapsedClass(this.$element, this.$trigger)\n }\n\n if (this.options.toggle) this.toggle()\n }\n\n Collapse.VERSION = '3.4.1'\n\n Collapse.TRANSITION_DURATION = 350\n\n Collapse.DEFAULTS = {\n toggle: true\n }\n\n Collapse.prototype.dimension = function () {\n var hasWidth = this.$element.hasClass('width')\n return hasWidth ? 'width' : 'height'\n }\n\n Collapse.prototype.show = function () {\n if (this.transitioning || this.$element.hasClass('in')) return\n\n var activesData\n var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')\n\n if (actives && actives.length) {\n activesData = actives.data('bs.collapse')\n if (activesData && activesData.transitioning) return\n }\n\n var startEvent = $.Event('show.bs.collapse')\n this.$element.trigger(startEvent)\n if (startEvent.isDefaultPrevented()) return\n\n if (actives && actives.length) {\n Plugin.call(actives, 'hide')\n activesData || actives.data('bs.collapse', null)\n }\n\n var dimension = this.dimension()\n\n this.$element\n .removeClass('collapse')\n .addClass('collapsing')[dimension](0)\n .attr('aria-expanded', true)\n\n this.$trigger\n .removeClass('collapsed')\n .attr('aria-expanded', true)\n\n this.transitioning = 1\n\n var complete = function () {\n this.$element\n .removeClass('collapsing')\n .addClass('collapse in')[dimension]('')\n this.transitioning = 0\n this.$element\n .trigger('shown.bs.collapse')\n }\n\n if (!$.support.transition) return complete.call(this)\n\n var scrollSize = $.camelCase(['scroll', dimension].join('-'))\n\n this.$element\n .one('bsTransitionEnd', $.proxy(complete, this))\n .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])\n }\n\n Collapse.prototype.hide = function () {\n if (this.transitioning || !this.$element.hasClass('in')) return\n\n var startEvent = $.Event('hide.bs.collapse')\n this.$element.trigger(startEvent)\n if (startEvent.isDefaultPrevented()) return\n\n var dimension = this.dimension()\n\n this.$element[dimension](this.$element[dimension]())[0].offsetHeight\n\n this.$element\n .addClass('collapsing')\n .removeClass('collapse in')\n .attr('aria-expanded', false)\n\n this.$trigger\n .addClass('collapsed')\n .attr('aria-expanded', false)\n\n this.transitioning = 1\n\n var complete = function () {\n this.transitioning = 0\n this.$element\n .removeClass('collapsing')\n .addClass('collapse')\n .trigger('hidden.bs.collapse')\n }\n\n if (!$.support.transition) return complete.call(this)\n\n this.$element\n [dimension](0)\n .one('bsTransitionEnd', $.proxy(complete, this))\n .emulateTransitionEnd(Collapse.TRANSITION_DURATION)\n }\n\n Collapse.prototype.toggle = function () {\n this[this.$element.hasClass('in') ? 'hide' : 'show']()\n }\n\n Collapse.prototype.getParent = function () {\n return $(document).find(this.options.parent)\n .find('[data-toggle=\"collapse\"][data-parent=\"' + this.options.parent + '\"]')\n .each($.proxy(function (i, element) {\n var $element = $(element)\n this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)\n }, this))\n .end()\n }\n\n Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {\n var isOpen = $element.hasClass('in')\n\n $element.attr('aria-expanded', isOpen)\n $trigger\n .toggleClass('collapsed', !isOpen)\n .attr('aria-expanded', isOpen)\n }\n\n function getTargetFromTrigger($trigger) {\n var href\n var target = $trigger.attr('data-target')\n || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\\s]+$)/, '') // strip for ie7\n\n return $(document).find(target)\n }\n\n\n // COLLAPSE PLUGIN DEFINITION\n // ==========================\n\n function Plugin(option) {\n return this.each(function () {\n var $this = $(this)\n var data = $this.data('bs.collapse')\n var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false\n if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))\n if (typeof option == 'string') data[option]()\n })\n }\n\n var old = $.fn.collapse\n\n $.fn.collapse = Plugin\n $.fn.collapse.Constructor = Collapse\n\n\n // COLLAPSE NO CONFLICT\n // ====================\n\n $.fn.collapse.noConflict = function () {\n $.fn.collapse = old\n return this\n }\n\n\n // COLLAPSE DATA-API\n // =================\n\n $(document).on('click.bs.collapse.data-api', '[data-toggle=\"collapse\"]', function (e) {\n var $this = $(this)\n\n if (!$this.attr('data-target')) e.preventDefault()\n\n var $target = getTargetFromTrigger($this)\n var data = $target.data('bs.collapse')\n var option = data ? 'toggle' : $this.data()\n\n Plugin.call($target, option)\n })\n\n}(jQuery);\n","/* ========================================================================\n * Bootstrap: dropdown.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#dropdowns\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n 'use strict';\n\n // DROPDOWN CLASS DEFINITION\n // =========================\n\n var backdrop = '.dropdown-backdrop'\n var toggle = '[data-toggle=\"dropdown\"]'\n var Dropdown = function (element) {\n $(element).on('click.bs.dropdown', this.toggle)\n }\n\n Dropdown.VERSION = '3.4.1'\n\n function getParent($this) {\n var selector = $this.attr('data-target')\n\n if (!selector) {\n selector = $this.attr('href')\n selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n }\n\n var $parent = selector !== '#' ? $(document).find(selector) : null\n\n return $parent && $parent.length ? $parent : $this.parent()\n }\n\n function clearMenus(e) {\n if (e && e.which === 3) return\n $(backdrop).remove()\n $(toggle).each(function () {\n var $this = $(this)\n var $parent = getParent($this)\n var relatedTarget = { relatedTarget: this }\n\n if (!$parent.hasClass('open')) return\n\n if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return\n\n $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))\n\n if (e.isDefaultPrevented()) return\n\n $this.attr('aria-expanded', 'false')\n $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))\n })\n }\n\n Dropdown.prototype.toggle = function (e) {\n var $this = $(this)\n\n if ($this.is('.disabled, :disabled')) return\n\n var $parent = getParent($this)\n var isActive = $parent.hasClass('open')\n\n clearMenus()\n\n if (!isActive) {\n if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {\n // if mobile we use a backdrop because click events don't delegate\n $(document.createElement('div'))\n .addClass('dropdown-backdrop')\n .insertAfter($(this))\n .on('click', clearMenus)\n }\n\n var relatedTarget = { relatedTarget: this }\n $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))\n\n if (e.isDefaultPrevented()) return\n\n $this\n .trigger('focus')\n .attr('aria-expanded', 'true')\n\n $parent\n .toggleClass('open')\n .trigger($.Event('shown.bs.dropdown', relatedTarget))\n }\n\n return false\n }\n\n Dropdown.prototype.keydown = function (e) {\n if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return\n\n var $this = $(this)\n\n e.preventDefault()\n e.stopPropagation()\n\n if ($this.is('.disabled, :disabled')) return\n\n var $parent = getParent($this)\n var isActive = $parent.hasClass('open')\n\n if (!isActive && e.which != 27 || isActive && e.which == 27) {\n if (e.which == 27) $parent.find(toggle).trigger('focus')\n return $this.trigger('click')\n }\n\n var desc = ' li:not(.disabled):visible a'\n var $items = $parent.find('.dropdown-menu' + desc)\n\n if (!$items.length) return\n\n var index = $items.index(e.target)\n\n if (e.which == 38 && index > 0) index-- // up\n if (e.which == 40 && index < $items.length - 1) index++ // down\n if (!~index) index = 0\n\n $items.eq(index).trigger('focus')\n }\n\n\n // DROPDOWN PLUGIN DEFINITION\n // ==========================\n\n function Plugin(option) {\n return this.each(function () {\n var $this = $(this)\n var data = $this.data('bs.dropdown')\n\n if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))\n if (typeof option == 'string') data[option].call($this)\n })\n }\n\n var old = $.fn.dropdown\n\n $.fn.dropdown = Plugin\n $.fn.dropdown.Constructor = Dropdown\n\n\n // DROPDOWN NO CONFLICT\n // ====================\n\n $.fn.dropdown.noConflict = function () {\n $.fn.dropdown = old\n return this\n }\n\n\n // APPLY TO STANDARD DROPDOWN ELEMENTS\n // ===================================\n\n $(document)\n .on('click.bs.dropdown.data-api', clearMenus)\n .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })\n .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)\n .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)\n .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)\n\n}(jQuery);\n","/* ========================================================================\n * Bootstrap: modal.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#modals\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n 'use strict';\n\n // MODAL CLASS DEFINITION\n // ======================\n\n var Modal = function (element, options) {\n this.options = options\n this.$body = $(document.body)\n this.$element = $(element)\n this.$dialog = this.$element.find('.modal-dialog')\n this.$backdrop = null\n this.isShown = null\n this.originalBodyPad = null\n this.scrollbarWidth = 0\n this.ignoreBackdropClick = false\n this.fixedContent = '.navbar-fixed-top, .navbar-fixed-bottom'\n\n if (this.options.remote) {\n this.$element\n .find('.modal-content')\n .load(this.options.remote, $.proxy(function () {\n this.$element.trigger('loaded.bs.modal')\n }, this))\n }\n }\n\n Modal.VERSION = '3.4.1'\n\n Modal.TRANSITION_DURATION = 300\n Modal.BACKDROP_TRANSITION_DURATION = 150\n\n Modal.DEFAULTS = {\n backdrop: true,\n keyboard: true,\n show: true\n }\n\n Modal.prototype.toggle = function (_relatedTarget) {\n return this.isShown ? this.hide() : this.show(_relatedTarget)\n }\n\n Modal.prototype.show = function (_relatedTarget) {\n var that = this\n var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })\n\n this.$element.trigger(e)\n\n if (this.isShown || e.isDefaultPrevented()) return\n\n this.isShown = true\n\n this.checkScrollbar()\n this.setScrollbar()\n this.$body.addClass('modal-open')\n\n this.escape()\n this.resize()\n\n this.$element.on('click.dismiss.bs.modal', '[data-dismiss=\"modal\"]', $.proxy(this.hide, this))\n\n this.$dialog.on('mousedown.dismiss.bs.modal', function () {\n that.$element.one('mouseup.dismiss.bs.modal', function (e) {\n if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true\n })\n })\n\n this.backdrop(function () {\n var transition = $.support.transition && that.$element.hasClass('fade')\n\n if (!that.$element.parent().length) {\n that.$element.appendTo(that.$body) // don't move modals dom position\n }\n\n that.$element\n .show()\n .scrollTop(0)\n\n that.adjustDialog()\n\n if (transition) {\n that.$element[0].offsetWidth // force reflow\n }\n\n that.$element.addClass('in')\n\n that.enforceFocus()\n\n var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })\n\n transition ?\n that.$dialog // wait for modal to slide in\n .one('bsTransitionEnd', function () {\n that.$element.trigger('focus').trigger(e)\n })\n .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n that.$element.trigger('focus').trigger(e)\n })\n }\n\n Modal.prototype.hide = function (e) {\n if (e) e.preventDefault()\n\n e = $.Event('hide.bs.modal')\n\n this.$element.trigger(e)\n\n if (!this.isShown || e.isDefaultPrevented()) return\n\n this.isShown = false\n\n this.escape()\n this.resize()\n\n $(document).off('focusin.bs.modal')\n\n this.$element\n .removeClass('in')\n .off('click.dismiss.bs.modal')\n .off('mouseup.dismiss.bs.modal')\n\n this.$dialog.off('mousedown.dismiss.bs.modal')\n\n $.support.transition && this.$element.hasClass('fade') ?\n this.$element\n .one('bsTransitionEnd', $.proxy(this.hideModal, this))\n .emulateTransitionEnd(Modal.TRANSITION_DURATION) :\n this.hideModal()\n }\n\n Modal.prototype.enforceFocus = function () {\n $(document)\n .off('focusin.bs.modal') // guard against infinite focus loop\n .on('focusin.bs.modal', $.proxy(function (e) {\n if (document !== e.target &&\n this.$element[0] !== e.target &&\n !this.$element.has(e.target).length) {\n this.$element.trigger('focus')\n }\n }, this))\n }\n\n Modal.prototype.escape = function () {\n if (this.isShown && this.options.keyboard) {\n this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {\n e.which == 27 && this.hide()\n }, this))\n } else if (!this.isShown) {\n this.$element.off('keydown.dismiss.bs.modal')\n }\n }\n\n Modal.prototype.resize = function () {\n if (this.isShown) {\n $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))\n } else {\n $(window).off('resize.bs.modal')\n }\n }\n\n Modal.prototype.hideModal = function () {\n var that = this\n this.$element.hide()\n this.backdrop(function () {\n that.$body.removeClass('modal-open')\n that.resetAdjustments()\n that.resetScrollbar()\n that.$element.trigger('hidden.bs.modal')\n })\n }\n\n Modal.prototype.removeBackdrop = function () {\n this.$backdrop && this.$backdrop.remove()\n this.$backdrop = null\n }\n\n Modal.prototype.backdrop = function (callback) {\n var that = this\n var animate = this.$element.hasClass('fade') ? 'fade' : ''\n\n if (this.isShown && this.options.backdrop) {\n var doAnimate = $.support.transition && animate\n\n this.$backdrop = $(document.createElement('div'))\n .addClass('modal-backdrop ' + animate)\n .appendTo(this.$body)\n\n this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {\n if (this.ignoreBackdropClick) {\n this.ignoreBackdropClick = false\n return\n }\n if (e.target !== e.currentTarget) return\n this.options.backdrop == 'static'\n ? this.$element[0].focus()\n : this.hide()\n }, this))\n\n if (doAnimate) this.$backdrop[0].offsetWidth // force reflow\n\n this.$backdrop.addClass('in')\n\n if (!callback) return\n\n doAnimate ?\n this.$backdrop\n .one('bsTransitionEnd', callback)\n .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n callback()\n\n } else if (!this.isShown && this.$backdrop) {\n this.$backdrop.removeClass('in')\n\n var callbackRemove = function () {\n that.removeBackdrop()\n callback && callback()\n }\n $.support.transition && this.$element.hasClass('fade') ?\n this.$backdrop\n .one('bsTransitionEnd', callbackRemove)\n .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :\n callbackRemove()\n\n } else if (callback) {\n callback()\n }\n }\n\n // these following methods are used to handle overflowing modals\n\n Modal.prototype.handleUpdate = function () {\n this.adjustDialog()\n }\n\n Modal.prototype.adjustDialog = function () {\n var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight\n\n this.$element.css({\n paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',\n paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''\n })\n }\n\n Modal.prototype.resetAdjustments = function () {\n this.$element.css({\n paddingLeft: '',\n paddingRight: ''\n })\n }\n\n Modal.prototype.checkScrollbar = function () {\n var fullWindowWidth = window.innerWidth\n if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8\n var documentElementRect = document.documentElement.getBoundingClientRect()\n fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)\n }\n this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth\n this.scrollbarWidth = this.measureScrollbar()\n }\n\n Modal.prototype.setScrollbar = function () {\n var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)\n this.originalBodyPad = document.body.style.paddingRight || ''\n var scrollbarWidth = this.scrollbarWidth\n if (this.bodyIsOverflowing) {\n this.$body.css('padding-right', bodyPad + scrollbarWidth)\n $(this.fixedContent).each(function (index, element) {\n var actualPadding = element.style.paddingRight\n var calculatedPadding = $(element).css('padding-right')\n $(element)\n .data('padding-right', actualPadding)\n .css('padding-right', parseFloat(calculatedPadding) + scrollbarWidth + 'px')\n })\n }\n }\n\n Modal.prototype.resetScrollbar = function () {\n this.$body.css('padding-right', this.originalBodyPad)\n $(this.fixedContent).each(function (index, element) {\n var padding = $(element).data('padding-right')\n $(element).removeData('padding-right')\n element.style.paddingRight = padding ? padding : ''\n })\n }\n\n Modal.prototype.measureScrollbar = function () { // thx walsh\n var scrollDiv = document.createElement('div')\n scrollDiv.className = 'modal-scrollbar-measure'\n this.$body.append(scrollDiv)\n var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth\n this.$body[0].removeChild(scrollDiv)\n return scrollbarWidth\n }\n\n\n // MODAL PLUGIN DEFINITION\n // =======================\n\n function Plugin(option, _relatedTarget) {\n return this.each(function () {\n var $this = $(this)\n var data = $this.data('bs.modal')\n var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)\n\n if (!data) $this.data('bs.modal', (data = new Modal(this, options)))\n if (typeof option == 'string') data[option](_relatedTarget)\n else if (options.show) data.show(_relatedTarget)\n })\n }\n\n var old = $.fn.modal\n\n $.fn.modal = Plugin\n $.fn.modal.Constructor = Modal\n\n\n // MODAL NO CONFLICT\n // =================\n\n $.fn.modal.noConflict = function () {\n $.fn.modal = old\n return this\n }\n\n\n // MODAL DATA-API\n // ==============\n\n $(document).on('click.bs.modal.data-api', '[data-toggle=\"modal\"]', function (e) {\n var $this = $(this)\n var href = $this.attr('href')\n var target = $this.attr('data-target') ||\n (href && href.replace(/.*(?=#[^\\s]+$)/, '')) // strip for ie7\n\n var $target = $(document).find(target)\n var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())\n\n if ($this.is('a')) e.preventDefault()\n\n $target.one('show.bs.modal', function (showEvent) {\n if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown\n $target.one('hidden.bs.modal', function () {\n $this.is(':visible') && $this.trigger('focus')\n })\n })\n Plugin.call($target, option, this)\n })\n\n}(jQuery);\n","/* ========================================================================\n * Bootstrap: tooltip.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#tooltip\n * Inspired by the original jQuery.tipsy by Jason Frame\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n+function ($) {\n 'use strict';\n\n var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']\n\n var uriAttrs = [\n 'background',\n 'cite',\n 'href',\n 'itemtype',\n 'longdesc',\n 'poster',\n 'src',\n 'xlink:href'\n ]\n\n var ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i\n\n var DefaultWhitelist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n div: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n }\n\n /**\n * A pattern that recognizes a commonly useful subset of URLs that are safe.\n *\n * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts\n */\n var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi\n\n /**\n * A pattern that matches safe data URLs. Only matches image, video and audio types.\n *\n * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts\n */\n var DATA_URL_PATTERN = /^data:(?:image\\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\\/(?:mpeg|mp4|ogg|webm)|audio\\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i\n\n function allowedAttribute(attr, allowedAttributeList) {\n var attrName = attr.nodeName.toLowerCase()\n\n if ($.inArray(attrName, allowedAttributeList) !== -1) {\n if ($.inArray(attrName, uriAttrs) !== -1) {\n return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN))\n }\n\n return true\n }\n\n var regExp = $(allowedAttributeList).filter(function (index, value) {\n return value instanceof RegExp\n })\n\n // Check if a regular expression validates the attribute.\n for (var i = 0, l = regExp.length; i < l; i++) {\n if (attrName.match(regExp[i])) {\n return true\n }\n }\n\n return false\n }\n\n function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {\n if (unsafeHtml.length === 0) {\n return unsafeHtml\n }\n\n if (sanitizeFn && typeof sanitizeFn === 'function') {\n return sanitizeFn(unsafeHtml)\n }\n\n // IE 8 and below don't support createHTMLDocument\n if (!document.implementation || !document.implementation.createHTMLDocument) {\n return unsafeHtml\n }\n\n var createdDocument = document.implementation.createHTMLDocument('sanitization')\n createdDocument.body.innerHTML = unsafeHtml\n\n var whitelistKeys = $.map(whiteList, function (el, i) { return i })\n var elements = $(createdDocument.body).find('*')\n\n for (var i = 0, len = elements.length; i < len; i++) {\n var el = elements[i]\n var elName = el.nodeName.toLowerCase()\n\n if ($.inArray(elName, whitelistKeys) === -1) {\n el.parentNode.removeChild(el)\n\n continue\n }\n\n var attributeList = $.map(el.attributes, function (el) { return el })\n var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || [])\n\n for (var j = 0, len2 = attributeList.length; j < len2; j++) {\n if (!allowedAttribute(attributeList[j], whitelistedAttributes)) {\n el.removeAttribute(attributeList[j].nodeName)\n }\n }\n }\n\n return createdDocument.body.innerHTML\n }\n\n // TOOLTIP PUBLIC CLASS DEFINITION\n // ===============================\n\n var Tooltip = function (element, options) {\n this.type = null\n this.options = null\n this.enabled = null\n this.timeout = null\n this.hoverState = null\n this.$element = null\n this.inState = null\n\n this.init('tooltip', element, options)\n }\n\n Tooltip.VERSION = '3.4.1'\n\n Tooltip.TRANSITION_DURATION = 150\n\n Tooltip.DEFAULTS = {\n animation: true,\n placement: 'top',\n selector: false,\n template: '
',\n trigger: 'hover focus',\n title: '',\n delay: 0,\n html: false,\n container: false,\n viewport: {\n selector: 'body',\n padding: 0\n },\n sanitize : true,\n sanitizeFn : null,\n whiteList : DefaultWhitelist\n }\n\n Tooltip.prototype.init = function (type, element, options) {\n this.enabled = true\n this.type = type\n this.$element = $(element)\n this.options = this.getOptions(options)\n this.$viewport = this.options.viewport && $(document).find($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))\n this.inState = { click: false, hover: false, focus: false }\n\n if (this.$element[0] instanceof document.constructor && !this.options.selector) {\n throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')\n }\n\n var triggers = this.options.trigger.split(' ')\n\n for (var i = triggers.length; i--;) {\n var trigger = triggers[i]\n\n if (trigger == 'click') {\n this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))\n } else if (trigger != 'manual') {\n var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'\n var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'\n\n this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))\n this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))\n }\n }\n\n this.options.selector ?\n (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :\n this.fixTitle()\n }\n\n Tooltip.prototype.getDefaults = function () {\n return Tooltip.DEFAULTS\n }\n\n Tooltip.prototype.getOptions = function (options) {\n var dataAttributes = this.$element.data()\n\n for (var dataAttr in dataAttributes) {\n if (dataAttributes.hasOwnProperty(dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) {\n delete dataAttributes[dataAttr]\n }\n }\n\n options = $.extend({}, this.getDefaults(), dataAttributes, options)\n\n if (options.delay && typeof options.delay == 'number') {\n options.delay = {\n show: options.delay,\n hide: options.delay\n }\n }\n\n if (options.sanitize) {\n options.template = sanitizeHtml(options.template, options.whiteList, options.sanitizeFn)\n }\n\n return options\n }\n\n Tooltip.prototype.getDelegateOptions = function () {\n var options = {}\n var defaults = this.getDefaults()\n\n this._options && $.each(this._options, function (key, value) {\n if (defaults[key] != value) options[key] = value\n })\n\n return options\n }\n\n Tooltip.prototype.enter = function (obj) {\n var self = obj instanceof this.constructor ?\n obj : $(obj.currentTarget).data('bs.' + this.type)\n\n if (!self) {\n self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n $(obj.currentTarget).data('bs.' + this.type, self)\n }\n\n if (obj instanceof $.Event) {\n self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true\n }\n\n if (self.tip().hasClass('in') || self.hoverState == 'in') {\n self.hoverState = 'in'\n return\n }\n\n clearTimeout(self.timeout)\n\n self.hoverState = 'in'\n\n if (!self.options.delay || !self.options.delay.show) return self.show()\n\n self.timeout = setTimeout(function () {\n if (self.hoverState == 'in') self.show()\n }, self.options.delay.show)\n }\n\n Tooltip.prototype.isInStateTrue = function () {\n for (var key in this.inState) {\n if (this.inState[key]) return true\n }\n\n return false\n }\n\n Tooltip.prototype.leave = function (obj) {\n var self = obj instanceof this.constructor ?\n obj : $(obj.currentTarget).data('bs.' + this.type)\n\n if (!self) {\n self = new this.constructor(obj.currentTarget, this.getDelegateOptions())\n $(obj.currentTarget).data('bs.' + this.type, self)\n }\n\n if (obj instanceof $.Event) {\n self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false\n }\n\n if (self.isInStateTrue()) return\n\n clearTimeout(self.timeout)\n\n self.hoverState = 'out'\n\n if (!self.options.delay || !self.options.delay.hide) return self.hide()\n\n self.timeout = setTimeout(function () {\n if (self.hoverState == 'out') self.hide()\n }, self.options.delay.hide)\n }\n\n Tooltip.prototype.show = function () {\n var e = $.Event('show.bs.' + this.type)\n\n if (this.hasContent() && this.enabled) {\n this.$element.trigger(e)\n\n var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])\n if (e.isDefaultPrevented() || !inDom) return\n var that = this\n\n var $tip = this.tip()\n\n var tipId = this.getUID(this.type)\n\n this.setContent()\n $tip.attr('id', tipId)\n this.$element.attr('aria-describedby', tipId)\n\n if (this.options.animation) $tip.addClass('fade')\n\n var placement = typeof this.options.placement == 'function' ?\n this.options.placement.call(this, $tip[0], this.$element[0]) :\n this.options.placement\n\n var autoToken = /\\s?auto?\\s?/i\n var autoPlace = autoToken.test(placement)\n if (autoPlace) placement = placement.replace(autoToken, '') || 'top'\n\n $tip\n .detach()\n .css({ top: 0, left: 0, display: 'block' })\n .addClass(placement)\n .data('bs.' + this.type, this)\n\n this.options.container ? $tip.appendTo($(document).find(this.options.container)) : $tip.insertAfter(this.$element)\n this.$element.trigger('inserted.bs.' + this.type)\n\n var pos = this.getPosition()\n var actualWidth = $tip[0].offsetWidth\n var actualHeight = $tip[0].offsetHeight\n\n if (autoPlace) {\n var orgPlacement = placement\n var viewportDim = this.getPosition(this.$viewport)\n\n placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :\n placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :\n placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :\n placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :\n placement\n\n $tip\n .removeClass(orgPlacement)\n .addClass(placement)\n }\n\n var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)\n\n this.applyPlacement(calculatedOffset, placement)\n\n var complete = function () {\n var prevHoverState = that.hoverState\n that.$element.trigger('shown.bs.' + that.type)\n that.hoverState = null\n\n if (prevHoverState == 'out') that.leave(that)\n }\n\n $.support.transition && this.$tip.hasClass('fade') ?\n $tip\n .one('bsTransitionEnd', complete)\n .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n complete()\n }\n }\n\n Tooltip.prototype.applyPlacement = function (offset, placement) {\n var $tip = this.tip()\n var width = $tip[0].offsetWidth\n var height = $tip[0].offsetHeight\n\n // manually read margins because getBoundingClientRect includes difference\n var marginTop = parseInt($tip.css('margin-top'), 10)\n var marginLeft = parseInt($tip.css('margin-left'), 10)\n\n // we must check for NaN for ie 8/9\n if (isNaN(marginTop)) marginTop = 0\n if (isNaN(marginLeft)) marginLeft = 0\n\n offset.top += marginTop\n offset.left += marginLeft\n\n // $.fn.offset doesn't round pixel values\n // so we use setOffset directly with our own function B-0\n $.offset.setOffset($tip[0], $.extend({\n using: function (props) {\n $tip.css({\n top: Math.round(props.top),\n left: Math.round(props.left)\n })\n }\n }, offset), 0)\n\n $tip.addClass('in')\n\n // check to see if placing tip in new offset caused the tip to resize itself\n var actualWidth = $tip[0].offsetWidth\n var actualHeight = $tip[0].offsetHeight\n\n if (placement == 'top' && actualHeight != height) {\n offset.top = offset.top + height - actualHeight\n }\n\n var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)\n\n if (delta.left) offset.left += delta.left\n else offset.top += delta.top\n\n var isVertical = /top|bottom/.test(placement)\n var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight\n var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'\n\n $tip.offset(offset)\n this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)\n }\n\n Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {\n this.arrow()\n .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')\n .css(isVertical ? 'top' : 'left', '')\n }\n\n Tooltip.prototype.setContent = function () {\n var $tip = this.tip()\n var title = this.getTitle()\n\n if (this.options.html) {\n if (this.options.sanitize) {\n title = sanitizeHtml(title, this.options.whiteList, this.options.sanitizeFn)\n }\n\n $tip.find('.tooltip-inner').html(title)\n } else {\n $tip.find('.tooltip-inner').text(title)\n }\n\n $tip.removeClass('fade in top bottom left right')\n }\n\n Tooltip.prototype.hide = function (callback) {\n var that = this\n var $tip = $(this.$tip)\n var e = $.Event('hide.bs.' + this.type)\n\n function complete() {\n if (that.hoverState != 'in') $tip.detach()\n if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.\n that.$element\n .removeAttr('aria-describedby')\n .trigger('hidden.bs.' + that.type)\n }\n callback && callback()\n }\n\n this.$element.trigger(e)\n\n if (e.isDefaultPrevented()) return\n\n $tip.removeClass('in')\n\n $.support.transition && $tip.hasClass('fade') ?\n $tip\n .one('bsTransitionEnd', complete)\n .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :\n complete()\n\n this.hoverState = null\n\n return this\n }\n\n Tooltip.prototype.fixTitle = function () {\n var $e = this.$element\n if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {\n $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')\n }\n }\n\n Tooltip.prototype.hasContent = function () {\n return this.getTitle()\n }\n\n Tooltip.prototype.getPosition = function ($element) {\n $element = $element || this.$element\n\n var el = $element[0]\n var isBody = el.tagName == 'BODY'\n\n var elRect = el.getBoundingClientRect()\n if (elRect.width == null) {\n // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093\n elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })\n }\n var isSvg = window.SVGElement && el instanceof window.SVGElement\n // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.\n // See https://github.com/twbs/bootstrap/issues/20280\n var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())\n var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }\n var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null\n\n return $.extend({}, elRect, scroll, outerDims, elOffset)\n }\n\n Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {\n return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :\n placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :\n placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :\n /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }\n\n }\n\n Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {\n var delta = { top: 0, left: 0 }\n if (!this.$viewport) return delta\n\n var viewportPadding = this.options.viewport && this.options.viewport.padding || 0\n var viewportDimensions = this.getPosition(this.$viewport)\n\n if (/right|left/.test(placement)) {\n var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll\n var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight\n if (topEdgeOffset < viewportDimensions.top) { // top overflow\n delta.top = viewportDimensions.top - topEdgeOffset\n } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow\n delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset\n }\n } else {\n var leftEdgeOffset = pos.left - viewportPadding\n var rightEdgeOffset = pos.left + viewportPadding + actualWidth\n if (leftEdgeOffset < viewportDimensions.left) { // left overflow\n delta.left = viewportDimensions.left - leftEdgeOffset\n } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow\n delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset\n }\n }\n\n return delta\n }\n\n Tooltip.prototype.getTitle = function () {\n var title\n var $e = this.$element\n var o = this.options\n\n title = $e.attr('data-original-title')\n || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)\n\n return title\n }\n\n Tooltip.prototype.getUID = function (prefix) {\n do prefix += ~~(Math.random() * 1000000)\n while (document.getElementById(prefix))\n return prefix\n }\n\n Tooltip.prototype.tip = function () {\n if (!this.$tip) {\n this.$tip = $(this.options.template)\n if (this.$tip.length != 1) {\n throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')\n }\n }\n return this.$tip\n }\n\n Tooltip.prototype.arrow = function () {\n return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))\n }\n\n Tooltip.prototype.enable = function () {\n this.enabled = true\n }\n\n Tooltip.prototype.disable = function () {\n this.enabled = false\n }\n\n Tooltip.prototype.toggleEnabled = function () {\n this.enabled = !this.enabled\n }\n\n Tooltip.prototype.toggle = function (e) {\n var self = this\n if (e) {\n self = $(e.currentTarget).data('bs.' + this.type)\n if (!self) {\n self = new this.constructor(e.currentTarget, this.getDelegateOptions())\n $(e.currentTarget).data('bs.' + this.type, self)\n }\n }\n\n if (e) {\n self.inState.click = !self.inState.click\n if (self.isInStateTrue()) self.enter(self)\n else self.leave(self)\n } else {\n self.tip().hasClass('in') ? self.leave(self) : self.enter(self)\n }\n }\n\n Tooltip.prototype.destroy = function () {\n var that = this\n clearTimeout(this.timeout)\n this.hide(function () {\n that.$element.off('.' + that.type).removeData('bs.' + that.type)\n if (that.$tip) {\n that.$tip.detach()\n }\n that.$tip = null\n that.$arrow = null\n that.$viewport = null\n that.$element = null\n })\n }\n\n Tooltip.prototype.sanitizeHtml = function (unsafeHtml) {\n return sanitizeHtml(unsafeHtml, this.options.whiteList, this.options.sanitizeFn)\n }\n\n // TOOLTIP PLUGIN DEFINITION\n // =========================\n\n function Plugin(option) {\n return this.each(function () {\n var $this = $(this)\n var data = $this.data('bs.tooltip')\n var options = typeof option == 'object' && option\n\n if (!data && /destroy|hide/.test(option)) return\n if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))\n if (typeof option == 'string') data[option]()\n })\n }\n\n var old = $.fn.tooltip\n\n $.fn.tooltip = Plugin\n $.fn.tooltip.Constructor = Tooltip\n\n\n // TOOLTIP NO CONFLICT\n // ===================\n\n $.fn.tooltip.noConflict = function () {\n $.fn.tooltip = old\n return this\n }\n\n}(jQuery);\n","/* ========================================================================\n * Bootstrap: popover.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#popovers\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n 'use strict';\n\n // POPOVER PUBLIC CLASS DEFINITION\n // ===============================\n\n var Popover = function (element, options) {\n this.init('popover', element, options)\n }\n\n if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')\n\n Popover.VERSION = '3.4.1'\n\n Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {\n placement: 'right',\n trigger: 'click',\n content: '',\n template: '

'\n })\n\n\n // NOTE: POPOVER EXTENDS tooltip.js\n // ================================\n\n Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)\n\n Popover.prototype.constructor = Popover\n\n Popover.prototype.getDefaults = function () {\n return Popover.DEFAULTS\n }\n\n Popover.prototype.setContent = function () {\n var $tip = this.tip()\n var title = this.getTitle()\n var content = this.getContent()\n\n if (this.options.html) {\n var typeContent = typeof content\n\n if (this.options.sanitize) {\n title = this.sanitizeHtml(title)\n\n if (typeContent === 'string') {\n content = this.sanitizeHtml(content)\n }\n }\n\n $tip.find('.popover-title').html(title)\n $tip.find('.popover-content').children().detach().end()[\n typeContent === 'string' ? 'html' : 'append'\n ](content)\n } else {\n $tip.find('.popover-title').text(title)\n $tip.find('.popover-content').children().detach().end().text(content)\n }\n\n $tip.removeClass('fade top bottom left right in')\n\n // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do\n // this manually by checking the contents.\n if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()\n }\n\n Popover.prototype.hasContent = function () {\n return this.getTitle() || this.getContent()\n }\n\n Popover.prototype.getContent = function () {\n var $e = this.$element\n var o = this.options\n\n return $e.attr('data-content')\n || (typeof o.content == 'function' ?\n o.content.call($e[0]) :\n o.content)\n }\n\n Popover.prototype.arrow = function () {\n return (this.$arrow = this.$arrow || this.tip().find('.arrow'))\n }\n\n\n // POPOVER PLUGIN DEFINITION\n // =========================\n\n function Plugin(option) {\n return this.each(function () {\n var $this = $(this)\n var data = $this.data('bs.popover')\n var options = typeof option == 'object' && option\n\n if (!data && /destroy|hide/.test(option)) return\n if (!data) $this.data('bs.popover', (data = new Popover(this, options)))\n if (typeof option == 'string') data[option]()\n })\n }\n\n var old = $.fn.popover\n\n $.fn.popover = Plugin\n $.fn.popover.Constructor = Popover\n\n\n // POPOVER NO CONFLICT\n // ===================\n\n $.fn.popover.noConflict = function () {\n $.fn.popover = old\n return this\n }\n\n}(jQuery);\n","/* ========================================================================\n * Bootstrap: scrollspy.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#scrollspy\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n 'use strict';\n\n // SCROLLSPY CLASS DEFINITION\n // ==========================\n\n function ScrollSpy(element, options) {\n this.$body = $(document.body)\n this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)\n this.options = $.extend({}, ScrollSpy.DEFAULTS, options)\n this.selector = (this.options.target || '') + ' .nav li > a'\n this.offsets = []\n this.targets = []\n this.activeTarget = null\n this.scrollHeight = 0\n\n this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))\n this.refresh()\n this.process()\n }\n\n ScrollSpy.VERSION = '3.4.1'\n\n ScrollSpy.DEFAULTS = {\n offset: 10\n }\n\n ScrollSpy.prototype.getScrollHeight = function () {\n return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)\n }\n\n ScrollSpy.prototype.refresh = function () {\n var that = this\n var offsetMethod = 'offset'\n var offsetBase = 0\n\n this.offsets = []\n this.targets = []\n this.scrollHeight = this.getScrollHeight()\n\n if (!$.isWindow(this.$scrollElement[0])) {\n offsetMethod = 'position'\n offsetBase = this.$scrollElement.scrollTop()\n }\n\n this.$body\n .find(this.selector)\n .map(function () {\n var $el = $(this)\n var href = $el.data('target') || $el.attr('href')\n var $href = /^#./.test(href) && $(href)\n\n return ($href\n && $href.length\n && $href.is(':visible')\n && [[$href[offsetMethod]().top + offsetBase, href]]) || null\n })\n .sort(function (a, b) { return a[0] - b[0] })\n .each(function () {\n that.offsets.push(this[0])\n that.targets.push(this[1])\n })\n }\n\n ScrollSpy.prototype.process = function () {\n var scrollTop = this.$scrollElement.scrollTop() + this.options.offset\n var scrollHeight = this.getScrollHeight()\n var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()\n var offsets = this.offsets\n var targets = this.targets\n var activeTarget = this.activeTarget\n var i\n\n if (this.scrollHeight != scrollHeight) {\n this.refresh()\n }\n\n if (scrollTop >= maxScroll) {\n return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)\n }\n\n if (activeTarget && scrollTop < offsets[0]) {\n this.activeTarget = null\n return this.clear()\n }\n\n for (i = offsets.length; i--;) {\n activeTarget != targets[i]\n && scrollTop >= offsets[i]\n && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])\n && this.activate(targets[i])\n }\n }\n\n ScrollSpy.prototype.activate = function (target) {\n this.activeTarget = target\n\n this.clear()\n\n var selector = this.selector +\n '[data-target=\"' + target + '\"],' +\n this.selector + '[href=\"' + target + '\"]'\n\n var active = $(selector)\n .parents('li')\n .addClass('active')\n\n if (active.parent('.dropdown-menu').length) {\n active = active\n .closest('li.dropdown')\n .addClass('active')\n }\n\n active.trigger('activate.bs.scrollspy')\n }\n\n ScrollSpy.prototype.clear = function () {\n $(this.selector)\n .parentsUntil(this.options.target, '.active')\n .removeClass('active')\n }\n\n\n // SCROLLSPY PLUGIN DEFINITION\n // ===========================\n\n function Plugin(option) {\n return this.each(function () {\n var $this = $(this)\n var data = $this.data('bs.scrollspy')\n var options = typeof option == 'object' && option\n\n if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))\n if (typeof option == 'string') data[option]()\n })\n }\n\n var old = $.fn.scrollspy\n\n $.fn.scrollspy = Plugin\n $.fn.scrollspy.Constructor = ScrollSpy\n\n\n // SCROLLSPY NO CONFLICT\n // =====================\n\n $.fn.scrollspy.noConflict = function () {\n $.fn.scrollspy = old\n return this\n }\n\n\n // SCROLLSPY DATA-API\n // ==================\n\n $(window).on('load.bs.scrollspy.data-api', function () {\n $('[data-spy=\"scroll\"]').each(function () {\n var $spy = $(this)\n Plugin.call($spy, $spy.data())\n })\n })\n\n}(jQuery);\n","/* ========================================================================\n * Bootstrap: tab.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#tabs\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n 'use strict';\n\n // TAB CLASS DEFINITION\n // ====================\n\n var Tab = function (element) {\n // jscs:disable requireDollarBeforejQueryAssignment\n this.element = $(element)\n // jscs:enable requireDollarBeforejQueryAssignment\n }\n\n Tab.VERSION = '3.4.1'\n\n Tab.TRANSITION_DURATION = 150\n\n Tab.prototype.show = function () {\n var $this = this.element\n var $ul = $this.closest('ul:not(.dropdown-menu)')\n var selector = $this.data('target')\n\n if (!selector) {\n selector = $this.attr('href')\n selector = selector && selector.replace(/.*(?=#[^\\s]*$)/, '') // strip for ie7\n }\n\n if ($this.parent('li').hasClass('active')) return\n\n var $previous = $ul.find('.active:last a')\n var hideEvent = $.Event('hide.bs.tab', {\n relatedTarget: $this[0]\n })\n var showEvent = $.Event('show.bs.tab', {\n relatedTarget: $previous[0]\n })\n\n $previous.trigger(hideEvent)\n $this.trigger(showEvent)\n\n if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return\n\n var $target = $(document).find(selector)\n\n this.activate($this.closest('li'), $ul)\n this.activate($target, $target.parent(), function () {\n $previous.trigger({\n type: 'hidden.bs.tab',\n relatedTarget: $this[0]\n })\n $this.trigger({\n type: 'shown.bs.tab',\n relatedTarget: $previous[0]\n })\n })\n }\n\n Tab.prototype.activate = function (element, container, callback) {\n var $active = container.find('> .active')\n var transition = callback\n && $.support.transition\n && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)\n\n function next() {\n $active\n .removeClass('active')\n .find('> .dropdown-menu > .active')\n .removeClass('active')\n .end()\n .find('[data-toggle=\"tab\"]')\n .attr('aria-expanded', false)\n\n element\n .addClass('active')\n .find('[data-toggle=\"tab\"]')\n .attr('aria-expanded', true)\n\n if (transition) {\n element[0].offsetWidth // reflow for transition\n element.addClass('in')\n } else {\n element.removeClass('fade')\n }\n\n if (element.parent('.dropdown-menu').length) {\n element\n .closest('li.dropdown')\n .addClass('active')\n .end()\n .find('[data-toggle=\"tab\"]')\n .attr('aria-expanded', true)\n }\n\n callback && callback()\n }\n\n $active.length && transition ?\n $active\n .one('bsTransitionEnd', next)\n .emulateTransitionEnd(Tab.TRANSITION_DURATION) :\n next()\n\n $active.removeClass('in')\n }\n\n\n // TAB PLUGIN DEFINITION\n // =====================\n\n function Plugin(option) {\n return this.each(function () {\n var $this = $(this)\n var data = $this.data('bs.tab')\n\n if (!data) $this.data('bs.tab', (data = new Tab(this)))\n if (typeof option == 'string') data[option]()\n })\n }\n\n var old = $.fn.tab\n\n $.fn.tab = Plugin\n $.fn.tab.Constructor = Tab\n\n\n // TAB NO CONFLICT\n // ===============\n\n $.fn.tab.noConflict = function () {\n $.fn.tab = old\n return this\n }\n\n\n // TAB DATA-API\n // ============\n\n var clickHandler = function (e) {\n e.preventDefault()\n Plugin.call($(this), 'show')\n }\n\n $(document)\n .on('click.bs.tab.data-api', '[data-toggle=\"tab\"]', clickHandler)\n .on('click.bs.tab.data-api', '[data-toggle=\"pill\"]', clickHandler)\n\n}(jQuery);\n","/* ========================================================================\n * Bootstrap: affix.js v3.4.1\n * https://getbootstrap.com/docs/3.4/javascript/#affix\n * ========================================================================\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * ======================================================================== */\n\n\n+function ($) {\n 'use strict';\n\n // AFFIX CLASS DEFINITION\n // ======================\n\n var Affix = function (element, options) {\n this.options = $.extend({}, Affix.DEFAULTS, options)\n\n var target = this.options.target === Affix.DEFAULTS.target ? $(this.options.target) : $(document).find(this.options.target)\n\n this.$target = target\n .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))\n .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))\n\n this.$element = $(element)\n this.affixed = null\n this.unpin = null\n this.pinnedOffset = null\n\n this.checkPosition()\n }\n\n Affix.VERSION = '3.4.1'\n\n Affix.RESET = 'affix affix-top affix-bottom'\n\n Affix.DEFAULTS = {\n offset: 0,\n target: window\n }\n\n Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {\n var scrollTop = this.$target.scrollTop()\n var position = this.$element.offset()\n var targetHeight = this.$target.height()\n\n if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false\n\n if (this.affixed == 'bottom') {\n if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'\n return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'\n }\n\n var initializing = this.affixed == null\n var colliderTop = initializing ? scrollTop : position.top\n var colliderHeight = initializing ? targetHeight : height\n\n if (offsetTop != null && scrollTop <= offsetTop) return 'top'\n if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'\n\n return false\n }\n\n Affix.prototype.getPinnedOffset = function () {\n if (this.pinnedOffset) return this.pinnedOffset\n this.$element.removeClass(Affix.RESET).addClass('affix')\n var scrollTop = this.$target.scrollTop()\n var position = this.$element.offset()\n return (this.pinnedOffset = position.top - scrollTop)\n }\n\n Affix.prototype.checkPositionWithEventLoop = function () {\n setTimeout($.proxy(this.checkPosition, this), 1)\n }\n\n Affix.prototype.checkPosition = function () {\n if (!this.$element.is(':visible')) return\n\n var height = this.$element.height()\n var offset = this.options.offset\n var offsetTop = offset.top\n var offsetBottom = offset.bottom\n var scrollHeight = Math.max($(document).height(), $(document.body).height())\n\n if (typeof offset != 'object') offsetBottom = offsetTop = offset\n if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)\n if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)\n\n var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)\n\n if (this.affixed != affix) {\n if (this.unpin != null) this.$element.css('top', '')\n\n var affixType = 'affix' + (affix ? '-' + affix : '')\n var e = $.Event(affixType + '.bs.affix')\n\n this.$element.trigger(e)\n\n if (e.isDefaultPrevented()) return\n\n this.affixed = affix\n this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null\n\n this.$element\n .removeClass(Affix.RESET)\n .addClass(affixType)\n .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')\n }\n\n if (affix == 'bottom') {\n this.$element.offset({\n top: scrollHeight - height - offsetBottom\n })\n }\n }\n\n\n // AFFIX PLUGIN DEFINITION\n // =======================\n\n function Plugin(option) {\n return this.each(function () {\n var $this = $(this)\n var data = $this.data('bs.affix')\n var options = typeof option == 'object' && option\n\n if (!data) $this.data('bs.affix', (data = new Affix(this, options)))\n if (typeof option == 'string') data[option]()\n })\n }\n\n var old = $.fn.affix\n\n $.fn.affix = Plugin\n $.fn.affix.Constructor = Affix\n\n\n // AFFIX NO CONFLICT\n // =================\n\n $.fn.affix.noConflict = function () {\n $.fn.affix = old\n return this\n }\n\n\n // AFFIX DATA-API\n // ==============\n\n $(window).on('load', function () {\n $('[data-spy=\"affix\"]').each(function () {\n var $spy = $(this)\n var data = $spy.data()\n\n data.offset = data.offset || {}\n\n if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom\n if (data.offsetTop != null) data.offset.top = data.offsetTop\n\n Plugin.call($spy, data)\n })\n })\n\n}(jQuery);\n","/*! ========================================================================\n * Bootstrap Toggle: bootstrap-toggle.js v2.2.0\n * http://www.bootstraptoggle.com\n * ========================================================================\n * Copyright 2014 Min Hur, The New York Times Company\n * Licensed under MIT\n * ======================================================================== */\n\n\n +function ($) {\n \t'use strict';\n\n\t// TOGGLE PUBLIC CLASS DEFINITION\n\t// ==============================\n\n\tvar Toggle = function (element, options) {\n\t\tthis.$element = $(element)\n\t\tthis.options = $.extend({}, this.defaults(), options)\n\t\tthis.render()\n\t}\n\n\tToggle.VERSION = '2.2.0'\n\n\tToggle.DEFAULTS = {\n\t\ton: 'On',\n\t\toff: 'Off',\n\t\tonstyle: 'primary',\n\t\toffstyle: 'default',\n\t\tsize: 'normal',\n\t\tstyle: '',\n\t\twidth: null,\n\t\theight: null\n\t}\n\n\tToggle.prototype.defaults = function() {\n\t\treturn {\n\t\t\ton: this.$element.attr('data-on') || Toggle.DEFAULTS.on,\n\t\t\toff: this.$element.attr('data-off') || Toggle.DEFAULTS.off,\n\t\t\tonstyle: this.$element.attr('data-onstyle') || Toggle.DEFAULTS.onstyle,\n\t\t\toffstyle: this.$element.attr('data-offstyle') || Toggle.DEFAULTS.offstyle,\n\t\t\tsize: this.$element.attr('data-size') || Toggle.DEFAULTS.size,\n\t\t\tstyle: this.$element.attr('data-style') || Toggle.DEFAULTS.style,\n\t\t\twidth: this.$element.attr('data-width') || Toggle.DEFAULTS.width,\n\t\t\theight: this.$element.attr('data-height') || Toggle.DEFAULTS.height\n\t\t}\n\t}\n\n\tToggle.prototype.render = function () {\n\t\tthis._onstyle = 'btn-' + this.options.onstyle\n\t\tthis._offstyle = 'btn-' + this.options.offstyle\n\t\tvar size = this.options.size === 'large' ? 'btn-lg'\n\t\t\t: this.options.size === 'small' ? 'btn-sm'\n\t\t\t: this.options.size === 'mini' ? 'btn-xs'\n\t\t\t: ''\n\t\tvar $toggleOn = $('